Merge branch 'master' into rename-workspaceedit-proto

This commit is contained in:
Johannes Rieken
2018-01-23 17:19:06 +01:00
179 changed files with 4224 additions and 1684 deletions
+4
View File
@@ -0,0 +1,4 @@
[
{ "name": "ms-vscode.node-debug", "version": "1.20.3" },
{ "name": "ms-vscode.node-debug2", "version": "1.20.1" }
]
+21 -5
View File
@@ -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
+12 -1
View File
@@ -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'));
+32
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+8
View File
@@ -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(),
+4
View File
@@ -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
View File
@@ -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",
+2 -2
View File
@@ -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
View File
@@ -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"
}
}
}
+3
View File
@@ -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
+3 -3
View File
@@ -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;
}
+1
View File
@@ -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;
+2
View File
@@ -9,4 +9,6 @@ declare module 'windows-mutex' {
isActive(): boolean;
release(): void;
}
export function isActive(name: string): boolean;
}
+2 -6
View File
@@ -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);
}
}
+34 -20
View File
@@ -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;
}
+7 -3
View File
@@ -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
View File
@@ -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);
}
+1
View File
@@ -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);
+225 -1
View File
@@ -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;
}
}
}
}
+11 -2
View File
@@ -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));
+18 -9
View File
@@ -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(
+2 -2
View File
@@ -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()));
}
+37 -31
View File
@@ -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();
}
})];
}
}
+1 -1
View File
@@ -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;
}
+24 -11
View File
@@ -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);
}
+18 -7
View File
@@ -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;
+12 -1
View File
@@ -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);
+9 -1
View File
@@ -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;
}
+6 -3
View File
@@ -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());
+15 -14
View File
@@ -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);
});
});
+5
View File
@@ -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();
}
}
+2 -3
View File
@@ -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);
});
});
+14 -1
View File
@@ -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.
*/
+40 -28
View 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)
]);
}
+1
View File
@@ -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; }; };
+58 -24
View File
@@ -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>;
}
+16 -24
View File
@@ -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']
});
}
}
}
+77 -3
View File
@@ -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
+30 -2
View File
@@ -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;
+1 -1
View File
@@ -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