Merge branch 'master' into ben/workspace-api

This commit is contained in:
Benjamin Pasero
2018-01-23 11:00:47 +01:00
138 changed files with 3546 additions and 1467 deletions
+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'));
+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 }
# }
+2 -1
View File
@@ -9,5 +9,6 @@
"always"
],
"triple-equals": true
}
},
"defaultSeverity": "warning"
}
+69 -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,62 @@ 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;
function ShouldRunAfterUpdate(): Boolean;
begin
if IsBackgroundUpdate() then
// 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
Result := not FileExists(ExpandConstant('{param:update}'))
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;
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('--apply-update _ "{app}\unins000.dat"'), '', 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",
@@ -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;
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.20.0",
"distro": "3e04fd7a1141705f5e82fb1175edc03d0d4ddf9c",
"distro": "2478cca5311e147817eef29cb81c0995a3517842",
"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": {
+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.languagId && localizationContribution.translations) {
const languageSources = languagePacks[localizationContribution.languagId] || [];
languageSources.splice(0, 0, { extensionIdentifier, translations: join(extension.path, localizationContribution.translations), version: extension.manifest.version });
languagePacks[localizationContribution.languagId] = 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();
}
})];
}
}
+11 -1
View File
@@ -1326,7 +1326,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 {
@@ -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);
@@ -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
@@ -343,6 +343,14 @@ export interface CodeAction {
command?: Command;
edit?: WorkspaceEdit;
diagnostics?: IMarkerData[];
kind?: string;
}
/**
* @internal
*/
export interface CodeActionContext {
only?: string;
}
/**
@@ -354,7 +362,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,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 { createBulkEdit } 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 {
@@ -148,5 +165,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, IResourceEdit } from 'vs/editor/common/modes';
import { CodeActionProviderRegistry, LanguageIdentifier, CodeActionProvider, Command, WorkspaceEdit, IResourceEdit, 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;
}
/**
@@ -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;
}
/**
@@ -6,13 +6,15 @@
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import Event from 'vs/base/common/event';
import { match } from 'vs/base/common/glob';
export enum ContextKeyExprType {
Defined = 1,
Not = 2,
Equals = 3,
NotEquals = 4,
And = 5
And = 5,
Glob = 6
}
export abstract class ContextKeyExpr {
@@ -29,6 +31,10 @@ export abstract class ContextKeyExpr {
return new ContextKeyNotEqualsExpr(key, value);
}
public static glob(key: string, value: string): ContextKeyExpr {
return new ContextKeyGlobExpr(key, value);
}
public static not(key: string): ContextKeyExpr {
return new ContextKeyNotExpr(key);
}
@@ -60,6 +66,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 ContextKeyGlobExpr(pieces[0].trim(), this._deserializeValue(pieces[1]));
}
if (/^\!\s*/.test(serializedOne)) {
return new ContextKeyNotExpr(serializedOne.substr(1).trim());
}
@@ -109,6 +120,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.Glob:
return (<ContextKeyGlobExpr>a).cmp(<ContextKeyGlobExpr>b);
default:
throw new Error('Unknown ContextKeyExpr!');
}
@@ -320,6 +333,55 @@ export class ContextKeyNotExpr implements ContextKeyExpr {
}
}
export class ContextKeyGlobExpr implements ContextKeyExpr {
constructor(private key: string, private value: any) {
}
public getType(): ContextKeyExprType {
return ContextKeyExprType.Glob;
}
public cmp(other: ContextKeyGlobExpr): number {
if (this.key < other.key) {
return -1;
}
if (this.key > other.key) {
return 1;
}
if (this.value < other.value) {
return -1;
}
if (this.value > other.value) {
return 1;
}
return 0;
}
public equals(other: ContextKeyExpr): boolean {
if (other instanceof ContextKeyGlobExpr) {
return (this.key === other.key && this.value === other.value);
}
return false;
}
public evaluate(context: IContext): boolean {
return match(this.value, context.getValue(this.key));
}
public normalize(): ContextKeyExpr {
return this;
}
public serialize(): string {
return this.key + ' =~ \'' + this.value + '\'';
}
public keys(): string[] {
return [this.key];
}
}
export class ContextKeyAndExpr implements ContextKeyExpr {
public readonly expr: ContextKeyExpr[];
@@ -6,6 +6,7 @@
import * as assert from 'assert';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { match } from 'vs/base/common/glob';
function createContext(ctx: any) {
return {
@@ -21,6 +22,8 @@ suite('ContextKeyExpr', () => {
ContextKeyExpr.has('a1'),
ContextKeyExpr.and(ContextKeyExpr.has('and.a')),
ContextKeyExpr.has('a2'),
ContextKeyExpr.glob('d3', '**/d*'),
ContextKeyExpr.glob('d4', '**/3*'),
ContextKeyExpr.equals('b1', 'bb1'),
ContextKeyExpr.equals('b2', 'bb2'),
ContextKeyExpr.notEquals('c1', 'cc1'),
@@ -32,9 +35,11 @@ suite('ContextKeyExpr', () => {
ContextKeyExpr.equals('b2', 'bb2'),
ContextKeyExpr.notEquals('c1', 'cc1'),
ContextKeyExpr.not('d1'),
ContextKeyExpr.glob('d4', '**/3*'),
ContextKeyExpr.notEquals('c2', 'cc2'),
ContextKeyExpr.has('a2'),
ContextKeyExpr.equals('b1', 'bb1'),
ContextKeyExpr.glob('d3', '**/d*'),
ContextKeyExpr.has('a1'),
ContextKeyExpr.and(ContextKeyExpr.equals('and.a', true)),
ContextKeyExpr.not('d2')
@@ -59,9 +64,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 +81,13 @@ suite('ContextKeyExpr', () => {
testExpression(expr + ' == 5', value == <any>'5');
testExpression(expr + ' != 5', value != <any>'5');
testExpression('!' + expr, !value);
testExpression(expr + ' =~ **/d*', match('**/d*', 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 {
languagId: 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 {
@@ -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: [{ languagId: '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)
]);
}
+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,114 @@
/*---------------------------------------------------------------------------------------------
* 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.logService.error, this.logService, this.disposables);
this.onRawUpdateAvailable(this.onUpdateAvailable, this, this.disposables);
this.onRawUpdateDownloaded(this.onUpdateDownloaded, this, this.disposables);
this.onRawUpdateNotAvailable(this.onUpdateNotAvailable, this, this.disposables);
}
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']
});
}
}
}
@@ -198,7 +198,7 @@ export class Workspace implements IWorkspace {
}
public toJSON(): IWorkspace {
return { id: this.id, folders: this.folders, name: this.name };
return { id: this.id, folders: this.folders, name: this.name, configuration: this.configuration };
}
}
+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)));
@@ -206,8 +206,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;
}
@@ -559,6 +559,7 @@ export function createApiFactory(
Breakpoint: extHostTypes.Breakpoint,
CancellationTokenSource: CancellationTokenSource,
CodeAction: extHostTypes.CodeAction,
CodeActionKind: extHostTypes.CodeActionKind,
CodeLens: extHostTypes.CodeLens,
Color: extHostTypes.Color,
ColorPresentation: extHostTypes.ColorPresentation,
@@ -71,6 +71,7 @@ export interface IWorkspaceData {
id: string;
name: string;
folders: { uri: UriComponents, name: string, index: number }[];
configuration: UriComponents;
}
export interface IInitData {
@@ -640,6 +641,7 @@ export interface CodeActionDto {
edit?: WorkspaceEditDto;
diagnostics?: IMarkerData[];
command?: modes.Command;
scope?: string;
}
export interface ExtHostLanguageFeaturesShape {
@@ -652,7 +654,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[]>;
@@ -417,8 +417,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;
}
});
@@ -22,6 +22,7 @@ import { Barrier } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import URI from 'vs/base/common/uri';
class ExtensionMemento implements IExtensionMemento {
@@ -108,6 +109,7 @@ class ExtensionStoragePath {
join(storagePath, 'meta.json'),
JSON.stringify({
id: this._workspace.id,
configuration: URI.revive(this._workspace.configuration).toString(),
name: this._workspace.name
}, undefined, 2)
);
@@ -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,7 @@ class CodeActionAdapter {
this._provider = provider;
}
provideCodeActions(resource: URI, range: IRange): TPromise<modes.CodeAction[]> {
provideCodeActions(resource: URI, range: IRange, context: modes.CodeActionContext): TPromise<modes.CodeAction[]> {
const doc = this._documents.getDocumentData(resource).document;
const ran = <vscode.Range>TypeConverters.toRange(range);
@@ -289,8 +289,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 +318,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 +948,8 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
return this._createDisposable(handle);
}
$provideCodeActions(handle: number, resource: UriComponents, range: IRange): TPromise<modes.CodeAction[]> {
return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(URI.revive(resource), range));
$provideCodeActions(handle: number, resource: UriComponents, range: IRange, context: modes.CodeActionContext): TPromise<modes.CodeAction[]> {
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 {
@@ -818,12 +819,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();
}
}
}
@@ -8,7 +8,7 @@ import * as types from 'vs/base/common/types';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { ActiveEditorMoveArguments, ActiveEditorMovePositioning, ActiveEditorMovePositioningBy, EditorCommands, TextCompareEditorVisible, EditorInput, IEditorIdentifier } from 'vs/workbench/common/editor';
import { ActiveEditorMoveArguments, ActiveEditorMovePositioning, ActiveEditorMovePositioningBy, EditorCommands, TextCompareEditorVisible, EditorInput, IEditorIdentifier, IEditorCommandsContext } from 'vs/workbench/common/editor';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditor, Position, POSITIONS, Direction, IEditorInput } from 'vs/platform/editor/common/editor';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
@@ -190,10 +190,18 @@ function registerDiffEditorCommands(): void {
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: void 0,
handler: (accessor) => {
handler: (accessor, resource, context: IEditorCommandsContext) => {
const editorService = accessor.get(IWorkbenchEditorService);
const editorGroupService = accessor.get(IEditorGroupService);
let editor: IEditor;
if (context) {
const position = positionAndInput(editorGroupService, editorService, context).position;
editor = editorService.getVisibleEditors()[position];
} else {
editor = editorService.getActiveEditor();
}
const editor = editorService.getActiveEditor();
if (editor instanceof TextDiffEditor) {
const control = editor.getControl();
const isInlineMode = !control.renderSideBySide;
@@ -262,15 +270,15 @@ function registerEditorCommands() {
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U),
handler: (accessor, resource: URI, editorContext: IEditorIdentifier) => {
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const model = editorGroupService.getStacksModel();
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(editorContext, accessor.get(IListService));
const contexts = getMultiSelectedEditorContexts(toEditorIdentifier(context, editorGroupService), accessor.get(IListService));
let positionOne: { unmodifiedOnly: boolean } = undefined;
let positionTwo: { unmodifiedOnly: boolean } = undefined;
let positionThree: { unmodifiedOnly: boolean } = undefined;
let positionOne: { unmodifiedOnly: boolean } = void 0;
let positionTwo: { unmodifiedOnly: boolean } = void 0;
let positionThree: { unmodifiedOnly: boolean } = void 0;
contexts.forEach(c => {
switch (model.positionOfGroup(c.group)) {
case Position.ONE: positionOne = { unmodifiedOnly: true }; break;
@@ -288,10 +296,10 @@ function registerEditorCommands() {
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W),
handler: (accessor, resource: URI, editorContext: IEditorIdentifier) => {
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(editorContext, accessor.get(IListService));
const contexts = getMultiSelectedEditorContexts(toEditorIdentifier(context, editorGroupService), accessor.get(IListService));
const distinctGroups = distinct(contexts.map(c => c.group));
if (distinctGroups.length) {
@@ -312,11 +320,11 @@ function registerEditorCommands() {
when: void 0,
primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
handler: (accessor, resource: URI, editorContext: IEditorIdentifier) => {
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(editorContext, accessor.get(IListService));
const contexts = getMultiSelectedEditorContexts(toEditorIdentifier(context, editorGroupService), accessor.get(IListService));
const groups = distinct(contexts.map(context => context.group));
const editorsToClose = new Map<Position, IEditorInput[]>();
@@ -326,7 +334,7 @@ function registerEditorCommands() {
if (position >= 0) {
editorsToClose.set(position, contexts.map(c => {
if (group === c.group) {
let input = c ? c.editor : undefined;
let input = c ? c.editor : void 0;
if (!input) {
// Get Top Editor at Position
@@ -339,7 +347,7 @@ function registerEditorCommands() {
return input;
}
return undefined;
return void 0;
}).filter(input => !!input));
}
});
@@ -365,10 +373,10 @@ function registerEditorCommands() {
when: void 0,
primary: void 0,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
handler: (accessor, resource: URI, editorContext: IEditorIdentifier) => {
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(editorContext, accessor.get(IListService));
const contexts = getMultiSelectedEditorContexts(toEditorIdentifier(context, editorGroupService), accessor.get(IListService));
const groups = distinct(contexts.map(context => context.group));
const editorsToClose = new Map<Position, IEditorInput[]>();
@@ -378,7 +386,7 @@ function registerEditorCommands() {
return c.editor;
}
return undefined;
return void 0;
}).filter(input => !!input);
const toClose = group.getEditors().filter(input => inputsToSkip.indexOf(input) === -1);
@@ -398,11 +406,11 @@ function registerEditorCommands() {
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: void 0,
handler: (accessor, resource: URI, editorContext: IEditorIdentifier) => {
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const { position, input } = positionAndInput(editorGroupService, editorService, editorContext);
const { position, input } = positionAndInput(editorGroupService, editorService, context);
if (typeof position === 'number' && input) {
return editorService.closeEditors(position, { except: input, direction: Direction.RIGHT });
@@ -417,11 +425,11 @@ function registerEditorCommands() {
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter),
handler: (accessor, resource: URI, editorContext: IEditorIdentifier) => {
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const { position, input } = positionAndInput(editorGroupService, editorService, editorContext);
const { position, input } = positionAndInput(editorGroupService, editorService, context);
if (typeof position === 'number' && input) {
return editorGroupService.pinEditor(position, input);
@@ -436,7 +444,7 @@ function registerEditorCommands() {
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: void 0,
handler: (accessor, resource: URI, editorContext: IEditorIdentifier) => {
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const quickOpenService = accessor.get(IQuickOpenService);
@@ -447,7 +455,7 @@ function registerEditorCommands() {
return quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
}
const { position } = positionAndInput(editorGroupService, editorService, editorContext);
const { position } = positionAndInput(editorGroupService, editorService, context);
switch (position) {
case Position.TWO:
@@ -481,7 +489,10 @@ function registerEditorCommands() {
});
}
function positionAndInput(editorGroupService: IEditorGroupService, editorService: IWorkbenchEditorService, editorContext?: IEditorIdentifier): { position: Position, input: IEditorInput } {
function positionAndInput(editorGroupService: IEditorGroupService, editorService: IWorkbenchEditorService, context?: IEditorIdentifier | IEditorCommandsContext): { position: Position, input: IEditorInput } {
// Resolve from context
const editorContext = toEditorIdentifier(context, editorGroupService);
let position = editorContext ? editorGroupService.getStacksModel().positionOfGroup(editorContext.group) : null;
let input = editorContext ? editorContext.editor : null;
@@ -496,14 +507,14 @@ function positionAndInput(editorGroupService: IEditorGroupService, editorService
}
export function getMultiSelectedEditorContexts(editorContext: IEditorIdentifier, listService: IListService): IEditorIdentifier[] {
const list = listService.lastFocusedList;
// Mapping for open editors view
const isEditorIdentifier = (element: any) => 'group' in element && 'editor' in element;
const elementToContext = (element: IEditorIdentifier | EditorGroup) => element instanceof EditorGroup ? { group: element, editor: undefined } : element;
const elementToContext = (element: IEditorIdentifier | EditorGroup) => element instanceof EditorGroup ? { group: element, editor: void 0 } : element;
// First check for a focused list to return the selected items from
const list = listService.lastFocusedList;
if (list instanceof List && list.isDOMFocused()) {
const selection = list.getSelectedElements();
const focus = list.getFocusedElements();
// Only respect selection if it contains focused element
if (focus.length && selection && selection.indexOf(focus[0]) >= 0) {
return list.getSelectedElements().filter(e => e instanceof EditorGroup || isEditorIdentifier(e)).map(elementToContext);
@@ -514,5 +525,35 @@ export function getMultiSelectedEditorContexts(editorContext: IEditorIdentifier,
}
}
// Otherwise go with passed in context
return !!editorContext ? [editorContext] : [];
}
function isEditorIdentifier(object: any): object is IEditorIdentifier {
const identifier = object as IEditorIdentifier;
return identifier && !!identifier.group && !!identifier.editor;
}
function isEditorGroupContext(object: any): object is IEditorCommandsContext {
const context = object as IEditorCommandsContext;
return context && typeof context.groupId === 'number';
}
function toEditorIdentifier(object: IEditorIdentifier | IEditorCommandsContext, editorGroupService: IEditorGroupService): IEditorIdentifier {
if (isEditorIdentifier(object)) {
return object as IEditorIdentifier;
}
if (isEditorGroupContext(object)) {
const stacks = editorGroupService.getStacksModel();
const group = stacks.getGroup(object.groupId);
return {
group,
editor: typeof object.editorIndex === 'number' ? group.getEditor(object.editorIndex) : void 0
};
}
return void 0;
}
@@ -7,7 +7,7 @@
import 'vs/css!./media/notabstitle';
import errors = require('vs/base/common/errors');
import { IEditorGroup, toResource } from 'vs/workbench/common/editor';
import { toResource } from 'vs/workbench/common/editor';
import DOM = require('vs/base/browser/dom');
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
import { ResourceLabel } from 'vs/workbench/browser/labels';
@@ -19,12 +19,6 @@ export class NoTabsTitleControl extends TitleControl {
private titleContainer: HTMLElement;
private editorLabel: ResourceLabel;
public setContext(group: IEditorGroup): void {
super.setContext(group);
this.editorActionsToolbar.context = { group };
}
public create(parent: HTMLElement): void {
super.create(parent);
@@ -118,12 +118,6 @@ export class TabsTitleControl extends TitleControl {
return this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService]));
}
public setContext(group: IEditorGroup): void {
super.setContext(group);
this.editorActionsToolbar.context = { group };
}
public create(parent: HTMLElement): void {
super.create(parent);
@@ -15,7 +15,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { RunOnceScheduler } from 'vs/base/common/async';
import arrays = require('vs/base/common/arrays');
import { IEditorStacksModel, IEditorGroup, IEditorIdentifier, EditorInput, IStacksModelChangeEvent, toResource } from 'vs/workbench/common/editor';
import { IEditorStacksModel, IEditorGroup, IEditorIdentifier, EditorInput, IStacksModelChangeEvent, toResource, IEditorCommandsContext } from 'vs/workbench/common/editor';
import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -170,6 +170,8 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
public setContext(group: IEditorGroup): void {
this.context = group;
this.editorActionsToolbar.context = { groupId: group ? group.id : void 0 } as IEditorCommandsContext;
}
public hasContext(): boolean {
@@ -396,7 +398,7 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => TPromise.as(actions),
getActionsContext: () => identifier,
getActionsContext: () => ({ groupId: identifier.group.id, editorIndex: identifier.group.indexOf(identifier.editor) } as IEditorCommandsContext),
getKeyBinding: (action) => this.getKeybinding(action),
onHide: (cancel) => {
+10
View File
@@ -788,6 +788,16 @@ export interface IEditorIdentifier {
editor: IEditorInput;
}
/**
* The editor commands context is used for editor commands (e.g. in the editor title)
* and we must ensure that the context is serializable because it potentially travels
* to the extension host!
*/
export interface IEditorCommandsContext {
groupId: GroupIdentifier;
editorIndex?: number;
}
export interface IEditorCloseEvent extends IEditorIdentifier {
replaced: boolean;
index: number;
@@ -5,7 +5,7 @@
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { EndOfLinePreference, ITextModel, ITextBufferFactory } from 'vs/editor/common/model';
import { ITextModel, ITextBufferFactory } from 'vs/editor/common/model';
import { IMode } from 'vs/editor/common/modes';
import { EditorModel } from 'vs/workbench/common/editor';
import URI from 'vs/base/common/uri';
@@ -13,6 +13,7 @@ import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { ITextSnapshot } from 'vs/platform/files/common/files';
/**
* The base text editor model leverages the code editor model. This class is only intended to be subclassed and not instantiated.
@@ -90,7 +91,9 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd
return this;
}
protected getFirstLineText(value: string | ITextBufferFactory): string {
protected getFirstLineText(value: string | ITextBufferFactory | ITextSnapshot): string {
// string
if (typeof value === 'string') {
const firstLineText = value.substr(0, 100);
@@ -105,9 +108,17 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd
}
return firstLineText.substr(0, Math.min(crIndex, lfIndex));
} else {
return value.getFirstLineText(100);
}
// text buffer factory
const textBufferFactory = value as ITextBufferFactory;
if (typeof textBufferFactory.getFirstLineText === 'function') {
return textBufferFactory.getFirstLineText(100);
}
// text snapshot
const textSnapshot = value as ITextSnapshot;
return this.getFirstLineText(textSnapshot.read() || '');
}
/**
@@ -130,13 +141,10 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd
this.modelService.updateModel(this.textEditorModel, newValue);
}
/**
* Returns the textual value of this editor model or null if it has not yet been created.
*/
public getValue(): string {
public createSnapshot(): ITextSnapshot {
const model = this.textEditorModel;
if (model) {
return model.getValue(EndOfLinePreference.TextDefined, true /* Preserve BOM */);
return model.createSnapshot(true /* Preserve BOM */);
}
return null;
@@ -10,7 +10,6 @@ import { IEncodingSupport } from 'vs/workbench/common/editor';
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import URI from 'vs/base/common/uri';
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
import { EndOfLinePreference } from 'vs/editor/common/model';
import { CONTENT_CHANGE_EVENT_BUFFER_DELAY } from 'vs/platform/files/common/files';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
@@ -113,14 +112,6 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin
return this.versionId;
}
public getValue(): string {
if (this.textEditorModel) {
return this.textEditorModel.getValue(EndOfLinePreference.TextDefined, true /* Preserve BOM */);
}
return null;
}
public getModeId(): string {
if (this.textEditorModel) {
return this.textEditorModel.getLanguageIdentifier().language;
@@ -256,14 +256,15 @@ configurationRegistry.registerConfiguration({
},
'workbench.fontAliasing': {
'type': 'string',
'enum': ['default', 'antialiased', 'none'],
'enum': ['default', 'antialiased', 'none', 'auto'],
'default': 'default',
'description':
nls.localize('fontAliasing', "Controls font aliasing method in the workbench.\n- default: Sub-pixel font smoothing. On most non-retina displays this will give the sharpest text\n- antialiased: Smooth the font on the level of the pixel, as opposed to the subpixel. Can make the font appear lighter overall\n- none: Disables font smoothing. Text will show with jagged sharp edges"),
nls.localize('fontAliasing', "Controls font aliasing method in the workbench.\n- default: Sub-pixel font smoothing. On most non-retina displays this will give the sharpest text\n- antialiased: Smooth the font on the level of the pixel, as opposed to the subpixel. Can make the font appear lighter overall\n- none: Disables font smoothing. Text will show with jagged sharp edges\n- auto: Applies `default` or `antialiased` automatically based on the DPI of displays."),
'enumDescriptions': [
nls.localize('workbench.fontAliasing.default', "Sub-pixel font smoothing. On most non-retina displays this will give the sharpest text."),
nls.localize('workbench.fontAliasing.antialiased', "Smooth the font on the level of the pixel, as opposed to the subpixel. Can make the font appear lighter overall."),
nls.localize('workbench.fontAliasing.none', "Disables font smoothing. Text will show with jagged sharp edges.")
nls.localize('workbench.fontAliasing.none', "Disables font smoothing. Text will show with jagged sharp edges."),
nls.localize('workbench.fontAliasing.auto', "Applies `default` or `antialiased` automatically based on the DPI of displays.")
],
'included': isMacintosh
},
@@ -26,4 +26,18 @@
.monaco-workbench.windows .monaco-action-bar .select-box {
margin-top: 7px; /* Center the select box */
}
.monaco-font-aliasing-antialiased {
-webkit-font-smoothing: antialiased;
}
.monaco-font-aliasing-none {
-webkit-font-smoothing: none;
}
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
.monaco-font-aliasing-auto {
-webkit-font-smoothing: antialiased;
}
}
+15 -6
View File
@@ -124,6 +124,8 @@ export interface IWorkbenchStartedInfo {
restoredEditors: string[];
}
type FontAliasingOption = 'default' | 'antialiased' | 'none' | 'auto';
const Identifiers = {
WORKBENCH_CONTAINER: 'workbench.main.container',
TITLEBAR_PART: 'workbench.parts.titlebar',
@@ -202,7 +204,7 @@ export class Workbench implements IPartService {
private inZenMode: IContextKey<boolean>;
private sideBarVisibleContext: IContextKey<boolean>;
private hasFilesToCreateOpenOrDiff: boolean;
private fontAliasing: string;
private fontAliasing: FontAliasingOption;
private zenMode: {
active: boolean;
transitionedToFullScreen: boolean;
@@ -643,7 +645,7 @@ export class Workbench implements IPartService {
this.activityBarHidden = !activityBarVisible;
// Font aliasing
this.fontAliasing = this.configurationService.getValue<string>(Workbench.fontAliasingConfigurationKey);
this.fontAliasing = this.configurationService.getValue<FontAliasingOption>(Workbench.fontAliasingConfigurationKey);
// Zen mode
this.zenMode = {
@@ -937,10 +939,17 @@ export class Workbench implements IPartService {
});
}
private setFontAliasing(aliasing: string) {
private setFontAliasing(aliasing: FontAliasingOption) {
this.fontAliasing = aliasing;
document.body.style['-webkit-font-smoothing'] = (aliasing === 'default' ? '' : aliasing);
const fontAliasingClassNames = [
'monaco-font-aliasing-antialiased',
'monaco-font-aliasing-none',
'monaco-font-aliasing-auto'
];
document.body.classList.remove(...fontAliasingClassNames);
if (aliasing !== 'default') {
document.body.classList.add(`monaco-font-aliasing-${aliasing}`);
}
}
public dispose(reason = ShutdownReason.QUIT): void {
@@ -1088,7 +1097,7 @@ export class Workbench implements IPartService {
this.setPanelPositionFromStorageOrConfig();
const fontAliasing = this.configurationService.getValue<string>(Workbench.fontAliasingConfigurationKey);
const fontAliasing = this.configurationService.getValue<FontAliasingOption>(Workbench.fontAliasingConfigurationKey);
if (fontAliasing !== this.fontAliasing) {
this.setFontAliasing(fontAliasing);
}
@@ -72,14 +72,14 @@ export class BackupModelTracker implements IWorkbenchContribution {
// Do not backup when auto save after delay is configured
if (!this.configuredAutoSaveAfterDelay) {
const model = this.textFileService.models.get(event.resource);
this.backupFileService.backupResource(model.getResource(), model.getValue(), model.getVersionId()).done(null, errors.onUnexpectedError);
this.backupFileService.backupResource(model.getResource(), model.createSnapshot(), model.getVersionId()).done(null, errors.onUnexpectedError);
}
}
}
private onUntitledModelChanged(resource: Uri): void {
if (this.untitledEditorService.isDirty(resource)) {
this.untitledEditorService.loadOrCreate({ resource }).then(model => this.backupFileService.backupResource(resource, model.getValue(), model.getVersionId())).done(null, errors.onUnexpectedError);
this.untitledEditorService.loadOrCreate({ resource }).then(model => this.backupFileService.backupResource(resource, model.createSnapshot(), model.getVersionId())).done(null, errors.onUnexpectedError);
} else {
this.discardBackup(resource);
}
@@ -22,6 +22,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
export function registerCommands(): void {
// TODO@Isidor remove in february
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.logToDebugConsole',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
@@ -1692,6 +1692,17 @@ CommandsRegistry.registerCommand('workbench.extensions.action.showExtensionsForL
});
});
CommandsRegistry.registerCommand('workbench.extensions.action.showExtensionsWithId', function (accessor: ServicesAccessor, extensionId: string) {
const viewletService = accessor.get(IViewletService);
return viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search(`@id:${extensionId}`);
viewlet.focus();
});
});
export const extensionButtonProminentBackground = registerColor('extensionButton.prominentBackground', {
dark: '#327e36',
light: '#327e36',
@@ -404,25 +404,17 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
return;
}
// Suggest the search only once as this is not a strong recommendation
fileExtensionSuggestionIgnoreList.push(fileExtension);
this.storageService.store(
'extensionsAssistant/fileExtensionsSuggestionIgnore',
JSON.stringify(fileExtensionSuggestionIgnoreList),
StorageScope.GLOBAL
);
const message = localize('showLanguageExtensions', "The Marketplace has extensions that can help with '.{0}' files", fileExtension);
const searchMarketplaceAction = this.instantiationService.createInstance(ShowLanguageExtensionsAction, fileExtension);
const options = [
localize('searchMarketplace', "Search Marketplace"),
choiceNever,
choiceClose
];
this.choiceService.choose(Severity.Info, message, options, 1).done(choice => {
this.choiceService.choose(Severity.Info, message, options, 2).done(choice => {
switch (choice) {
case 0:
/* __GDPR__
@@ -435,6 +427,20 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
searchMarketplaceAction.run();
break;
case 1:
fileExtensionSuggestionIgnoreList.push(fileExtension);
this.storageService.store(
'extensionsAssistant/fileExtensionsSuggestionIgnore',
JSON.stringify(fileExtensionSuggestionIgnoreList),
StorageScope.GLOBAL
);
/* __GDPR__
"fileExtensionSuggestion:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'neverShowAgain', fileExtension: fileExtension });
case 2:
/* __GDPR__
"fileExtensionSuggestion:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
@@ -234,7 +234,7 @@ export class ExtensionsListView extends ViewsViewletPanel {
const languageTag = languageName ? ` tag:"${languageName}"` : '';
// Construct a rich query
return `tag:"__ext_${ext}" ${keywords.map(tag => `tag:"${tag}"`).join(' ')}${languageTag}`;
return `tag:"__ext_${ext}" tag:"__ext_.${ext}" ${keywords.map(tag => `tag:"${tag}"`).join(' ')}${languageTag}`;
});
if (names.length) {
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.runtime-extensions-editor .monaco-list .monaco-list-rows > .monaco-list-row:nth-child(even):not(:hover):not(.focused) {
.runtime-extensions-editor .monaco-list .monaco-list-rows > .monaco-list-row.odd:not(:hover):not(.focused) {
background-color: rgba(130, 130, 130, 0.08);
}
@@ -291,6 +291,8 @@ export class RuntimeExtensionsEditor extends BaseEditor {
data.elementDisposables = dispose(data.elementDisposables);
toggleClass(data.root, 'odd', index % 2 === 1);
data.name.textContent = element.marketplaceInfo ? element.marketplaceInfo.displayName : element.description.displayName;
const activationTimes = element.status.activationTimes;

Some files were not shown because too many files have changed in this diff Show More