mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-19 06:39:55 +01:00
Merge branch 'master' into rename-workspaceedit-proto
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
[
|
||||
{ "name": "ms-vscode.node-debug", "version": "1.20.3" },
|
||||
{ "name": "ms-vscode.node-debug2", "version": "1.20.1" }
|
||||
]
|
||||
@@ -44,14 +44,13 @@ const nodeModules = ['electron', 'original-fs']
|
||||
|
||||
// Build
|
||||
|
||||
const builtInExtensions = [
|
||||
{ name: 'ms-vscode.node-debug', version: '1.20.3' },
|
||||
{ name: 'ms-vscode.node-debug2', version: '1.20.1' }
|
||||
];
|
||||
const builtInExtensions = require('./builtInExtensions');
|
||||
|
||||
const excludedExtensions = [
|
||||
'vscode-api-tests',
|
||||
'vscode-colorize-tests'
|
||||
'vscode-colorize-tests',
|
||||
'ms-vscode.node-debug',
|
||||
'ms-vscode.node-debug2',
|
||||
];
|
||||
|
||||
const vscodeEntryPoints = _.flatten([
|
||||
@@ -584,3 +583,20 @@ gulp.task('generate-vscode-configuration', () => {
|
||||
console.error(e.toString());
|
||||
});
|
||||
});
|
||||
|
||||
//#region Built-In Extensions
|
||||
gulp.task('clean-builtInExtensions', util.rimraf('.build/builtInExtensions'));
|
||||
|
||||
gulp.task('builtInExtensions', ['clean-builtInExtensions'], function() {
|
||||
const marketplaceExtensions = es.merge(...builtInExtensions.map(extension => {
|
||||
return ext.fromMarketplace(extension.name, extension.version)
|
||||
.pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`));
|
||||
}));
|
||||
|
||||
return (
|
||||
marketplaceExtensions
|
||||
.pipe(util.setExecutableBit(['**/*.sh']))
|
||||
.pipe(vfs.dest('.build/builtInExtensions'))
|
||||
);
|
||||
});
|
||||
//#endregion
|
||||
|
||||
@@ -13,6 +13,7 @@ const _7z = require('7zip')['7z'];
|
||||
const util = require('./lib/util');
|
||||
const pkg = require('../package.json');
|
||||
const product = require('../product.json');
|
||||
const vfs = require('vinyl-fs');
|
||||
|
||||
const repoPath = path.dirname(__dirname);
|
||||
const buildPath = arch => path.join(path.dirname(repoPath), `VSCode-win32-${arch}`);
|
||||
@@ -77,7 +78,7 @@ gulp.task('vscode-win32-x64-setup', ['clean-vscode-win32-x64-setup'], buildWin32
|
||||
|
||||
function archiveWin32Setup(arch) {
|
||||
return cb => {
|
||||
const args = ['a', '-tzip', zipPath(arch), '.', '-r'];
|
||||
const args = ['a', '-tzip', zipPath(arch), '.', '-r', '-x!inno_updater.exe'];
|
||||
|
||||
cp.spawn(_7z, args, { stdio: 'inherit', cwd: buildPath(arch) })
|
||||
.on('error', cb)
|
||||
@@ -90,3 +91,13 @@ gulp.task('vscode-win32-ia32-archive', ['clean-vscode-win32-ia32-archive'], arch
|
||||
|
||||
gulp.task('clean-vscode-win32-x64-archive', util.rimraf(zipDir('x64')));
|
||||
gulp.task('vscode-win32-x64-archive', ['clean-vscode-win32-x64-archive'], archiveWin32Setup('x64'));
|
||||
|
||||
function copyInnoUpdater(arch) {
|
||||
return () => {
|
||||
return gulp.src('build/win32/inno_updater.exe', { base: 'build/win32' })
|
||||
.pipe(vfs.dest(buildPath(arch)));
|
||||
};
|
||||
}
|
||||
|
||||
gulp.task('vscode-win32-ia32-copy-inno-updater', copyInnoUpdater('ia32'));
|
||||
gulp.task('vscode-win32-x64-copy-inno-updater', copyInnoUpdater('x64'));
|
||||
@@ -0,0 +1,32 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const root = path.dirname(path.dirname(__dirname));
|
||||
|
||||
function isUpToDate(extension) {
|
||||
const packagePath = path.join(root, '.build', 'builtInExtensions', extension.name, 'package.json');
|
||||
if (!fs.existsSync(packagePath)) {
|
||||
return false;
|
||||
}
|
||||
const packageContents = fs.readFileSync(packagePath);
|
||||
try {
|
||||
const diskVersion = JSON.parse(packageContents).version;
|
||||
return (diskVersion === extension.version);
|
||||
} catch(err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const builtInExtensions = require('../builtInExtensions');
|
||||
builtInExtensions.forEach((extension) => {
|
||||
if (!isUpToDate(extension)) {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
process.exit(0);
|
||||
+38
-3
@@ -14,6 +14,7 @@ var glob = require("glob");
|
||||
var https = require("https");
|
||||
var util = require('gulp-util');
|
||||
var iconv = require('iconv-lite');
|
||||
var NUMBER_OF_CONCURRENT_DOWNLOADS = 1;
|
||||
function log(message) {
|
||||
var rest = [];
|
||||
for (var _i = 1; _i < arguments.length; _i++) {
|
||||
@@ -207,6 +208,36 @@ var XLF = /** @class */ (function () {
|
||||
return XLF;
|
||||
}());
|
||||
exports.XLF = XLF;
|
||||
var Limiter = /** @class */ (function () {
|
||||
function Limiter(maxDegreeOfParalellism) {
|
||||
this.maxDegreeOfParalellism = maxDegreeOfParalellism;
|
||||
this.outstandingPromises = [];
|
||||
this.runningPromises = 0;
|
||||
}
|
||||
Limiter.prototype.queue = function (factory) {
|
||||
var _this = this;
|
||||
return new Promise(function (c, e) {
|
||||
_this.outstandingPromises.push({ factory: factory, c: c, e: e });
|
||||
_this.consume();
|
||||
});
|
||||
};
|
||||
Limiter.prototype.consume = function () {
|
||||
var _this = this;
|
||||
while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) {
|
||||
var iLimitedTask = this.outstandingPromises.shift();
|
||||
this.runningPromises++;
|
||||
var promise = iLimitedTask.factory();
|
||||
promise.then(iLimitedTask.c).catch(iLimitedTask.e);
|
||||
promise.then(function () { return _this.consumed(); }).catch(function () { return _this.consumed(); });
|
||||
}
|
||||
};
|
||||
Limiter.prototype.consumed = function () {
|
||||
this.runningPromises--;
|
||||
this.consume();
|
||||
};
|
||||
return Limiter;
|
||||
}());
|
||||
exports.Limiter = Limiter;
|
||||
var iso639_3_to_2 = {
|
||||
'chs': 'zh-cn',
|
||||
'cht': 'zh-tw',
|
||||
@@ -826,8 +857,9 @@ function pullXlfFiles(projectName, apiHostname, username, password, languages, r
|
||||
});
|
||||
}
|
||||
exports.pullXlfFiles = pullXlfFiles;
|
||||
var limiter = new Limiter(NUMBER_OF_CONCURRENT_DOWNLOADS);
|
||||
function retrieveResource(language, resource, apiHostname, credentials) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
return limiter.queue(function () { return new Promise(function (resolve, reject) {
|
||||
var slug = resource.name.replace(/\//g, '_');
|
||||
var project = resource.project;
|
||||
var iso639 = language.toLowerCase();
|
||||
@@ -835,6 +867,7 @@ function retrieveResource(language, resource, apiHostname, credentials) {
|
||||
hostname: apiHostname,
|
||||
path: "/api/2/project/" + project + "/resource/" + slug + "/translation/" + iso639 + "?file&mode=onlyreviewed",
|
||||
auth: credentials,
|
||||
port: 443,
|
||||
method: 'GET'
|
||||
};
|
||||
var request = https.request(options, function (res) {
|
||||
@@ -842,16 +875,18 @@ function retrieveResource(language, resource, apiHostname, credentials) {
|
||||
res.on('data', function (chunk) { return xlfBuffer.push(chunk); });
|
||||
res.on('end', function () {
|
||||
if (res.statusCode === 200) {
|
||||
console.log('success: ' + options.path);
|
||||
resolve(new File({ contents: Buffer.concat(xlfBuffer), path: project + "/" + iso639_2_to_3[language] + "/" + slug + ".xlf" }));
|
||||
}
|
||||
reject(slug + " in " + project + " returned no data. Response code: " + res.statusCode + ".");
|
||||
});
|
||||
});
|
||||
request.on('error', function (err) {
|
||||
reject("Failed to query resource " + slug + " with the following error: " + err);
|
||||
reject("Failed to query resource " + slug + " with the following error: " + err + ". " + options.path);
|
||||
});
|
||||
request.end();
|
||||
});
|
||||
console.log('started: ' + options.path);
|
||||
}); });
|
||||
}
|
||||
function prepareJsonFiles() {
|
||||
var parsePromises = [];
|
||||
|
||||
+52
-3
@@ -17,6 +17,8 @@ import * as https from 'https';
|
||||
var util = require('gulp-util');
|
||||
var iconv = require('iconv-lite');
|
||||
|
||||
const NUMBER_OF_CONCURRENT_DOWNLOADS = 1;
|
||||
|
||||
function log(message: any, ...rest: any[]): void {
|
||||
util.log(util.colors.green('[i18n]'), message, ...rest);
|
||||
}
|
||||
@@ -272,6 +274,49 @@ export class XLF {
|
||||
};
|
||||
}
|
||||
|
||||
export interface ITask<T> {
|
||||
(): T;
|
||||
}
|
||||
|
||||
interface ILimitedTaskFactory<T> {
|
||||
factory: ITask<Promise<T>>;
|
||||
c: (value?: T | Thenable<T>) => void;
|
||||
e: (error?: any) => void;
|
||||
}
|
||||
|
||||
export class Limiter<T> {
|
||||
private runningPromises: number;
|
||||
private outstandingPromises: ILimitedTaskFactory<any>[];
|
||||
|
||||
constructor(private maxDegreeOfParalellism: number) {
|
||||
this.outstandingPromises = [];
|
||||
this.runningPromises = 0;
|
||||
}
|
||||
|
||||
queue(factory: ITask<Promise<T>>): Promise<T> {
|
||||
return new Promise<T>((c, e) => {
|
||||
this.outstandingPromises.push({factory, c, e});
|
||||
this.consume();
|
||||
});
|
||||
}
|
||||
|
||||
private consume(): void {
|
||||
while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) {
|
||||
const iLimitedTask = this.outstandingPromises.shift();
|
||||
this.runningPromises++;
|
||||
|
||||
const promise = iLimitedTask.factory();
|
||||
promise.then(iLimitedTask.c).catch(iLimitedTask.e);
|
||||
promise.then(() => this.consumed()).catch(() => this.consumed());
|
||||
}
|
||||
}
|
||||
|
||||
private consumed(): void {
|
||||
this.runningPromises--;
|
||||
this.consume();
|
||||
}
|
||||
}
|
||||
|
||||
const iso639_3_to_2: Map<string> = {
|
||||
'chs': 'zh-cn',
|
||||
'cht': 'zh-tw',
|
||||
@@ -927,9 +972,10 @@ export function pullXlfFiles(projectName: string, apiHostname: string, username:
|
||||
callback();
|
||||
});
|
||||
}
|
||||
const limiter = new Limiter<File>(NUMBER_OF_CONCURRENT_DOWNLOADS);
|
||||
|
||||
function retrieveResource(language: string, resource: Resource, apiHostname, credentials): Promise<File> {
|
||||
return new Promise<File>((resolve, reject) => {
|
||||
return limiter.queue(() => new Promise<File>((resolve, reject) => {
|
||||
const slug = resource.name.replace(/\//g, '_');
|
||||
const project = resource.project;
|
||||
const iso639 = language.toLowerCase();
|
||||
@@ -937,6 +983,7 @@ function retrieveResource(language: string, resource: Resource, apiHostname, cre
|
||||
hostname: apiHostname,
|
||||
path: `/api/2/project/${project}/resource/${slug}/translation/${iso639}?file&mode=onlyreviewed`,
|
||||
auth: credentials,
|
||||
port: 443,
|
||||
method: 'GET'
|
||||
};
|
||||
|
||||
@@ -945,16 +992,18 @@ function retrieveResource(language: string, resource: Resource, apiHostname, cre
|
||||
res.on('data', (chunk: Buffer) => xlfBuffer.push(chunk));
|
||||
res.on('end', () => {
|
||||
if (res.statusCode === 200) {
|
||||
console.log('success: ' + options.path);
|
||||
resolve(new File({ contents: Buffer.concat(xlfBuffer), path: `${project}/${iso639_2_to_3[language]}/${slug}.xlf` }));
|
||||
}
|
||||
reject(`${slug} in ${project} returned no data. Response code: ${res.statusCode}.`);
|
||||
});
|
||||
});
|
||||
request.on('error', (err) => {
|
||||
reject(`Failed to query resource ${slug} with the following error: ${err}`);
|
||||
reject(`Failed to query resource ${slug} with the following error: ${err}. ${options.path}`);
|
||||
});
|
||||
request.end();
|
||||
});
|
||||
console.log('started: ' + options.path);
|
||||
}));
|
||||
}
|
||||
|
||||
export function prepareJsonFiles(): ThroughStream {
|
||||
|
||||
@@ -69,6 +69,7 @@ interface Asset {
|
||||
hash: string;
|
||||
sha256hash: string;
|
||||
size: number;
|
||||
supportsFastUpdate?: boolean;
|
||||
}
|
||||
|
||||
function createOrUpdate(commit: string, quality: string, platform: string, type: string, release: NewDocument, asset: Asset, isUpdate: boolean): Promise<void> {
|
||||
@@ -234,6 +235,13 @@ async function publish(commit: string, quality: string, platform: string, type:
|
||||
size
|
||||
};
|
||||
|
||||
// Remove this if we ever need to rollback fast updates for windows
|
||||
if (/win32/.test(platform)) {
|
||||
asset.supportsFastUpdate = true;
|
||||
}
|
||||
|
||||
console.log('Asset:', JSON.stringify(asset, null, ' '));
|
||||
|
||||
const release = {
|
||||
id: commit,
|
||||
timestamp: (new Date()).getTime(),
|
||||
|
||||
@@ -45,6 +45,10 @@ step "Build minified" {
|
||||
exec { & npm run gulp -- "vscode-win32-$global:arch-min" }
|
||||
}
|
||||
|
||||
step "Copy Inno updater" {
|
||||
exec { & npm run gulp -- "vscode-win32-$global:arch-copy-inno-updater" }
|
||||
}
|
||||
|
||||
# step "Create loader snapshot" {
|
||||
# exec { & node build\lib\snapshotLoader.js --arch=$global:arch }
|
||||
# }
|
||||
|
||||
+82
-8
@@ -18,7 +18,7 @@ OutputDir={#OutputDir}
|
||||
OutputBaseFilename=VSCodeSetup
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
AppMutex={#AppMutex}
|
||||
AppMutex={code:GetAppMutex}
|
||||
SetupMutex={#AppMutex}setup
|
||||
WizardImageFile={#RepoDir}\resources\win32\inno-big.bmp
|
||||
WizardSmallImageFile={#RepoDir}\resources\win32\inno-small.bmp
|
||||
@@ -47,11 +47,15 @@ Name: "simplifiedChinese"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.zh
|
||||
Name: "traditionalChinese"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.zh-tw.isl,{#RepoDir}\build\win32\i18n\messages.zh-tw.isl" {#LocalizedLanguageFile("cht")}
|
||||
|
||||
[InstallDelete]
|
||||
Type: filesandordirs; Name: {app}\resources\app\out
|
||||
Type: filesandordirs; Name: {app}\resources\app\plugins
|
||||
Type: filesandordirs; Name: {app}\resources\app\extensions
|
||||
Type: filesandordirs; Name: {app}\resources\app\node_modules
|
||||
Type: files; Name: {app}\resources\app\Credits_45.0.2454.85.html
|
||||
Type: filesandordirs; Name: "{app}\resources\app\out"; Check: IsNotUpdate
|
||||
Type: filesandordirs; Name: "{app}\resources\app\plugins"; Check: IsNotUpdate
|
||||
Type: filesandordirs; Name: "{app}\resources\app\extensions"; Check: IsNotUpdate
|
||||
Type: filesandordirs; Name: "{app}\resources\app\node_modules"; Check: IsNotUpdate
|
||||
Type: files; Name: "{app}\resources\app\Credits_45.0.2454.85.html"; Check: IsNotUpdate
|
||||
|
||||
[UninstallDelete]
|
||||
Type: filesandordirs; Name: "{app}\_"
|
||||
Type: filesandordirs; Name: "{app}\old"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
@@ -63,7 +67,8 @@ Name: "addtopath"; Description: "{cm:AddToPath}"; GroupDescription: "{cm:Other}"
|
||||
Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{cm:Other}"; Check: WizardSilent
|
||||
|
||||
[Files]
|
||||
Source: "*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "*"; Excludes: "inno_updater.exe"; DestDir: "{code:GetDestDir}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "inno_updater.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; AppUserModelID: "{#AppUserId}"
|
||||
@@ -71,7 +76,7 @@ Name: "{commondesktop}\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; Tasks
|
||||
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; Tasks: quicklaunchicon; AppUserModelID: "{#AppUserId}"
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#ExeBasename}.exe"; Description: "{cm:LaunchProgram,{#NameLong}}"; Tasks: runcode; Flags: nowait postinstall; Check: WizardSilent
|
||||
Filename: "{app}\{#ExeBasename}.exe"; Description: "{cm:LaunchProgram,{#NameLong}}"; Tasks: runcode; Flags: nowait postinstall; Check: ShouldRunAfterUpdate
|
||||
Filename: "{app}\{#ExeBasename}.exe"; Description: "{cm:LaunchProgram,{#NameLong}}"; Flags: nowait postinstall; Check: WizardNotSilent
|
||||
|
||||
[Registry]
|
||||
@@ -955,6 +960,75 @@ begin
|
||||
Result := not WizardSilent();
|
||||
end;
|
||||
|
||||
// Updates
|
||||
function IsBackgroundUpdate(): Boolean;
|
||||
begin
|
||||
Result := ExpandConstant('{param:update|false}') <> 'false';
|
||||
end;
|
||||
|
||||
function IsNotUpdate(): Boolean;
|
||||
begin
|
||||
Result := not IsBackgroundUpdate();
|
||||
end;
|
||||
|
||||
// VS Code will create a flag file before the update starts (/update=C:\foo\bar)
|
||||
// - if the file exists at this point, the user quit Code before the update finished, so don't start Code after update
|
||||
// - otherwise, the user has accepted to apply the update and Code should start
|
||||
function LockFileExists(): Boolean;
|
||||
begin
|
||||
Result := FileExists(ExpandConstant('{param:update}'))
|
||||
end;
|
||||
|
||||
function ShouldRunAfterUpdate(): Boolean;
|
||||
begin
|
||||
if IsBackgroundUpdate() then
|
||||
Result := not LockFileExists()
|
||||
else
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function GetAppMutex(Value: string): string;
|
||||
begin
|
||||
if IsBackgroundUpdate() then
|
||||
Result := ''
|
||||
else
|
||||
Result := '{#AppMutex}';
|
||||
end;
|
||||
|
||||
function GetDestDir(Value: string): string;
|
||||
begin
|
||||
if IsBackgroundUpdate() then
|
||||
Result := ExpandConstant('{app}\_')
|
||||
else
|
||||
Result := ExpandConstant('{app}');
|
||||
end;
|
||||
|
||||
function BoolToStr(Value: Boolean): String;
|
||||
begin
|
||||
if Value then
|
||||
Result := 'true'
|
||||
else
|
||||
Result := 'false';
|
||||
end;
|
||||
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
var
|
||||
UpdateResultCode: Integer;
|
||||
begin
|
||||
if IsBackgroundUpdate() and (CurStep = ssPostInstall) then
|
||||
begin
|
||||
CreateMutex('{#AppMutex}-ready');
|
||||
|
||||
while (CheckForMutexes('{#AppMutex}')) do
|
||||
begin
|
||||
Log('Application is still running, waiting');
|
||||
Sleep(1000);
|
||||
end;
|
||||
|
||||
Exec(ExpandConstant('{app}\inno_updater.exe'), ExpandConstant('_ "{app}\unins000.dat" ' + BoolToStr(LockFileExists())), '', SW_SHOW, ewWaitUntilTerminated, UpdateResultCode);
|
||||
end;
|
||||
end;
|
||||
|
||||
// http://stackoverflow.com/a/23838239/261019
|
||||
procedure Explode(var Dest: TArrayOfString; Text: String; Separator: String);
|
||||
var
|
||||
|
||||
Binary file not shown.
@@ -87,11 +87,11 @@ function registerLocaleCompletionsInLanguageDocument(): vscode.Disposable {
|
||||
function provideContributedLocalesProposals(range: vscode.Range): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList> {
|
||||
const contributedLocales: string[] = [];
|
||||
for (const extension of vscode.extensions.all) {
|
||||
if (extension.packageJSON && extension.packageJSON['contributes'] && extension.packageJSON['contributes']['locales'] && extension.packageJSON['contributes']['locales'].length) {
|
||||
const locales: { locale: string }[] = extension.packageJSON['contributes']['locales'];
|
||||
for (const locale of locales) {
|
||||
if (contributedLocales.indexOf(locale.locale) === -1) {
|
||||
contributedLocales.push(locale.locale);
|
||||
if (extension.packageJSON && extension.packageJSON['contributes'] && extension.packageJSON['contributes']['localizations'] && extension.packageJSON['contributes']['localizations'].length) {
|
||||
const localizations: { languageId: string }[] = extension.packageJSON['contributes']['localizations'];
|
||||
for (const localization of localizations) {
|
||||
if (contributedLocales.indexOf(localization.languageId) === -1) {
|
||||
contributedLocales.push(localization.languageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
func main() {
|
||||
dnsName := "test-vm-from-go"
|
||||
storageAccount := "mystorageaccount"
|
||||
c := make(chan int)
|
||||
|
||||
client, err := management.ClientFromPublishSettingsFile("path/to/downloaded.publishsettings", "")
|
||||
if err != nil {
|
||||
|
||||
@@ -428,6 +428,127 @@
|
||||
"hc_black": "default: #FFFFFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": "c",
|
||||
"t": "source.go variable.other.assignment.go",
|
||||
"r": {
|
||||
"dark_plus": "variable: #9CDCFE",
|
||||
"light_plus": "variable: #001080",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"hc_black": "variable: #9CDCFE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": " ",
|
||||
"t": "source.go",
|
||||
"r": {
|
||||
"dark_plus": "default: #D4D4D4",
|
||||
"light_plus": "default: #000000",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"hc_black": "default: #FFFFFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": ":=",
|
||||
"t": "source.go keyword.operator.assignment.go",
|
||||
"r": {
|
||||
"dark_plus": "keyword.operator: #D4D4D4",
|
||||
"light_plus": "keyword.operator: #000000",
|
||||
"dark_vs": "keyword.operator: #D4D4D4",
|
||||
"light_vs": "keyword.operator: #000000",
|
||||
"hc_black": "keyword.operator: #D4D4D4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": " ",
|
||||
"t": "source.go",
|
||||
"r": {
|
||||
"dark_plus": "default: #D4D4D4",
|
||||
"light_plus": "default: #000000",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"hc_black": "default: #FFFFFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": "make",
|
||||
"t": "source.go support.function.builtin.go",
|
||||
"r": {
|
||||
"dark_plus": "support.function: #DCDCAA",
|
||||
"light_plus": "support.function: #795E26",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"hc_black": "support.function: #DCDCAA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": "(",
|
||||
"t": "source.go punctuation.definition.begin.bracket.round.go",
|
||||
"r": {
|
||||
"dark_plus": "default: #D4D4D4",
|
||||
"light_plus": "default: #000000",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"hc_black": "default: #FFFFFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": "chan",
|
||||
"t": "source.go keyword.channel.go",
|
||||
"r": {
|
||||
"dark_plus": "keyword: #569CD6",
|
||||
"light_plus": "keyword: #0000FF",
|
||||
"dark_vs": "keyword: #569CD6",
|
||||
"light_vs": "keyword: #0000FF",
|
||||
"hc_black": "keyword: #569CD6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": " ",
|
||||
"t": "source.go",
|
||||
"r": {
|
||||
"dark_plus": "default: #D4D4D4",
|
||||
"light_plus": "default: #000000",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"hc_black": "default: #FFFFFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": "int",
|
||||
"t": "source.go storage.type.numeric.go",
|
||||
"r": {
|
||||
"dark_plus": "storage.type.numeric.go: #4EC9B0",
|
||||
"light_plus": "storage.type.numeric.go: #267F99",
|
||||
"dark_vs": "storage.type: #569CD6",
|
||||
"light_vs": "storage.type: #0000FF",
|
||||
"hc_black": "storage.type: #569CD6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": ")",
|
||||
"t": "source.go punctuation.definition.end.bracket.round.go",
|
||||
"r": {
|
||||
"dark_plus": "default: #D4D4D4",
|
||||
"light_plus": "default: #000000",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"hc_black": "default: #FFFFFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": " ",
|
||||
"t": "source.go",
|
||||
"r": {
|
||||
"dark_plus": "default: #D4D4D4",
|
||||
"light_plus": "default: #000000",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"hc_black": "default: #FFFFFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": "client",
|
||||
"t": "source.go variable.other.assignment.go",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "node-debug-placeholder",
|
||||
"name": "node-debug",
|
||||
"version": "1.6.0",
|
||||
"publisher": "vscode",
|
||||
"publisher": "ms-vscode",
|
||||
"engines": {
|
||||
"vscode": "1.6.x"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "node-debug2-placeholder",
|
||||
"name": "node-debug2",
|
||||
"version": "0.0.3",
|
||||
"publisher": "vscode",
|
||||
"publisher": "ms-vscode",
|
||||
"engines": {
|
||||
"vscode": "1.6.x"
|
||||
}
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
"support.type",
|
||||
"entity.name.type",
|
||||
"entity.name.class",
|
||||
"storage.type.numeric.go",
|
||||
"storage.type.byte.go",
|
||||
"storage.type.boolean.go",
|
||||
"storage.type.string.go",
|
||||
"storage.type.uintptr.go",
|
||||
"storage.type.cs",
|
||||
"storage.type.generic.cs",
|
||||
"storage.type.modifier.cs",
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
"support.type",
|
||||
"entity.name.type",
|
||||
"entity.name.class",
|
||||
"storage.type.numeric.go",
|
||||
"storage.type.byte.go",
|
||||
"storage.type.boolean.go",
|
||||
"storage.type.string.go",
|
||||
"storage.type.uintptr.go",
|
||||
"storage.type.cs",
|
||||
"storage.type.generic.cs",
|
||||
"storage.type.modifier.cs",
|
||||
|
||||
@@ -131,10 +131,8 @@ export default class TypeScriptQuickFixProvider implements vscode.CodeActionProv
|
||||
diagnostic: vscode.Diagnostic,
|
||||
tsAction: Proto.CodeFixAction
|
||||
): vscode.CodeAction {
|
||||
const codeAction = new vscode.CodeAction(
|
||||
tsAction.description,
|
||||
getEditForCodeAction(this.client, tsAction));
|
||||
|
||||
const codeAction = new vscode.CodeAction(tsAction.description, vscode.CodeActionKind.QuickFix);
|
||||
codeAction.edit = getEditForCodeAction(this.client, tsAction);
|
||||
codeAction.diagnostics = [diagnostic];
|
||||
if (tsAction.commands) {
|
||||
codeAction.command = {
|
||||
@@ -172,7 +170,8 @@ export default class TypeScriptQuickFixProvider implements vscode.CodeActionProv
|
||||
|
||||
const codeAction = new vscode.CodeAction(
|
||||
localize('fixAllInFileLabel', '{0} (Fix all in file)', tsAction.description),
|
||||
createWorkspaceEditFromFileCodeEdits(this.client, combinedCodeFixesResponse.body.changes));
|
||||
vscode.CodeActionKind.QuickFix);
|
||||
codeAction.edit = createWorkspaceEditFromFileCodeEdits(this.client, combinedCodeFixesResponse.body.changes);
|
||||
codeAction.diagnostics = [diagnostic];
|
||||
if (tsAction.commands) {
|
||||
codeAction.command = {
|
||||
|
||||
@@ -108,13 +108,17 @@ export default class TypeScriptRefactorProvider implements vscode.CodeActionProv
|
||||
public async provideCodeActions(
|
||||
document: vscode.TextDocument,
|
||||
_range: vscode.Range,
|
||||
_context: vscode.CodeActionContext,
|
||||
context: vscode.CodeActionContext,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.CodeAction[]> {
|
||||
if (!this.client.apiVersion.has240Features()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (context.only && !vscode.CodeActionKind.Refactor.contains(context.only)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!vscode.window.activeTextEditor) {
|
||||
return [];
|
||||
}
|
||||
@@ -140,24 +144,22 @@ export default class TypeScriptRefactorProvider implements vscode.CodeActionProv
|
||||
const actions: vscode.CodeAction[] = [];
|
||||
for (const info of response.body) {
|
||||
if (info.inlineable === false) {
|
||||
actions.push({
|
||||
const codeAction = new vscode.CodeAction(info.description, vscode.CodeActionKind.Refactor);
|
||||
codeAction.command = {
|
||||
title: info.description,
|
||||
command: {
|
||||
title: info.description,
|
||||
command: SelectRefactorCommand.ID,
|
||||
arguments: [document, file, info, range]
|
||||
}
|
||||
});
|
||||
command: SelectRefactorCommand.ID,
|
||||
arguments: [document, file, info, range]
|
||||
};
|
||||
actions.push(codeAction);
|
||||
} else {
|
||||
for (const action of info.actions) {
|
||||
actions.push({
|
||||
const codeAction = new vscode.CodeAction(action.description, TypeScriptRefactorProvider.getKind(action));
|
||||
codeAction.command = {
|
||||
title: action.description,
|
||||
command: {
|
||||
title: action.description,
|
||||
command: ApplyRefactoringCommand.ID,
|
||||
arguments: [document, file, info.name, action.name, range]
|
||||
}
|
||||
});
|
||||
command: ApplyRefactoringCommand.ID,
|
||||
arguments: [document, file, info.name, action.name, range]
|
||||
};
|
||||
actions.push(codeAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,4 +168,11 @@ export default class TypeScriptRefactorProvider implements vscode.CodeActionProv
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private static getKind(refactor: Proto.RefactorActionInfo) {
|
||||
if (refactor.name.startsWith('function_')) {
|
||||
return vscode.CodeActionKind.RefactorExtract.append('function');
|
||||
}
|
||||
return vscode.CodeActionKind.Refactor;
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.20.0",
|
||||
"distro": "3e04fd7a1141705f5e82fb1175edc03d0d4ddf9c",
|
||||
"distro": "e14a5a3afaae557ff651b344462ed39e776435a2",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
@@ -45,7 +45,7 @@
|
||||
"vscode-debugprotocol": "1.25.0",
|
||||
"vscode-ripgrep": "^0.7.1-patch.0",
|
||||
"vscode-textmate": "^3.2.0",
|
||||
"vscode-xterm": "3.1.0-beta5",
|
||||
"vscode-xterm": "3.1.0-beta7",
|
||||
"yauzl": "2.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -129,4 +129,4 @@
|
||||
"windows-mutex": "^0.2.0",
|
||||
"windows-process-tree": "0.1.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,9 @@ function code() {
|
||||
# Get electron
|
||||
node build/lib/electron.js || ./node_modules/.bin/gulp electron
|
||||
|
||||
# Get built-in extensions
|
||||
node build/lib/builtInExtensions.js || ./node_modules/.bin/gulp builtInExtensions
|
||||
|
||||
# Build
|
||||
test -d out || ./node_modules/.bin/gulp compile
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -6,13 +6,13 @@
|
||||
/// <reference path='./node.d.ts'/>
|
||||
|
||||
declare module 'iconv-lite' {
|
||||
export function decode(buffer: NodeBuffer, encoding: string, options?: any): string;
|
||||
export function decode(buffer: NodeBuffer, encoding: string): string;
|
||||
|
||||
export function encode(content: string, encoding: string, options?: any): NodeBuffer;
|
||||
export function encode(content: string, encoding: string, options?: { addBOM?: boolean }): NodeBuffer;
|
||||
|
||||
export function encodingExists(encoding: string): boolean;
|
||||
|
||||
export function decodeStream(encoding: string): NodeJS.ReadWriteStream;
|
||||
|
||||
export function encodeStream(encoding: string): NodeJS.ReadWriteStream;
|
||||
export function encodeStream(encoding: string, options?: { addBOM?: boolean }): NodeJS.ReadWriteStream;
|
||||
}
|
||||
Vendored
+1
@@ -1720,6 +1720,7 @@ declare module "child_process" {
|
||||
uid?: number;
|
||||
gid?: number;
|
||||
shell?: boolean | string;
|
||||
windowsVerbatimArguments?: boolean;
|
||||
}
|
||||
export function spawn(command: string, args?: string[], options?: SpawnOptions): ChildProcess;
|
||||
|
||||
|
||||
@@ -9,4 +9,6 @@ declare module 'windows-mutex' {
|
||||
isActive(): boolean;
|
||||
release(): void;
|
||||
}
|
||||
|
||||
export function isActive(name: string): boolean;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import 'vs/css!./list';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { range } from 'vs/base/common/arrays';
|
||||
import { IDelegate, IRenderer, IListEvent } from './list';
|
||||
import { List, IListCreationOptions, IListStyles, IListOptions } from './listWidget';
|
||||
import { List, IListStyles, IListOptions } from './listWidget';
|
||||
import { IPagedModel } from 'vs/base/common/paging';
|
||||
import Event, { mapEvent } from 'vs/base/common/event';
|
||||
|
||||
@@ -67,7 +67,7 @@ export class PagedList<T> {
|
||||
container: HTMLElement,
|
||||
delegate: IDelegate<number>,
|
||||
renderers: IPagedRenderer<T, any>[],
|
||||
options: IListCreationOptions<any> = {} // TODO@Joao: should be IListOptions<T>
|
||||
options: IListOptions<any> = {}
|
||||
) {
|
||||
const pagedRenderers = renderers.map(r => new PagedRenderer<T, ITemplateData<T>>(r, () => this.model));
|
||||
this.list = new List(container, delegate, pagedRenderers, options);
|
||||
@@ -181,8 +181,4 @@ export class PagedList<T> {
|
||||
style(styles: IListStyles): void {
|
||||
this.list.style(styles);
|
||||
}
|
||||
|
||||
updateOptions(options: IListOptions): void {
|
||||
this.list.updateOptions(options);
|
||||
}
|
||||
}
|
||||
@@ -265,7 +265,7 @@ class KeyboardController<T> implements IDisposable {
|
||||
constructor(
|
||||
private list: List<T>,
|
||||
private view: ListView<T>,
|
||||
options: IListCreationOptions<T>
|
||||
options: IListOptions<T>
|
||||
) {
|
||||
const multipleSelectionSupport = !(options.multipleSelectionSupport === false);
|
||||
this.disposables = [];
|
||||
@@ -344,9 +344,23 @@ class KeyboardController<T> implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
export function isSelectionSingleChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
return platform.isMacintosh ? event.browserEvent.metaKey : event.browserEvent.ctrlKey;
|
||||
}
|
||||
|
||||
export function isSelectionRangeChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
return event.browserEvent.shiftKey;
|
||||
}
|
||||
|
||||
const DefaultMultipleSelectionContoller = {
|
||||
isSelectionSingleChangeEvent,
|
||||
isSelectionRangeChangeEvent
|
||||
};
|
||||
|
||||
class MouseController<T> implements IDisposable {
|
||||
|
||||
private multipleSelectionSupport: boolean;
|
||||
private multipleSelectionController: IMultipleSelectionController<T> | undefined;
|
||||
private didJustPressContextMenuKey: boolean = false;
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
@@ -384,9 +398,13 @@ class MouseController<T> implements IDisposable {
|
||||
constructor(
|
||||
private list: List<T>,
|
||||
private view: ListView<T>,
|
||||
private options: IListCreationOptions<T> = {}
|
||||
private options: IListOptions<T> = {}
|
||||
) {
|
||||
this.multipleSelectionSupport = options.multipleSelectionSupport !== false;
|
||||
this.multipleSelectionSupport = !(options.multipleSelectionSupport === false);
|
||||
|
||||
if (this.multipleSelectionSupport) {
|
||||
this.multipleSelectionController = options.multipleSelectionController || DefaultMultipleSelectionContoller;
|
||||
}
|
||||
|
||||
view.onMouseDown(this.onMouseDown, this, this.disposables);
|
||||
view.onMouseClick(this.onPointer, this, this.disposables);
|
||||
@@ -396,19 +414,19 @@ class MouseController<T> implements IDisposable {
|
||||
Gesture.addTarget(view.domNode);
|
||||
}
|
||||
|
||||
updateOptions(options: IListOptions): void {
|
||||
this.options.useAltAsMultiSelectModifier = options.useAltAsMultiSelectModifier;
|
||||
}
|
||||
|
||||
private isSelectionSingleChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
if (this.options.useAltAsMultiSelectModifier) {
|
||||
return event.browserEvent.altKey;
|
||||
if (this.multipleSelectionController) {
|
||||
return this.multipleSelectionController.isSelectionSingleChangeEvent(event);
|
||||
}
|
||||
|
||||
return platform.isMacintosh ? event.browserEvent.metaKey : event.browserEvent.ctrlKey;
|
||||
}
|
||||
|
||||
private isSelectionRangeChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
if (this.multipleSelectionController) {
|
||||
return this.multipleSelectionController.isSelectionRangeChangeEvent(event);
|
||||
}
|
||||
|
||||
return event.browserEvent.shiftKey;
|
||||
}
|
||||
|
||||
@@ -500,11 +518,12 @@ class MouseController<T> implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IListOptions {
|
||||
useAltAsMultiSelectModifier?: boolean;
|
||||
export interface IMultipleSelectionController<T> {
|
||||
isSelectionSingleChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean;
|
||||
isSelectionRangeChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean;
|
||||
}
|
||||
|
||||
export interface IListCreationOptions<T> extends IListViewOptions, IListStyles, IListOptions {
|
||||
export interface IListOptions<T> extends IListViewOptions, IListStyles {
|
||||
identityProvider?: IIdentityProvider<T>;
|
||||
ariaLabel?: string;
|
||||
mouseSupport?: boolean;
|
||||
@@ -513,6 +532,7 @@ export interface IListCreationOptions<T> extends IListViewOptions, IListStyles,
|
||||
keyboardSupport?: boolean;
|
||||
verticalScrollMode?: ScrollbarVisibility;
|
||||
multipleSelectionSupport?: boolean;
|
||||
multipleSelectionController?: IMultipleSelectionController<T>;
|
||||
}
|
||||
|
||||
export interface IListStyles {
|
||||
@@ -545,7 +565,7 @@ const defaultStyles: IListStyles = {
|
||||
listDropBackground: Color.fromHex('#383B3D')
|
||||
};
|
||||
|
||||
const DefaultOptions: IListCreationOptions<any> = {
|
||||
const DefaultOptions: IListOptions<any> = {
|
||||
keyboardSupport: true,
|
||||
mouseSupport: true,
|
||||
multipleSelectionSupport: true
|
||||
@@ -722,7 +742,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
container: HTMLElement,
|
||||
delegate: IDelegate<T>,
|
||||
renderers: IRenderer<T, any>[],
|
||||
options: IListCreationOptions<T> = DefaultOptions
|
||||
options: IListOptions<T> = DefaultOptions
|
||||
) {
|
||||
const aria = new Aria();
|
||||
this.focus = new FocusTrait(i => this.getElementDomId(i));
|
||||
@@ -772,12 +792,6 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
this.style(options);
|
||||
}
|
||||
|
||||
updateOptions(options: IListOptions): void {
|
||||
if (this.mouseController) {
|
||||
this.mouseController.updateOptions(options);
|
||||
}
|
||||
}
|
||||
|
||||
splice(start: number, deleteCount: number, elements: T[] = []): void {
|
||||
if (deleteCount === 0 && elements.length === 0) {
|
||||
return;
|
||||
|
||||
@@ -10,11 +10,12 @@ import nls = require('vs/nls');
|
||||
import mimes = require('vs/base/common/mime');
|
||||
import URI from 'vs/base/common/uri';
|
||||
import paths = require('vs/base/common/paths');
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { Builder, $, Dimension } from 'vs/base/browser/builder';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { clamp } from 'vs/base/common/numbers';
|
||||
|
||||
interface MapExtToMediaMimes {
|
||||
[index: string]: string;
|
||||
@@ -78,30 +79,39 @@ export interface IResourceDescriptor {
|
||||
mime: string;
|
||||
}
|
||||
|
||||
// Chrome is caching images very aggressively and so we use the ETag information to find out if
|
||||
// we need to bypass the cache or not. We could always bypass the cache everytime we show the image
|
||||
// however that has very bad impact on memory consumption because each time the image gets shown,
|
||||
// memory grows (see also https://github.com/electron/electron/issues/6275)
|
||||
const IMAGE_RESOURCE_ETAG_CACHE = new LRUCache<string, { etag: string, src: string }>(100);
|
||||
function imageSrc(descriptor: IResourceDescriptor): string {
|
||||
if (descriptor.resource.scheme === Schemas.data) {
|
||||
return descriptor.resource.toString(true /* skip encoding */);
|
||||
enum ScaleDirection {
|
||||
IN, OUT,
|
||||
}
|
||||
|
||||
class BinarySize {
|
||||
public static readonly KB = 1024;
|
||||
public static readonly MB = BinarySize.KB * BinarySize.KB;
|
||||
public static readonly GB = BinarySize.MB * BinarySize.KB;
|
||||
public static readonly TB = BinarySize.GB * BinarySize.KB;
|
||||
|
||||
public static formatSize(size: number): string {
|
||||
if (size < BinarySize.KB) {
|
||||
return nls.localize('sizeB', "{0}B", size);
|
||||
}
|
||||
|
||||
if (size < BinarySize.MB) {
|
||||
return nls.localize('sizeKB', "{0}KB", (size / BinarySize.KB).toFixed(2));
|
||||
}
|
||||
|
||||
if (size < BinarySize.GB) {
|
||||
return nls.localize('sizeMB', "{0}MB", (size / BinarySize.MB).toFixed(2));
|
||||
}
|
||||
|
||||
if (size < BinarySize.TB) {
|
||||
return nls.localize('sizeGB', "{0}GB", (size / BinarySize.GB).toFixed(2));
|
||||
}
|
||||
|
||||
return nls.localize('sizeTB', "{0}TB", (size / BinarySize.TB).toFixed(2));
|
||||
}
|
||||
}
|
||||
|
||||
const src = descriptor.resource.toString();
|
||||
|
||||
let cached = IMAGE_RESOURCE_ETAG_CACHE.get(src);
|
||||
if (!cached) {
|
||||
cached = { etag: descriptor.etag, src };
|
||||
IMAGE_RESOURCE_ETAG_CACHE.set(src, cached);
|
||||
}
|
||||
|
||||
if (cached.etag !== descriptor.etag) {
|
||||
cached.etag = descriptor.etag;
|
||||
cached.src = `${src}?${Date.now()}`; // bypass cache with this trick
|
||||
}
|
||||
|
||||
return cached.src;
|
||||
export interface ResourceViewerContext {
|
||||
layout(dimension: Dimension);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,26 +119,30 @@ function imageSrc(descriptor: IResourceDescriptor): string {
|
||||
* progress of the binary resource.
|
||||
*/
|
||||
export class ResourceViewer {
|
||||
|
||||
private static readonly KB = 1024;
|
||||
private static readonly MB = ResourceViewer.KB * ResourceViewer.KB;
|
||||
private static readonly GB = ResourceViewer.MB * ResourceViewer.KB;
|
||||
private static readonly TB = ResourceViewer.GB * ResourceViewer.KB;
|
||||
|
||||
private static readonly MAX_IMAGE_SIZE = ResourceViewer.MB; // showing images inline is memory intense, so we have a limit
|
||||
|
||||
public static show(
|
||||
descriptor: IResourceDescriptor,
|
||||
container: Builder,
|
||||
scrollbar: DomScrollableElement,
|
||||
openExternal: (uri: URI) => void,
|
||||
metadataClb?: (meta: string) => void
|
||||
): void {
|
||||
|
||||
metadataClb: (meta: string) => void
|
||||
): ResourceViewerContext {
|
||||
// Ensure CSS class
|
||||
$(container).setClass('monaco-resource-viewer');
|
||||
|
||||
// Lookup media mime if any
|
||||
if (ResourceViewer.isImageResource(descriptor)) {
|
||||
return ImageView.create(container, descriptor, scrollbar, openExternal, metadataClb);
|
||||
}
|
||||
|
||||
GenericBinaryFileView.create(container, metadataClb, descriptor, scrollbar);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static isImageResource(descriptor: IResourceDescriptor) {
|
||||
const mime = ResourceViewer.getMime(descriptor);
|
||||
return mime.indexOf('image/') >= 0;
|
||||
}
|
||||
|
||||
private static getMime(descriptor: IResourceDescriptor): string {
|
||||
let mime = descriptor.mime;
|
||||
if (!mime && descriptor.resource.scheme === Schemas.file) {
|
||||
const ext = paths.extname(descriptor.resource.toString());
|
||||
@@ -136,72 +150,29 @@ export class ResourceViewer {
|
||||
mime = mapExtToMediaMimes[ext.toLowerCase()];
|
||||
}
|
||||
}
|
||||
return mime || mimes.MIME_BINARY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mime) {
|
||||
mime = mimes.MIME_BINARY;
|
||||
class ImageView {
|
||||
private static readonly MAX_IMAGE_SIZE = BinarySize.MB; // showing images inline is memory intense, so we have a limit
|
||||
|
||||
public static create(
|
||||
container: Builder,
|
||||
descriptor: IResourceDescriptor,
|
||||
scrollbar: DomScrollableElement,
|
||||
openExternal: (uri: URI) => void,
|
||||
metadataClb: (meta: string) => void
|
||||
): ResourceViewerContext | null {
|
||||
if (ImageView.shouldShowImageInline(descriptor)) {
|
||||
return InlineImageView.create(container, descriptor, scrollbar, metadataClb);
|
||||
}
|
||||
|
||||
// Show Image inline unless they are large
|
||||
if (mime.indexOf('image/') >= 0) {
|
||||
if (ResourceViewer.inlineImage(descriptor)) {
|
||||
$(container)
|
||||
.empty()
|
||||
.addClass('image')
|
||||
.img({ src: imageSrc(descriptor) })
|
||||
.on(DOM.EventType.LOAD, (e, img) => {
|
||||
const imgElement = <HTMLImageElement>img.getHTMLElement();
|
||||
if (imgElement.naturalWidth > imgElement.width || imgElement.naturalHeight > imgElement.height) {
|
||||
$(container).addClass('oversized');
|
||||
|
||||
img.on(DOM.EventType.CLICK, (e, img) => {
|
||||
$(container).toggleClass('full-size');
|
||||
|
||||
scrollbar.scanDomNode();
|
||||
});
|
||||
}
|
||||
|
||||
if (metadataClb) {
|
||||
metadataClb(nls.localize('imgMeta', "{0}x{1} {2}", imgElement.naturalWidth, imgElement.naturalHeight, ResourceViewer.formatSize(descriptor.size)));
|
||||
}
|
||||
|
||||
scrollbar.scanDomNode();
|
||||
});
|
||||
} else {
|
||||
const imageContainer = $(container)
|
||||
.empty()
|
||||
.p({
|
||||
text: nls.localize('largeImageError', "The image is too large to display in the editor. ")
|
||||
});
|
||||
|
||||
if (descriptor.resource.scheme !== Schemas.data) {
|
||||
imageContainer.append($('a', {
|
||||
role: 'button',
|
||||
class: 'open-external',
|
||||
text: nls.localize('resourceOpenExternalButton', "Open image using external program?")
|
||||
}).on(DOM.EventType.CLICK, (e) => {
|
||||
openExternal(descriptor.resource);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle generic Binary Files
|
||||
else {
|
||||
$(container)
|
||||
.empty()
|
||||
.span({
|
||||
text: nls.localize('nativeBinaryError', "The file will not be displayed in the editor because it is either binary, very large or uses an unsupported text encoding.")
|
||||
});
|
||||
|
||||
if (metadataClb) {
|
||||
metadataClb(ResourceViewer.formatSize(descriptor.size));
|
||||
}
|
||||
|
||||
scrollbar.scanDomNode();
|
||||
}
|
||||
LargeImageView.create(container, descriptor, openExternal);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static inlineImage(descriptor: IResourceDescriptor): boolean {
|
||||
private static shouldShowImageInline(descriptor: IResourceDescriptor): boolean {
|
||||
let skipInlineImage: boolean;
|
||||
|
||||
// Data URI
|
||||
@@ -210,34 +181,196 @@ export class ResourceViewer {
|
||||
const base64MarkerIndex = descriptor.resource.path.indexOf(BASE64_MARKER);
|
||||
const hasData = base64MarkerIndex >= 0 && descriptor.resource.path.substring(base64MarkerIndex + BASE64_MARKER.length).length > 0;
|
||||
|
||||
skipInlineImage = !hasData || descriptor.size > ResourceViewer.MAX_IMAGE_SIZE || descriptor.resource.path.length > ResourceViewer.MAX_IMAGE_SIZE;
|
||||
skipInlineImage = !hasData || descriptor.size > ImageView.MAX_IMAGE_SIZE || descriptor.resource.path.length > ImageView.MAX_IMAGE_SIZE;
|
||||
}
|
||||
|
||||
// File URI
|
||||
else {
|
||||
skipInlineImage = typeof descriptor.size !== 'number' || descriptor.size > ResourceViewer.MAX_IMAGE_SIZE;
|
||||
skipInlineImage = typeof descriptor.size !== 'number' || descriptor.size > ImageView.MAX_IMAGE_SIZE;
|
||||
}
|
||||
|
||||
return !skipInlineImage;
|
||||
}
|
||||
}
|
||||
|
||||
private static formatSize(size: number): string {
|
||||
if (size < ResourceViewer.KB) {
|
||||
return nls.localize('sizeB', "{0}B", size);
|
||||
class LargeImageView {
|
||||
public static create(
|
||||
container: Builder,
|
||||
descriptor: IResourceDescriptor,
|
||||
openExternal: (uri: URI) => void
|
||||
) {
|
||||
const imageContainer = $(container)
|
||||
.empty()
|
||||
.p({
|
||||
text: nls.localize('largeImageError', "The image is too large to display in the editor. ")
|
||||
});
|
||||
|
||||
if (descriptor.resource.scheme !== Schemas.data) {
|
||||
imageContainer.append($('a', {
|
||||
role: 'button',
|
||||
class: 'open-external',
|
||||
text: nls.localize('resourceOpenExternalButton', "Open image using external program?")
|
||||
}).on(DOM.EventType.CLICK, (e) => {
|
||||
openExternal(descriptor.resource);
|
||||
}));
|
||||
}
|
||||
|
||||
if (size < ResourceViewer.MB) {
|
||||
return nls.localize('sizeKB', "{0}KB", (size / ResourceViewer.KB).toFixed(2));
|
||||
}
|
||||
|
||||
if (size < ResourceViewer.GB) {
|
||||
return nls.localize('sizeMB', "{0}MB", (size / ResourceViewer.MB).toFixed(2));
|
||||
}
|
||||
|
||||
if (size < ResourceViewer.TB) {
|
||||
return nls.localize('sizeGB', "{0}GB", (size / ResourceViewer.GB).toFixed(2));
|
||||
}
|
||||
|
||||
return nls.localize('sizeTB', "{0}TB", (size / ResourceViewer.TB).toFixed(2));
|
||||
}
|
||||
}
|
||||
|
||||
class GenericBinaryFileView {
|
||||
public static create(
|
||||
container: Builder,
|
||||
metadataClb: (meta: string) => void,
|
||||
descriptor: IResourceDescriptor,
|
||||
scrollbar: DomScrollableElement
|
||||
) {
|
||||
$(container)
|
||||
.empty()
|
||||
.span({
|
||||
text: nls.localize('nativeBinaryError', "The file will not be displayed in the editor because it is either binary, very large or uses an unsupported text encoding.")
|
||||
});
|
||||
if (metadataClb) {
|
||||
metadataClb(BinarySize.formatSize(descriptor.size));
|
||||
}
|
||||
scrollbar.scanDomNode();
|
||||
}
|
||||
}
|
||||
|
||||
class InlineImageView {
|
||||
private static readonly SCALE_PINCH_FACTOR = 0.1;
|
||||
private static readonly SCALE_FACTOR = 1.5;
|
||||
private static readonly MAX_SCALE = 20;
|
||||
private static readonly MIN_SCALE = 0.1;
|
||||
private static readonly PIXELATION_THRESHOLD = 64; // enable image-rendering: pixelated for images less than this
|
||||
|
||||
/**
|
||||
* Chrome is caching images very aggressively and so we use the ETag information to find out if
|
||||
* we need to bypass the cache or not. We could always bypass the cache everytime we show the image
|
||||
* however that has very bad impact on memory consumption because each time the image gets shown,
|
||||
* memory grows (see also https://github.com/electron/electron/issues/6275)
|
||||
*/
|
||||
private static IMAGE_RESOURCE_ETAG_CACHE = new LRUCache<string, { etag: string, src: string }>(100);
|
||||
|
||||
/**
|
||||
* Store the scale of an image so it can be restored when changing editor tabs
|
||||
*/
|
||||
private static readonly IMAGE_SCALE_CACHE = new LRUCache<string, number>(100);
|
||||
|
||||
public static create(
|
||||
container: Builder,
|
||||
descriptor: IResourceDescriptor,
|
||||
scrollbar: DomScrollableElement,
|
||||
metadataClb: (meta: string) => void
|
||||
) {
|
||||
const context = {
|
||||
layout(dimension: Dimension) { }
|
||||
};
|
||||
$(container)
|
||||
.empty()
|
||||
.addClass('image', 'zoom-in')
|
||||
.img({ src: InlineImageView.imageSrc(descriptor) })
|
||||
.addClass('untouched')
|
||||
.on(DOM.EventType.LOAD, (e, img) => {
|
||||
const imgElement = <HTMLImageElement>img.getHTMLElement();
|
||||
const cacheKey = descriptor.resource.toString();
|
||||
let scaleDirection = ScaleDirection.IN;
|
||||
let scale = InlineImageView.IMAGE_SCALE_CACHE.get(cacheKey) || null;
|
||||
if (scale) {
|
||||
img.removeClass('untouched');
|
||||
updateScale(scale);
|
||||
}
|
||||
if (imgElement.naturalWidth < InlineImageView.PIXELATION_THRESHOLD
|
||||
|| imgElement.naturalHeight < InlineImageView.PIXELATION_THRESHOLD) {
|
||||
img.addClass('pixelated');
|
||||
}
|
||||
function setImageWidth(width) {
|
||||
img.style('width', `${width}px`);
|
||||
img.style('height', 'auto');
|
||||
}
|
||||
function updateScale(newScale) {
|
||||
scale = clamp(newScale, InlineImageView.MIN_SCALE, InlineImageView.MAX_SCALE);
|
||||
setImageWidth(Math.floor(imgElement.naturalWidth * scale));
|
||||
InlineImageView.IMAGE_SCALE_CACHE.set(cacheKey, scale);
|
||||
scrollbar.scanDomNode();
|
||||
updateMetadata();
|
||||
}
|
||||
function updateMetadata() {
|
||||
if (metadataClb) {
|
||||
const scale = Math.round((imgElement.width / imgElement.naturalWidth) * 10000) / 100;
|
||||
metadataClb(nls.localize('imgMeta', '{0}% {1}x{2} {3}', scale, imgElement.naturalWidth, imgElement.naturalHeight, BinarySize.formatSize(descriptor.size)));
|
||||
}
|
||||
}
|
||||
context.layout = updateMetadata;
|
||||
function firstZoom() {
|
||||
const { clientWidth, naturalWidth } = imgElement;
|
||||
setImageWidth(clientWidth);
|
||||
img.removeClass('untouched');
|
||||
scale = clientWidth / naturalWidth;
|
||||
}
|
||||
$(container)
|
||||
.on(DOM.EventType.KEY_DOWN, (e: KeyboardEvent, c) => {
|
||||
if (e.altKey) {
|
||||
scaleDirection = ScaleDirection.OUT;
|
||||
c.removeClass('zoom-in').addClass('zoom-out');
|
||||
}
|
||||
})
|
||||
.on(DOM.EventType.KEY_UP, (e: KeyboardEvent, c) => {
|
||||
if (!e.altKey) {
|
||||
scaleDirection = ScaleDirection.IN;
|
||||
c.removeClass('zoom-out').addClass('zoom-in');
|
||||
}
|
||||
});
|
||||
$(container).on(DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
|
||||
if (scale === null) {
|
||||
firstZoom();
|
||||
}
|
||||
// right click
|
||||
if (e.button === 2) {
|
||||
updateScale(1);
|
||||
}
|
||||
else {
|
||||
const scaleFactor = scaleDirection === ScaleDirection.IN
|
||||
? InlineImageView.SCALE_FACTOR
|
||||
: 1 / InlineImageView.SCALE_FACTOR;
|
||||
updateScale(scale * scaleFactor);
|
||||
}
|
||||
});
|
||||
$(container).on(DOM.EventType.WHEEL, (e: WheelEvent) => {
|
||||
// pinching is reported as scroll wheel + ctrl
|
||||
if (!e.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
if (scale === null) {
|
||||
firstZoom();
|
||||
}
|
||||
// scrolling up, pinching out should increase the scale
|
||||
const delta = -e.deltaY;
|
||||
updateScale(scale + delta * InlineImageView.SCALE_PINCH_FACTOR);
|
||||
});
|
||||
updateMetadata();
|
||||
scrollbar.scanDomNode();
|
||||
});
|
||||
return context;
|
||||
}
|
||||
|
||||
private static imageSrc(descriptor: IResourceDescriptor): string {
|
||||
if (descriptor.resource.scheme === Schemas.data) {
|
||||
return descriptor.resource.toString(true /* skip encoding */);
|
||||
}
|
||||
|
||||
const src = descriptor.resource.toString();
|
||||
|
||||
let cached = InlineImageView.IMAGE_RESOURCE_ETAG_CACHE.get(src);
|
||||
if (!cached) {
|
||||
cached = { etag: descriptor.etag, src };
|
||||
InlineImageView.IMAGE_RESOURCE_ETAG_CACHE.set(src, cached);
|
||||
}
|
||||
|
||||
if (cached.etag !== descriptor.etag) {
|
||||
cached.etag = descriptor.etag;
|
||||
cached.src = `${src}?${Date.now()}`; // bypass cache with this trick
|
||||
}
|
||||
|
||||
return cached.src;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
padding: 10px 10px 0 10px;
|
||||
background-position: 0 0, 8px 8px;
|
||||
background-size: 16px 16px;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer.image.full-size {
|
||||
@@ -34,18 +35,25 @@
|
||||
linear-gradient(45deg, rgb(20, 20, 20) 25%, transparent 25%, transparent 75%, rgb(20, 20, 20) 75%, rgb(20, 20, 20));
|
||||
}
|
||||
|
||||
.monaco-resource-viewer img {
|
||||
max-width: 100%;
|
||||
max-height: calc(100% - 10px); /* somehow this prevents scrollbars from showing up */
|
||||
.monaco-resource-viewer img.pixelated {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer.oversized img {
|
||||
.monaco-resource-viewer img.untouched {
|
||||
max-width: 100%;
|
||||
object-fit: contain;
|
||||
image-rendering: auto;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer img {
|
||||
margin: auto; /* centers the image */
|
||||
}
|
||||
|
||||
.monaco-resource-viewer.zoom-in {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer.full-size img {
|
||||
max-width: initial;
|
||||
max-height: initial;
|
||||
.monaco-resource-viewer.zoom-out {
|
||||
cursor: zoom-out;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,11 +28,11 @@ export function bomLength(encoding: string): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function decode(buffer: NodeBuffer, encoding: string, options?: any): string {
|
||||
return iconv.decode(buffer, toNodeEncoding(encoding), options);
|
||||
export function decode(buffer: NodeBuffer, encoding: string): string {
|
||||
return iconv.decode(buffer, toNodeEncoding(encoding));
|
||||
}
|
||||
|
||||
export function encode(content: string, encoding: string, options?: any): NodeBuffer {
|
||||
export function encode(content: string, encoding: string, options?: { addBOM?: boolean }): NodeBuffer {
|
||||
return iconv.encode(content, toNodeEncoding(encoding), options);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,10 @@ export function decodeStream(encoding: string): NodeJS.ReadWriteStream {
|
||||
return iconv.decodeStream(toNodeEncoding(encoding));
|
||||
}
|
||||
|
||||
export function encodeStream(encoding: string, options?: { addBOM?: boolean }): NodeJS.ReadWriteStream {
|
||||
return iconv.encodeStream(toNodeEncoding(encoding), options);
|
||||
}
|
||||
|
||||
function toNodeEncoding(enc: string): string {
|
||||
if (enc === UTF8_with_bom) {
|
||||
return UTF8; // iconv does not distinguish UTF 8 with or without BOM, so we need to help it
|
||||
|
||||
+125
-40
@@ -54,7 +54,7 @@ export function copy(source: string, target: string, callback: (error: Error) =>
|
||||
}
|
||||
|
||||
if (!stat.isDirectory()) {
|
||||
return pipeFs(source, target, stat.mode & 511, callback);
|
||||
return doCopyFile(source, target, stat.mode & 511, callback);
|
||||
}
|
||||
|
||||
if (copiedSources[source]) {
|
||||
@@ -75,6 +75,38 @@ export function copy(source: string, target: string, callback: (error: Error) =>
|
||||
});
|
||||
}
|
||||
|
||||
function doCopyFile(source: string, target: string, mode: number, callback: (error: Error) => void): void {
|
||||
const reader = fs.createReadStream(source);
|
||||
const writer = fs.createWriteStream(target, { mode });
|
||||
|
||||
let finished = false;
|
||||
const finish = (error?: Error) => {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
|
||||
// in error cases, pass to callback
|
||||
if (error) {
|
||||
callback(error);
|
||||
}
|
||||
|
||||
// we need to explicitly chmod because of https://github.com/nodejs/node/issues/1104
|
||||
else {
|
||||
fs.chmod(target, mode, callback);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// handle errors properly
|
||||
reader.once('error', error => finish(error));
|
||||
writer.once('error', error => finish(error));
|
||||
|
||||
// we are done (underlying fd has been closed)
|
||||
writer.once('close', () => finish());
|
||||
|
||||
// start piping
|
||||
reader.pipe(writer);
|
||||
}
|
||||
|
||||
export function mkdirp(path: string, mode?: number): TPromise<boolean> {
|
||||
const mkdir = () => nfcall(fs.mkdir, path, mode)
|
||||
.then(null, (err: NodeJS.ErrnoException) => {
|
||||
@@ -88,11 +120,12 @@ export function mkdirp(path: string, mode?: number): TPromise<boolean> {
|
||||
return TPromise.wrapError<boolean>(err);
|
||||
});
|
||||
|
||||
// is root?
|
||||
// stop at root
|
||||
if (path === paths.dirname(path)) {
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
// recursively mkdir
|
||||
return mkdir().then(null, (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === 'ENOENT') {
|
||||
return mkdirp(paths.dirname(path), mode).then(mkdir);
|
||||
@@ -102,40 +135,6 @@ export function mkdirp(path: string, mode?: number): TPromise<boolean> {
|
||||
});
|
||||
}
|
||||
|
||||
function pipeFs(source: string, target: string, mode: number, callback: (error: Error) => void): void {
|
||||
let callbackHandled = false;
|
||||
|
||||
const readStream = fs.createReadStream(source);
|
||||
const writeStream = fs.createWriteStream(target, { mode: mode });
|
||||
|
||||
const onError = (error: Error) => {
|
||||
if (!callbackHandled) {
|
||||
callbackHandled = true;
|
||||
callback(error);
|
||||
}
|
||||
};
|
||||
|
||||
readStream.on('error', onError);
|
||||
writeStream.on('error', onError);
|
||||
|
||||
readStream.on('end', () => {
|
||||
(<any>writeStream).end(() => { // In this case the write stream is known to have an end signature with callback
|
||||
if (!callbackHandled) {
|
||||
callbackHandled = true;
|
||||
|
||||
fs.chmod(target, mode, callback); // we need to explicitly chmod because of https://github.com/nodejs/node/issues/1104
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// In node 0.8 there is no easy way to find out when the pipe operation has finished. As such, we use the end property = false
|
||||
// so that we are in charge of calling end() on the write stream and we will be notified when the write stream is really done.
|
||||
// We can do this because file streams have an end() method that allows to pass in a callback.
|
||||
// In node 0.10 there is an event 'finish' emitted from the write stream that can be used. See
|
||||
// https://groups.google.com/forum/?fromgroups=#!topic/nodejs/YWQ1sRoXOdI
|
||||
readStream.pipe(writeStream, { end: false });
|
||||
}
|
||||
|
||||
// Deletes the given path by first moving it out of the workspace. This has two benefits. For one, the operation can return fast because
|
||||
// after the rename, the contents are out of the workspace although not yet deleted. The greater benefit however is that this operation
|
||||
// will fail in case any file is used by another process. fs.unlink() in node will not bail if a file unlinked is used by another process.
|
||||
@@ -320,15 +319,101 @@ export function mv(source: string, target: string, callback: (error: Error) => v
|
||||
});
|
||||
}
|
||||
|
||||
let canFlush = true;
|
||||
export function writeFileAndFlush(path: string, data: string | NodeBuffer | NodeJS.ReadableStream, options: { mode?: number; flag?: string; }, callback: (error?: Error) => void): void {
|
||||
options = ensureOptions(options);
|
||||
|
||||
if (typeof data === 'string' || Buffer.isBuffer(data)) {
|
||||
doWriteFileAndFlush(path, data, options, callback);
|
||||
} else {
|
||||
doWriteFileStreamAndFlush(path, data, options, callback);
|
||||
}
|
||||
}
|
||||
|
||||
function doWriteFileStreamAndFlush(path: string, reader: NodeJS.ReadableStream, options: { mode?: number; flag?: string; }, callback: (error?: Error) => void): void {
|
||||
|
||||
// finish only once
|
||||
let finished = false;
|
||||
const finish = (error?: Error) => {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
|
||||
// in error cases we need to manually close streams
|
||||
// if the write stream was successfully opened
|
||||
if (error) {
|
||||
if (isOpen) {
|
||||
writer.once('close', () => callback(error));
|
||||
writer.close();
|
||||
} else {
|
||||
callback(error);
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise just return without error
|
||||
else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// create writer to target. we set autoClose: false because we want to use the streams
|
||||
// file descriptor to call fs.fdatasync to ensure the data is flushed to disk
|
||||
const writer = fs.createWriteStream(path, { mode: options.mode, flags: options.flag, autoClose: false });
|
||||
|
||||
// Event: 'open'
|
||||
// Purpose: save the fd for later use
|
||||
// Notes: will not be called when there is an error opening the file descriptor!
|
||||
let fd: number;
|
||||
let isOpen: boolean;
|
||||
writer.once('open', descriptor => {
|
||||
fd = descriptor;
|
||||
isOpen = true;
|
||||
});
|
||||
|
||||
// Event: 'error'
|
||||
// Purpose: to return the error to the outside and to close the write stream (does not happen automatically)
|
||||
reader.once('error', error => finish(error));
|
||||
writer.once('error', error => finish(error));
|
||||
|
||||
// Event: 'finish'
|
||||
// Purpose: use fs.fdatasync to flush the contents to disk
|
||||
// Notes: event is called when the writer has finished writing to the underlying resource. we must call writer.close()
|
||||
// because we have created the WriteStream with autoClose: false
|
||||
writer.once('finish', () => {
|
||||
|
||||
// flush to disk
|
||||
if (canFlush && isOpen) {
|
||||
fs.fdatasync(fd, (syncError: Error) => {
|
||||
|
||||
// In some exotic setups it is well possible that node fails to sync
|
||||
// In that case we disable flushing and warn to the console
|
||||
if (syncError) {
|
||||
console.warn('[node.js fs] fdatasync is now disabled for this session because it failed: ', syncError);
|
||||
canFlush = false;
|
||||
}
|
||||
|
||||
writer.close();
|
||||
});
|
||||
} else {
|
||||
writer.close();
|
||||
}
|
||||
});
|
||||
|
||||
// Event: 'close'
|
||||
// Purpose: signal we are done to the outside
|
||||
// Notes: event is called when the writer's filedescriptor is closed
|
||||
writer.once('close', () => finish());
|
||||
|
||||
// start data piping
|
||||
reader.pipe(writer);
|
||||
}
|
||||
|
||||
// Calls fs.writeFile() followed by a fs.sync() call to flush the changes to disk
|
||||
// We do this in cases where we want to make sure the data is really on disk and
|
||||
// not in some cache.
|
||||
//
|
||||
// See https://github.com/nodejs/node/blob/v5.10.0/lib/fs.js#L1194
|
||||
let canFlush = true;
|
||||
export function writeFileAndFlush(path: string, data: string | NodeBuffer, options: { mode?: number; flag?: string; }, callback: (error: Error) => void): void {
|
||||
options = ensureOptions(options);
|
||||
|
||||
function doWriteFileAndFlush(path: string, data: string | NodeBuffer, options: { mode?: number; flag?: string; }, callback: (error?: Error) => void): void {
|
||||
if (!canFlush) {
|
||||
return fs.writeFile(path, data, options, callback);
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ const writeFilePathQueue: { [path: string]: Queue<void> } = Object.create(null);
|
||||
|
||||
export function writeFile(path: string, data: string, options?: { mode?: number; flag?: string; }): TPromise<void>;
|
||||
export function writeFile(path: string, data: NodeBuffer, options?: { mode?: number; flag?: string; }): TPromise<void>;
|
||||
export function writeFile(path: string, data: NodeJS.ReadableStream, options?: { mode?: number; flag?: string; }): TPromise<void>;
|
||||
export function writeFile(path: string, data: any, options?: { mode?: number; flag?: string; }): TPromise<void> {
|
||||
let queueKey = toQueueKey(path);
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ import uuid = require('vs/base/common/uuid');
|
||||
import strings = require('vs/base/common/strings');
|
||||
import extfs = require('vs/base/node/extfs');
|
||||
import { onError } from 'vs/base/test/common/utils';
|
||||
import { Readable } from 'stream';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
|
||||
const ignore = () => { };
|
||||
|
||||
@@ -22,6 +24,38 @@ const mkdirp = (path: string, mode: number, callback: (error) => void) => {
|
||||
extfs.mkdirp(path, mode).done(() => callback(null), error => callback(error));
|
||||
};
|
||||
|
||||
const chunkSize = 64 * 1024;
|
||||
const readError = 'Error while reading';
|
||||
function toReadable(value: string, throwError?: boolean): Readable {
|
||||
const totalChunks = Math.ceil(value.length / chunkSize);
|
||||
const stringChunks: string[] = [];
|
||||
|
||||
for (let i = 0, j = 0; i < totalChunks; ++i, j += chunkSize) {
|
||||
stringChunks[i] = value.substr(j, chunkSize);
|
||||
}
|
||||
|
||||
let counter = 0;
|
||||
return new Readable({
|
||||
read: function () {
|
||||
if (throwError) {
|
||||
this.emit('error', new Error(readError));
|
||||
}
|
||||
|
||||
let res: string;
|
||||
let canPush = true;
|
||||
while (canPush && (res = stringChunks[counter++])) {
|
||||
canPush = this.push(res);
|
||||
}
|
||||
|
||||
// EOS
|
||||
if (!res) {
|
||||
this.push(null);
|
||||
}
|
||||
},
|
||||
encoding: 'utf8'
|
||||
});
|
||||
}
|
||||
|
||||
suite('Extfs', () => {
|
||||
|
||||
test('mkdirp', function (done: () => void) {
|
||||
@@ -174,7 +208,7 @@ suite('Extfs', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('writeFileAndFlush', function (done: () => void) {
|
||||
test('writeFileAndFlush (string)', function (done: () => void) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
@@ -209,6 +243,196 @@ suite('Extfs', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (stream)', function (done: () => void) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return onError(error, done);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
extfs.writeFileAndFlush(testFile, toReadable('Hello World'), null, error => {
|
||||
if (error) {
|
||||
return onError(error, done);
|
||||
}
|
||||
|
||||
assert.equal(fs.readFileSync(testFile), 'Hello World');
|
||||
|
||||
const largeString = (new Array(100 * 1024)).join('Large String\n');
|
||||
|
||||
extfs.writeFileAndFlush(testFile, toReadable(largeString), null, error => {
|
||||
if (error) {
|
||||
return onError(error, done);
|
||||
}
|
||||
|
||||
assert.equal(fs.readFileSync(testFile), largeString);
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (file stream)', function (done: () => void) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const sourceFile = require.toUrl('./fixtures/index.html');
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return onError(error, done);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
extfs.writeFileAndFlush(testFile, fs.createReadStream(sourceFile), null, error => {
|
||||
if (error) {
|
||||
return onError(error, done);
|
||||
}
|
||||
|
||||
assert.equal(fs.readFileSync(testFile).toString(), fs.readFileSync(sourceFile).toString());
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (string, error handling)', function (done: () => void) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return onError(error, done);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
fs.mkdirSync(testFile); // this will trigger an error because testFile is now a directory!
|
||||
|
||||
extfs.writeFileAndFlush(testFile, 'Hello World', null, error => {
|
||||
if (!error) {
|
||||
return onError(new Error('Expected error for writing to readonly file'), done);
|
||||
}
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (stream, error handling EISDIR)', function (done: () => void) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return onError(error, done);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
fs.mkdirSync(testFile); // this will trigger an error because testFile is now a directory!
|
||||
|
||||
extfs.writeFileAndFlush(testFile, toReadable('Hello World'), null, error => {
|
||||
if (!error || (<any>error).code !== 'EISDIR') {
|
||||
return onError(new Error('Expected EISDIR error for writing to folder but got: ' + (error ? (<any>error).code : 'no error')), done);
|
||||
}
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (stream, error handling READERROR)', function (done: () => void) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return onError(error, done);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
extfs.writeFileAndFlush(testFile, toReadable('Hello World', true /* throw error */), null, error => {
|
||||
if (!error || error.message !== readError) {
|
||||
return onError(new Error('Expected error for writing to folder'), done);
|
||||
}
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (stream, error handling EACCES)', function (done: () => void) {
|
||||
if (isLinux) {
|
||||
return done(); // somehow this test fails on Linux in our TFS builds
|
||||
}
|
||||
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return onError(error, done);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
fs.writeFileSync(testFile, '');
|
||||
fs.chmodSync(testFile, 33060); // make readonly
|
||||
|
||||
extfs.writeFileAndFlush(testFile, toReadable('Hello World'), null, error => {
|
||||
if (!error || !((<any>error).code !== 'EACCES' || (<any>error).code !== 'EPERM')) {
|
||||
return onError(new Error('Expected EACCES/EPERM error for writing to folder but got: ' + (error ? (<any>error).code : 'no error')), done);
|
||||
}
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (file stream, error handling)', function (done: () => void) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const sourceFile = require.toUrl('./fixtures/index.html');
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return onError(error, done);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
fs.mkdirSync(testFile); // this will trigger an error because testFile is now a directory!
|
||||
|
||||
extfs.writeFileAndFlush(testFile, fs.createReadStream(sourceFile), null, error => {
|
||||
if (!error) {
|
||||
return onError(new Error('Expected error for writing to folder'), done);
|
||||
}
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFileAndFlushSync', function (done: () => void) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div id="issue-reporter">
|
||||
|
||||
<div class="input-group">
|
||||
<label for="issue-type">This is a</label>
|
||||
<label for="issue-type">I want to submit a</label>
|
||||
<select id="issue-type" class="form-control">
|
||||
<option value="0">Bug Report</option>
|
||||
<option value="1">Performance Issue</option>
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<div class="input-group">
|
||||
<div class="two-col">
|
||||
<label for="vscode-version">VS Code version <span class="required-input">*</span></label>
|
||||
<label for="vscode-version">VS Code Version <span class="required-input">*</span></label>
|
||||
<input id="vscode-version" type="text" value="Loading..." disabled/>
|
||||
</div>
|
||||
<div class="two-col">
|
||||
@@ -42,7 +42,7 @@
|
||||
<details>
|
||||
<summary>My System Info
|
||||
<input type="checkbox" id="includeSystemInfo" checked>
|
||||
<label class="caption" for="includeSystemInfo">Send this data</label>
|
||||
<label class="caption" for="includeSystemInfo">Send my data</label>
|
||||
</input>
|
||||
</summary>
|
||||
<div class="block-info">
|
||||
@@ -54,7 +54,7 @@
|
||||
<details>
|
||||
<summary>Currently Running Processes
|
||||
<input type="checkbox" id="includeProcessInfo" checked>
|
||||
<label class="caption" for="includeProcessInfo">Send this data</label>
|
||||
<label class="caption" for="includeProcessInfo">Send my data</label>
|
||||
</input>
|
||||
</summary>
|
||||
<div class="block-info">
|
||||
@@ -66,7 +66,7 @@
|
||||
<details>
|
||||
<summary>My Workspace Stats
|
||||
<input type="checkbox" id="includeWorkspaceInfo" checked>
|
||||
<label class="caption" for="includeWorkspaceInfo">Send this data</label>
|
||||
<label class="caption" for="includeWorkspaceInfo">Send my data</label>
|
||||
</input>
|
||||
</summary>
|
||||
<pre class="block-info">
|
||||
@@ -86,7 +86,7 @@
|
||||
<div class="block-info-text">
|
||||
<small>
|
||||
We support GitHub-flavored Markdown.
|
||||
After submitting, you will still be able to edit your issue on GitHub.
|
||||
You will still be able to edit your issue when we preview it on GitHub.
|
||||
</small>
|
||||
<div id="description-validation-error" class="validation-error hidden">Please enter a description.</div>
|
||||
<textarea name="description" id="description" cols="100" rows="15" required></textarea>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/issueReporter';
|
||||
import { shell, ipcRenderer, webFrame } from 'electron';
|
||||
import { shell, ipcRenderer, webFrame, remote } from 'electron';
|
||||
import { $ } from 'vs/base/browser/dom';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import product from 'vs/platform/node/product';
|
||||
@@ -95,7 +95,7 @@ export class IssueReporter extends Disposable {
|
||||
if (styles.inputBorder) {
|
||||
content.push(`input, textarea, select { border: 1px solid ${styles.inputBorder}; }`);
|
||||
} else {
|
||||
content.push(`input, textarea, select { border: none; }`);
|
||||
content.push(`input, textarea, select { border: 1px solid transparent; }`);
|
||||
}
|
||||
|
||||
if (styles.inputForeground) {
|
||||
@@ -228,6 +228,15 @@ export class IssueReporter extends Disposable {
|
||||
});
|
||||
|
||||
document.getElementById('github-submit-btn').addEventListener('click', () => this.createIssue());
|
||||
|
||||
document.onkeydown = (e: KeyboardEvent) => {
|
||||
if (e.shiftKey && e.keyCode === 13) {
|
||||
// Close the window if the issue was successfully created
|
||||
if (this.createIssue()) {
|
||||
remote.getCurrentWindow().close();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private renderBlocks(): void {
|
||||
@@ -246,9 +255,9 @@ export class IssueReporter extends Disposable {
|
||||
hide(processBlock);
|
||||
hide(workspaceBlock);
|
||||
|
||||
descriptionTitle.innerHTML = 'Steps to reproduce <span class="required-input">*</span>';
|
||||
descriptionTitle.innerHTML = 'Steps to Reproduce <span class="required-input">*</span>';
|
||||
show(descriptionSubtitle);
|
||||
descriptionSubtitle.innerHTML = 'How did you encounter this problem? Clear steps to reproduce the problem help our investigation. What did you expect to happen and what actually happened?';
|
||||
descriptionSubtitle.innerHTML = 'How did you encounter this problem? Please provide clear steps to reproduce the problem during our investigation. What did you expect to happen and what actually did happen?';
|
||||
}
|
||||
// 2 - Perf Issue
|
||||
else if (issueType === 1) {
|
||||
@@ -256,7 +265,7 @@ export class IssueReporter extends Disposable {
|
||||
show(processBlock);
|
||||
show(workspaceBlock);
|
||||
|
||||
descriptionTitle.innerHTML = 'Steps to reproduce <span class="required-input">*</span>';
|
||||
descriptionTitle.innerHTML = 'Steps to Reproduce <span class="required-input">*</span>';
|
||||
show(descriptionSubtitle);
|
||||
descriptionSubtitle.innerHTML = 'When did this performance issue happen? For example, does it occur on startup or after a specific series of actions? Any details you can provide help our investigation.';
|
||||
}
|
||||
@@ -294,7 +303,7 @@ export class IssueReporter extends Disposable {
|
||||
return isValid;
|
||||
}
|
||||
|
||||
private createIssue(): void {
|
||||
private createIssue(): boolean {
|
||||
if (!this.validateInputs()) {
|
||||
// If inputs are invalid, set focus to the first one and add listeners on them
|
||||
// to detect further changes
|
||||
@@ -307,7 +316,8 @@ export class IssueReporter extends Disposable {
|
||||
document.getElementById('description').addEventListener('input', (event) => {
|
||||
this.validateInput('description');
|
||||
});
|
||||
return;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.telemetryService) {
|
||||
@@ -323,6 +333,7 @@ export class IssueReporter extends Disposable {
|
||||
const baseUrl = `https://github.com/microsoft/vscode/issues/new?title=${issueTitle}&body=`;
|
||||
const issueBody = this.issueReporterModel.serialize();
|
||||
shell.openExternal(baseUrl + encodeURIComponent(issueBody));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -165,9 +165,14 @@ button:disabled {
|
||||
}
|
||||
|
||||
select, input, textarea {
|
||||
border: 1px solid transparent;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
summary {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.validation-error {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
interface ILanguageSource {
|
||||
extensionIdentifier: IExtensionIdentifier;
|
||||
version: string;
|
||||
path: string;
|
||||
translations: string;
|
||||
}
|
||||
|
||||
export class LanguagePackExtensions extends Disposable {
|
||||
@@ -52,7 +52,7 @@ export class LanguagePackExtensions extends Disposable {
|
||||
}
|
||||
|
||||
private onDidInstallExtension(extension: ILocalExtension): void {
|
||||
if (extension && extension.manifest && extension.manifest.contributes && extension.manifest.contributes.locales && extension.manifest.contributes.locales.length) {
|
||||
if (extension && extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length) {
|
||||
this.logService.debug('Adding language packs from the extension', extension.identifier.id);
|
||||
this.withLanguagePacks(languagePacks => {
|
||||
this.removeLanguagePacksFromExtensions(languagePacks, { id: getGalleryExtensionIdFromLocal(extension), uuid: extension.identifier.uuid });
|
||||
@@ -68,12 +68,14 @@ export class LanguagePackExtensions extends Disposable {
|
||||
|
||||
private addLanguagePacksFromExtensions(languagePacks: { [language: string]: ILanguageSource[] }, ...extensions: ILocalExtension[]): void {
|
||||
for (const extension of extensions) {
|
||||
if (extension && extension.manifest && extension.manifest.contributes && extension.manifest.contributes.locales && extension.manifest.contributes.locales.length) {
|
||||
if (extension && extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length) {
|
||||
const extensionIdentifier = { id: getGalleryExtensionIdFromLocal(extension), uuid: extension.identifier.uuid };
|
||||
for (const localeContribution of extension.manifest.contributes.locales) {
|
||||
const languageSources = languagePacks[localeContribution.locale] || [];
|
||||
languageSources.splice(0, 0, { extensionIdentifier, path: join(extension.path, localeContribution.path), version: extension.manifest.version });
|
||||
languagePacks[localeContribution.locale] = languageSources;
|
||||
for (const localizationContribution of extension.manifest.contributes.localizations) {
|
||||
if (localizationContribution.languageId && localizationContribution.translations) {
|
||||
const languageSources = languagePacks[localizationContribution.languageId] || [];
|
||||
languageSources.splice(0, 0, { extensionIdentifier, translations: join(extension.path, localizationContribution.translations), version: extension.manifest.version });
|
||||
languagePacks[localizationContribution.languageId] = languageSources;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import { CodeMenu } from 'vs/code/electron-main/menus';
|
||||
import { getShellEnvironment } from 'vs/code/node/shellEnv';
|
||||
import { IUpdateService } from 'vs/platform/update/common/update';
|
||||
import { UpdateChannel } from 'vs/platform/update/common/updateIpc';
|
||||
import { UpdateService } from 'vs/platform/update/electron-main/updateService';
|
||||
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main';
|
||||
import { Server, connect, Client } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { SharedProcess } from 'vs/code/electron-main/sharedProcess';
|
||||
@@ -52,6 +51,9 @@ import URI from 'vs/base/common/uri';
|
||||
import { WorkspacesChannel } from 'vs/platform/workspaces/common/workspacesIpc';
|
||||
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { getMachineId } from 'vs/base/node/id';
|
||||
import { Win32UpdateService } from 'vs/platform/update/electron-main/updateService.win32';
|
||||
import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux';
|
||||
import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin';
|
||||
import { IIssueService } from 'vs/platform/issue/common/issue';
|
||||
import { IssueChannel } from 'vs/platform/issue/common/issueIpc';
|
||||
import { IssueService } from 'vs/platform/issue/electron-main/issueService';
|
||||
@@ -307,7 +309,14 @@ export class CodeApplication {
|
||||
private initServices(machineId: string): IInstantiationService {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
services.set(IUpdateService, new SyncDescriptor(UpdateService));
|
||||
if (process.platform === 'win32') {
|
||||
services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
|
||||
} else if (process.platform === 'linux') {
|
||||
services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService));
|
||||
} else if (process.platform === 'darwin') {
|
||||
services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService));
|
||||
}
|
||||
|
||||
services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, machineId));
|
||||
services.set(IWindowsService, new SyncDescriptor(WindowsService, this.sharedProcess));
|
||||
services.set(ILaunchService, new SyncDescriptor(LaunchService));
|
||||
|
||||
@@ -83,10 +83,9 @@ async function postLogs(
|
||||
result = await requestService.request({
|
||||
url: endpoint.url,
|
||||
type: 'POST',
|
||||
data: fs.createReadStream(outZip),
|
||||
data: new Buffer(fs.readFileSync(outZip)).toString('base64'),
|
||||
headers: {
|
||||
'Content-Type': 'application/zip',
|
||||
'Content-Length': fs.statSync(outZip).size
|
||||
'Content-Type': 'application/zip'
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -94,12 +93,22 @@ async function postLogs(
|
||||
throw e;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(result.stream.toString());
|
||||
} catch (e) {
|
||||
console.log(localize('parseError', 'Error parsing response'));
|
||||
throw e;
|
||||
}
|
||||
return new TPromise<PostResult>((res, reject) => {
|
||||
const parts: Buffer[] = [];
|
||||
result.stream.on('data', data => {
|
||||
parts.push(data);
|
||||
});
|
||||
|
||||
result.stream.on('end', () => {
|
||||
try {
|
||||
const result = Buffer.concat(parts).toString('utf-8');
|
||||
res(JSON.parse(result));
|
||||
} catch (e) {
|
||||
console.log(localize('parseError', 'Error parsing response'));
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function zipLogs(
|
||||
|
||||
@@ -45,6 +45,7 @@ import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { printDiagnostics } from 'vs/code/electron-main/diagnostics';
|
||||
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
|
||||
import { uploadLogs } from 'vs/code/electron-main/logUploader';
|
||||
|
||||
function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
|
||||
const services = new ServiceCollection();
|
||||
@@ -198,8 +199,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
|
||||
|
||||
// Log uploader
|
||||
if (environmentService.args['upload-logs']) {
|
||||
return import('vs/code/electron-main/logUploader')
|
||||
.then(logUploader => logUploader.uploadLogs(channel, requestService))
|
||||
return uploadLogs(channel, requestService)
|
||||
.then(() => TPromise.wrapError(new ExpectedError()));
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { OpenContext, IRunActionInWindowRequest } from 'vs/platform/windows/comm
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { AutoSaveConfiguration } from 'vs/platform/files/common/files';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IUpdateService, State as UpdateState } from 'vs/platform/update/common/update';
|
||||
import { IUpdateService, StateType } from 'vs/platform/update/common/update';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -1040,45 +1040,51 @@ export class CodeMenu {
|
||||
}
|
||||
|
||||
private getUpdateMenuItems(): Electron.MenuItem[] {
|
||||
switch (this.updateService.state) {
|
||||
case UpdateState.Uninitialized:
|
||||
const state = this.updateService.state;
|
||||
|
||||
switch (state.type) {
|
||||
case StateType.Uninitialized:
|
||||
return [];
|
||||
|
||||
case UpdateState.UpdateDownloaded:
|
||||
case StateType.Idle:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miRestartToUpdate', "Restart to Update..."), click: () => {
|
||||
this.reportMenuActionTelemetry('RestartToUpdate');
|
||||
this.updateService.quitAndInstall();
|
||||
}
|
||||
})];
|
||||
|
||||
case UpdateState.CheckingForUpdate:
|
||||
return [new MenuItem({ label: nls.localize('miCheckingForUpdates', "Checking For Updates..."), enabled: false })];
|
||||
|
||||
case UpdateState.UpdateAvailable:
|
||||
if (isLinux) {
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miDownloadUpdate', "Download Available Update"), click: () => {
|
||||
this.updateService.quitAndInstall();
|
||||
}
|
||||
})];
|
||||
}
|
||||
|
||||
const updateAvailableLabel = isWindows
|
||||
? nls.localize('miDownloadingUpdate', "Downloading Update...")
|
||||
: nls.localize('miInstallingUpdate', "Installing Update...");
|
||||
|
||||
return [new MenuItem({ label: updateAvailableLabel, enabled: false })];
|
||||
|
||||
default:
|
||||
const result = [new MenuItem({
|
||||
label: nls.localize('miCheckForUpdates', "Check for Updates..."), click: () => setTimeout(() => {
|
||||
this.reportMenuActionTelemetry('CheckForUpdate');
|
||||
this.updateService.checkForUpdates(true);
|
||||
}, 0)
|
||||
})];
|
||||
|
||||
return result;
|
||||
case StateType.CheckingForUpdates:
|
||||
return [new MenuItem({ label: nls.localize('miCheckingForUpdates', "Checking For Updates..."), enabled: false })];
|
||||
|
||||
case StateType.AvailableForDownload:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miDownloadUpdate', "Download Available Update"), click: () => {
|
||||
this.updateService.downloadUpdate();
|
||||
}
|
||||
})];
|
||||
|
||||
case StateType.Downloading:
|
||||
return [new MenuItem({ label: nls.localize('miDownloadingUpdate', "Downloading Update..."), enabled: false })];
|
||||
|
||||
case StateType.Downloaded:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miInstallUpdate', "Install Update..."), click: () => {
|
||||
this.reportMenuActionTelemetry('InstallUpdate');
|
||||
this.updateService.applyUpdate();
|
||||
}
|
||||
})];
|
||||
|
||||
case StateType.Updating:
|
||||
return [new MenuItem({ label: nls.localize('miInstallingUpdate', "Installing Update..."), enabled: false })];
|
||||
|
||||
case StateType.Ready:
|
||||
return [new MenuItem({
|
||||
label: nls.localize('miRestartToUpdate', "Restart to Update..."), click: () => {
|
||||
this.reportMenuActionTelemetry('RestartToUpdate');
|
||||
this.updateService.quitAndInstall();
|
||||
}
|
||||
})];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -953,7 +953,7 @@ export class CodeWindow implements ICodeWindow {
|
||||
const segments: ITouchBarSegment[] = items.map(item => {
|
||||
let icon: Electron.NativeImage;
|
||||
if (item.iconPath) {
|
||||
icon = nativeImage.createFromPath(item.iconPath);
|
||||
icon = nativeImage.createFromPath(item.iconPath.dark);
|
||||
if (icon.isEmpty()) {
|
||||
icon = void 0;
|
||||
}
|
||||
|
||||
@@ -1318,7 +1318,10 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
if (e.window.config && !!e.window.config.extensionDevelopmentPath) {
|
||||
return; // do not ask to save workspace when doing extension development
|
||||
// do not ask to save workspace when doing extension development
|
||||
// but still delete it.
|
||||
this.workspacesMainService.deleteUntitledWorkspaceSync(workspace);
|
||||
return;
|
||||
}
|
||||
|
||||
if (windowClosing && !isMacintosh && this.getWindowCount() === 1) {
|
||||
@@ -1326,7 +1329,17 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
// Handle untitled workspaces with prompt as needed
|
||||
e.veto(this.workspacesManager.promptToSaveUntitledWorkspace(this.getWindowById(e.window.id), workspace));
|
||||
e.veto(this.workspacesManager.promptToSaveUntitledWorkspace(this.getWindowById(e.window.id), workspace).then(veto => {
|
||||
if (veto) {
|
||||
return veto;
|
||||
}
|
||||
|
||||
// Bug in electron: somehow we need this timeout so that the window closes properly. That
|
||||
// might be related to the fact that the untitled workspace prompt shows up async and this
|
||||
// code can execute before the dialog is fully closed which then blocks the window from closing.
|
||||
// Issue: https://github.com/Microsoft/vscode/issues/41989
|
||||
return TPromise.timeout(0).then(() => veto);
|
||||
}));
|
||||
}
|
||||
|
||||
public focusLastActive(cli: ParsedArgs, context: OpenContext): CodeWindow {
|
||||
@@ -1706,8 +1719,8 @@ class Dialogs {
|
||||
class WorkspacesManager {
|
||||
|
||||
constructor(
|
||||
private workspacesService: IWorkspacesMainService,
|
||||
private backupService: IBackupMainService,
|
||||
private workspacesMainService: IWorkspacesMainService,
|
||||
private backupMainService: IBackupMainService,
|
||||
private environmentService: IEnvironmentService,
|
||||
private windowsMainService: IWindowsMainService
|
||||
) {
|
||||
@@ -1731,7 +1744,7 @@ class WorkspacesManager {
|
||||
return TPromise.as(null); // return early if the workspace is not valid
|
||||
}
|
||||
|
||||
return this.workspacesService.createWorkspace(folders).then(workspace => {
|
||||
return this.workspacesMainService.createWorkspace(folders).then(workspace => {
|
||||
return this.doSaveAndOpenWorkspace(window, workspace, path);
|
||||
});
|
||||
});
|
||||
@@ -1748,7 +1761,7 @@ class WorkspacesManager {
|
||||
}
|
||||
|
||||
// Prevent overwriting a workspace that is currently opened in another window
|
||||
if (findWindowOnWorkspace(this.windowsMainService.getWindows(), { id: this.workspacesService.getWorkspaceId(path), configPath: path })) {
|
||||
if (findWindowOnWorkspace(this.windowsMainService.getWindows(), { id: this.workspacesMainService.getWorkspaceId(path), configPath: path })) {
|
||||
const options: Electron.MessageBoxOptions = {
|
||||
title: product.nameLong,
|
||||
type: 'info',
|
||||
@@ -1767,7 +1780,7 @@ class WorkspacesManager {
|
||||
private doSaveAndOpenWorkspace(window: CodeWindow, workspace: IWorkspaceIdentifier, path?: string): TPromise<IEnterWorkspaceResult> {
|
||||
let savePromise: TPromise<IWorkspaceIdentifier>;
|
||||
if (path) {
|
||||
savePromise = this.workspacesService.saveWorkspace(workspace, path);
|
||||
savePromise = this.workspacesMainService.saveWorkspace(workspace, path);
|
||||
} else {
|
||||
savePromise = TPromise.as(workspace);
|
||||
}
|
||||
@@ -1778,7 +1791,7 @@ class WorkspacesManager {
|
||||
// Register window for backups and migrate current backups over
|
||||
let backupPath: string;
|
||||
if (!window.config.extensionDevelopmentPath) {
|
||||
backupPath = this.backupService.registerWorkspaceBackupSync(workspace, window.config.backupPath);
|
||||
backupPath = this.backupMainService.registerWorkspaceBackupSync(workspace, window.config.backupPath);
|
||||
}
|
||||
|
||||
// Update window configuration properly based on transition to workspace
|
||||
@@ -1851,7 +1864,7 @@ class WorkspacesManager {
|
||||
|
||||
// Don't Save: delete workspace
|
||||
case ConfirmResult.DONT_SAVE:
|
||||
this.workspacesService.deleteUntitledWorkspaceSync(workspace);
|
||||
this.workspacesMainService.deleteUntitledWorkspaceSync(workspace);
|
||||
return false;
|
||||
|
||||
// Save: save workspace, but do not veto unload
|
||||
@@ -1863,7 +1876,7 @@ class WorkspacesManager {
|
||||
defaultPath: this.getUntitledWorkspaceSaveDialogDefaultPath(workspace)
|
||||
}, window).then(target => {
|
||||
if (target) {
|
||||
return this.workspacesService.saveWorkspace(workspace, target).then(() => false, () => false);
|
||||
return this.workspacesMainService.saveWorkspace(workspace, target).then(() => false, () => false);
|
||||
}
|
||||
|
||||
return true; // keep veto if no target was provided
|
||||
@@ -1879,7 +1892,7 @@ class WorkspacesManager {
|
||||
return dirname(workspace);
|
||||
}
|
||||
|
||||
const resolvedWorkspace = this.workspacesService.resolveWorkspaceSync(workspace.configPath);
|
||||
const resolvedWorkspace = this.workspacesMainService.resolveWorkspaceSync(workspace.configPath);
|
||||
if (resolvedWorkspace && resolvedWorkspace.folders.length > 0) {
|
||||
for (const folder of resolvedWorkspace.folders) {
|
||||
if (folder.uri.scheme === Schemas.file) {
|
||||
|
||||
@@ -228,9 +228,12 @@ export class ViewLine implements IVisibleLine {
|
||||
isRegularASCII = strings.isBasicASCII(lineData.content);
|
||||
}
|
||||
|
||||
if (isRegularASCII && lineData.content.length < 1000) {
|
||||
if (isRegularASCII && lineData.content.length < 1000 && renderLineInput.lineTokens.getCount() < 100) {
|
||||
// Browser rounding errors have been observed in Chrome and IE, so using the fast
|
||||
// view line only for short lines. Please test before removing the length check...
|
||||
// ---
|
||||
// Another rounding error has been observed on Linux in VSCode, where <span> width
|
||||
// rounding errors add up to an observable large number...
|
||||
renderedViewLine = new FastRenderedViewLine(
|
||||
this._renderedViewLine ? this._renderedViewLine.domNode : null,
|
||||
renderLineInput,
|
||||
@@ -278,8 +281,27 @@ export class ViewLine implements IVisibleLine {
|
||||
}
|
||||
|
||||
public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] {
|
||||
startColumn = startColumn | 0; // @perf
|
||||
endColumn = endColumn | 0; // @perf
|
||||
|
||||
startColumn = Math.min(this._renderedViewLine.input.lineContent.length + 1, Math.max(1, startColumn));
|
||||
endColumn = Math.min(this._renderedViewLine.input.lineContent.length + 1, Math.max(1, endColumn));
|
||||
|
||||
const stopRenderingLineAfter = this._renderedViewLine.input.stopRenderingLineAfter | 0; // @perf
|
||||
|
||||
if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter && endColumn > stopRenderingLineAfter) {
|
||||
// This range is obviously not visible
|
||||
return null;
|
||||
}
|
||||
|
||||
if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter) {
|
||||
startColumn = stopRenderingLineAfter;
|
||||
}
|
||||
|
||||
if (stopRenderingLineAfter !== -1 && endColumn > stopRenderingLineAfter) {
|
||||
endColumn = stopRenderingLineAfter;
|
||||
}
|
||||
|
||||
return this._renderedViewLine.getVisibleRangesForRange(startColumn, endColumn, context);
|
||||
}
|
||||
|
||||
@@ -325,23 +347,6 @@ class FastRenderedViewLine implements IRenderedViewLine {
|
||||
}
|
||||
|
||||
public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] {
|
||||
startColumn = startColumn | 0; // @perf
|
||||
endColumn = endColumn | 0; // @perf
|
||||
const stopRenderingLineAfter = this.input.stopRenderingLineAfter | 0; // @perf
|
||||
|
||||
if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter && endColumn > stopRenderingLineAfter) {
|
||||
// This range is obviously not visible
|
||||
return null;
|
||||
}
|
||||
|
||||
if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter) {
|
||||
startColumn = stopRenderingLineAfter;
|
||||
}
|
||||
|
||||
if (stopRenderingLineAfter !== -1 && endColumn > stopRenderingLineAfter) {
|
||||
endColumn = stopRenderingLineAfter;
|
||||
}
|
||||
|
||||
const startPosition = this._getCharPosition(startColumn);
|
||||
const endPosition = this._getCharPosition(endColumn);
|
||||
return [new HorizontalRange(startPosition, endPosition - startPosition)];
|
||||
@@ -432,23 +437,6 @@ class RenderedViewLine implements IRenderedViewLine {
|
||||
* Visible ranges for a model range
|
||||
*/
|
||||
public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] {
|
||||
startColumn = startColumn | 0; // @perf
|
||||
endColumn = endColumn | 0; // @perf
|
||||
const stopRenderingLineAfter = this.input.stopRenderingLineAfter | 0; // @perf
|
||||
|
||||
if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter && endColumn > stopRenderingLineAfter) {
|
||||
// This range is obviously not visible
|
||||
return null;
|
||||
}
|
||||
|
||||
if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter) {
|
||||
startColumn = stopRenderingLineAfter;
|
||||
}
|
||||
|
||||
if (stopRenderingLineAfter !== -1 && endColumn > stopRenderingLineAfter) {
|
||||
endColumn = stopRenderingLineAfter;
|
||||
}
|
||||
|
||||
if (this._pixelOffsetCache !== null) {
|
||||
// the text is LTR
|
||||
let startOffset = this._readPixelOffset(startColumn, context);
|
||||
|
||||
@@ -34,7 +34,6 @@ class ViewCursorRenderData {
|
||||
|
||||
export class ViewCursor {
|
||||
private readonly _context: ViewContext;
|
||||
private readonly _isSecondary: boolean;
|
||||
private readonly _domNode: FastDomNode<HTMLElement>;
|
||||
|
||||
private _cursorStyle: TextEditorCursorStyle;
|
||||
@@ -49,9 +48,8 @@ export class ViewCursor {
|
||||
private _lastRenderedContent: string;
|
||||
private _renderData: ViewCursorRenderData;
|
||||
|
||||
constructor(context: ViewContext, isSecondary: boolean) {
|
||||
constructor(context: ViewContext) {
|
||||
this._context = context;
|
||||
this._isSecondary = isSecondary;
|
||||
|
||||
this._cursorStyle = this._context.configuration.editor.viewInfo.cursorStyle;
|
||||
this._lineHeight = this._context.configuration.editor.lineHeight;
|
||||
@@ -62,11 +60,7 @@ export class ViewCursor {
|
||||
|
||||
// Create the dom node
|
||||
this._domNode = createFastDomNode(document.createElement('div'));
|
||||
if (this._isSecondary) {
|
||||
this._domNode.setClassName('cursor secondary');
|
||||
} else {
|
||||
this._domNode.setClassName('cursor');
|
||||
}
|
||||
this._domNode.setClassName('cursor');
|
||||
this._domNode.setHeight(this._lineHeight);
|
||||
this._domNode.setTop(0);
|
||||
this._domNode.setLeft(0);
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
cursor: text;
|
||||
overflow: hidden;
|
||||
}
|
||||
.monaco-editor .cursors-layer > .cursor.secondary {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* -- block-outline-style -- */
|
||||
.monaco-editor .cursors-layer.cursor-block-outline-style > .cursor {
|
||||
|
||||
@@ -49,7 +49,7 @@ export class ViewCursors extends ViewPart {
|
||||
this._cursorStyle = this._context.configuration.editor.viewInfo.cursorStyle;
|
||||
this._selectionIsEmpty = true;
|
||||
|
||||
this._primaryCursor = new ViewCursor(this._context, false);
|
||||
this._primaryCursor = new ViewCursor(this._context);
|
||||
this._secondaryCursors = [];
|
||||
this._renderData = [];
|
||||
|
||||
@@ -109,7 +109,7 @@ export class ViewCursors extends ViewPart {
|
||||
// Create new cursors
|
||||
let addCnt = secondaryPositions.length - this._secondaryCursors.length;
|
||||
for (let i = 0; i < addCnt; i++) {
|
||||
let newCursor = new ViewCursor(this._context, true);
|
||||
let newCursor = new ViewCursor(this._context);
|
||||
this._domNode.domNode.insertBefore(newCursor.getDomNode().domNode, this._primaryCursor.getDomNode().domNode.nextSibling);
|
||||
this._secondaryCursors.push(newCursor);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,16 @@ import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions';
|
||||
*/
|
||||
const GOLDEN_LINE_HEIGHT_RATIO = platform.isMacintosh ? 1.5 : 1.35;
|
||||
|
||||
/**
|
||||
* Font settings maximum and minimum limits
|
||||
*/
|
||||
const MINIMUM_FONT_SIZE = 8;
|
||||
const MAXIMUM_FONT_SIZE = 100;
|
||||
const MINIMUM_LINE_HEIGHT = 8;
|
||||
const MAXIMUM_LINE_HEIGHT = 150;
|
||||
const MINIMUM_LETTER_SPACING = -5;
|
||||
const MAXIMUM_LETTER_SPACING = 20;
|
||||
|
||||
function safeParseFloat(n: number | string, defaultValue: number): number {
|
||||
if (typeof n === 'number') {
|
||||
return n;
|
||||
@@ -70,24 +80,25 @@ export class BareFontInfo {
|
||||
let fontFamily = _string(opts.fontFamily, EDITOR_FONT_DEFAULTS.fontFamily);
|
||||
let fontWeight = _string(opts.fontWeight, EDITOR_FONT_DEFAULTS.fontWeight);
|
||||
|
||||
|
||||
let fontSize = safeParseFloat(opts.fontSize, EDITOR_FONT_DEFAULTS.fontSize);
|
||||
fontSize = clamp(fontSize, 0, 100);
|
||||
fontSize = clamp(fontSize, 0, MAXIMUM_FONT_SIZE);
|
||||
if (fontSize === 0) {
|
||||
fontSize = EDITOR_FONT_DEFAULTS.fontSize;
|
||||
} else if (fontSize < 8) {
|
||||
fontSize = 8;
|
||||
} else if (fontSize < MINIMUM_FONT_SIZE) {
|
||||
fontSize = MINIMUM_FONT_SIZE;
|
||||
}
|
||||
|
||||
let lineHeight = safeParseInt(opts.lineHeight, 0);
|
||||
lineHeight = clamp(lineHeight, 0, 150);
|
||||
lineHeight = clamp(lineHeight, 0, MAXIMUM_LINE_HEIGHT);
|
||||
if (lineHeight === 0) {
|
||||
lineHeight = Math.round(GOLDEN_LINE_HEIGHT_RATIO * fontSize);
|
||||
} else if (lineHeight < 8) {
|
||||
lineHeight = 8;
|
||||
} else if (lineHeight < MINIMUM_LINE_HEIGHT) {
|
||||
lineHeight = MINIMUM_LINE_HEIGHT;
|
||||
}
|
||||
|
||||
let letterSpacing = safeParseFloat(opts.letterSpacing, 0);
|
||||
letterSpacing = clamp(letterSpacing, -20, 20);
|
||||
letterSpacing = clamp(letterSpacing, MINIMUM_LETTER_SPACING, MAXIMUM_LETTER_SPACING);
|
||||
|
||||
let editorZoomLevelMultiplier = 1 + (EditorZoom.getZoomLevel() * 0.1);
|
||||
fontSize *= editorZoomLevelMultiplier;
|
||||
|
||||
@@ -164,14 +164,27 @@ class PieceTreeSnapshot implements ITextSnapshot {
|
||||
this._nodes = [];
|
||||
this._tree = tree;
|
||||
this._BOM = BOM;
|
||||
tree.iterate(tree.root, node => {
|
||||
this._nodes.push(node);
|
||||
return true;
|
||||
});
|
||||
this._index = 0;
|
||||
if (tree.root !== SENTINEL) {
|
||||
tree.iterate(tree.root, node => {
|
||||
if (node !== SENTINEL) {
|
||||
this._nodes.push(node);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
read(): string {
|
||||
if (this._nodes.length === 0) {
|
||||
if (this._index === 0) {
|
||||
this._index++;
|
||||
return this._BOM;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._index > this._nodes.length - 1) {
|
||||
return null;
|
||||
}
|
||||
@@ -189,7 +202,6 @@ export class PieceTreeBase {
|
||||
protected _lineCnt: number;
|
||||
protected _length: number;
|
||||
private _lastChangeBufferPos: BufferCursor;
|
||||
private _lastNodePosition: NodePosition;
|
||||
|
||||
constructor(chunks: StringBuffer[]) {
|
||||
this.create(chunks);
|
||||
@@ -200,7 +212,6 @@ export class PieceTreeBase {
|
||||
new StringBuffer('', [0])
|
||||
];
|
||||
this._lastChangeBufferPos = { line: 0, column: 0 };
|
||||
this._lastNodePosition = null;
|
||||
this.root = SENTINEL;
|
||||
this._lineCnt = 1;
|
||||
this._length = 0;
|
||||
@@ -277,6 +288,9 @@ export class PieceTreeBase {
|
||||
|
||||
let offset = 0;
|
||||
let ret = this.iterate(this.root, node => {
|
||||
if (node === SENTINEL) {
|
||||
return true;
|
||||
}
|
||||
let str = this.getNodeContent(node);
|
||||
let len = str.length;
|
||||
let startPosition = other.nodeAt(offset);
|
||||
@@ -437,7 +451,6 @@ export class PieceTreeBase {
|
||||
// changed buffer
|
||||
this.appendToNode(node, value);
|
||||
this.computeBufferMetadata();
|
||||
this._lastNodePosition = { node, remainder, nodeStartOffset };
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -921,33 +934,7 @@ export class PieceTreeBase {
|
||||
updateTreeMetadata(this, node, value.length, lf_delta);
|
||||
}
|
||||
|
||||
readNodePositionFromCache(offset: number): NodePosition {
|
||||
if (!this._lastNodePosition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this._lastNodePosition.node.parent === null) {
|
||||
this._lastNodePosition = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this._lastNodePosition.nodeStartOffset > offset || this._lastNodePosition.nodeStartOffset + this._lastNodePosition.node.piece.length < offset) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
node: this._lastNodePosition.node,
|
||||
remainder: offset - this._lastNodePosition.nodeStartOffset,
|
||||
nodeStartOffset: this._lastNodePosition.nodeStartOffset
|
||||
};
|
||||
}
|
||||
|
||||
nodeAt(offset: number): NodePosition {
|
||||
let cachedNodePosition = this.readNodePositionFromCache(offset);
|
||||
if (cachedNodePosition) {
|
||||
return cachedNodePosition;
|
||||
}
|
||||
|
||||
let x = this.root;
|
||||
let nodeStartOffset = 0;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeText
|
||||
import { ChunksTextBufferBuilder } from 'vs/editor/common/model/chunksTextBuffer/chunksTextBufferBuilder';
|
||||
|
||||
// Here is the master switch for the text buffer implementation:
|
||||
const USE_PIECE_TREE_IMPLEMENTATION = false;
|
||||
const USE_PIECE_TREE_IMPLEMENTATION = true;
|
||||
const USE_CHUNKS_TEXT_BUFFER = false;
|
||||
|
||||
function createTextBufferBuilder() {
|
||||
@@ -82,6 +82,17 @@ export function createTextBufferFactoryFromStream(stream: IStringStream): TPromi
|
||||
});
|
||||
}
|
||||
|
||||
export function createTextBufferFactoryFromSnapshot(snapshot: ITextSnapshot): model.ITextBufferFactory {
|
||||
let builder = createTextBufferBuilder();
|
||||
|
||||
let chunk: string;
|
||||
while (typeof (chunk = snapshot.read()) === 'string') {
|
||||
builder.acceptChunk(chunk);
|
||||
}
|
||||
|
||||
return builder.finish();
|
||||
}
|
||||
|
||||
export function createTextBuffer(value: string | model.ITextBufferFactory, defaultEOL: model.DefaultEndOfLine): model.ITextBuffer {
|
||||
const factory = (typeof value === 'string' ? createTextBufferFactory(value) : value);
|
||||
return factory.create(defaultEOL);
|
||||
|
||||
@@ -136,6 +136,23 @@ export class TextModelSearch {
|
||||
}
|
||||
|
||||
if (searchData.regex.multiline) {
|
||||
if (searchData.regex.source === '\\n') {
|
||||
// Fast path for searching for EOL
|
||||
let result: FindMatch[] = [], resultLen = 0;
|
||||
for (let lineNumber = 1, lineCount = model.getLineCount(); lineNumber < lineCount; lineNumber++) {
|
||||
const range = new Range(lineNumber, model.getLineMaxColumn(lineNumber), lineNumber + 1, 1);
|
||||
if (captureMatches) {
|
||||
result[resultLen++] = new FindMatch(range, null);
|
||||
} else {
|
||||
result[resultLen++] = new FindMatch(range, ['\n']);
|
||||
}
|
||||
|
||||
if (resultLen >= limitResultCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return this._doFindMatchesMultiline(model, searchRange, new Searcher(searchData.wordSeparators, searchData.regex), captureMatches, limitResultCount);
|
||||
}
|
||||
return this._doFindMatchesLineByLine(model, searchRange, searchData, captureMatches, limitResultCount);
|
||||
|
||||
@@ -344,6 +344,14 @@ export interface CodeAction {
|
||||
command?: Command;
|
||||
edit?: WorkspaceEdit;
|
||||
diagnostics?: IMarkerData[];
|
||||
kind?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface CodeActionContext {
|
||||
only?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -355,7 +363,7 @@ export interface CodeActionProvider {
|
||||
/**
|
||||
* Provide commands for the given document and range.
|
||||
*/
|
||||
provideCodeActions(model: model.ITextModel, range: Range, token: CancellationToken): CodeAction[] | Thenable<CodeAction[]>;
|
||||
provideCodeActions(model: model.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): CodeAction[] | Thenable<CodeAction[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Position } from 'vs/editor/common/core/position';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions';
|
||||
import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { editorBracketMatchBackground, editorBracketMatchBorder } from 'vs/editor/common/view/editorColorRegistry';
|
||||
@@ -22,7 +22,7 @@ import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { TrackedRangeStickiness, IModelDeltaDecoration } from 'vs/editor/common/model';
|
||||
|
||||
class SelectBracketAction extends EditorAction {
|
||||
class JumpToBracketAction extends EditorAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.jumpToBracket',
|
||||
@@ -45,6 +45,25 @@ class SelectBracketAction extends EditorAction {
|
||||
}
|
||||
}
|
||||
|
||||
class SelectToBracketAction extends EditorAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.selectToBracket',
|
||||
label: nls.localize('smartSelect.selectToBracket', "Select to Bracket"),
|
||||
alias: 'Select to Bracket',
|
||||
precondition: null
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
let controller = BracketMatchingController.get(editor);
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
controller.selectToBracket();
|
||||
}
|
||||
}
|
||||
|
||||
type Brackets = [Range, Range];
|
||||
|
||||
class BracketsData {
|
||||
@@ -149,6 +168,50 @@ export class BracketMatchingController extends Disposable implements editorCommo
|
||||
this._editor.revealRange(newSelections[0]);
|
||||
}
|
||||
|
||||
public selectToBracket(): void {
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
const selection = this._editor.getSelection();
|
||||
if (!selection.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const position = selection.getStartPosition();
|
||||
|
||||
let brackets = model.matchBracket(position);
|
||||
|
||||
let openBracket: Position = null;
|
||||
let closeBracket: Position = null;
|
||||
|
||||
if (!brackets) {
|
||||
const nextBracket = model.findNextBracket(position);
|
||||
if (nextBracket && nextBracket.range) {
|
||||
brackets = model.matchBracket(nextBracket.range.getStartPosition());
|
||||
}
|
||||
}
|
||||
|
||||
if (brackets) {
|
||||
if (brackets[0].startLineNumber === brackets[1].startLineNumber) {
|
||||
openBracket = brackets[1].startColumn < brackets[0].startColumn ?
|
||||
brackets[1].getStartPosition() : brackets[0].getStartPosition();
|
||||
closeBracket = brackets[1].startColumn < brackets[0].startColumn ?
|
||||
brackets[0].getEndPosition() : brackets[1].getEndPosition();
|
||||
} else {
|
||||
openBracket = brackets[1].startLineNumber < brackets[0].startLineNumber ?
|
||||
brackets[1].getStartPosition() : brackets[0].getStartPosition();
|
||||
closeBracket = brackets[1].startLineNumber < brackets[0].startLineNumber ?
|
||||
brackets[0].getEndPosition() : brackets[1].getEndPosition();
|
||||
}
|
||||
}
|
||||
|
||||
if (openBracket && closeBracket) {
|
||||
this._editor.setSelection(new Range(openBracket.lineNumber, openBracket.column, closeBracket.lineNumber, closeBracket.column));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'bracket-match'
|
||||
@@ -228,7 +291,8 @@ export class BracketMatchingController extends Disposable implements editorCommo
|
||||
}
|
||||
|
||||
registerEditorContribution(BracketMatchingController);
|
||||
registerEditorAction(SelectBracketAction);
|
||||
registerEditorAction(SelectToBracketAction);
|
||||
registerEditorAction(JumpToBracketAction);
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
let bracketMatchBackground = theme.getColor(editorBracketMatchBackground);
|
||||
if (bracketMatchBackground) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import * as assert from 'assert';
|
||||
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
|
||||
@@ -98,4 +99,49 @@ suite('bracket matching', () => {
|
||||
model.dispose();
|
||||
mode.dispose();
|
||||
});
|
||||
|
||||
test('Select to next bracket', () => {
|
||||
let mode = new BracketMode();
|
||||
let model = TextModel.createFromString('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier());
|
||||
|
||||
withTestCodeEditor(null, { model: model }, (editor, cursor) => {
|
||||
let bracketMatchingController = editor.registerAndInstantiateContribution<BracketMatchingController>(BracketMatchingController);
|
||||
|
||||
|
||||
// start position in open brackets
|
||||
editor.setPosition(new Position(1, 9));
|
||||
bracketMatchingController.selectToBracket();
|
||||
assert.deepEqual(editor.getPosition(), new Position(1, 20));
|
||||
assert.deepEqual(editor.getSelection(), new Selection(1, 9, 1, 20));
|
||||
|
||||
// start position in close brackets
|
||||
editor.setPosition(new Position(1, 20));
|
||||
bracketMatchingController.selectToBracket();
|
||||
assert.deepEqual(editor.getPosition(), new Position(1, 20));
|
||||
assert.deepEqual(editor.getSelection(), new Selection(1, 9, 1, 20));
|
||||
|
||||
// start position between brackets
|
||||
editor.setPosition(new Position(1, 16));
|
||||
bracketMatchingController.selectToBracket();
|
||||
assert.deepEqual(editor.getPosition(), new Position(1, 19));
|
||||
assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 19));
|
||||
|
||||
// start position outside brackets
|
||||
editor.setPosition(new Position(1, 21));
|
||||
bracketMatchingController.selectToBracket();
|
||||
assert.deepEqual(editor.getPosition(), new Position(1, 25));
|
||||
assert.deepEqual(editor.getSelection(), new Selection(1, 23, 1, 25));
|
||||
|
||||
// do not break if no brackets are available
|
||||
editor.setPosition(new Position(1, 26));
|
||||
bracketMatchingController.selectToBracket();
|
||||
assert.deepEqual(editor.getPosition(), new Position(1, 26));
|
||||
assert.deepEqual(editor.getSelection(), new Selection(1, 26, 1, 26));
|
||||
|
||||
bracketMatchingController.dispose();
|
||||
});
|
||||
|
||||
model.dispose();
|
||||
mode.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,20 +14,7 @@ import { DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinit
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
|
||||
function outputResults(promises: TPromise<Location | Location[]>[]) {
|
||||
return TPromise.join(promises).then(allReferences => {
|
||||
let result: Location[] = [];
|
||||
for (let references of allReferences) {
|
||||
if (Array.isArray(references)) {
|
||||
result.push(...references);
|
||||
} else if (references) {
|
||||
result.push(references);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
import { flatten } from 'vs/base/common/arrays';
|
||||
|
||||
function getDefinitions<T>(
|
||||
model: ITextModel,
|
||||
@@ -38,7 +25,7 @@ function getDefinitions<T>(
|
||||
const provider = registry.ordered(model);
|
||||
|
||||
// get results
|
||||
const promises = provider.map((provider, idx) => {
|
||||
const promises = provider.map((provider, idx): TPromise<Location | Location[]> => {
|
||||
return asWinJsPromise((token) => {
|
||||
return provide(provider, model, position, token);
|
||||
}).then(undefined, err => {
|
||||
@@ -46,7 +33,9 @@ function getDefinitions<T>(
|
||||
return null;
|
||||
});
|
||||
});
|
||||
return outputResults(promises);
|
||||
return TPromise.join(promises)
|
||||
.then(flatten)
|
||||
.then(references => references.filter(x => !!x));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
|
||||
export class CodeActionKind {
|
||||
private static readonly sep = '.';
|
||||
|
||||
public static readonly Empty = new CodeActionKind('');
|
||||
public static readonly Refactor = new CodeActionKind('refactor');
|
||||
|
||||
constructor(
|
||||
public readonly value: string
|
||||
) { }
|
||||
|
||||
public contains(other: string): boolean {
|
||||
return this.value === other || startsWith(other, this.value + CodeActionKind.sep);
|
||||
}
|
||||
}
|
||||
|
||||
export enum CodeActionAutoApply {
|
||||
IfSingle = 1,
|
||||
First = 2,
|
||||
Never = 3
|
||||
}
|
||||
|
||||
export interface CodeActionTrigger {
|
||||
type: 'auto' | 'manual';
|
||||
kind?: CodeActionKind;
|
||||
autoApply?: CodeActionAutoApply;
|
||||
}
|
||||
@@ -14,16 +14,19 @@ import { onUnexpectedExternalError, illegalArgument } from 'vs/base/common/error
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { CodeActionKind } from './codeActionTrigger';
|
||||
|
||||
export function getCodeActions(model: ITextModel, range: Range): TPromise<CodeAction[]> {
|
||||
export function getCodeActions(model: ITextModel, range: Range, scope?: CodeActionKind): TPromise<CodeAction[]> {
|
||||
|
||||
const allResults: CodeAction[] = [];
|
||||
const promises = CodeActionProviderRegistry.all(model).map(support => {
|
||||
return asWinJsPromise(token => support.provideCodeActions(model, range, token)).then(result => {
|
||||
return asWinJsPromise(token => support.provideCodeActions(model, range, { only: scope ? scope.value : undefined }, token)).then(result => {
|
||||
if (Array.isArray(result)) {
|
||||
for (const quickFix of result) {
|
||||
if (quickFix) {
|
||||
allResults.push(quickFix);
|
||||
if (!scope || (quickFix.kind && scope.contains(quickFix.kind))) {
|
||||
allResults.push(quickFix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,12 @@ import { optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IMarkerService } from 'vs/platform/markers/common/markers';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions';
|
||||
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { QuickFixContextMenu } from './quickFixWidget';
|
||||
import { LightBulbWidget } from './lightBulbWidget';
|
||||
import { QuickFixModel, QuickFixComputeEvent } from './quickFixModel';
|
||||
import { CodeActionKind, CodeActionAutoApply } from './codeActionTrigger';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { CodeAction } from 'vs/editor/common/modes';
|
||||
import { BulkEdit } from 'vs/editor/browser/services/bulkEdit';
|
||||
@@ -57,7 +58,7 @@ export class QuickFixController implements IEditorContribution {
|
||||
this._updateLightBulbTitle();
|
||||
|
||||
this._disposables.push(
|
||||
this._quickFixContextMenu.onDidExecuteCodeAction(_ => this._model.trigger('auto')),
|
||||
this._quickFixContextMenu.onDidExecuteCodeAction(_ => this._model.trigger({ type: 'auto' })),
|
||||
this._lightBulbWidget.onClick(this._handleLightBulbSelect, this),
|
||||
this._model.onDidChangeFixes(e => this._onQuickFixEvent(e)),
|
||||
this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitle, this)
|
||||
@@ -70,9 +71,21 @@ export class QuickFixController implements IEditorContribution {
|
||||
}
|
||||
|
||||
private _onQuickFixEvent(e: QuickFixComputeEvent): void {
|
||||
if (e && e.type === 'manual') {
|
||||
this._quickFixContextMenu.show(e.fixes, e.position);
|
||||
if (e && e.trigger.kind) {
|
||||
// Triggered for specific scope
|
||||
// Apply if we only have one action or requested autoApply, otherwise show menu
|
||||
e.fixes.then(fixes => {
|
||||
if (e.trigger.autoApply === CodeActionAutoApply.First || (e.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.length === 1)) {
|
||||
this._onApplyCodeAction(fixes[0]);
|
||||
} else {
|
||||
this._quickFixContextMenu.show(e.fixes, e.position);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (e && e.trigger.type === 'manual') {
|
||||
this._quickFixContextMenu.show(e.fixes, e.position);
|
||||
} else if (e && e.fixes) {
|
||||
// auto magically triggered
|
||||
// * update an existing list of code actions
|
||||
@@ -96,7 +109,11 @@ export class QuickFixController implements IEditorContribution {
|
||||
}
|
||||
|
||||
public triggerFromEditorSelection(): void {
|
||||
this._model.trigger('manual');
|
||||
this._model.trigger({ type: 'manual' });
|
||||
}
|
||||
|
||||
public triggerCodeActionFromEditorSelection(kind?: CodeActionKind, autoApply?: CodeActionAutoApply): void {
|
||||
this._model.trigger({ type: 'manual', kind, autoApply });
|
||||
}
|
||||
|
||||
private _updateLightBulbTitle(): void {
|
||||
@@ -146,5 +163,87 @@ export class QuickFixAction extends EditorAction {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CodeActionCommandArgs {
|
||||
public static fromUser(arg: any): CodeActionCommandArgs {
|
||||
if (!arg || typeof arg !== 'object') {
|
||||
return new CodeActionCommandArgs(CodeActionKind.Empty, CodeActionAutoApply.IfSingle);
|
||||
}
|
||||
return new CodeActionCommandArgs(
|
||||
CodeActionCommandArgs.getKindFromUser(arg),
|
||||
CodeActionCommandArgs.getApplyFromUser(arg));
|
||||
}
|
||||
|
||||
private static getApplyFromUser(arg: any) {
|
||||
switch (typeof arg.apply === 'string' ? arg.apply.toLowerCase() : '') {
|
||||
case 'first':
|
||||
return CodeActionAutoApply.First;
|
||||
|
||||
case 'never':
|
||||
return CodeActionAutoApply.Never;
|
||||
|
||||
case 'ifsingle':
|
||||
default:
|
||||
return CodeActionAutoApply.IfSingle;
|
||||
}
|
||||
}
|
||||
|
||||
private static getKindFromUser(arg: any) {
|
||||
return typeof arg.kind === 'string'
|
||||
? new CodeActionKind(arg.kind)
|
||||
: CodeActionKind.Empty;
|
||||
}
|
||||
|
||||
private constructor(
|
||||
public readonly kind: CodeActionKind,
|
||||
public readonly apply: CodeActionAutoApply
|
||||
) { }
|
||||
}
|
||||
|
||||
export class CodeActionCommand extends EditorCommand {
|
||||
|
||||
static readonly Id = 'editor.action.codeAction';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: CodeActionCommand.Id,
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider)
|
||||
});
|
||||
}
|
||||
|
||||
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, userArg: any) {
|
||||
const controller = QuickFixController.get(editor);
|
||||
if (controller) {
|
||||
const args = CodeActionCommandArgs.fromUser(userArg);
|
||||
controller.triggerCodeActionFromEditorSelection(args.kind, args.apply);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class RefactorAction extends EditorAction {
|
||||
|
||||
static readonly Id = 'editor.action.refactor';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: RefactorAction.Id,
|
||||
label: nls.localize('refactor.label', "Refactor"),
|
||||
alias: 'Refactor',
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider)
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
const controller = QuickFixController.get(editor);
|
||||
if (controller) {
|
||||
controller.triggerCodeActionFromEditorSelection(CodeActionKind.Refactor, CodeActionAutoApply.Never);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
registerEditorContribution(QuickFixController);
|
||||
registerEditorAction(QuickFixAction);
|
||||
registerEditorAction(RefactorAction);
|
||||
registerEditorCommand(new CodeActionCommand());
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { CodeActionProviderRegistry, CodeAction } from 'vs/editor/common/modes';
|
||||
import { getCodeActions } from './quickFix';
|
||||
import { CodeActionTrigger } from './codeActionTrigger';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
|
||||
@@ -36,26 +37,26 @@ export class QuickFixOracle {
|
||||
this._disposables = dispose(this._disposables);
|
||||
}
|
||||
|
||||
trigger(type: 'manual' | 'auto'): void {
|
||||
trigger(trigger: CodeActionTrigger): void {
|
||||
let rangeOrSelection = this._getRangeOfMarker() || this._getRangeOfSelectionUnlessWhitespaceEnclosed();
|
||||
if (!rangeOrSelection && type === 'manual') {
|
||||
if (!rangeOrSelection && trigger.type === 'manual') {
|
||||
rangeOrSelection = this._editor.getSelection();
|
||||
}
|
||||
this._createEventAndSignalChange(type, rangeOrSelection);
|
||||
this._createEventAndSignalChange(trigger, rangeOrSelection);
|
||||
}
|
||||
|
||||
private _onMarkerChanges(resources: URI[]): void {
|
||||
const { uri } = this._editor.getModel();
|
||||
for (const resource of resources) {
|
||||
if (resource.toString() === uri.toString()) {
|
||||
this.trigger('auto');
|
||||
this.trigger({ type: 'auto' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _onCursorChange(): void {
|
||||
this.trigger('auto');
|
||||
this.trigger({ type: 'auto' });
|
||||
}
|
||||
|
||||
private _getRangeOfMarker(): Range {
|
||||
@@ -98,24 +99,24 @@ export class QuickFixOracle {
|
||||
return selection;
|
||||
}
|
||||
|
||||
private _createEventAndSignalChange(type: 'auto' | 'manual', rangeOrSelection: Range | Selection): void {
|
||||
private _createEventAndSignalChange(trigger: CodeActionTrigger, rangeOrSelection: Range | Selection): void {
|
||||
if (!rangeOrSelection) {
|
||||
// cancel
|
||||
this._signalChange({
|
||||
type,
|
||||
trigger,
|
||||
range: undefined,
|
||||
position: undefined,
|
||||
fixes: undefined
|
||||
fixes: undefined,
|
||||
});
|
||||
} else {
|
||||
// actual
|
||||
const model = this._editor.getModel();
|
||||
const range = model.validateRange(rangeOrSelection);
|
||||
const position = rangeOrSelection instanceof Selection ? rangeOrSelection.getPosition() : rangeOrSelection.getStartPosition();
|
||||
const fixes = getCodeActions(model, range);
|
||||
const fixes = getCodeActions(model, range, trigger && trigger.kind);
|
||||
|
||||
this._signalChange({
|
||||
type,
|
||||
trigger,
|
||||
range,
|
||||
position,
|
||||
fixes
|
||||
@@ -125,7 +126,7 @@ export class QuickFixOracle {
|
||||
}
|
||||
|
||||
export interface QuickFixComputeEvent {
|
||||
type: 'auto' | 'manual';
|
||||
trigger: CodeActionTrigger;
|
||||
range: Range;
|
||||
position: Position;
|
||||
fixes: TPromise<CodeAction[]>;
|
||||
@@ -172,13 +173,13 @@ export class QuickFixModel {
|
||||
&& !this._editor.getConfiguration().readOnly) {
|
||||
|
||||
this._quickFixOracle = new QuickFixOracle(this._editor, this._markerService, p => this._onDidChangeFixes.fire(p));
|
||||
this._quickFixOracle.trigger('auto');
|
||||
this._quickFixOracle.trigger({ type: 'auto' });
|
||||
}
|
||||
}
|
||||
|
||||
trigger(type: 'auto' | 'manual'): void {
|
||||
trigger(trigger: CodeActionTrigger): void {
|
||||
if (this._quickFixOracle) {
|
||||
this._quickFixOracle.trigger(type);
|
||||
this._quickFixOracle.trigger(trigger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ import * as assert from 'assert';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { CodeActionProviderRegistry, LanguageIdentifier, CodeActionProvider, Command, WorkspaceEdit, ResourceTextEdit } from 'vs/editor/common/modes';
|
||||
import { CodeActionProviderRegistry, LanguageIdentifier, CodeActionProvider, Command, WorkspaceEdit, ResourceTextEdit, CodeAction, CodeActionContext } from 'vs/editor/common/modes';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { getCodeActions } from 'vs/editor/contrib/quickFix/quickFix';
|
||||
import { CodeActionKind } from 'vs/editor/contrib/quickFix/codeActionTrigger';
|
||||
|
||||
suite('QuickFix', () => {
|
||||
|
||||
@@ -120,4 +121,52 @@ suite('QuickFix', () => {
|
||||
assert.equal(actions.length, 6);
|
||||
assert.deepEqual(actions, expected);
|
||||
});
|
||||
|
||||
test('getCodeActions should filter by scope', async function () {
|
||||
const provider = new class implements CodeActionProvider {
|
||||
provideCodeActions(): CodeAction[] {
|
||||
return [
|
||||
{ title: 'a', kind: 'a' },
|
||||
{ title: 'b', kind: 'b' },
|
||||
{ title: 'a.b', kind: 'a.b' }
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), new CodeActionKind('a'));
|
||||
assert.equal(actions.length, 2);
|
||||
assert.strictEqual(actions[0].title, 'a');
|
||||
assert.strictEqual(actions[1].title, 'a.b');
|
||||
}
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), new CodeActionKind('a.b'));
|
||||
assert.equal(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, 'a.b');
|
||||
}
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), new CodeActionKind('a.b.c'));
|
||||
assert.equal(actions.length, 0);
|
||||
}
|
||||
});
|
||||
|
||||
test('getCodeActions should forward requested scope to providers', async function () {
|
||||
const provider = new class implements CodeActionProvider {
|
||||
provideCodeActions(_model: any, _range: Range, context: CodeActionContext, _token: any): CodeAction[] {
|
||||
return [
|
||||
{ title: context.only, kind: context.only }
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), new CodeActionKind('a'));
|
||||
assert.equal(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, 'a');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ suite('QuickFix', () => {
|
||||
test('Orcale -> marker added', done => {
|
||||
|
||||
const oracle = new QuickFixOracle(editor, markerService, e => {
|
||||
assert.equal(e.type, 'auto');
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
assert.ok(e.fixes);
|
||||
|
||||
e.fixes.then(fixes => {
|
||||
@@ -83,7 +83,7 @@ suite('QuickFix', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const oracle = new QuickFixOracle(editor, markerService, e => {
|
||||
assert.equal(e.type, 'auto');
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
assert.ok(e.fixes);
|
||||
e.fixes.then(fixes => {
|
||||
oracle.dispose();
|
||||
@@ -160,7 +160,7 @@ suite('QuickFix', () => {
|
||||
await new Promise(resolve => {
|
||||
|
||||
let oracle = new QuickFixOracle(editor, markerService, e => {
|
||||
assert.equal(e.type, 'auto');
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
assert.deepEqual(e.range, { startLineNumber: 3, startColumn: 1, endLineNumber: 3, endColumn: 4 });
|
||||
assert.deepEqual(e.position, { lineNumber: 3, column: 1 });
|
||||
|
||||
|
||||
@@ -667,7 +667,7 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
|
||||
this.show();
|
||||
break;
|
||||
case State.Frozen:
|
||||
hide(this.messageElement, this.details.element);
|
||||
hide(this.messageElement);
|
||||
show(this.listElement);
|
||||
this.show();
|
||||
break;
|
||||
|
||||
@@ -329,11 +329,11 @@ export function registerCodeLensProvider(languageId: string, provider: modes.Cod
|
||||
*/
|
||||
export function registerCodeActionProvider(languageId: string, provider: CodeActionProvider): IDisposable {
|
||||
return modes.CodeActionProviderRegistry.register(languageId, {
|
||||
provideCodeActions: (model: model.ITextModel, range: Range, token: CancellationToken): (modes.Command | modes.CodeAction)[] | Thenable<(modes.Command | modes.CodeAction)[]> => {
|
||||
provideCodeActions: (model: model.ITextModel, range: Range, context: modes.CodeActionContext, token: CancellationToken): (modes.Command | modes.CodeAction)[] | Thenable<(modes.Command | modes.CodeAction)[]> => {
|
||||
let markers = StaticServices.markerService.get().read({ resource: model.uri }).filter(m => {
|
||||
return Range.areIntersectingOrTouching(m, range);
|
||||
});
|
||||
return provider.provideCodeActions(model, range, { markers }, token);
|
||||
return provider.provideCodeActions(model, range, { markers, only: context.only }, token);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -401,6 +401,11 @@ export interface CodeActionContext {
|
||||
* @readonly
|
||||
*/
|
||||
readonly markers: IMarkerData[];
|
||||
|
||||
/**
|
||||
* Requested kind of actions to return.
|
||||
*/
|
||||
readonly only?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,6 +40,7 @@ import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyServ
|
||||
import { IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService';
|
||||
import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl';
|
||||
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export interface IEditorContextViewService extends IContextViewService {
|
||||
dispose(): void;
|
||||
@@ -142,6 +143,8 @@ export module StaticServices {
|
||||
|
||||
export const storageService = define(IStorageService, () => NullStorageService);
|
||||
|
||||
export const logService = define(ILogService, () => new NullLogService());
|
||||
|
||||
}
|
||||
|
||||
export class DynamicStandaloneServices extends Disposable {
|
||||
|
||||
@@ -1517,4 +1517,76 @@ suite('buffer api', () => {
|
||||
assert(!a.equal(c));
|
||||
assert(!a.equal(d));
|
||||
});
|
||||
|
||||
test('equal 2, empty buffer', () => {
|
||||
let a = createTextBuffer(['']);
|
||||
let b = createTextBuffer(['']);
|
||||
|
||||
assert(a.equal(b));
|
||||
});
|
||||
|
||||
test('equal 3, empty buffer', () => {
|
||||
let a = createTextBuffer(['a']);
|
||||
let b = createTextBuffer(['']);
|
||||
|
||||
assert(!a.equal(b));
|
||||
});
|
||||
});
|
||||
|
||||
suite('search offset cache', () => {
|
||||
test('render white space exception', () => {
|
||||
let pieceTable = createTextBuffer(['class Name{\n\t\n\t\t\tget() {\n\n\t\t\t}\n\t\t}']);
|
||||
let str = 'class Name{\n\t\n\t\t\tget() {\n\n\t\t\t}\n\t\t}';
|
||||
|
||||
pieceTable.insert(12, 's');
|
||||
str = str.substring(0, 12) + 's' + str.substring(12);
|
||||
|
||||
pieceTable.insert(13, 'e');
|
||||
str = str.substring(0, 13) + 'e' + str.substring(13);
|
||||
|
||||
pieceTable.insert(14, 't');
|
||||
str = str.substring(0, 14) + 't' + str.substring(14);
|
||||
|
||||
pieceTable.insert(15, '()');
|
||||
str = str.substring(0, 15) + '()' + str.substring(15);
|
||||
|
||||
pieceTable.delete(16, 1);
|
||||
str = str.substring(0, 16) + str.substring(16 + 1);
|
||||
|
||||
pieceTable.insert(17, '()');
|
||||
str = str.substring(0, 17) + '()' + str.substring(17);
|
||||
|
||||
pieceTable.delete(18, 1);
|
||||
str = str.substring(0, 18) + str.substring(18 + 1);
|
||||
|
||||
pieceTable.insert(18, '}');
|
||||
str = str.substring(0, 18) + '}' + str.substring(18);
|
||||
|
||||
pieceTable.insert(12, '\n');
|
||||
str = str.substring(0, 12) + '\n' + str.substring(12);
|
||||
|
||||
pieceTable.delete(12, 1);
|
||||
str = str.substring(0, 12) + str.substring(12 + 1);
|
||||
|
||||
pieceTable.delete(18, 1);
|
||||
str = str.substring(0, 18) + str.substring(18 + 1);
|
||||
|
||||
pieceTable.insert(18, '}');
|
||||
str = str.substring(0, 18) + '}' + str.substring(18);
|
||||
|
||||
pieceTable.delete(17, 2);
|
||||
str = str.substring(0, 17) + str.substring(17 + 2);
|
||||
|
||||
pieceTable.delete(16, 1);
|
||||
str = str.substring(0, 16) + str.substring(16 + 1);
|
||||
|
||||
pieceTable.insert(16, ')');
|
||||
str = str.substring(0, 16) + ')' + str.substring(16);
|
||||
|
||||
pieceTable.delete(15, 2);
|
||||
str = str.substring(0, 15) + str.substring(15 + 2);
|
||||
|
||||
var content = pieceTable.getLinesRawContent();
|
||||
assert(content === str);
|
||||
});
|
||||
});
|
||||
Vendored
+5
@@ -4043,6 +4043,10 @@ declare module monaco.languages {
|
||||
* @readonly
|
||||
*/
|
||||
readonly markers: editor.IMarkerData[];
|
||||
/**
|
||||
* Requested kind of actions to return.
|
||||
*/
|
||||
readonly only?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4495,6 +4499,7 @@ declare module monaco.languages {
|
||||
command?: Command;
|
||||
edit?: WorkspaceEdit;
|
||||
diagnostics?: editor.IMarkerData[];
|
||||
kind?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IMenu, MenuItemAction, IMenuActionOptions } from 'vs/platform/actions/common/actions';
|
||||
import { IMenu, MenuItemAction, IMenuActionOptions, ICommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
@@ -17,6 +17,9 @@ import { domEvent } from 'vs/base/browser/event';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { IdGenerator } from 'vs/base/common/idGenerator';
|
||||
import { createCSSRule } from 'vs/base/browser/dom';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
class AltKeyEmitter extends Emitter<boolean> {
|
||||
|
||||
@@ -114,17 +117,22 @@ export function createActionItem(action: IAction, keybindingService: IKeybinding
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const ids = new IdGenerator('menu-item-action-item-icon-');
|
||||
|
||||
export class MenuItemActionItem extends ActionItem {
|
||||
|
||||
static readonly ICON_PATH_TO_CSS_RULES: Map<string /* path*/, string /* CSS rule */> = new Map<string, string>();
|
||||
|
||||
private _wantsAltCommand: boolean = false;
|
||||
private _itemClassDispose: IDisposable;
|
||||
|
||||
constructor(
|
||||
action: MenuItemAction,
|
||||
public _action: MenuItemAction,
|
||||
@IKeybindingService private _keybindingService: IKeybindingService,
|
||||
@IMessageService protected _messageService: IMessageService,
|
||||
@IContextMenuService private _contextMenuService: IContextMenuService
|
||||
) {
|
||||
super(undefined, action, { icon: !!action.class, label: !action.class });
|
||||
super(undefined, _action, { icon: !!(_action.class || _action.item.iconPath), label: !_action.class && !_action.item.iconPath });
|
||||
}
|
||||
|
||||
protected get _commandAction(): IAction {
|
||||
@@ -142,6 +150,8 @@ export class MenuItemActionItem extends ActionItem {
|
||||
render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
this._updateItemClass(this._action.item);
|
||||
|
||||
let mouseOver = false;
|
||||
let altDown = false;
|
||||
|
||||
@@ -189,13 +199,41 @@ export class MenuItemActionItem extends ActionItem {
|
||||
|
||||
_updateClass(): void {
|
||||
if (this.options.icon) {
|
||||
const element = this.$e.getHTMLElement();
|
||||
if (this._commandAction !== this._action) {
|
||||
element.classList.remove(this._action.class);
|
||||
this._updateItemClass(this._action.alt.item);
|
||||
} else if ((<MenuItemAction>this._action).alt) {
|
||||
element.classList.remove((<MenuItemAction>this._action).alt.class);
|
||||
this._updateItemClass(this._action.item);
|
||||
}
|
||||
element.classList.add('icon', this._commandAction.class);
|
||||
}
|
||||
}
|
||||
|
||||
_updateItemClass(item: ICommandAction): void {
|
||||
dispose(this._itemClassDispose);
|
||||
this._itemClassDispose = undefined;
|
||||
|
||||
if (item.iconPath) {
|
||||
let iconClass: string;
|
||||
|
||||
if (MenuItemActionItem.ICON_PATH_TO_CSS_RULES.has(item.iconPath.dark)) {
|
||||
iconClass = MenuItemActionItem.ICON_PATH_TO_CSS_RULES.get(item.iconPath.dark);
|
||||
} else {
|
||||
iconClass = ids.nextId();
|
||||
createCSSRule(`.icon.${iconClass}`, `background-image: url("${URI.file(item.iconPath.light || item.iconPath.dark).toString()}")`);
|
||||
createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: url("${URI.file(item.iconPath.dark).toString()}")`);
|
||||
MenuItemActionItem.ICON_PATH_TO_CSS_RULES.set(item.iconPath.dark, iconClass);
|
||||
}
|
||||
|
||||
this.$e.getHTMLElement().classList.add('icon', iconClass);
|
||||
this._itemClassDispose = { dispose: () => this.$e.getHTMLElement().classList.remove('icon', iconClass) };
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this._itemClassDispose) {
|
||||
dispose(this._itemClassDispose);
|
||||
this._itemClassDispose = undefined;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ export interface ICommandAction {
|
||||
id: string;
|
||||
title: string | ILocalizedString;
|
||||
category?: string | ILocalizedString;
|
||||
iconClass?: string;
|
||||
iconPath?: string;
|
||||
iconPath?: { dark: string; light?: string; };
|
||||
precondition?: ContextKeyExpr;
|
||||
}
|
||||
|
||||
@@ -180,7 +179,7 @@ export class MenuItemAction extends ExecuteCommandAction {
|
||||
@ICommandService commandService: ICommandService
|
||||
) {
|
||||
typeof item.title === 'string' ? super(item.id, item.title, commandService) : super(item.id, item.title.value, commandService);
|
||||
this._cssClass = item.iconClass;
|
||||
this._cssClass = undefined;
|
||||
this._enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition);
|
||||
this._options = options || {};
|
||||
|
||||
|
||||
@@ -4,12 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { createCSSRule } from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
|
||||
import { join } from 'path';
|
||||
import { IdGenerator } from 'vs/base/common/idGenerator';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { forEach } from 'vs/base/common/collections';
|
||||
import { IExtensionPointUser, ExtensionMessageCollector, ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry';
|
||||
@@ -281,33 +278,27 @@ namespace schema {
|
||||
|
||||
ExtensionsRegistry.registerExtensionPoint<schema.IUserFriendlyCommand | schema.IUserFriendlyCommand[]>('commands', [], schema.commandsContribution).setHandler(extensions => {
|
||||
|
||||
const ids = new IdGenerator('contrib-cmd-icon-');
|
||||
|
||||
function handleCommand(userFriendlyCommand: schema.IUserFriendlyCommand, extension: IExtensionPointUser<any>) {
|
||||
|
||||
if (!schema.isValidCommand(userFriendlyCommand, extension.collector)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { icon, category, title, command } = userFriendlyCommand;
|
||||
let iconClass: string;
|
||||
let iconPath: string;
|
||||
if (icon) {
|
||||
iconClass = ids.nextId();
|
||||
if (typeof icon === 'string') {
|
||||
iconPath = join(extension.description.extensionFolderPath, icon);
|
||||
createCSSRule(`.icon.${iconClass}`, `background-image: url("${URI.file(iconPath).toString()}")`);
|
||||
} else {
|
||||
const light = join(extension.description.extensionFolderPath, icon.light);
|
||||
const dark = join(extension.description.extensionFolderPath, icon.dark);
|
||||
createCSSRule(`.icon.${iconClass}`, `background-image: url("${URI.file(light).toString()}")`);
|
||||
createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: url("${URI.file(dark).toString()}")`);
|
||||
const { icon, category, title, command } = userFriendlyCommand;
|
||||
|
||||
iconPath = join(extension.description.extensionFolderPath, icon.dark);
|
||||
let absoluteIcon: { dark: string; light?: string; };
|
||||
if (icon) {
|
||||
if (typeof icon === 'string') {
|
||||
absoluteIcon = { dark: join(extension.description.extensionFolderPath, icon) };
|
||||
} else {
|
||||
absoluteIcon = {
|
||||
dark: join(extension.description.extensionFolderPath, icon.dark),
|
||||
light: join(extension.description.extensionFolderPath, icon.light)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (MenuRegistry.addCommand({ id: command, title, category, iconClass, iconPath })) {
|
||||
if (MenuRegistry.addCommand({ id: command, title, category, iconPath: absoluteIcon })) {
|
||||
extension.collector.info(localize('dup', "Command `{0}` appears multiple times in the `commands` section.", userFriendlyCommand.command));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ export enum ContextKeyExprType {
|
||||
Not = 2,
|
||||
Equals = 3,
|
||||
NotEquals = 4,
|
||||
And = 5
|
||||
And = 5,
|
||||
Regex = 6
|
||||
}
|
||||
|
||||
export abstract class ContextKeyExpr {
|
||||
@@ -29,6 +30,10 @@ export abstract class ContextKeyExpr {
|
||||
return new ContextKeyNotEqualsExpr(key, value);
|
||||
}
|
||||
|
||||
public static regex(key: string, value: string): ContextKeyExpr {
|
||||
return new ContextKeyRegexExpr(key, value);
|
||||
}
|
||||
|
||||
public static not(key: string): ContextKeyExpr {
|
||||
return new ContextKeyNotExpr(key);
|
||||
}
|
||||
@@ -60,6 +65,11 @@ export abstract class ContextKeyExpr {
|
||||
return new ContextKeyEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1]));
|
||||
}
|
||||
|
||||
if (serializedOne.indexOf('=~') >= 0) {
|
||||
let pieces = serializedOne.split('=~');
|
||||
return new ContextKeyRegexExpr(pieces[0].trim(), this._deserializeValue(pieces[1]));
|
||||
}
|
||||
|
||||
if (/^\!\s*/.test(serializedOne)) {
|
||||
return new ContextKeyNotExpr(serializedOne.substr(1).trim());
|
||||
}
|
||||
@@ -109,6 +119,8 @@ function cmp(a: ContextKeyExpr, b: ContextKeyExpr): number {
|
||||
return (<ContextKeyEqualsExpr>a).cmp(<ContextKeyEqualsExpr>b);
|
||||
case ContextKeyExprType.NotEquals:
|
||||
return (<ContextKeyNotEqualsExpr>a).cmp(<ContextKeyNotEqualsExpr>b);
|
||||
case ContextKeyExprType.Regex:
|
||||
return (<ContextKeyRegexExpr>a).cmp(<ContextKeyRegexExpr>b);
|
||||
default:
|
||||
throw new Error('Unknown ContextKeyExpr!');
|
||||
}
|
||||
@@ -320,6 +332,63 @@ export class ContextKeyNotExpr implements ContextKeyExpr {
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextKeyRegexExpr implements ContextKeyExpr {
|
||||
|
||||
private regexp: { source: string, test(s: string): boolean };
|
||||
|
||||
constructor(private key: string, value: any) {
|
||||
try {
|
||||
this.regexp = new RegExp(value);
|
||||
} catch (e) {
|
||||
this.regexp = { source: '', test() { return false; } };
|
||||
console.warn(`Bad value for glob-context key expression: ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
public getType(): ContextKeyExprType {
|
||||
return ContextKeyExprType.Regex;
|
||||
}
|
||||
|
||||
public cmp(other: ContextKeyRegexExpr): number {
|
||||
if (this.key < other.key) {
|
||||
return -1;
|
||||
}
|
||||
if (this.key > other.key) {
|
||||
return 1;
|
||||
}
|
||||
if (this.regexp.source < other.regexp.source) {
|
||||
return -1;
|
||||
}
|
||||
if (this.regexp.source > other.regexp.source) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public equals(other: ContextKeyExpr): boolean {
|
||||
if (other instanceof ContextKeyRegexExpr) {
|
||||
return (this.key === other.key && this.regexp.source === other.regexp.source);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public evaluate(context: IContext): boolean {
|
||||
return this.regexp.test(context.getValue(this.key));
|
||||
}
|
||||
|
||||
public normalize(): ContextKeyExpr {
|
||||
return this;
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
return this.key + ' =~ \'' + this.regexp.source + '\'';
|
||||
}
|
||||
|
||||
public keys(): string[] {
|
||||
return [this.key];
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextKeyAndExpr implements ContextKeyExpr {
|
||||
public readonly expr: ContextKeyExpr[];
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ suite('ContextKeyExpr', () => {
|
||||
ContextKeyExpr.has('a1'),
|
||||
ContextKeyExpr.and(ContextKeyExpr.has('and.a')),
|
||||
ContextKeyExpr.has('a2'),
|
||||
ContextKeyExpr.regex('d3', 'd.*'),
|
||||
ContextKeyExpr.regex('d4', '\\*\\*/3*'),
|
||||
ContextKeyExpr.equals('b1', 'bb1'),
|
||||
ContextKeyExpr.equals('b2', 'bb2'),
|
||||
ContextKeyExpr.notEquals('c1', 'cc1'),
|
||||
@@ -32,9 +34,11 @@ suite('ContextKeyExpr', () => {
|
||||
ContextKeyExpr.equals('b2', 'bb2'),
|
||||
ContextKeyExpr.notEquals('c1', 'cc1'),
|
||||
ContextKeyExpr.not('d1'),
|
||||
ContextKeyExpr.regex('d4', '\\*\\*/3*'),
|
||||
ContextKeyExpr.notEquals('c2', 'cc2'),
|
||||
ContextKeyExpr.has('a2'),
|
||||
ContextKeyExpr.equals('b1', 'bb1'),
|
||||
ContextKeyExpr.regex('d3', 'd.*'),
|
||||
ContextKeyExpr.has('a1'),
|
||||
ContextKeyExpr.and(ContextKeyExpr.equals('and.a', true)),
|
||||
ContextKeyExpr.not('d2')
|
||||
@@ -59,9 +63,11 @@ suite('ContextKeyExpr', () => {
|
||||
let context = createContext({
|
||||
'a': true,
|
||||
'b': false,
|
||||
'c': '5'
|
||||
'c': '5',
|
||||
'd': 'd'
|
||||
});
|
||||
function testExpression(expr: string, expected: boolean): void {
|
||||
// console.log(expr + ' ' + expected);
|
||||
let rules = ContextKeyExpr.deserialize(expr);
|
||||
assert.equal(rules.evaluate(context), expected, expr);
|
||||
}
|
||||
@@ -74,11 +80,13 @@ suite('ContextKeyExpr', () => {
|
||||
testExpression(expr + ' == 5', value == <any>'5');
|
||||
testExpression(expr + ' != 5', value != <any>'5');
|
||||
testExpression('!' + expr, !value);
|
||||
testExpression(expr + ' =~ d.*', /d.*/.test(value));
|
||||
}
|
||||
|
||||
testBatch('a', true);
|
||||
testBatch('b', false);
|
||||
testBatch('c', '5');
|
||||
testBatch('d', 'd');
|
||||
testBatch('z', undefined);
|
||||
|
||||
testExpression('a && !b', true && !false);
|
||||
|
||||
@@ -78,7 +78,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
|
||||
}
|
||||
|
||||
canChangeEnablement(extension: ILocalExtension): boolean {
|
||||
return !this.environmentService.disableExtensions && !(extension.manifest && extension.manifest.contributes && extension.manifest.contributes.locales && extension.manifest.contributes.locales.length);
|
||||
return !this.environmentService.disableExtensions && !(extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length);
|
||||
}
|
||||
|
||||
setEnablement(arg: ILocalExtension | IExtensionIdentifier, newState: EnablementState): TPromise<boolean> {
|
||||
|
||||
@@ -85,9 +85,10 @@ export interface IColor {
|
||||
defaults: { light: string, dark: string, highContrast: string };
|
||||
}
|
||||
|
||||
export interface ILocale {
|
||||
locale: string;
|
||||
path: string;
|
||||
export interface ILocalization {
|
||||
languageId: string;
|
||||
languageName?: string;
|
||||
translations: string;
|
||||
}
|
||||
|
||||
export interface IExtensionContributions {
|
||||
@@ -104,7 +105,7 @@ export interface IExtensionContributions {
|
||||
iconThemes?: ITheme[];
|
||||
views?: { [location: string]: IView[] };
|
||||
colors?: IColor[];
|
||||
locales?: ILocale[];
|
||||
localizations?: ILocalization[];
|
||||
}
|
||||
|
||||
export interface IExtensionManifest {
|
||||
@@ -228,6 +229,12 @@ export enum StatisticType {
|
||||
Uninstall = 'uninstall'
|
||||
}
|
||||
|
||||
export interface IReportedExtension {
|
||||
id: IExtensionIdentifier;
|
||||
malicious: boolean;
|
||||
slow: boolean;
|
||||
}
|
||||
|
||||
export interface IExtensionGalleryService {
|
||||
_serviceBrand: any;
|
||||
isEnabled(): boolean;
|
||||
@@ -239,6 +246,7 @@ export interface IExtensionGalleryService {
|
||||
getChangelog(extension: IGalleryExtension): TPromise<string>;
|
||||
loadCompatibleVersion(extension: IGalleryExtension): TPromise<IGalleryExtension>;
|
||||
loadAllDependencies(dependencies: IExtensionIdentifier[]): TPromise<IGalleryExtension[]>;
|
||||
getExtensionsReport(): TPromise<IReportedExtension[]>;
|
||||
}
|
||||
|
||||
export interface InstallExtensionEvent {
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as path from 'path';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionManifest, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionManifest, IExtensionIdentifier, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { getGalleryExtensionId, getGalleryExtensionTelemetryData, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { assign, getOrDefault } from 'vs/base/common/objects';
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
@@ -23,6 +23,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
|
||||
import { readFile } from 'vs/base/node/pfs';
|
||||
import { writeFileAndFlushSync } from 'vs/base/node/extfs';
|
||||
import { generateUuid, isUUID } from 'vs/base/common/uuid';
|
||||
import { values } from 'vs/base/common/map';
|
||||
|
||||
interface IRawGalleryExtensionFile {
|
||||
assetType: string;
|
||||
@@ -309,11 +310,17 @@ function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUr
|
||||
};
|
||||
}
|
||||
|
||||
interface IRawExtensionsReport {
|
||||
malicious: string[];
|
||||
slow: string[];
|
||||
}
|
||||
|
||||
export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private extensionsGalleryUrl: string;
|
||||
private extensionsControlUrl: string;
|
||||
|
||||
private readonly commonHeadersPromise: TPromise<{ [key: string]: string; }>;
|
||||
|
||||
@@ -324,6 +331,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
) {
|
||||
const config = product.extensionsGallery;
|
||||
this.extensionsGalleryUrl = config && config.serviceUrl;
|
||||
this.extensionsControlUrl = config && config.controlUrl;
|
||||
this.commonHeadersPromise = resolveMarketplaceHeaders(this.environmentService);
|
||||
}
|
||||
|
||||
@@ -711,6 +719,40 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getExtensionsReport(): TPromise<IReportedExtension[]> {
|
||||
if (!this.isEnabled()) {
|
||||
return TPromise.wrapError(new Error('No extension gallery service configured.'));
|
||||
}
|
||||
|
||||
if (!this.extensionsControlUrl) {
|
||||
return TPromise.as([]);
|
||||
}
|
||||
|
||||
return this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }).then(context => {
|
||||
if (context.res.statusCode !== 200) {
|
||||
return TPromise.wrapError(new Error('Could not get extensions report.'));
|
||||
}
|
||||
|
||||
return asJson<IRawExtensionsReport>(context).then(result => {
|
||||
const map = new Map<string, IReportedExtension>();
|
||||
|
||||
for (const id of result.malicious) {
|
||||
const ext = map.get(id) || { id: { id }, malicious: true, slow: false };
|
||||
ext.malicious = true;
|
||||
map.set(id, ext);
|
||||
}
|
||||
|
||||
for (const id of result.slow) {
|
||||
const ext = map.get(id) || { id: { id }, malicious: false, slow: true };
|
||||
ext.slow = true;
|
||||
map.set(id, ext);
|
||||
}
|
||||
|
||||
return TPromise.as(values(map));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveMarketplaceHeaders(environmentService: IEnvironmentService): TPromise<{ [key: string]: string; }> {
|
||||
|
||||
@@ -326,7 +326,7 @@ suite('ExtensionEnablementService Test', () => {
|
||||
});
|
||||
|
||||
test('test canChangeEnablement return false for language packs', () => {
|
||||
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { locales: [{ locale: 'gr', path: 'somepath' }] })), false);
|
||||
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { localizations: [{ languageId: 'gr', translations: 'somepath' }] })), false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ export interface IFileService {
|
||||
/**
|
||||
* Updates the content replacing its previous value.
|
||||
*/
|
||||
updateContent(resource: URI, value: string, options?: IUpdateContentOptions): TPromise<IFileStat>;
|
||||
updateContent(resource: URI, value: string | ITextSnapshot, options?: IUpdateContentOptions): TPromise<IFileStat>;
|
||||
|
||||
/**
|
||||
* Moves the file to a new path identified by the resource.
|
||||
@@ -468,6 +468,19 @@ export interface ITextSnapshot {
|
||||
read(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to convert a snapshot into its full string form.
|
||||
*/
|
||||
export function snapshotToString(snapshot: ITextSnapshot): string {
|
||||
const chunks: string[] = [];
|
||||
let chunk: string;
|
||||
while (typeof (chunk = snapshot.read()) === 'string') {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
return chunks.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Streamable content and meta information of a file.
|
||||
*/
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
'use strict';
|
||||
|
||||
import { ITree, ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree';
|
||||
import { List, IListCreationOptions } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { List, IListOptions, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, IMultipleSelectionController } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable, toDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IContextKeyService, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { PagedList, IPagedRenderer } from 'vs/base/browser/ui/list/listPaging';
|
||||
import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { IDelegate, IRenderer, IListMouseEvent, IListTouchEvent } from 'vs/base/browser/ui/list/list';
|
||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
@@ -96,8 +96,25 @@ function createScopedContextKeyService(contextKeyService: IContextKeyService, wi
|
||||
|
||||
export const multiSelectModifierSettingKey = 'workbench.multiSelectModifier';
|
||||
|
||||
function useAltAsMultiSelectModifier(configurationService: IConfigurationService): { useAltAsMultiSelectModifier: boolean } {
|
||||
return { useAltAsMultiSelectModifier: configurationService.getValue(multiSelectModifierSettingKey) === 'alt' };
|
||||
export function useAltAsMultipleSelectionModifier(configurationService: IConfigurationService): boolean {
|
||||
return configurationService.getValue(multiSelectModifierSettingKey) === 'alt';
|
||||
}
|
||||
|
||||
class MultipleSelectionController<T> implements IMultipleSelectionController<T> {
|
||||
|
||||
constructor(private configurationService: IConfigurationService) { }
|
||||
|
||||
isSelectionSingleChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean {
|
||||
if (useAltAsMultipleSelectionModifier(this.configurationService)) {
|
||||
return event.browserEvent.altKey;
|
||||
}
|
||||
|
||||
return isSelectionSingleChangeEvent(event);
|
||||
}
|
||||
|
||||
isSelectionRangeChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean {
|
||||
return isSelectionRangeChangeEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
export class WorkbenchList<T> extends List<T> {
|
||||
@@ -109,34 +126,28 @@ export class WorkbenchList<T> extends List<T> {
|
||||
container: HTMLElement,
|
||||
delegate: IDelegate<T>,
|
||||
renderers: IRenderer<T, any>[],
|
||||
private options: IListCreationOptions<T>,
|
||||
options: IListOptions<T>,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IListService listService: IListService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super(container, delegate, renderers, mixin(options, useAltAsMultiSelectModifier(configurationService)));
|
||||
const multipleSelectionSupport = !(options.multipleSelectionSupport === false);
|
||||
|
||||
if (multipleSelectionSupport && !options.multipleSelectionController) {
|
||||
options.multipleSelectionController = new MultipleSelectionController(configurationService);
|
||||
}
|
||||
|
||||
super(container, delegate, renderers, mixin(options, useAltAsMultipleSelectionModifier(configurationService)));
|
||||
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
|
||||
this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);
|
||||
|
||||
this.disposables.push(combinedDisposable([
|
||||
this.contextKeyService,
|
||||
(listService as ListService).register(this),
|
||||
attachListStyler(this, themeService)
|
||||
attachListStyler(this, themeService),
|
||||
this.onSelectionChange(() => this.listDoubleSelection.set(this.getSelection().length === 2))
|
||||
]));
|
||||
this.disposables.push(this.onSelectionChange(() => {
|
||||
const selection = this.getSelection();
|
||||
this.listDoubleSelection.set(selection && selection.length === 2);
|
||||
}));
|
||||
this.disposables.push(configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
|
||||
this.updateOptions(useAltAsMultiSelectModifier(configurationService));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public get useAltAsMultiSelectModifier(): boolean {
|
||||
return this.options.useAltAsMultiSelectModifier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,24 +160,25 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
|
||||
container: HTMLElement,
|
||||
delegate: IDelegate<number>,
|
||||
renderers: IPagedRenderer<T, any>[],
|
||||
options: IListCreationOptions<any>,
|
||||
options: IListOptions<any>,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IListService listService: IListService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super(container, delegate, renderers, mixin(options, useAltAsMultiSelectModifier(configurationService)));
|
||||
const multipleSelectionSupport = !(options.multipleSelectionSupport === false);
|
||||
|
||||
if (multipleSelectionSupport && !options.multipleSelectionController) {
|
||||
options.multipleSelectionController = new MultipleSelectionController(configurationService);
|
||||
}
|
||||
|
||||
super(container, delegate, renderers, mixin(options, useAltAsMultipleSelectionModifier(configurationService)));
|
||||
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
|
||||
|
||||
this.disposable = combinedDisposable([
|
||||
this.contextKeyService,
|
||||
(listService as ListService).register(this),
|
||||
attachListStyler(this, themeService),
|
||||
configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
|
||||
this.updateOptions(useAltAsMultiSelectModifier(configurationService));
|
||||
}
|
||||
})
|
||||
attachListStyler(this, themeService)
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface IProductConfiguration {
|
||||
extensionsGallery: {
|
||||
serviceUrl: string;
|
||||
itemUrl: string;
|
||||
controlUrl: string;
|
||||
};
|
||||
extensionTips: { [id: string]: string; };
|
||||
extensionImportantTips: { [id: string]: { name: string; pattern: string; }; };
|
||||
|
||||
@@ -9,35 +9,71 @@ import Event, { NodeEventEmitter } from 'vs/base/common/event';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
export enum State {
|
||||
Uninitialized,
|
||||
Idle,
|
||||
CheckingForUpdate,
|
||||
UpdateAvailable,
|
||||
UpdateDownloaded
|
||||
}
|
||||
|
||||
export enum ExplicitState {
|
||||
Implicit,
|
||||
Explicit
|
||||
}
|
||||
|
||||
export interface IRawUpdate {
|
||||
releaseNotes: string;
|
||||
version: string;
|
||||
date: Date;
|
||||
}
|
||||
|
||||
export interface IUpdate {
|
||||
version: string;
|
||||
productVersion: string;
|
||||
date?: Date;
|
||||
releaseNotes?: string;
|
||||
supportsFastUpdate?: boolean;
|
||||
url?: string;
|
||||
hash?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates are run as a state machine:
|
||||
*
|
||||
* Uninitialized
|
||||
* ↓
|
||||
* Idle
|
||||
* ↓ ↑
|
||||
* Checking for Updates → Available for Download
|
||||
* ↓
|
||||
* Downloading → Ready
|
||||
* ↓ ↑
|
||||
* Downloaded → Updating
|
||||
*
|
||||
* Available: There is an update available for download (linux).
|
||||
* Ready: Code will be updated as soon as it restarts (win32, darwin).
|
||||
* Donwloaded: There is an update ready to be installed in the background (win32).
|
||||
*/
|
||||
|
||||
export enum StateType {
|
||||
Uninitialized = 'uninitialized',
|
||||
Idle = 'idle',
|
||||
CheckingForUpdates = 'checking for updates',
|
||||
AvailableForDownload = 'available for download',
|
||||
Downloading = 'downloading',
|
||||
Downloaded = 'downloaded',
|
||||
Updating = 'updating',
|
||||
Ready = 'ready',
|
||||
}
|
||||
|
||||
export type Uninitialized = { type: StateType.Uninitialized };
|
||||
export type Idle = { type: StateType.Idle };
|
||||
export type CheckingForUpdates = { type: StateType.CheckingForUpdates, explicit: boolean };
|
||||
export type AvailableForDownload = { type: StateType.AvailableForDownload, update: IUpdate };
|
||||
export type Downloading = { type: StateType.Downloading, update: IUpdate };
|
||||
export type Downloaded = { type: StateType.Downloaded, update: IUpdate };
|
||||
export type Updating = { type: StateType.Updating, update: IUpdate };
|
||||
export type Ready = { type: StateType.Ready, update: IUpdate };
|
||||
|
||||
export type State = Uninitialized | Idle | CheckingForUpdates | AvailableForDownload | Downloading | Downloaded | Updating | Ready;
|
||||
|
||||
export const State = {
|
||||
Uninitialized: { type: StateType.Uninitialized } as Uninitialized,
|
||||
Idle: { type: StateType.Idle } as Idle,
|
||||
CheckingForUpdates: (explicit: boolean) => ({ type: StateType.CheckingForUpdates, explicit } as CheckingForUpdates),
|
||||
AvailableForDownload: (update: IUpdate) => ({ type: StateType.AvailableForDownload, update } as AvailableForDownload),
|
||||
Downloading: (update: IUpdate) => ({ type: StateType.Downloading, update } as Downloading),
|
||||
Downloaded: (update: IUpdate) => ({ type: StateType.Downloaded, update } as Downloaded),
|
||||
Updating: (update: IUpdate) => ({ type: StateType.Updating, update } as Updating),
|
||||
Ready: (update: IUpdate) => ({ type: StateType.Ready, update } as Ready),
|
||||
};
|
||||
|
||||
export interface IAutoUpdater extends NodeEventEmitter {
|
||||
setFeedURL(url: string): void;
|
||||
checkForUpdates(): void;
|
||||
applyUpdate?(): TPromise<void>;
|
||||
quitAndInstall(): void;
|
||||
}
|
||||
|
||||
@@ -46,13 +82,11 @@ export const IUpdateService = createDecorator<IUpdateService>('updateService');
|
||||
export interface IUpdateService {
|
||||
_serviceBrand: any;
|
||||
|
||||
readonly onError: Event<any>;
|
||||
readonly onUpdateAvailable: Event<{ url: string; version: string; }>;
|
||||
readonly onUpdateNotAvailable: Event<boolean>;
|
||||
readonly onUpdateReady: Event<IRawUpdate>;
|
||||
readonly onStateChange: Event<State>;
|
||||
readonly state: State;
|
||||
|
||||
checkForUpdates(explicit: boolean): TPromise<IUpdate>;
|
||||
checkForUpdates(explicit: boolean): TPromise<void>;
|
||||
downloadUpdate(): TPromise<void>;
|
||||
applyUpdate(): TPromise<void>;
|
||||
quitAndInstall(): TPromise<void>;
|
||||
}
|
||||
@@ -9,15 +9,12 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IUpdateService, IRawUpdate, State, IUpdate } from './update';
|
||||
import { IUpdateService, State } from './update';
|
||||
|
||||
export interface IUpdateChannel extends IChannel {
|
||||
call(command: 'event:onError'): TPromise<void>;
|
||||
call(command: 'event:onUpdateAvailable'): TPromise<void>;
|
||||
call(command: 'event:onUpdateNotAvailable'): TPromise<void>;
|
||||
call(command: 'event:onUpdateReady'): TPromise<void>;
|
||||
call(command: 'event:onStateChange'): TPromise<void>;
|
||||
call(command: 'checkForUpdates', arg: boolean): TPromise<IUpdate>;
|
||||
call(command: 'checkForUpdates', arg: boolean): TPromise<void>;
|
||||
call(command: 'downloadUpdate'): TPromise<void>;
|
||||
call(command: 'applyUpdate'): TPromise<void>;
|
||||
call(command: 'quitAndInstall'): TPromise<void>;
|
||||
call(command: '_getInitialState'): TPromise<State>;
|
||||
call(command: string, arg?: any): TPromise<any>;
|
||||
@@ -29,12 +26,10 @@ export class UpdateChannel implements IUpdateChannel {
|
||||
|
||||
call(command: string, arg?: any): TPromise<any> {
|
||||
switch (command) {
|
||||
case 'event:onError': return eventToCall(this.service.onError);
|
||||
case 'event:onUpdateAvailable': return eventToCall(this.service.onUpdateAvailable);
|
||||
case 'event:onUpdateNotAvailable': return eventToCall(this.service.onUpdateNotAvailable);
|
||||
case 'event:onUpdateReady': return eventToCall(this.service.onUpdateReady);
|
||||
case 'event:onStateChange': return eventToCall(this.service.onStateChange);
|
||||
case 'checkForUpdates': return this.service.checkForUpdates(arg);
|
||||
case 'downloadUpdate': return this.service.downloadUpdate();
|
||||
case 'applyUpdate': return this.service.applyUpdate();
|
||||
case 'quitAndInstall': return this.service.quitAndInstall();
|
||||
case '_getInitialState': return TPromise.as(this.service.state);
|
||||
}
|
||||
@@ -46,19 +41,8 @@ export class UpdateChannelClient implements IUpdateService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private _onError = eventFromCall<any>(this.channel, 'event:onError');
|
||||
get onError(): Event<any> { return this._onError; }
|
||||
|
||||
private _onUpdateAvailable = eventFromCall<{ url: string; version: string; }>(this.channel, 'event:onUpdateAvailable');
|
||||
get onUpdateAvailable(): Event<{ url: string; version: string; }> { return this._onUpdateAvailable; }
|
||||
|
||||
private _onUpdateNotAvailable = eventFromCall<boolean>(this.channel, 'event:onUpdateNotAvailable');
|
||||
get onUpdateNotAvailable(): Event<boolean> { return this._onUpdateNotAvailable; }
|
||||
|
||||
private _onUpdateReady = eventFromCall<IRawUpdate>(this.channel, 'event:onUpdateReady');
|
||||
get onUpdateReady(): Event<IRawUpdate> { return this._onUpdateReady; }
|
||||
|
||||
private _onRemoteStateChange = eventFromCall<State>(this.channel, 'event:onStateChange');
|
||||
|
||||
private _onStateChange = new Emitter<State>();
|
||||
get onStateChange(): Event<State> { return this._onStateChange.event; }
|
||||
|
||||
@@ -78,10 +62,18 @@ export class UpdateChannelClient implements IUpdateService {
|
||||
}, onUnexpectedError);
|
||||
}
|
||||
|
||||
checkForUpdates(explicit: boolean): TPromise<IUpdate> {
|
||||
checkForUpdates(explicit: boolean): TPromise<void> {
|
||||
return this.channel.call('checkForUpdates', explicit);
|
||||
}
|
||||
|
||||
downloadUpdate(): TPromise<void> {
|
||||
return this.channel.call('downloadUpdate');
|
||||
}
|
||||
|
||||
applyUpdate(): TPromise<void> {
|
||||
return this.channel.call('applyUpdate');
|
||||
}
|
||||
|
||||
quitAndInstall(): TPromise<void> {
|
||||
return this.channel.call('quitAndInstall');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Throttler } from 'vs/base/common/async';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IUpdateService, State, StateType, AvailableForDownload } from 'vs/platform/update/common/update';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export function createUpdateURL(platform: string, quality: string): string {
|
||||
return `${product.updateUrl}/api/update/${platform}/${quality}/${product.commit}`;
|
||||
}
|
||||
|
||||
export abstract class AbstractUpdateService implements IUpdateService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private _state: State = State.Uninitialized;
|
||||
private throttler: Throttler = new Throttler();
|
||||
|
||||
private _onStateChange = new Emitter<State>();
|
||||
get onStateChange(): Event<State> { return this._onStateChange.event; }
|
||||
|
||||
get state(): State {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
protected setState(state: State): void {
|
||||
this.logService.info('update#setState', state.type);
|
||||
this._state = state;
|
||||
this._onStateChange.fire(state);
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ILifecycleService private lifecycleService: ILifecycleService,
|
||||
@IConfigurationService protected configurationService: IConfigurationService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@ILogService protected logService: ILogService
|
||||
) {
|
||||
if (this.environmentService.disableUpdates) {
|
||||
this.logService.info('update#ctor - updates are disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!product.updateUrl || !product.commit) {
|
||||
this.logService.info('update#ctor - updates are disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
const quality = this.getProductQuality();
|
||||
|
||||
if (!quality) {
|
||||
this.logService.info('update#ctor - updates are disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.setUpdateFeedUrl(quality)) {
|
||||
this.logService.info('update#ctor - updates are disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ type: StateType.Idle });
|
||||
|
||||
// Start checking for updates after 30 seconds
|
||||
this.scheduleCheckForUpdates(30 * 1000)
|
||||
.done(null, err => this.logService.error(err));
|
||||
}
|
||||
|
||||
private getProductQuality(): string {
|
||||
const quality = this.configurationService.getValue<string>('update.channel');
|
||||
return quality === 'none' ? null : product.quality;
|
||||
}
|
||||
|
||||
private scheduleCheckForUpdates(delay = 60 * 60 * 1000): TPromise<void> {
|
||||
return TPromise.timeout(delay)
|
||||
.then(() => this.checkForUpdates())
|
||||
.then(update => {
|
||||
if (update) {
|
||||
// Update found, no need to check more
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
// Check again after 1 hour
|
||||
return this.scheduleCheckForUpdates(60 * 60 * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
checkForUpdates(explicit = false): TPromise<void> {
|
||||
this.logService.trace('update#checkForUpdates, state = ', this.state.type);
|
||||
|
||||
if (this.state.type !== StateType.Idle) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
return this.throttler.queue(() => TPromise.as(this.doCheckForUpdates(explicit)));
|
||||
}
|
||||
|
||||
downloadUpdate(): TPromise<void> {
|
||||
this.logService.trace('update#downloadUpdate, state = ', this.state.type);
|
||||
|
||||
if (this.state.type !== StateType.AvailableForDownload) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
return this.doDownloadUpdate(this.state);
|
||||
}
|
||||
|
||||
protected doDownloadUpdate(state: AvailableForDownload): TPromise<void> {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
applyUpdate(): TPromise<void> {
|
||||
this.logService.trace('update#applyUpdate, state = ', this.state.type);
|
||||
|
||||
if (this.state.type !== StateType.Downloaded) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
return this.doApplyUpdate();
|
||||
}
|
||||
|
||||
protected doApplyUpdate(): TPromise<void> {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
quitAndInstall(): TPromise<void> {
|
||||
this.logService.trace('update#quitAndInstall, state = ', this.state.type);
|
||||
|
||||
if (this.state.type !== StateType.Ready) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
this.logService.trace('update#quitAndInstall(): before lifecycle quit()');
|
||||
|
||||
this.lifecycleService.quit(true /* from update */).done(vetod => {
|
||||
this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`);
|
||||
if (vetod) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()');
|
||||
this.doQuitAndInstall();
|
||||
});
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
protected doQuitAndInstall(): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
protected abstract setUpdateFeedUrl(quality: string): boolean;
|
||||
protected abstract doCheckForUpdates(explicit: boolean): void;
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { Promise } from 'vs/base/common/winjs.base';
|
||||
import { asJson } from 'vs/base/node/request';
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import { IAutoUpdater } from 'vs/platform/update/common/update';
|
||||
import product from 'vs/platform/node/product';
|
||||
|
||||
interface IUpdate {
|
||||
url: string;
|
||||
name: string;
|
||||
releaseNotes?: string;
|
||||
version: string;
|
||||
productVersion: string;
|
||||
hash: string;
|
||||
}
|
||||
|
||||
export class LinuxAutoUpdaterImpl extends EventEmitter implements IAutoUpdater {
|
||||
|
||||
private url: string;
|
||||
private currentRequest: Promise;
|
||||
|
||||
constructor(
|
||||
@IRequestService private requestService: IRequestService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.url = null;
|
||||
this.currentRequest = null;
|
||||
}
|
||||
|
||||
setFeedURL(url: string): void {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
checkForUpdates(): void {
|
||||
if (!this.url) {
|
||||
throw new Error('No feed url set.');
|
||||
}
|
||||
|
||||
if (this.currentRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('checking-for-update');
|
||||
|
||||
this.currentRequest = this.requestService.request({ url: this.url })
|
||||
.then<IUpdate>(asJson)
|
||||
.then(update => {
|
||||
if (!update || !update.url || !update.version || !update.productVersion) {
|
||||
this.emit('update-not-available');
|
||||
} else {
|
||||
this.emit('update-available', null, product.downloadUrl, update.productVersion);
|
||||
}
|
||||
})
|
||||
.then(null, e => {
|
||||
if (isString(e) && /^Server returned/.test(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('update-not-available');
|
||||
this.emit('error', e);
|
||||
})
|
||||
.then(() => this.currentRequest = null);
|
||||
}
|
||||
|
||||
quitAndInstall(): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { checksum } from 'vs/base/node/crypto';
|
||||
import { EventEmitter } from 'events';
|
||||
import { tmpdir } from 'os';
|
||||
import { spawn } from 'child_process';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { Promise, TPromise } from 'vs/base/common/winjs.base';
|
||||
import { download, asJson } from 'vs/base/node/request';
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import { IAutoUpdater } from 'vs/platform/update/common/update';
|
||||
import product from 'vs/platform/node/product';
|
||||
|
||||
interface IUpdate {
|
||||
url: string;
|
||||
name: string;
|
||||
releaseNotes?: string;
|
||||
version: string;
|
||||
productVersion: string;
|
||||
hash: string;
|
||||
}
|
||||
|
||||
export class Win32AutoUpdaterImpl extends EventEmitter implements IAutoUpdater {
|
||||
|
||||
private url: string = null;
|
||||
private currentRequest: Promise = null;
|
||||
private updatePackagePath: string = null;
|
||||
|
||||
constructor(
|
||||
@IRequestService private requestService: IRequestService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
get cachePath(): TPromise<string> {
|
||||
const result = path.join(tmpdir(), `vscode-update-${process.arch}`);
|
||||
return pfs.mkdirp(result, null).then(() => result);
|
||||
}
|
||||
|
||||
setFeedURL(url: string): void {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
checkForUpdates(): void {
|
||||
if (!this.url) {
|
||||
throw new Error('No feed url set.');
|
||||
}
|
||||
|
||||
if (this.currentRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('checking-for-update');
|
||||
|
||||
this.currentRequest = this.requestService.request({ url: this.url })
|
||||
.then<IUpdate>(asJson)
|
||||
.then(update => {
|
||||
if (!update || !update.url || !update.version) {
|
||||
this.emit('update-not-available');
|
||||
return this.cleanup();
|
||||
}
|
||||
|
||||
this.emit('update-available');
|
||||
|
||||
return this.cleanup(update.version).then(() => {
|
||||
return this.getUpdatePackagePath(update.version).then(updatePackagePath => {
|
||||
return pfs.exists(updatePackagePath).then(exists => {
|
||||
if (exists) {
|
||||
return TPromise.as(updatePackagePath);
|
||||
}
|
||||
|
||||
const url = update.url;
|
||||
const hash = update.hash;
|
||||
const downloadPath = `${updatePackagePath}.tmp`;
|
||||
|
||||
return this.requestService.request({ url })
|
||||
.then(context => download(downloadPath, context))
|
||||
.then(hash ? () => checksum(downloadPath, update.hash) : () => null)
|
||||
.then(() => pfs.rename(downloadPath, updatePackagePath))
|
||||
.then(() => updatePackagePath);
|
||||
});
|
||||
}).then(updatePackagePath => {
|
||||
this.updatePackagePath = updatePackagePath;
|
||||
|
||||
this.emit('update-downloaded',
|
||||
{},
|
||||
update.releaseNotes,
|
||||
update.productVersion,
|
||||
new Date(),
|
||||
this.url
|
||||
);
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(null, e => {
|
||||
if (isString(e) && /^Server returned/.test(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('update-not-available');
|
||||
this.emit('error', e);
|
||||
})
|
||||
.then(() => this.currentRequest = null);
|
||||
}
|
||||
|
||||
private getUpdatePackagePath(version: string): TPromise<string> {
|
||||
return this.cachePath.then(cachePath => path.join(cachePath, `CodeSetup-${product.quality}-${version}.exe`));
|
||||
}
|
||||
|
||||
private cleanup(exceptVersion: string = null): Promise {
|
||||
const filter = exceptVersion ? one => !(new RegExp(`${product.quality}-${exceptVersion}\\.exe$`).test(one)) : () => true;
|
||||
|
||||
return this.cachePath
|
||||
.then(cachePath => pfs.readdir(cachePath)
|
||||
.then(all => Promise.join(all
|
||||
.filter(filter)
|
||||
.map(one => pfs.unlink(path.join(cachePath, one)).then(null, () => null))
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
quitAndInstall(): void {
|
||||
if (!this.updatePackagePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
spawn(this.updatePackagePath, ['/silent', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'ignore', 'ignore']
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as electron from 'electron';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import Event, { fromNodeEventEmitter } from 'vs/base/common/event';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { State, IUpdate, StateType } from 'vs/platform/update/common/update';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { AbstractUpdateService, createUpdateURL } from 'vs/platform/update/electron-main/abstractUpdateService';
|
||||
|
||||
export class DarwinUpdateService extends AbstractUpdateService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
@memoize private get onRawError(): Event<string> { return fromNodeEventEmitter(electron.autoUpdater, 'error', (_, message) => message); }
|
||||
@memoize private get onRawUpdateNotAvailable(): Event<void> { return fromNodeEventEmitter<void>(electron.autoUpdater, 'update-not-available'); }
|
||||
@memoize private get onRawUpdateAvailable(): Event<IUpdate> { return fromNodeEventEmitter(electron.autoUpdater, 'update-available', (_, url, version) => ({ url, version, productVersion: version })); }
|
||||
@memoize private get onRawUpdateDownloaded(): Event<IUpdate> { return fromNodeEventEmitter(electron.autoUpdater, 'update-downloaded', (_, releaseNotes, version, date) => ({ releaseNotes, version, productVersion: version, date })); }
|
||||
|
||||
constructor(
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@ILogService logService: ILogService
|
||||
) {
|
||||
super(lifecycleService, configurationService, environmentService, logService);
|
||||
this.onRawError(this.onError, this, this.disposables);
|
||||
this.onRawUpdateAvailable(this.onUpdateAvailable, this, this.disposables);
|
||||
this.onRawUpdateDownloaded(this.onUpdateDownloaded, this, this.disposables);
|
||||
this.onRawUpdateNotAvailable(this.onUpdateNotAvailable, this, this.disposables);
|
||||
}
|
||||
|
||||
private onError(err: string): void {
|
||||
this.logService.error('UpdateService error: ', err);
|
||||
this.setState(State.Idle);
|
||||
}
|
||||
|
||||
protected setUpdateFeedUrl(quality: string): boolean {
|
||||
try {
|
||||
electron.autoUpdater.setFeedURL(createUpdateURL('darwin', quality));
|
||||
} catch (e) {
|
||||
// application is very likely not signed
|
||||
this.logService.error('Failed to set update feed URL');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected doCheckForUpdates(explicit: boolean): void {
|
||||
this.setState(State.CheckingForUpdates(explicit));
|
||||
electron.autoUpdater.checkForUpdates();
|
||||
}
|
||||
|
||||
private onUpdateAvailable(update: IUpdate): void {
|
||||
if (this.state.type !== StateType.CheckingForUpdates) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(State.Downloading(update));
|
||||
}
|
||||
|
||||
private onUpdateDownloaded(update: IUpdate): void {
|
||||
if (this.state.type !== StateType.Downloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* __GDPR__
|
||||
"update:downloaded" : {
|
||||
"version" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('update:downloaded', { version: update.version });
|
||||
|
||||
this.setState(State.Ready(update));
|
||||
}
|
||||
|
||||
private onUpdateNotAvailable(): void {
|
||||
if (this.state.type !== StateType.CheckingForUpdates) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* __GDPR__
|
||||
"update:notAvailable" : {
|
||||
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit: this.state.explicit });
|
||||
|
||||
this.setState(State.Idle);
|
||||
}
|
||||
|
||||
protected doQuitAndInstall(): void {
|
||||
// for some reason updating on Mac causes the local storage not to be flushed.
|
||||
// we workaround this issue by forcing an explicit flush of the storage data.
|
||||
// see also https://github.com/Microsoft/vscode/issues/172
|
||||
this.logService.trace('update#quitAndInstall(): calling flushStorageData()');
|
||||
electron.session.defaultSession.flushStorageData();
|
||||
|
||||
this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()');
|
||||
electron.autoUpdater.quitAndInstall();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import { State, IUpdate, AvailableForDownload } from 'vs/platform/update/common/update';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { createUpdateURL, AbstractUpdateService } from 'vs/platform/update/electron-main/abstractUpdateService';
|
||||
import { asJson } from 'vs/base/node/request';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { shell } from 'electron';
|
||||
|
||||
export class LinuxUpdateService extends AbstractUpdateService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private url: string | undefined;
|
||||
|
||||
constructor(
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IRequestService private requestService: IRequestService,
|
||||
@ILogService logService: ILogService
|
||||
) {
|
||||
super(lifecycleService, configurationService, environmentService, logService);
|
||||
}
|
||||
|
||||
protected setUpdateFeedUrl(quality: string): boolean {
|
||||
this.url = createUpdateURL(`linux-${process.arch}`, quality);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected doCheckForUpdates(explicit: boolean): void {
|
||||
if (!this.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(State.CheckingForUpdates(explicit));
|
||||
|
||||
this.requestService.request({ url: this.url })
|
||||
.then<IUpdate>(asJson)
|
||||
.then(update => {
|
||||
if (!update || !update.url || !update.version || !update.productVersion) {
|
||||
/* __GDPR__
|
||||
"update:notAvailable" : {
|
||||
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit });
|
||||
|
||||
this.setState(State.Idle);
|
||||
} else {
|
||||
this.setState(State.AvailableForDownload(update));
|
||||
}
|
||||
})
|
||||
.then(null, err => {
|
||||
this.logService.error(err);
|
||||
|
||||
/* __GDPR__
|
||||
"update:notAvailable" : {
|
||||
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit });
|
||||
this.setState(State.Idle);
|
||||
});
|
||||
}
|
||||
|
||||
protected doDownloadUpdate(state: AvailableForDownload): TPromise<void> {
|
||||
shell.openExternal(state.update.url);
|
||||
this.setState(State.Idle);
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
@@ -1,295 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as fs from 'original-fs';
|
||||
import * as path from 'path';
|
||||
import * as electron from 'electron';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter, once, filterEvent, fromNodeEventEmitter } from 'vs/base/common/event';
|
||||
import { always, Throttler } from 'vs/base/common/async';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Win32AutoUpdaterImpl } from './auto-updater.win32';
|
||||
import { LinuxAutoUpdaterImpl } from './auto-updater.linux';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IUpdateService, State, IAutoUpdater, IUpdate, IRawUpdate } from 'vs/platform/update/common/update';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export class UpdateService implements IUpdateService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private _state: State = State.Uninitialized;
|
||||
private _availableUpdate: IUpdate = null;
|
||||
private raw: IAutoUpdater;
|
||||
private throttler: Throttler = new Throttler();
|
||||
|
||||
private _onError = new Emitter<any>();
|
||||
get onError(): Event<any> { return this._onError.event; }
|
||||
|
||||
private _onCheckForUpdate = new Emitter<void>();
|
||||
get onCheckForUpdate(): Event<void> { return this._onCheckForUpdate.event; }
|
||||
|
||||
private _onUpdateAvailable = new Emitter<{ url: string; version: string; }>();
|
||||
get onUpdateAvailable(): Event<{ url: string; version: string; }> { return this._onUpdateAvailable.event; }
|
||||
|
||||
private _onUpdateNotAvailable = new Emitter<boolean>();
|
||||
get onUpdateNotAvailable(): Event<boolean> { return this._onUpdateNotAvailable.event; }
|
||||
|
||||
private _onUpdateReady = new Emitter<IRawUpdate>();
|
||||
get onUpdateReady(): Event<IRawUpdate> { return this._onUpdateReady.event; }
|
||||
|
||||
private _onStateChange = new Emitter<State>();
|
||||
get onStateChange(): Event<State> { return this._onStateChange.event; }
|
||||
|
||||
@memoize
|
||||
private get onRawError(): Event<string> {
|
||||
return fromNodeEventEmitter(this.raw, 'error', (_, message) => message);
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get onRawUpdateNotAvailable(): Event<void> {
|
||||
return fromNodeEventEmitter<void>(this.raw, 'update-not-available');
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get onRawUpdateAvailable(): Event<{ url: string; version: string; }> {
|
||||
return filterEvent(fromNodeEventEmitter(this.raw, 'update-available', (_, url, version) => ({ url, version })), ({ url }) => !!url);
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get onRawUpdateDownloaded(): Event<IRawUpdate> {
|
||||
return fromNodeEventEmitter(this.raw, 'update-downloaded', (_, releaseNotes, version, date, url) => ({ releaseNotes, version, date }));
|
||||
}
|
||||
|
||||
get state(): State {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
set state(state: State) {
|
||||
this._state = state;
|
||||
this._onStateChange.fire(state);
|
||||
}
|
||||
|
||||
get availableUpdate(): IUpdate {
|
||||
return this._availableUpdate;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@IRequestService requestService: IRequestService,
|
||||
@ILifecycleService private lifecycleService: ILifecycleService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@ILogService private logService: ILogService
|
||||
) {
|
||||
if (process.platform === 'win32') {
|
||||
this.raw = new Win32AutoUpdaterImpl(requestService);
|
||||
} else if (process.platform === 'linux') {
|
||||
this.raw = new LinuxAutoUpdaterImpl(requestService);
|
||||
} else if (process.platform === 'darwin') {
|
||||
this.raw = electron.autoUpdater;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.environmentService.disableUpdates) {
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = this.getUpdateChannel();
|
||||
const feedUrl = this.getUpdateFeedUrl(channel);
|
||||
|
||||
if (!feedUrl) {
|
||||
return; // updates not available
|
||||
}
|
||||
|
||||
try {
|
||||
this.raw.setFeedURL(feedUrl);
|
||||
} catch (e) {
|
||||
return; // application not signed
|
||||
}
|
||||
|
||||
this.state = State.Idle;
|
||||
|
||||
// Start checking for updates after 30 seconds
|
||||
this.scheduleCheckForUpdates(30 * 1000)
|
||||
.done(null, err => this.logService.error(err));
|
||||
}
|
||||
|
||||
private scheduleCheckForUpdates(delay = 60 * 60 * 1000): TPromise<void> {
|
||||
return TPromise.timeout(delay)
|
||||
.then(() => this.checkForUpdates())
|
||||
.then(update => {
|
||||
if (update) {
|
||||
// Update found, no need to check more
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
// Check again after 1 hour
|
||||
return this.scheduleCheckForUpdates(60 * 60 * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
checkForUpdates(explicit = false): TPromise<IUpdate> {
|
||||
return this.throttler.queue(() => this._checkForUpdates(explicit))
|
||||
.then(null, err => {
|
||||
if (explicit) {
|
||||
this._onError.fire(err);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private _checkForUpdates(explicit: boolean): TPromise<IUpdate> {
|
||||
if (this.state !== State.Idle) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
this._onCheckForUpdate.fire();
|
||||
this.state = State.CheckingForUpdate;
|
||||
|
||||
const listeners: IDisposable[] = [];
|
||||
const result = new TPromise<IUpdate>((c, e) => {
|
||||
once(this.onRawError)(e, null, listeners);
|
||||
once(this.onRawUpdateNotAvailable)(() => c(null), null, listeners);
|
||||
once(this.onRawUpdateAvailable)(({ url, version }) => url && c({ url, version }), null, listeners);
|
||||
once(this.onRawUpdateDownloaded)(({ version, date, releaseNotes }) => c({ version, date, releaseNotes }), null, listeners);
|
||||
|
||||
this.raw.checkForUpdates();
|
||||
}).then(update => {
|
||||
if (!update) {
|
||||
this._onUpdateNotAvailable.fire(explicit);
|
||||
this.state = State.Idle;
|
||||
/* __GDPR__
|
||||
"update:notAvailable" : {
|
||||
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit });
|
||||
|
||||
} else if (update.url) {
|
||||
const data: IUpdate = {
|
||||
url: update.url,
|
||||
releaseNotes: '',
|
||||
version: update.version,
|
||||
date: new Date()
|
||||
};
|
||||
|
||||
this._availableUpdate = data;
|
||||
this._onUpdateAvailable.fire({ url: update.url, version: update.version });
|
||||
this.state = State.UpdateAvailable;
|
||||
/* __GDPR__
|
||||
"update:available" : {
|
||||
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"version": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"currentVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('update:available', { explicit, version: update.version, currentVersion: product.commit });
|
||||
|
||||
} else {
|
||||
const data: IRawUpdate = {
|
||||
releaseNotes: update.releaseNotes,
|
||||
version: update.version,
|
||||
date: update.date
|
||||
};
|
||||
|
||||
this._availableUpdate = data;
|
||||
this._onUpdateReady.fire(data);
|
||||
this.state = State.UpdateDownloaded;
|
||||
/* __GDPR__
|
||||
"update:downloaded" : {
|
||||
"version" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('update:downloaded', { version: update.version });
|
||||
}
|
||||
|
||||
return update;
|
||||
}, err => {
|
||||
this.state = State.Idle;
|
||||
return TPromise.wrapError<IUpdate>(err);
|
||||
});
|
||||
|
||||
return always(result, () => dispose(listeners));
|
||||
}
|
||||
|
||||
private getUpdateChannel(): string {
|
||||
const channel = this.configurationService.getValue<string>('update.channel');
|
||||
return channel === 'none' ? null : product.quality;
|
||||
}
|
||||
|
||||
private getUpdateFeedUrl(channel: string): string {
|
||||
if (!channel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32' && !fs.existsSync(path.join(path.dirname(process.execPath), 'unins000.exe'))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!product.updateUrl || !product.commit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const platform = this.getUpdatePlatform();
|
||||
|
||||
return `${product.updateUrl}/api/update/${platform}/${channel}/${product.commit}`;
|
||||
}
|
||||
|
||||
private getUpdatePlatform(): string {
|
||||
if (process.platform === 'linux') {
|
||||
return `linux-${process.arch}`;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32' && process.arch === 'x64') {
|
||||
return 'win32-x64';
|
||||
}
|
||||
|
||||
return process.platform;
|
||||
}
|
||||
|
||||
quitAndInstall(): TPromise<void> {
|
||||
if (!this._availableUpdate) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
if (this._availableUpdate.url) {
|
||||
electron.shell.openExternal(this._availableUpdate.url);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
this.logService.trace('update#quitAndInstall(): before lifecycle quit()');
|
||||
|
||||
this.lifecycleService.quit(true /* from update */).done(vetod => {
|
||||
this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`);
|
||||
if (vetod) {
|
||||
return;
|
||||
}
|
||||
|
||||
// for some reason updating on Mac causes the local storage not to be flushed.
|
||||
// we workaround this issue by forcing an explicit flush of the storage data.
|
||||
// see also https://github.com/Microsoft/vscode/issues/172
|
||||
if (process.platform === 'darwin') {
|
||||
this.logService.trace('update#quitAndInstall(): calling flushStorageData()');
|
||||
electron.session.defaultSession.flushStorageData();
|
||||
}
|
||||
|
||||
this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()');
|
||||
this.raw.quitAndInstall();
|
||||
});
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as fs from 'original-fs';
|
||||
import * as path from 'path';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { TPromise, Promise } from 'vs/base/common/winjs.base';
|
||||
import { State, IUpdate, StateType } from 'vs/platform/update/common/update';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { createUpdateURL, AbstractUpdateService } from 'vs/platform/update/electron-main/abstractUpdateService';
|
||||
import { download, asJson } from 'vs/base/node/request';
|
||||
import { checksum } from 'vs/base/node/crypto';
|
||||
import { tmpdir } from 'os';
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
function pollUntil(fn: () => boolean, timeout = 1000): TPromise<void> {
|
||||
return new TPromise<void>(c => {
|
||||
const poll = () => {
|
||||
if (fn()) {
|
||||
c(null);
|
||||
} else {
|
||||
setTimeout(poll, timeout);
|
||||
}
|
||||
};
|
||||
|
||||
poll();
|
||||
});
|
||||
}
|
||||
|
||||
interface IAvailableUpdate {
|
||||
packagePath: string;
|
||||
updateFilePath?: string;
|
||||
}
|
||||
|
||||
export class Win32UpdateService extends AbstractUpdateService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private url: string | undefined;
|
||||
private availableUpdate: IAvailableUpdate | undefined;
|
||||
|
||||
@memoize
|
||||
get cachePath(): TPromise<string> {
|
||||
const result = path.join(tmpdir(), `vscode-update-${process.arch}`);
|
||||
return pfs.mkdirp(result, null).then(() => result);
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IRequestService private requestService: IRequestService,
|
||||
@ILogService logService: ILogService
|
||||
) {
|
||||
super(lifecycleService, configurationService, environmentService, logService);
|
||||
}
|
||||
|
||||
protected setUpdateFeedUrl(quality: string): boolean {
|
||||
if (!fs.existsSync(path.join(path.dirname(process.execPath), 'unins000.exe'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.url = createUpdateURL(process.arch === 'x64' ? 'win32-x64' : 'win32', quality);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected doCheckForUpdates(explicit: boolean): void {
|
||||
if (!this.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(State.CheckingForUpdates(explicit));
|
||||
|
||||
this.requestService.request({ url: this.url })
|
||||
.then<IUpdate>(asJson)
|
||||
.then(update => {
|
||||
if (!update || !update.url || !update.version) {
|
||||
/* __GDPR__
|
||||
"update:notAvailable" : {
|
||||
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit });
|
||||
|
||||
this.setState(State.Idle);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
this.setState(State.Downloading(update));
|
||||
|
||||
return this.cleanup(update.version).then(() => {
|
||||
return this.getUpdatePackagePath(update.version).then(updatePackagePath => {
|
||||
return pfs.exists(updatePackagePath).then(exists => {
|
||||
if (exists) {
|
||||
return TPromise.as(updatePackagePath);
|
||||
}
|
||||
|
||||
const url = update.url;
|
||||
const hash = update.hash;
|
||||
const downloadPath = `${updatePackagePath}.tmp`;
|
||||
|
||||
return this.requestService.request({ url })
|
||||
.then(context => download(downloadPath, context))
|
||||
.then(hash ? () => checksum(downloadPath, update.hash) : () => null)
|
||||
.then(() => pfs.rename(downloadPath, updatePackagePath))
|
||||
.then(() => updatePackagePath);
|
||||
});
|
||||
}).then(packagePath => {
|
||||
const fastUpdatesEnabled = this.configurationService.getValue<boolean>('update.enableWindowsBackgroundUpdates');
|
||||
|
||||
this.availableUpdate = { packagePath };
|
||||
|
||||
if (fastUpdatesEnabled && update.supportsFastUpdate) {
|
||||
this.setState(State.Downloaded(update));
|
||||
} else {
|
||||
this.setState(State.Ready(update));
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(null, err => {
|
||||
this.logService.error(err);
|
||||
/* __GDPR__
|
||||
"update:notAvailable" : {
|
||||
"explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('update:notAvailable', { explicit });
|
||||
this.setState(State.Idle);
|
||||
});
|
||||
}
|
||||
|
||||
private getUpdatePackagePath(version: string): TPromise<string> {
|
||||
return this.cachePath.then(cachePath => path.join(cachePath, `CodeSetup-${product.quality}-${version}.exe`));
|
||||
}
|
||||
|
||||
private cleanup(exceptVersion: string = null): Promise {
|
||||
const filter = exceptVersion ? one => !(new RegExp(`${product.quality}-${exceptVersion}\\.exe$`).test(one)) : () => true;
|
||||
|
||||
return this.cachePath
|
||||
.then(cachePath => pfs.readdir(cachePath)
|
||||
.then(all => Promise.join(all
|
||||
.filter(filter)
|
||||
.map(one => pfs.unlink(path.join(cachePath, one)).then(null, () => null))
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
protected doApplyUpdate(): TPromise<void> {
|
||||
if (this.state.type !== StateType.Downloaded || !this.availableUpdate) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
const update = this.state.update;
|
||||
this.setState(State.Updating(update));
|
||||
|
||||
return this.cachePath.then(cachePath => {
|
||||
this.availableUpdate.updateFilePath = path.join(cachePath, `CodeSetup-${product.quality}-${update.version}.flag`);
|
||||
|
||||
return pfs.writeFile(this.availableUpdate.updateFilePath, 'flag').then(() => {
|
||||
const child = spawn(this.availableUpdate.packagePath, ['/verysilent', `/update="${this.availableUpdate.updateFilePath}"`, '/nocloseapplications', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'ignore', 'ignore'],
|
||||
windowsVerbatimArguments: true
|
||||
});
|
||||
|
||||
child.once('exit', () => {
|
||||
this.availableUpdate = undefined;
|
||||
this.setState(State.Idle);
|
||||
});
|
||||
|
||||
const readyMutexName = `${product.win32MutexName}-ready`;
|
||||
const isActive = (require.__$__nodeRequire('windows-mutex') as any).isActive;
|
||||
|
||||
// poll for mutex-ready
|
||||
pollUntil(() => isActive(readyMutexName))
|
||||
.then(() => this.setState(State.Ready(update)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected doQuitAndInstall(): void {
|
||||
if (this.state.type !== StateType.Ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()');
|
||||
|
||||
if (this.state.update.supportsFastUpdate && this.availableUpdate.updateFilePath) {
|
||||
fs.unlinkSync(this.availableUpdate.updateFilePath);
|
||||
} else {
|
||||
spawn(this.availableUpdate.packagePath, ['/silent', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'ignore', 'ignore']
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+77
-3
@@ -1812,6 +1812,66 @@ declare module 'vscode' {
|
||||
*/
|
||||
export type ProviderResult<T> = T | undefined | null | Thenable<T | undefined | null>;
|
||||
|
||||
/**
|
||||
* Kind of a code action.
|
||||
*
|
||||
* Kinds are a hierarchical list of identifiers separated by `.`, e.g. `"refactor.extract.function"`.
|
||||
*/
|
||||
export class CodeActionKind {
|
||||
/**
|
||||
* Empty kind.
|
||||
*/
|
||||
static readonly Empty: CodeActionKind;
|
||||
|
||||
/**
|
||||
* Base kind for quickfix actions.
|
||||
*/
|
||||
static readonly QuickFix: CodeActionKind;
|
||||
|
||||
/**
|
||||
* Base kind for refactoring actions.
|
||||
*/
|
||||
static readonly Refactor: CodeActionKind;
|
||||
|
||||
/**
|
||||
* Base kind for refactoring extraction actions.
|
||||
*/
|
||||
static readonly RefactorExtract: CodeActionKind;
|
||||
|
||||
/**
|
||||
* Base kind for refactoring inline actions.
|
||||
*/
|
||||
static readonly RefactorInline: CodeActionKind;
|
||||
|
||||
/**
|
||||
* Base kind for refactoring rewite actions.
|
||||
*/
|
||||
static readonly RefactorRewrite: CodeActionKind;
|
||||
|
||||
private constructor(value: string);
|
||||
|
||||
/**
|
||||
* String value of the kind, e.g. `"refactor.extract.function"`.
|
||||
*/
|
||||
readonly value?: string;
|
||||
|
||||
/**
|
||||
* Create a new kind by appending a more specific selector to the current kind.
|
||||
*
|
||||
* Does not modify the current kind.
|
||||
*/
|
||||
append(parts: string): CodeActionKind;
|
||||
|
||||
/**
|
||||
* Does this kind contain `other`?
|
||||
*
|
||||
* The kind `"refactor"` for example contains `"refactor.extract"` and ``"refactor.extract.function"`, but not `"unicorn.refactor.extract"` or `"refactory.extract"`
|
||||
*
|
||||
* @param other Kind to check.
|
||||
*/
|
||||
contains(other: CodeActionKind): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains additional diagnostic information about the context in which
|
||||
* a [code action](#CodeActionProvider.provideCodeActions) is run.
|
||||
@@ -1821,6 +1881,13 @@ declare module 'vscode' {
|
||||
* An array of diagnostics.
|
||||
*/
|
||||
readonly diagnostics: Diagnostic[];
|
||||
|
||||
/**
|
||||
* Requested kind of actions to return.
|
||||
*
|
||||
* Actions not of this kind are filtered out before being shown by the lightbulb.
|
||||
*/
|
||||
readonly only?: CodeActionKind;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1830,7 +1897,7 @@ declare module 'vscode' {
|
||||
export class CodeAction {
|
||||
|
||||
/**
|
||||
* A short, human-readanle, title for this code action.
|
||||
* A short, human-readable, title for this code action.
|
||||
*/
|
||||
title: string;
|
||||
|
||||
@@ -1853,6 +1920,13 @@ declare module 'vscode' {
|
||||
*/
|
||||
command?: Command;
|
||||
|
||||
/**
|
||||
* Kind of the code action.
|
||||
*
|
||||
* Used to filter code actions.
|
||||
*/
|
||||
kind?: CodeActionKind;
|
||||
|
||||
/**
|
||||
* Creates a new code action.
|
||||
*
|
||||
@@ -1860,9 +1934,9 @@ declare module 'vscode' {
|
||||
* or a [command](#CodeAction.command).
|
||||
*
|
||||
* @param title The title of the code action.
|
||||
* @param edits The edit of the code action.
|
||||
* @param kind The kind of the code action.
|
||||
*/
|
||||
constructor(title: string, edit?: WorkspaceEdit);
|
||||
constructor(title: string, kind?: CodeActionKind);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { ExtensionMessageCollector, ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry';
|
||||
|
||||
namespace schema {
|
||||
|
||||
// --localizations contribution point
|
||||
|
||||
export interface ILocalizationDescriptor {
|
||||
languageId: string;
|
||||
languageName: string;
|
||||
translations: string;
|
||||
}
|
||||
|
||||
export function validateLocalizationDescriptors(localizationDescriptors: ILocalizationDescriptor[], collector: ExtensionMessageCollector): boolean {
|
||||
if (!Array.isArray(localizationDescriptors)) {
|
||||
collector.error(localize('requirearray', "localizations must be an array"));
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let descriptor of localizationDescriptors) {
|
||||
if (typeof descriptor.languageId !== 'string') {
|
||||
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'languageId'));
|
||||
return false;
|
||||
}
|
||||
if (typeof descriptor.languageName !== 'string') {
|
||||
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'languageName'));
|
||||
return false;
|
||||
}
|
||||
if (descriptor.translations && typeof descriptor.translations !== 'string') {
|
||||
collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'translations'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export const localizationsContribution: IJSONSchema = {
|
||||
description: localize('vscode.extension.contributes.localizations', "Contributes localizations to the editor"),
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
description: localize('vscode.extension.contributes.localizations.languageId', 'Id of the language into which the display strings are translated.'),
|
||||
type: 'string'
|
||||
},
|
||||
name: {
|
||||
description: localize('vscode.extension.contributes.localizations.languageName', 'Name of the language into which the display strings are translated.'),
|
||||
type: 'string'
|
||||
},
|
||||
translations: {
|
||||
description: localize('vscode.extension.contributes.localizations.translations', 'A relative path to the folder containing all translation files for the contributed language.'),
|
||||
type: 'string',
|
||||
default: 'translations'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ExtensionsRegistry.registerExtensionPoint<schema.ILocalizationDescriptor[]>('localizations', [], schema.localizationsContribution)
|
||||
.setHandler((extensions) => extensions.forEach(extension => schema.validateLocalizationDescriptors(extension.value, extension.collector)));
|
||||
@@ -194,8 +194,8 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
|
||||
$registerQuickFixSupport(handle: number, selector: vscode.DocumentSelector): void {
|
||||
this._registrations[handle] = modes.CodeActionProviderRegistry.register(toLanguageSelector(selector), <modes.CodeActionProvider>{
|
||||
provideCodeActions: (model: ITextModel, range: EditorRange, token: CancellationToken): Thenable<modes.CodeAction[]> => {
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, range))).then(MainThreadLanguageFeatures._reviveCodeActionDto);
|
||||
provideCodeActions: (model: ITextModel, range: EditorRange, context: modes.CodeActionContext, token: CancellationToken): Thenable<modes.CodeAction[]> => {
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, range, context))).then(MainThreadLanguageFeatures._reviveCodeActionDto);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ export class TrimFinalNewLinesParticipant implements ISaveParticipantParticipant
|
||||
const lineCount = model.getLineCount();
|
||||
|
||||
// Do not insert new line if file does not end with new line
|
||||
if (!lineCount) {
|
||||
if (lineCount === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -556,6 +556,7 @@ export function createApiFactory(
|
||||
Breakpoint: extHostTypes.Breakpoint,
|
||||
CancellationTokenSource: CancellationTokenSource,
|
||||
CodeAction: extHostTypes.CodeAction,
|
||||
CodeActionKind: extHostTypes.CodeActionKind,
|
||||
CodeLens: extHostTypes.CodeLens,
|
||||
Color: extHostTypes.Color,
|
||||
ColorPresentation: extHostTypes.ColorPresentation,
|
||||
|
||||
@@ -648,6 +648,7 @@ export interface CodeActionDto {
|
||||
edit?: WorkspaceEditDto;
|
||||
diagnostics?: IMarkerData[];
|
||||
command?: modes.Command;
|
||||
scope?: string;
|
||||
}
|
||||
|
||||
export interface ExtHostLanguageFeaturesShape {
|
||||
@@ -660,7 +661,7 @@ export interface ExtHostLanguageFeaturesShape {
|
||||
$provideHover(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.Hover>;
|
||||
$provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.DocumentHighlight[]>;
|
||||
$provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext): TPromise<LocationDto[]>;
|
||||
$provideCodeActions(handle: number, resource: UriComponents, range: IRange): TPromise<CodeActionDto[]>;
|
||||
$provideCodeActions(handle: number, resource: UriComponents, range: IRange, context: modes.CodeActionContext): TPromise<CodeActionDto[]>;
|
||||
$provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: modes.FormattingOptions): TPromise<ISingleEditOperation[]>;
|
||||
$provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: modes.FormattingOptions): TPromise<ISingleEditOperation[]>;
|
||||
$provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: modes.FormattingOptions): TPromise<ISingleEditOperation[]>;
|
||||
|
||||
@@ -413,8 +413,11 @@ export class ExtHostApiCommands {
|
||||
} else {
|
||||
const ret = new types.CodeAction(
|
||||
codeAction.title,
|
||||
typeConverters.WorkspaceEdit.to(codeAction.edit)
|
||||
codeAction.kind ? new types.CodeActionKind(codeAction.kind) : undefined
|
||||
);
|
||||
if (codeAction.edit) {
|
||||
ret.edit = typeConverters.WorkspaceEdit.to(codeAction.edit);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import * as vscode from 'vscode';
|
||||
import * as TypeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { Range, Disposable, CompletionList, SnippetString, Color } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { Range, Disposable, CompletionList, SnippetString, Color, CodeActionKind } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { ISingleEditOperation } from 'vs/editor/common/model';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService';
|
||||
@@ -273,7 +273,8 @@ class CodeActionAdapter {
|
||||
this._provider = provider;
|
||||
}
|
||||
|
||||
provideCodeActions(resource: URI, range: IRange): TPromise<CodeActionDto[]> {
|
||||
|
||||
provideCodeActions(resource: URI, range: IRange, context: modes.CodeActionContext): TPromise<CodeActionDto[]> {
|
||||
|
||||
const doc = this._documents.getDocumentData(resource).document;
|
||||
const ran = <vscode.Range>TypeConverters.toRange(range);
|
||||
@@ -289,8 +290,12 @@ class CodeActionAdapter {
|
||||
}
|
||||
});
|
||||
|
||||
const codeActionContext: vscode.CodeActionContext = {
|
||||
diagnostics: allDiagnostics,
|
||||
only: context.only ? new CodeActionKind(context.only) : undefined
|
||||
};
|
||||
return asWinJsPromise(token =>
|
||||
this._provider.provideCodeActions(doc, ran, { diagnostics: allDiagnostics }, token)
|
||||
this._provider.provideCodeActions(doc, ran, codeActionContext, token)
|
||||
).then(commandsOrActions => {
|
||||
if (isFalsyOrEmpty(commandsOrActions)) {
|
||||
return undefined;
|
||||
@@ -314,6 +319,7 @@ class CodeActionAdapter {
|
||||
command: candidate.command && this._commands.toInternal(candidate.command),
|
||||
diagnostics: candidate.diagnostics && candidate.diagnostics.map(DiagnosticCollection.toMarkerData),
|
||||
edit: candidate.edit && TypeConverters.WorkspaceEdit.from(candidate.edit),
|
||||
kind: candidate.kind && candidate.kind.value
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -943,8 +949,9 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
$provideCodeActions(handle: number, resource: UriComponents, range: IRange): TPromise<CodeActionDto[]> {
|
||||
return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(URI.revive(resource), range));
|
||||
|
||||
$provideCodeActions(handle: number, resource: UriComponents, range: IRange, context: modes.CodeActionContext): TPromise<CodeActionDto[]> {
|
||||
return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(URI.revive(resource), range, context));
|
||||
}
|
||||
|
||||
// --- formatting
|
||||
|
||||
@@ -12,6 +12,7 @@ import * as vscode from 'vscode';
|
||||
import { isMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { IRelativePattern } from 'vs/base/common/glob';
|
||||
import { relative } from 'path';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
|
||||
export class Disposable {
|
||||
|
||||
@@ -861,12 +862,39 @@ export class CodeAction {
|
||||
|
||||
dianostics?: Diagnostic[];
|
||||
|
||||
constructor(title: string, edit?: WorkspaceEdit) {
|
||||
kind?: CodeActionKind;
|
||||
|
||||
constructor(title: string, kind?: CodeActionKind) {
|
||||
this.title = title;
|
||||
this.edit = edit;
|
||||
this.kind = kind;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class CodeActionKind {
|
||||
private static readonly sep = '.';
|
||||
|
||||
public static readonly Empty = new CodeActionKind('');
|
||||
public static readonly QuickFix = CodeActionKind.Empty.append('quickfix');
|
||||
public static readonly Refactor = CodeActionKind.Empty.append('refactor');
|
||||
public static readonly RefactorExtract = CodeActionKind.Refactor.append('extract');
|
||||
public static readonly RefactorInline = CodeActionKind.Refactor.append('inline');
|
||||
public static readonly RefactorRewrite = CodeActionKind.Refactor.append('rewrite');
|
||||
|
||||
constructor(
|
||||
public readonly value: string
|
||||
) { }
|
||||
|
||||
public append(parts: string): CodeActionKind {
|
||||
return new CodeActionKind(this.value ? this.value + CodeActionKind.sep + parts : parts);
|
||||
}
|
||||
|
||||
public contains(other: CodeActionKind): boolean {
|
||||
return this.value === other.value || startsWith(other.value, this.value + CodeActionKind.sep);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class CodeLens {
|
||||
|
||||
range: Range;
|
||||
|
||||
@@ -169,7 +169,7 @@ export function prepareActions(actions: IAction[]): IAction[] {
|
||||
for (let l = 0; l < actions.length; l++) {
|
||||
const a = <any>actions[l];
|
||||
if (types.isUndefinedOrNull(a.order)) {
|
||||
a.order = lastOrder++;
|
||||
a.order = ++lastOrder;
|
||||
orderOffset++;
|
||||
} else {
|
||||
a.order += orderOffset;
|
||||
|
||||
@@ -93,10 +93,10 @@ export class ActivitybarPart extends Part {
|
||||
return this.compositeBar.showActivity(viewletOrActionId, badge, clazz, priority);
|
||||
}
|
||||
|
||||
return this.showGlobalActivity(viewletOrActionId, badge);
|
||||
return this.showGlobalActivity(viewletOrActionId, badge, clazz);
|
||||
}
|
||||
|
||||
private showGlobalActivity(globalActivityId: string, badge: IBadge): IDisposable {
|
||||
private showGlobalActivity(globalActivityId: string, badge: IBadge, clazz?: string): IDisposable {
|
||||
if (!badge) {
|
||||
throw illegalArgument('badge');
|
||||
}
|
||||
@@ -106,7 +106,7 @@ export class ActivitybarPart extends Part {
|
||||
throw illegalArgument('globalActivityId');
|
||||
}
|
||||
|
||||
action.setBadge(badge);
|
||||
action.setBadge(badge, clazz);
|
||||
|
||||
return toDisposable(() => action.setBadge(undefined));
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import * as dom from 'vs/base/browser/dom';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { BaseActionItem, IBaseActionItemOptions, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { dispose, IDisposable, empty, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
@@ -53,6 +53,7 @@ export interface ICompositeBar {
|
||||
|
||||
export class ActivityAction extends Action {
|
||||
private badge: IBadge;
|
||||
private clazz: string | undefined;
|
||||
private _onDidChangeBadge = new Emitter<this>();
|
||||
|
||||
constructor(private _activity: IActivity) {
|
||||
@@ -85,8 +86,13 @@ export class ActivityAction extends Action {
|
||||
return this.badge;
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge): void {
|
||||
public getClass(): string | undefined {
|
||||
return this.clazz;
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge, clazz?: string): void {
|
||||
this.badge = badge;
|
||||
this.clazz = clazz;
|
||||
this._onDidChangeBadge.fire(this);
|
||||
}
|
||||
}
|
||||
@@ -110,6 +116,7 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
protected options: IActivityActionItemOptions;
|
||||
|
||||
private $badgeContent: Builder;
|
||||
private badgeDisposable: IDisposable = empty;
|
||||
private mouseUpTimeout: number;
|
||||
|
||||
constructor(
|
||||
@@ -199,7 +206,10 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
protected updateBadge(badge: IBadge): void {
|
||||
protected updateBadge(badge: IBadge, clazz?: string): void {
|
||||
this.badgeDisposable.dispose();
|
||||
this.badgeDisposable = empty;
|
||||
|
||||
this.$badgeContent.empty();
|
||||
this.$badge.hide();
|
||||
|
||||
@@ -234,6 +244,11 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
else if (badge instanceof ProgressBadge) {
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
if (clazz) {
|
||||
this.$badge.addClass(clazz);
|
||||
this.badgeDisposable = toDisposable(() => this.$badge.removeClass(clazz));
|
||||
}
|
||||
}
|
||||
|
||||
// Title
|
||||
@@ -259,7 +274,7 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
private handleBadgeChangeEvenet(): void {
|
||||
const action = this.getAction();
|
||||
if (action instanceof ActivityAction) {
|
||||
this.updateBadge(action.getBadge());
|
||||
this.updateBadge(action.getBadge(), action.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import Event, { Emitter } from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Dimension, Builder, $ } from 'vs/base/browser/builder';
|
||||
import { ResourceViewer } from 'vs/base/browser/ui/resourceviewer/resourceViewer';
|
||||
import { ResourceViewer, ResourceViewerContext } from 'vs/base/browser/ui/resourceviewer/resourceViewer';
|
||||
import { EditorModel, EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
|
||||
@@ -29,6 +29,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
|
||||
private binaryContainer: Builder;
|
||||
private scrollbar: DomScrollableElement;
|
||||
private resourceViewerContext: ResourceViewerContext;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -87,7 +88,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
|
||||
// Render Input
|
||||
const model = <BinaryEditorModel>resolvedModel;
|
||||
ResourceViewer.show(
|
||||
this.resourceViewerContext = ResourceViewer.show(
|
||||
{ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() },
|
||||
this.binaryContainer,
|
||||
this.scrollbar,
|
||||
@@ -132,6 +133,9 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
// Pass on to Binary Container
|
||||
this.binaryContainer.size(dimension.width, dimension.height);
|
||||
this.scrollbar.scanDomNode();
|
||||
if (this.resourceViewerContext) {
|
||||
this.resourceViewerContext.layout(dimension);
|
||||
}
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
@@ -146,4 +150,4 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,12 +403,12 @@ editorCommands.setup();
|
||||
// Touch Bar
|
||||
if (isMacintosh) {
|
||||
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
|
||||
command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, iconPath: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')).fsPath },
|
||||
command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, iconPath: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')).fsPath } },
|
||||
group: 'navigation'
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
|
||||
command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconPath: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')).fsPath },
|
||||
command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconPath: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')).fsPath } },
|
||||
group: 'navigation'
|
||||
});
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user