Merge branch 'master' into ben/workspace-api

This commit is contained in:
Benjamin Pasero
2018-01-24 13:25:06 +01:00
115 changed files with 1881 additions and 13315 deletions
+12
View File
@@ -0,0 +1,12 @@
[
{
"name": "ms-vscode.node-debug",
"version": "1.20.3",
"repo": "https://github.com/Microsoft/vscode-node-debug"
},
{
"name": "ms-vscode.node-debug2",
"version": "1.20.1",
"repo": "https://github.com/Microsoft/vscode-node-debug2"
}
]
+1 -1
View File
@@ -198,7 +198,7 @@ const hygiene = exports.hygiene = (some, options) => {
tsfmt.processString(file.path, file.contents.toString('utf8'), {
verify: true,
tsfmt: true,
// verbose: true
verbose: true
}).then(result => {
if (result.error) {
console.error(result.message);
+18 -5
View File
@@ -44,14 +44,13 @@ const nodeModules = ['electron', 'original-fs']
// Build
const builtInExtensions = [
{ name: 'ms-vscode.node-debug', version: '1.20.3' },
{ name: 'ms-vscode.node-debug2', version: '1.20.1' }
];
const builtInExtensions = require('./builtInExtensions');
const excludedExtensions = [
'vscode-api-tests',
'vscode-colorize-tests'
'vscode-colorize-tests',
'ms-vscode.node-debug',
'ms-vscode.node-debug2',
];
const vscodeEntryPoints = _.flatten([
@@ -584,3 +583,17 @@ gulp.task('generate-vscode-configuration', () => {
console.error(e.toString());
});
});
//#region Built-In Extensions
gulp.task('clean-builtin-extensions', util.rimraf('.build/builtInExtensions'));
gulp.task('download-builtin-extensions', ['clean-builtin-extensions'], function () {
const marketplaceExtensions = es.merge(...builtInExtensions.map(extension => {
return ext.fromMarketplace(extension.name, extension.version)
.pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`));
}));
return marketplaceExtensions
.pipe(util.setExecutableBit(['**/*.sh']))
.pipe(vfs.dest('.build/builtInExtensions'));
});
//#endregion
+32
View File
@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
const fs = require('fs');
const path = require('path');
const root = path.dirname(path.dirname(__dirname));
function isUpToDate(extension) {
const packagePath = path.join(root, '.build', 'builtInExtensions', extension.name, 'package.json');
if (!fs.existsSync(packagePath)) {
return false;
}
const packageContents = fs.readFileSync(packagePath);
try {
const diskVersion = JSON.parse(packageContents).version;
return (diskVersion === extension.version);
} catch(err) {
return false;
}
}
const builtInExtensions = require('../builtInExtensions');
builtInExtensions.forEach((extension) => {
if (!isUpToDate(extension)) {
process.exit(1);
}
});
process.exit(0);
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -1,8 +0,0 @@
{
"name": "node-debug-placeholder",
"version": "1.6.0",
"publisher": "vscode",
"engines": {
"vscode": "1.6.x"
}
}
File diff suppressed because it is too large Load Diff
@@ -1,8 +0,0 @@
{
"name": "node-debug2-placeholder",
"version": "0.0.3",
"publisher": "vscode",
"engines": {
"vscode": "1.6.x"
}
}
+108 -66
View File
@@ -1,133 +1,175 @@
{
"print(\"...\")": {
"prefix": "pr",
"body": "print(\"$1\")$0"
"print": {
"prefix": "print",
"body": "print(\"$1\")\n$0",
"description": "print(\"...\")"
},
"print(\"\\(...)\")": {
"prefix": "po",
"body": "print(\"\\($1)\")$0"
"print value": {
"prefix": "printv",
"body": "print(\"\\($1)\")\n$0",
"description": "print(\"\\(...)\")"
},
"repeat...while loop": {
"while": {
"prefix": "while",
"body": [
"while ${1:condition} {",
"\t$0",
"}"
],
"description": "while statement"
},
"repeat-while": {
"prefix": "repeat",
"body": [
"repeat {",
"\t$0",
"} while ${1:true}"
"} while ${1:condition}"
],
"description": "repeat...while loop"
"description": "repeat-while statement"
},
"While loop": {
"prefix": "while",
"body": [
"while ${1:true} {",
"\t$0",
"}"
],
"description": "While loop"
},
"For-In statement": {
"prefix": "forin",
"for": {
"prefix": "for",
"body": [
"for ${1:item} in ${2:collection} {",
"\t$0",
"}"
],
"description": "For-In statement"
"description": "for-in statement"
},
"Reverse for loop": {
"prefix": "forr",
"body": [
"for var ${1:i} = ${2:length} - 1; ${1:i} >= 0; ${1:i}-- {",
"\t$0",
"}"
],
"description": "Reverse for loop"
},
"for loop": {
"prefix": "for",
"body": [
"for var ${1:i} = 0; ${1:i} < ${2:length}; ${1:i}++ {",
"\t$0",
"}"
],
"description": "for loop"
},
"if statement": {
"if": {
"prefix": "if",
"body": [
"if ${1:true} {",
"if ${1:condition} {",
"\t$0",
"}"
],
"description": "if statement"
},
"else-if statement": {
"else if": {
"prefix": "elif",
"body": [
"else if ${1:true} {",
"else if ${1:condition} {",
"\t$0",
"}"
],
"description": "if statement"
"description": "else clause with a nested if statement"
},
"Else statement": {
"else": {
"prefix": "else",
"body": [
"else {",
"\t$0",
"}"
],
"description": "Else statement"
"description": "else clause"
},
"Guard statement": {
"if let": {
"prefix": "iflet",
"body": [
"if let ${1:value} = ${2:optional} {",
"\t$0",
"}"
],
"description": "if statement with optional binding"
},
"guard": {
"prefix": "guard",
"body": [
"guard let ${1:a} = ${2:optional} else {",
"guard ${1:condition} else {",
"\t$0",
"}"
],
"description": "Guard statement"
"description": "guard statement"
},
"Optional Binding statement": {
"prefix": "ifnil",
"guard let": {
"prefix": "guardlet",
"body": [
"if let ${1:a} = ${2:optional} {",
"guard let ${1:value} = ${2:optional} else {",
"\t$0",
"}"
],
"description": "Optional Binding statement"
"description": "guard statement with optional binding"
},
"Switch statement": {
"switch": {
"prefix": "switch",
"body": [
"switch ${1:switch_on} {",
"case ${2:a}:",
"switch ${1:value} {",
"case ${2:pattern}:",
"\t$0",
"default:",
"\t$1",
"\t",
"}"
],
"description": "Switch statement"
"description": "switch statement"
},
"Do catch": {
"prefix": "docatch",
"do": {
"prefix": "do",
"body": [
"do {",
"\ttry ${1:function that throws}",
"} catch ${2:pattern} {",
"\t$0",
"} catch ${1:error} {",
"\t$2",
"}"
],
"description": "do statement"
},
"func": {
"prefix": "func",
"body": [
"func ${1:name}(${2:parameters}) -> ${3:Type} {",
"\t$0",
"}"
],
"description": "Try catch"
"description": "function declaration"
},
"Enum": {
"struct": {
"prefix": "struct",
"body": [
"struct ${1:Name} {",
"",
"\t$0",
"}"
],
"description": "struct declaration"
},
"enum": {
"prefix": "enum",
"body": [
"enum ${1:Name} {",
"",
"\tcase $0",
"}"
],
"description": "Enum"
"description": "enum declaration"
},
"class": {
"prefix": "class",
"body": [
"class ${1:Name} {",
"",
"\t$0",
"}"
],
"description": "class declaration"
},
"protocol": {
"prefix": "protocol",
"body": [
"protocol ${1:Name} {",
"",
"\t$0",
"}"
],
"description": "protocol declaration"
},
"extension": {
"prefix": "extension",
"body": [
"extension ${1:Type} {",
"",
"\t$0",
"}"
],
"description": "extension declaration"
}
}
@@ -48,10 +48,7 @@ class SyncedBuffer {
}
if (this.client.apiVersion.has230Features()) {
const root = this.client.getWorkspaceRootForResource(this.document.uri);
if (root) {
args.projectRootPath = root;
}
args.projectRootPath = this.client.getWorkspaceRootForResource(this.document.uri);
}
if (this.client.apiVersion.has240Features()) {
@@ -578,12 +578,12 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
}
public normalizePath(resource: Uri): string | null {
if (resource.scheme === fileSchemes.walkThroughSnippet) {
return resource.toString();
}
if (resource.scheme === fileSchemes.untitled && this._apiVersion.has213Features()) {
return resource.toString();
if (this._apiVersion.has213Features()) {
if (resource.scheme === fileSchemes.walkThroughSnippet || resource.scheme === fileSchemes.untitled) {
const dirName = path.dirname(resource.path);
const fileName = this.inMemoryResourcePrefix + path.basename(resource.path);
return resource.with({ path: path.join(dirName, fileName) }).toString(true);
}
}
if (resource.scheme !== fileSchemes.file) {
@@ -599,11 +599,24 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
return result.replace(new RegExp('\\' + this.pathSeparator, 'g'), '/');
}
private get inMemoryResourcePrefix(): string {
return this._apiVersion.has270Features() ? '^' : '';
}
public asUrl(filepath: string): Uri {
if (filepath.startsWith(TypeScriptServiceClient.WALK_THROUGH_SNIPPET_SCHEME_COLON)
|| (filepath.startsWith(fileSchemes.untitled + ':') && this._apiVersion.has213Features())
) {
return Uri.parse(filepath);
if (this._apiVersion.has213Features()) {
if (filepath.startsWith(TypeScriptServiceClient.WALK_THROUGH_SNIPPET_SCHEME_COLON) || (filepath.startsWith(fileSchemes.untitled + ':'))
) {
let resource = Uri.parse(filepath);
if (this.inMemoryResourcePrefix) {
const dirName = path.dirname(resource.path);
const fileName = path.basename(resource.path);
if (fileName.startsWith(this.inMemoryResourcePrefix)) {
resource = resource.with({ path: path.join(dirName, fileName.slice(this.inMemoryResourcePrefix.length)) });
}
}
return resource;
}
}
return Uri.file(filepath);
}
@@ -620,8 +633,10 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
return root.uri.fsPath;
}
}
return roots[0].uri.fsPath;
}
return roots[0].uri.fsPath;
return undefined;
}
public execute(command: string, args: any, expectsResultOrToken?: boolean | CancellationToken): Promise<any> {
@@ -10,6 +10,7 @@ import * as vscode from 'vscode';
import { createRandomFile, deleteFile, closeAllEditors, pathEquals } from './utils';
import { join, basename } from 'path';
import * as fs from 'fs';
import { Uri } from 'vscode';
suite('workspace-namespace', () => {
@@ -505,4 +506,44 @@ suite('workspace-namespace', () => {
return vscode.workspace.applyEdit(edit);
});
});
test('applyEdit should fail when editing deleted resource', async () => {
const resource = await createRandomFile();
const edit = new vscode.WorkspaceEdit();
edit.deleteResource(resource);
try {
edit.insert(resource, new vscode.Position(0, 0), '');
assert.fail(false, 'Should disallow edit of deleted resource');
} catch {
// noop
}
});
test('applyEdit should fail when renaming deleted resource', async () => {
const resource = await createRandomFile();
const edit = new vscode.WorkspaceEdit();
edit.deleteResource(resource);
try {
edit.renameResource(resource, resource);
assert.fail(false, 'Should disallow rename of deleted resource');
} catch {
// noop
}
});
test('applyEdit should fail when editing renamed from resource', async () => {
const resource = await createRandomFile();
const newResource = Uri.parse(resource.fsPath + '.1');
const edit = new vscode.WorkspaceEdit();
edit.renameResource(resource, newResource);
try {
edit.insert(resource, new vscode.Position(0, 0), '');
assert.fail(false, 'Should disallow editing renamed file');
} catch {
// noop
}
});
});
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.20.0",
"distro": "e14a5a3afaae557ff651b344462ed39e776435a2",
"distro": "d41bd1b8193403ffe9849b49fc37153e3d5b5a49",
"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-beta7",
"vscode-xterm": "3.1.0-beta10",
"yauzl": "2.8.0"
},
"devDependencies": {
@@ -129,4 +129,4 @@
"windows-mutex": "^0.2.0",
"windows-process-tree": "0.1.6"
}
}
}
+4
View File
@@ -17,6 +17,10 @@ set CODE=".build\electron\%NAMESHORT%"
node build\lib\electron.js
if %errorlevel% neq 0 node .\node_modules\gulp\bin\gulp.js electron
:: Get built-in extensions
node build\lib\builtInExtensions.js
if %errorlevel% neq 0 node .\node_modules\gulp\bin\gulp.js download-builtin-extensions
:: Build
if not exist out node .\node_modules\gulp\bin\gulp.js compile
+3
View File
@@ -24,6 +24,9 @@ function code() {
# Get electron
node build/lib/electron.js || ./node_modules/.bin/gulp electron
# Get built-in extensions
node build/lib/builtInExtensions.js || ./node_modules/.bin/gulp download-builtin-extensions
# Build
test -d out || ./node_modules/.bin/gulp compile
+15
View File
@@ -8,6 +8,11 @@
*/
declare module 'vscode-xterm' {
/**
* A string representing text font weight.
*/
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
/**
* An object containing start up options for the terminal.
*/
@@ -57,6 +62,16 @@ declare module 'vscode-xterm' {
*/
fontFamily?: string;
/**
* The font weight used to render non-bold text.
*/
fontWeight?: FontWeight;
/**
* The font weight used to render bold text.
*/
fontWeightBold?: FontWeight;
/**
* The spacing in whole pixels between characters..
*/
@@ -156,6 +156,7 @@ export class ResourceViewer {
class ImageView {
private static readonly MAX_IMAGE_SIZE = BinarySize.MB; // showing images inline is memory intense, so we have a limit
private static readonly BASE64_MARKER = 'base64,';
public static create(
container: Builder,
@@ -177,9 +178,8 @@ class ImageView {
// Data URI
if (descriptor.resource.scheme === Schemas.data) {
const BASE64_MARKER = 'base64,';
const base64MarkerIndex = descriptor.resource.path.indexOf(BASE64_MARKER);
const hasData = base64MarkerIndex >= 0 && descriptor.resource.path.substring(base64MarkerIndex + BASE64_MARKER.length).length > 0;
const base64MarkerIndex = descriptor.resource.path.indexOf(ImageView.BASE64_MARKER);
const hasData = base64MarkerIndex >= 0 && descriptor.resource.path.substring(base64MarkerIndex + ImageView.BASE64_MARKER.length).length > 0;
skipInlineImage = !hasData || descriptor.size > ImageView.MAX_IMAGE_SIZE || descriptor.resource.path.length > ImageView.MAX_IMAGE_SIZE;
}
@@ -241,7 +241,11 @@ class InlineImageView {
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
/**
* Enable image-rendering: pixelated for images scaled by more than this.
*/
private static readonly PIXELATION_THRESHOLD = 3;
/**
* Chrome is caching images very aggressively and so we use the ETag information to find out if
@@ -279,16 +283,17 @@ class InlineImageView {
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);
if (scale >= InlineImageView.PIXELATION_THRESHOLD) {
img.addClass('pixelated');
} else {
img.removeClass('pixelated');
}
setImageWidth(Math.floor(imgElement.naturalWidth * scale));
InlineImageView.IMAGE_SCALE_CACHE.set(cacheKey, scale);
scrollbar.scanDomNode();
@@ -42,7 +42,6 @@
.monaco-resource-viewer img.untouched {
max-width: 100%;
object-fit: contain;
image-rendering: auto;
}
.monaco-resource-viewer img {
+6 -1
View File
@@ -38,8 +38,13 @@ export function isEqual(first: uri, second: uri, ignoreCase?: boolean): boolean
}
export function dirname(resource: uri): uri {
const dirname = paths.dirname(resource.path);
if (resource.authority && dirname && !paths.isAbsolute(dirname)) {
return null; // If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character
}
return resource.with({
path: paths.dirname(resource.path)
path: dirname
});
}
+12 -1
View File
@@ -6,7 +6,8 @@
import * as assert from 'assert';
import URI from 'vs/base/common/uri';
import { distinctParents } from 'vs/base/common/resources';
import { distinctParents, dirname } from 'vs/base/common/resources';
import { normalize } from 'vs/base/common/paths';
suite('Resources', () => {
@@ -40,4 +41,14 @@ suite('Resources', () => {
assert.equal(distinct[1].toString(), resources[3].toString());
assert.equal(distinct[2].toString(), resources[4].toString());
});
test('dirname', (done) => {
const f = URI.file('/some/file/test.txt');
const d = dirname(f);
assert.equal(d.fsPath, normalize('/some/file', true));
// does not explode (https://github.com/Microsoft/vscode/issues/41987)
URI.from({ scheme: 'file', authority: '/users/someone/portal.h' });
done();
});
});
@@ -1,102 +1,17 @@
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https:;">
</head>
<body>
<div id="issue-reporter">
<div class="input-group">
<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>
<option value="2">Feature Request</option>
</select>
</div>
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https:;">
<style>body{display: none}</style>
</head>
<div class="input-group">
<label for="issue-title">Title <span class="required-input">*</span></label>
<div id="issue-title-validation-error" class="validation-error hidden">Please enter a title.</div>
<input id="issue-title" type="text" required>
<small id="similar-issues">
<!-- To be dynamically filled -->
</small>
</div>
<body>
</body>
<div class="input-group">
<div class="two-col">
<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">
<label for="os">OS Version <span class="required-input">*</span></label>
<input id="os" type="text" value="Loading..." disabled/>
</div>
</div>
<!-- Startup via issueReporter.js -->
<script src="issueReporter.js"></script>
<div id="block-container">
<div class="block block-system">
<details>
<summary>My System Info
<input type="checkbox" id="includeSystemInfo" checked>
<label class="caption" for="includeSystemInfo">Send my data</label>
</input>
</summary>
<div class="block-info">
<!-- To be dynamically filled -->
</div>
</details>
</div>
<div class="block block-process">
<details>
<summary>Currently Running Processes
<input type="checkbox" id="includeProcessInfo" checked>
<label class="caption" for="includeProcessInfo">Send my data</label>
</input>
</summary>
<div class="block-info">
<!-- To be dynamically filled -->
</div>
</details>
</div>
<div class="block block-workspace">
<details>
<summary>My Workspace Stats
<input type="checkbox" id="includeWorkspaceInfo" checked>
<label class="caption" for="includeWorkspaceInfo">Send my data</label>
</input>
</summary>
<pre class="block-info">
<code>
<!-- To be dynamically filled -->
</code>
</pre>
</details>
</div>
<div class="block block-description">
<label class="block-title">
<!-- To be dynamically filled -->
</label>
<small class="block-subtitle">
<!-- To be dynamically filled -->
</small>
<div class="block-info-text">
<small>
We support GitHub-flavored Markdown.
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>
</div>
</div>
</div>
<button id="github-submit-btn" disabled>Loading data...</button>
</div>
</body>
<script src="issueReporter.js"></script>
</html>
</html>
@@ -65,6 +65,25 @@ function main() {
// Load the loader and start loading the workbench
const rootUrl = uriFromPath(configuration.appRoot) + '/out';
// Get the nls configuration into the process.env as early as possible.
var nlsConfig = { availableLanguages: {} };
const config = process.env['VSCODE_NLS_CONFIG'];
if (config) {
process.env['VSCODE_NLS_CONFIG'] = config;
try {
nlsConfig = JSON.parse(config);
} catch (e) { /*noop*/ }
}
var locale = nlsConfig.availableLanguages['*'] || 'en';
if (locale === 'zh-tw') {
locale = 'zh-Hant';
} else if (locale === 'zh-cn') {
locale = 'zh-Hans';
}
window.document.documentElement.setAttribute('lang', locale);
// In the bundled version the nls plugin is packaged with the loader so the NLS Plugins
// loads as soon as the loader loads. To be able to have pseudo translation
createScript(rootUrl + '/vs/loader.js', function () {
@@ -72,7 +91,6 @@ function main() {
window.MonacoEnvironment = {};
var nlsConfig = { availableLanguages: {} };
require.config({
baseUrl: rootUrl,
'vs/nls': nlsConfig,
@@ -80,6 +98,12 @@ function main() {
nodeModules: [/*BUILD->INSERT_NODE_MODULES*/]
});
if (nlsConfig.pseudo) {
require(['vs/nls'], function (nlsPlugin) {
nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
});
}
require(['vs/code/electron-browser/issue/issueReporterMain'], (issueReporter) => {
issueReporter.startup(configuration);
});
@@ -7,6 +7,7 @@
import 'vs/css!./media/issueReporter';
import { shell, ipcRenderer, webFrame, remote } from 'electron';
import { localize } from 'vs/nls';
import { $ } from 'vs/base/browser/dom';
import * as browser from 'vs/base/browser/browser';
import product from 'vs/platform/node/product';
@@ -28,10 +29,17 @@ import { WindowsChannelClient } from 'vs/platform/windows/common/windowsIpc';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IssueReporterModel, IssueReporterData } from 'vs/code/electron-browser/issue/issueReporterModel';
import { IssueReporterStyles } from 'vs/platform/issue/common/issue';
import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage';
export function startup(configuration: IWindowConfiguration) {
document.body.innerHTML = BaseHtml();
const issueReporter = new IssueReporter(configuration);
issueReporter.render();
// workaround for flickering on page load as css is applied
setTimeout(() => {
issueReporter.render();
document.body.style.display = 'block';
}, 10);
}
export class IssueReporter extends Disposable {
@@ -61,7 +69,7 @@ export class IssueReporter extends Disposable {
const submitButton = <HTMLButtonElement>document.getElementById('github-submit-btn');
submitButton.disabled = false;
submitButton.textContent = 'Preview on GitHub';
submitButton.textContent = localize('previewOnGitHub', "Preview on GitHub");
});
ipcRenderer.send('issueInfoRequest');
@@ -69,6 +77,10 @@ export class IssueReporter extends Disposable {
this.initServices(configuration);
this.setEventHandlers();
if (window.document.documentElement.lang !== 'en') {
show(document.getElementById('english'));
}
}
render(): void {
@@ -103,7 +115,8 @@ export class IssueReporter extends Disposable {
}
if (styles.inputErrorBorder) {
content.push(`.invalid-input, .invalid-input:focus { border: 1px solid ${styles.inputErrorBorder}; }`);
content.push(`.invalid-input, .invalid-input:focus { border: 1px solid ${styles.inputErrorBorder} !important; }`);
content.push(`.validation-error { color: ${styles.inputErrorBorder}; }`);
}
if (styles.inputActiveBorder) {
@@ -132,8 +145,6 @@ export class IssueReporter extends Disposable {
styleTag.innerHTML = content.join('\n');
document.head.appendChild(styleTag);
document.body.style.backgroundColor = styles.backgroundColor;
document.body.style.color = styles.color;
}
@@ -215,7 +226,7 @@ export class IssueReporter extends Disposable {
if (result.items.length) {
const issues = $('ul');
const issuesText = $('div.list-title');
issuesText.textContent = 'Similar issues:';
issuesText.textContent = localize('similarIssues', "Similar issues");
addIssuesToList(issues, result.items);
similarIssues.appendChild(issuesText);
similarIssues.appendChild(issues);
@@ -246,8 +257,8 @@ export class IssueReporter extends Disposable {
const processBlock = document.querySelector('.block-process');
const workspaceBlock = document.querySelector('.block-workspace');
const descriptionTitle = document.querySelector('.block-description .block-title');
const descriptionSubtitle = document.querySelector('.block-description .block-subtitle');
const descriptionTitle = document.getElementById('issue-description-label');
const descriptionSubtitle = document.getElementById('issue-description-subtitle');
// 1 - Bug
if (issueType === 0) {
@@ -255,9 +266,9 @@ export class IssueReporter extends Disposable {
hide(processBlock);
hide(workspaceBlock);
descriptionTitle.innerHTML = 'Steps to Reproduce <span class="required-input">*</span>';
descriptionTitle.innerHTML = `${localize('stepsToReproduce', "Steps to Reproduce")} <span class="required-input">*</span>`;
show(descriptionSubtitle);
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?';
descriptionSubtitle.innerHTML = localize('bugDescription', "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) {
@@ -265,9 +276,9 @@ export class IssueReporter extends Disposable {
show(processBlock);
show(workspaceBlock);
descriptionTitle.innerHTML = 'Steps to Reproduce <span class="required-input">*</span>';
descriptionTitle.innerHTML = `${localize('stepsToReproduce', "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.';
descriptionSubtitle.innerHTML = localize('performanceIssueDesciption', "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.");
}
// 3 - Feature Request
else {
@@ -275,7 +286,7 @@ export class IssueReporter extends Disposable {
hide(processBlock);
hide(workspaceBlock);
descriptionTitle.innerHTML = 'Description <span class="required-input">*</span>';
descriptionTitle.innerHTML = `${localize('description', "Description")} <span class="required-input">*</span>`;
hide(descriptionSubtitle);
}
}
@@ -0,0 +1,99 @@
/*---------------------------------------------------------------------------------------------
* 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 { escape } from 'vs/base/common/strings';
import { localize } from 'vs/nls';
export default (): string => `
<div id="issue-reporter">
<div id="english" class="input-group hidden">${escape(localize('completeInEnglish', "Please complete the form in English."))}</div>
<div class="input-group">
<label for="issue-type">${escape(localize('issueTypeLabel', "I want to submit a"))}</label>
<select id="issue-type" class="form-control">
<option value="0">${escape(localize('bugReporter', "Bug Report"))}</option>
<option value="1">${escape(localize('performanceIssue', "Performance Issue"))}</option>
<option value="2">${escape(localize('featureRequest', "Feature Request"))}</option>
</select>
</div>
<div class="input-group">
<label for="issue-title">${escape(localize('issueTitleLabel', "Title"))} <span class="required-input">*</span></label>
<div id="issue-title-validation-error" class="validation-error hidden">${escape(localize('issueTitleRequired', "Please enter a title."))}</div>
<input id="issue-title" type="text" required>
<small id="similar-issues">
<!-- To be dynamically filled -->
</small>
</div>
<div class="input-group">
<div class="two-col">
<label for="vscode-version">${escape(localize('vscodeVersion', "VS Code Version"))} <span class="required-input">*</span></label>
<input id="vscode-version" type="text" value="${escape(localize('loading', "Loading..."))}" disabled/>
</div>
<div class="two-col">
<label for="os">${escape(localize('osVersion', "OS Version"))} <span class="required-input">*</span></label>
<input id="os" type="text" value="${escape(localize('loading', "Loading..."))}" disabled/>
</div>
</div>
<div id="block-container" class="input-group">
<div class="block block-system">
<details>
<summary>${escape(localize('systemInfo', "My System Info"))}
<input type="checkbox" id="includeSystemInfo" checked>
<label class="caption" for="includeSystemInfo">${escape(localize('sendData', "Send my data"))}</label>
</input>
</summary>
<div class="block-info">
<!-- To be dynamically filled -->
</div>
</details>
</div>
<div class="block block-process">
<details>
<summary>${escape(localize('processes', "Currently Running Processes"))}
<input type="checkbox" id="includeProcessInfo" checked>
<label class="caption" for="includeProcessInfo">${escape(localize('sendData', "Send my data"))}</label>
</input>
</summary>
<div class="block-info">
<!-- To be dynamically filled -->
</div>
</details>
</div>
<div class="block block-workspace">
<details>
<summary>${escape(localize('workspaceStats', "My Workspace Stats"))}
<input type="checkbox" id="includeWorkspaceInfo" checked>
<label class="caption" for="includeWorkspaceInfo">${escape(localize('sendData', "Send my data"))}</label>
</input>
</summary>
<pre class="block-info">
<code>
<!-- To be dynamically filled -->
</code>
</pre>
</details>
</div>
</div>
<div class="input-group">
<label for="description" id="issue-description-label">
<!-- To be dynamically filled -->
</label>
<small id="issue-description-subtitle">
<!-- To be dynamically filled -->
</small>
<div class="block-info-text">
<small>${escape(localize('githubMarkdown', "We support GitHub-flavored Markdown. 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">${escape(localize('issueDescriptionRequired', "Please enter a description."))}</div>
<textarea name="description" id="description" cols="100" rows="15" required></textarea>
</div>
</div>
<button id="github-submit-btn" disabled>${escape(localize('loadingData', "Loading data..."))}</button>
</div>`;
@@ -97,7 +97,6 @@ html {
body {
margin: 0;
background-color: #1E1E1E;
}
.hidden {
@@ -105,14 +104,7 @@ body {
}
#block-container {
margin-top: 20px;
}
.block {
margin-bottom: 20px;
}
.block summary {
margin-bottom: 16px;
margin-top: 1em;
}
.block .block-info {
@@ -171,23 +163,29 @@ select, input, textarea {
summary {
border: 1px solid transparent;
padding: 10px;
margin-bottom: 5px;
}
.validation-error {
font-size: 12px;
font-weight: bold;
margin-top: 1em;
}
.caption {
display: inline-block;
font-size: 12px;
vertical-align: middle;
height: 18px;
}
input[type="checkbox"] {
margin-left: 1em;
height: 18px;
width: auto;
display: inline-block;
margin-top: 0;
vertical-align: middle;
}
input:disabled {
@@ -214,7 +212,7 @@ a {
border: 1px solid #be1100;
}
.required-input {
.required-input, .validation-error {
color: #be1100;
}
@@ -38,7 +38,8 @@ import { ipcRenderer } from 'electron';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { createSharedProcessContributions } from 'vs/code/electron-browser/sharedProcess/contrib/contributions';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { ILogService } from 'vs/platform/log/common/log';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
export interface ISharedProcessConfiguration {
readonly machineId: string;
@@ -51,6 +52,7 @@ export function startup(configuration: ISharedProcessConfiguration) {
interface ISharedProcessInitData {
sharedIPCHandle: string;
args: ParsedArgs;
logLevel: LogLevel;
}
class ActiveWindowManager implements IDisposable {
@@ -81,7 +83,8 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
const services = new ServiceCollection();
const environmentService = new EnvironmentService(initData.args, process.execPath);
const logService = createSpdLogService('sharedprocess', environmentService);
const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', { route: () => 'main' }));
const logService = new FollowerLogService(logLevelClient, createSpdLogService('sharedprocess', initData.logLevel, environmentService.logsPath));
process.once('exit', () => logService.dispose());
logService.info('main', JSON.stringify(configuration));
+7 -1
View File
@@ -57,6 +57,7 @@ import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateServ
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';
import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc';
export class CodeApplication {
@@ -272,7 +273,7 @@ export class CodeApplication {
this.logService.trace(`Resolved machine identifier: ${machineId}`);
// Spawn shared process
this.sharedProcess = new SharedProcess(this.environmentService, machineId, this.userEnv);
this.sharedProcess = new SharedProcess(this.environmentService, machineId, this.userEnv, this.logService);
this.toDispose.push(this.sharedProcess);
this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));
@@ -378,6 +379,11 @@ export class CodeApplication {
this.electronIpcServer.registerChannel('windows', windowsChannel);
this.sharedProcessClient.done(client => client.registerChannel('windows', windowsChannel));
// Log level management
const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService));
this.electronIpcServer.registerChannel('loglevel', logLevelChannel);
this.sharedProcessClient.done(client => client.registerChannel('loglevel', logLevelChannel));
// Lifecycle
this.lifecycleService.ready();
+25 -20
View File
@@ -9,7 +9,6 @@ import * as os from 'os';
import * as cp from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as readline from 'readline';
import { localize } from 'vs/nls';
import { ILaunchChannel } from 'vs/code/electron-main/launch';
@@ -17,6 +16,8 @@ import { TPromise } from 'vs/base/common/winjs.base';
import product from 'vs/platform/node/product';
import { IRequestService } from 'vs/platform/request/node/request';
import { IRequestContext } from 'vs/base/node/request';
import { IChoiceService } from 'vs/platform/message/common/message';
import Severity from 'vs/base/common/severity';
interface PostResult {
readonly blob_id: string;
@@ -35,7 +36,8 @@ class Endpoint {
export async function uploadLogs(
channel: ILaunchChannel,
requestService: IRequestService
requestService: IRequestService,
choiceService: IChoiceService
): TPromise<any> {
const endpoint = Endpoint.getFromProduct();
if (!endpoint) {
@@ -45,7 +47,8 @@ export async function uploadLogs(
const logsPath = await channel.call('get-logs-path', null);
if (await promptUserToConfirmLogUpload(logsPath)) {
if (await promptUserToConfirmLogUpload(logsPath, choiceService)) {
console.log(localize('beginUploading', 'Uploading...'));
const outZip = await zipLogs(logsPath);
const result = await postLogs(endpoint, outZip, requestService);
console.log(localize('didUploadLogs', 'Uploaded logs ID: {0}', result.blob_id));
@@ -55,22 +58,18 @@ export async function uploadLogs(
}
async function promptUserToConfirmLogUpload(
logsPath: string
logsPath: string,
choiceService: IChoiceService
): Promise<boolean> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new TPromise<boolean>(resolve =>
rl.question(
localize('logUploadPromptHeader', 'Upload session logs to secure endpoint?')
+ '\n\n' + localize('logUploadPromptBody', 'Please review your log files: \'{0}\'', logsPath)
+ '\n\n' + localize('logUploadPromptKey', 'Enter \'y\' to confirm upload...'),
(answer: string) => {
rl.close();
resolve(answer && answer.trim()[0].toLowerCase() === 'y');
}));
const message = localize('logUploadPromptHeader', 'Upload session logs to secure endpoint?')
+ '\n\n' + localize('logUploadPromptBody', 'Please review your log files here: \'{0}\'', logsPath)
+ '\n\n' + localize('logUploadPromptBodyDetails', 'Logs may contain personal information such as full paths and file contents.')
+ '\n\n';
const choice = await choiceService.choose(Severity.Info, message, [
localize('logUploadPromptKey', 'I have reviewed my logs. Proceed with upload...'),
localize('logUploadPromptCancel', 'Cancel'),
], 1);
return choice === 0;
}
async function postLogs(
@@ -101,8 +100,14 @@ async function postLogs(
result.stream.on('end', () => {
try {
const result = Buffer.concat(parts).toString('utf-8');
res(JSON.parse(result));
const response = Buffer.concat(parts).toString('utf-8');
if (result.res.statusCode === 200) {
res(JSON.parse(response));
} else {
const errorMessage = localize('responseError', 'Error posting logs. Got {0}', result.res.statusCode);
console.log(errorMessage);
reject(new Error(errorMessage));
}
} catch (e) {
console.log(localize('parseError', 'Error parsing response'));
reject(e);
+8 -4
View File
@@ -21,7 +21,7 @@ import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiati
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ILogService, ConsoleLogMainService, MultiplexLogService } from 'vs/platform/log/common/log';
import { ILogService, ConsoleLogMainService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log';
import { StateService } from 'vs/platform/state/node/stateService';
import { IStateService } from 'vs/platform/state/common/state';
import { IBackupMainService } from 'vs/platform/backup/common/backup';
@@ -46,12 +46,14 @@ 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';
import { IChoiceService } from 'vs/platform/message/common/message';
import { ChoiceCliService } from 'vs/platform/message/node/messageCli';
function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
const services = new ServiceCollection();
const environmentService = new EnvironmentService(args, process.execPath);
const consoleLogService = new ConsoleLogMainService(environmentService);
const consoleLogService = new ConsoleLogMainService(getLogLevel(environmentService));
const logService = new MultiplexLogService([consoleLogService, bufferLogService]);
process.once('exit', () => logService.dispose());
@@ -69,6 +71,7 @@ function createServices(args: ParsedArgs, bufferLogService: BufferLogService): I
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IURLService, new SyncDescriptor(URLService, args['open-url'] ? args._urls : []));
services.set(IBackupMainService, new SyncDescriptor(BackupMainService));
services.set(IChoiceService, new SyncDescriptor(ChoiceCliService));
return new InstantiationService(services, true);
}
@@ -106,6 +109,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
const logService = accessor.get(ILogService);
const environmentService = accessor.get(IEnvironmentService);
const requestService = accessor.get(IRequestService);
const choiceService = accessor.get(IChoiceService);
function allowSetForegroundWindow(service: LaunchChannelClient): TPromise<void> {
let promise = TPromise.wrap<void>(void 0);
@@ -199,7 +203,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
// Log uploader
if (environmentService.args['upload-logs']) {
return uploadLogs(channel, requestService)
return uploadLogs(channel, requestService, choiceService)
.then(() => TPromise.wrapError(new ExpectedError()));
}
@@ -320,7 +324,7 @@ function main() {
return instantiationService.invokeFunction(a => createPaths(a.get(IEnvironmentService)))
.then(() => instantiationService.invokeFunction(setupIPC))
.then(mainIpcServer => {
bufferLogService.logger = createSpdLogService('main', environmentService);
bufferLogService.logger = createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath);
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnv).startup();
});
}).done(null, err => instantiationService.invokeFunction(quit, err));
+2
View File
@@ -683,6 +683,7 @@ export class CodeMenu {
}
const commands = this.createMenuItem(nls.localize({ key: 'miCommandPalette', comment: ['&& denotes a mnemonic'] }, "&&Command Palette..."), 'workbench.action.showCommands');
const openView = this.createMenuItem(nls.localize({ key: 'miOpenView', comment: ['&& denotes a mnemonic'] }, "&&Open View..."), 'workbench.action.openView');
const fullscreen = new MenuItem(this.withKeybinding('workbench.action.toggleFullScreen', { label: this.mnemonicLabel(nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen")), click: () => this.windowsMainService.getLastActiveWindow().toggleFullScreen(), enabled: this.windowsMainService.getWindowCount() > 0 }));
const toggleZenMode = this.createMenuItem(nls.localize('miToggleZenMode', "Toggle Zen Mode"), 'workbench.action.toggleZenMode');
@@ -729,6 +730,7 @@ export class CodeMenu {
arrays.coalesce([
commands,
openView,
__separator__(),
explorer,
search,
+5 -2
View File
@@ -12,6 +12,7 @@ import { IProcessEnvironment } from 'vs/base/common/platform';
import { BrowserWindow, ipcMain } from 'electron';
import { ISharedProcess } from 'vs/platform/windows/electron-main/windows';
import { Barrier } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log';
export class SharedProcess implements ISharedProcess {
@@ -23,7 +24,8 @@ export class SharedProcess implements ISharedProcess {
constructor(
private environmentService: IEnvironmentService,
private readonly machineId: string,
private readonly userEnv: IProcessEnvironment
private readonly userEnv: IProcessEnvironment,
private readonly logService: ILogService
) { }
@memoize
@@ -75,7 +77,8 @@ export class SharedProcess implements ISharedProcess {
ipcMain.once('handshake:hello', ({ sender }: { sender: any }) => {
sender.send('handshake:hey there', {
sharedIPCHandle: this.environmentService.sharedIPCHandle,
args: this.environmentService.args
args: this.environmentService.args,
logLevel: this.logService.getLevel()
});
ipcMain.once('handshake:im ready', () => c(null));
+2 -1
View File
@@ -565,6 +565,7 @@ export class CodeWindow implements ICodeWindow {
// Set window ID
windowConfiguration.windowId = this._win.id;
windowConfiguration.logLevel = this.logService.getLevel();
// Set zoomlevel
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
@@ -593,7 +594,7 @@ export class CodeWindow implements ICodeWindow {
const environment = parseArgs(process.argv);
const config = objects.assign(environment, windowConfiguration);
for (let key in config) {
if (!config[key]) {
if (config[key] === void 0 || config[key] === null || config[key] === '') {
delete config[key]; // only send over properties that have a true value
}
}
+2 -2
View File
@@ -36,7 +36,7 @@ import { getBaseLabel } from 'vs/base/common/labels';
import { IStateService } from 'vs/platform/state/common/state';
import { StateService } from 'vs/platform/state/node/stateService';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { ILogService } from 'vs/platform/log/common/log';
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
import { isPromiseCanceledError } from 'vs/base/common/errors';
const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id);
@@ -196,7 +196,7 @@ export function main(argv: ParsedArgs): TPromise<void> {
const services = new ServiceCollection();
const environmentService = new EnvironmentService(argv, process.execPath);
const logService = createSpdLogService('cli', environmentService);
const logService = createSpdLogService('cli', getLogLevel(environmentService), environmentService.logsPath);
process.once('exit', () => logService.dispose());
logService.info('main', argv);
+191 -192
View File
@@ -5,68 +5,48 @@
'use strict';
import * as nls from 'vs/nls';
import { flatten } from 'vs/base/common/arrays';
import { IStringDictionary, forEach, values, groupBy, size } from 'vs/base/common/collections';
import { IDisposable, dispose, IReference } from 'vs/base/common/lifecycle';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { IFileService, IFileChange } from 'vs/platform/files/common/files';
import { IFileService, FileChangeType } from 'vs/platform/files/common/files';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range, IRange } from 'vs/editor/common/core/range';
import { Selection, ISelection } from 'vs/editor/common/core/selection';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { IIdentifiedSingleEditOperation, ITextModel, EndOfLineSequence } from 'vs/editor/common/model';
import { IProgressRunner } from 'vs/platform/progress/common/progress';
import { IProgressRunner, emptyProgressRunner, IProgress } from 'vs/platform/progress/common/progress';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { optional } from 'vs/platform/instantiation/common/instantiation';
import { ResourceTextEdit, ResourceFileEdit, isResourceFileEdit, isResourceTextEdit } from 'vs/editor/common/modes';
import { getPathLabel } from 'vs/base/common/labels';
export interface IResourceEdit {
resource: URI;
range?: IRange;
newText: string;
newEol?: EndOfLineSequence;
}
interface IRecording {
stop(): void;
hasChanged(resource: URI): boolean;
allChanges(): IFileChange[];
}
abstract class IRecording {
class ChangeRecorder {
private _fileService: IFileService;
constructor(fileService?: IFileService) {
this._fileService = fileService;
}
public start(): IRecording {
const changes: IStringDictionary<IFileChange[]> = Object.create(null);
static start(fileService: IFileService): IRecording {
const _changes = new Set<string>();
let stop: IDisposable;
if (this._fileService) {
stop = this._fileService.onFileChanges((event) => {
event.changes.forEach(change => {
const key = String(change.resource);
let array = changes[key];
if (!array) {
changes[key] = array = [];
if (fileService) {
// watch only when there is a fileservice available
stop = fileService.onFileChanges(event => {
for (const change of event.changes) {
if (change.type === FileChangeType.UPDATED) {
_changes.add(change.resource.toString());
}
array.push(change);
});
}
});
}
return {
stop: () => { return stop && stop.dispose(); },
hasChanged: (resource: URI) => !!changes[resource.toString()],
allChanges: () => flatten(values(changes))
stop() { return dispose(stop); },
hasChanged(resource) { return _changes.has(resource.toString()); }
};
}
abstract stop(): void;
abstract hasChanged(resource: URI): boolean;
}
class EditTask implements IDisposable {
@@ -84,26 +64,34 @@ class EditTask implements IDisposable {
this._edits = [];
}
public addEdit(edit: IResourceEdit): void {
if (typeof edit.newEol === 'number') {
// honor eol-change
this._newEol = edit.newEol;
}
if (edit.range || edit.newText) {
// create edit operation
let range: Range;
if (!edit.range) {
range = this._model.getFullModelRange();
} else {
range = Range.lift(edit.range);
}
this._edits.push(EditOperation.replaceMove(range, edit.newText));
dispose() {
if (this._model) {
this._modelReference.dispose();
this._modelReference = null;
}
}
public apply(): void {
addEdit(resourceEdit: ResourceTextEdit): void {
for (const edit of resourceEdit.edits) {
if (typeof edit.eol === 'number') {
// honor eol-change
this._newEol = edit.eol;
}
if (edit.range || edit.text) {
// create edit operation
let range: Range;
if (!edit.range) {
range = this._model.getFullModelRange();
} else {
range = Range.lift(edit.range);
}
this._edits.push(EditOperation.replaceMove(range, edit.text));
}
}
}
apply(): void {
if (this._edits.length > 0) {
this._edits = this._edits.map((value, index) => ({ value, index })).sort((a, b) => {
@@ -160,16 +148,10 @@ class EditTask implements IDisposable {
return [this._endCursorSelection];
}
public getEndCursorSelection(): Selection {
getEndCursorSelection(): Selection {
return this._endCursorSelection;
}
dispose() {
if (this._model) {
this._modelReference.dispose();
this._modelReference = null;
}
}
}
class SourceModelEditTask extends EditTask {
@@ -189,34 +171,42 @@ class SourceModelEditTask extends EditTask {
class BulkEditModel implements IDisposable {
private _textModelResolverService: ITextModelService;
private _numberOfResourcesToModify: number = 0;
private _edits: IStringDictionary<IResourceEdit[]> = Object.create(null);
private _edits = new Map<string, ResourceTextEdit[]>();
private _tasks: EditTask[];
private _sourceModel: URI;
private _sourceSelections: Selection[];
private _sourceModelTask: SourceModelEditTask;
private _progress: IProgress<void>;
constructor(textModelResolverService: ITextModelService, sourceModel: URI, sourceSelections: Selection[], edits: IResourceEdit[], private progress: IProgressRunner = null) {
constructor(
textModelResolverService: ITextModelService,
editor: ICodeEditor,
edits: ResourceTextEdit[],
progress: IProgress<void>
) {
this._textModelResolverService = textModelResolverService;
this._sourceModel = sourceModel;
this._sourceSelections = sourceSelections;
this._sourceModelTask = null;
this._sourceModel = editor ? editor.getModel().uri : undefined;
this._sourceSelections = editor ? editor.getSelections() : undefined;
this._sourceModelTask = undefined;
this._progress = progress;
for (let edit of edits) {
this._addEdit(edit);
}
edits.forEach(this.addEdit, this);
}
private _addEdit(edit: IResourceEdit): void {
let array = this._edits[edit.resource.toString()];
dispose(): void {
this._tasks = dispose(this._tasks);
}
addEdit(edit: ResourceTextEdit): void {
let array = this._edits.get(edit.resource.toString());
if (!array) {
this._edits[edit.resource.toString()] = array = [];
this._numberOfResourcesToModify += 1;
array = [];
this._edits.set(edit.resource.toString(), array);
}
array.push(edit);
}
public prepare(): TPromise<BulkEditModel> {
async prepare(): TPromise<BulkEditModel> {
if (this._tasks) {
throw new Error('illegal state - already prepared');
@@ -225,145 +215,80 @@ class BulkEditModel implements IDisposable {
this._tasks = [];
const promises: TPromise<any>[] = [];
if (this.progress) {
this.progress.total(this._numberOfResourcesToModify * 2);
}
forEach(this._edits, entry => {
const promise = this._textModelResolverService.createModelReference(URI.parse(entry.key)).then(ref => {
this._edits.forEach((value, key) => {
const promise = this._textModelResolverService.createModelReference(URI.parse(key)).then(ref => {
const model = ref.object;
if (!model || !model.textEditorModel) {
throw new Error(`Cannot load file ${entry.key}`);
throw new Error(`Cannot load file ${key}`);
}
const textEditorModel = model.textEditorModel;
let task: EditTask;
if (this._sourceModel && textEditorModel.uri.toString() === this._sourceModel.toString()) {
if (this._sourceModel && model.textEditorModel.uri.toString() === this._sourceModel.toString()) {
this._sourceModelTask = new SourceModelEditTask(ref, this._sourceSelections);
task = this._sourceModelTask;
} else {
task = new EditTask(ref);
}
entry.value.forEach(edit => task.addEdit(edit));
value.forEach(edit => task.addEdit(edit));
this._tasks.push(task);
if (this.progress) {
this.progress.worked(1);
}
this._progress.report(undefined);
});
promises.push(promise);
});
await TPromise.join(promises);
return TPromise.join(promises).then(_ => this);
return this;
}
public apply(): Selection {
this._tasks.forEach(task => this.applyTask(task));
let r: Selection = null;
if (this._sourceModelTask) {
r = this._sourceModelTask.getEndCursorSelection();
apply(): Selection {
for (const task of this._tasks) {
task.apply();
this._progress.report(undefined);
}
return r;
}
private applyTask(task: EditTask): void {
task.apply();
if (this.progress) {
this.progress.worked(1);
}
}
dispose(): void {
this._tasks = dispose(this._tasks);
return this._sourceModelTask
? this._sourceModelTask.getEndCursorSelection()
: undefined;
}
}
export interface BulkEdit {
progress(progress: IProgressRunner): void;
add(edit: IResourceEdit[]): void;
finish(): TPromise<ISelection>;
ariaMessage(): string;
}
export type Edit = ResourceFileEdit | ResourceTextEdit;
export function bulkEdit(textModelResolverService: ITextModelService, editor: ICodeEditor, edits: IResourceEdit[], fileService?: IFileService, progress: IProgressRunner = null): TPromise<any> {
let bulk = createBulkEdit(textModelResolverService, editor, fileService);
bulk.add(edits);
bulk.progress(progress);
return bulk.finish();
}
export class BulkEdit {
export function createBulkEdit(textModelResolverService: ITextModelService, editor?: ICodeEditor, fileService?: IFileService): BulkEdit {
let all: IResourceEdit[] = [];
let recording = new ChangeRecorder(fileService).start();
let progressRunner: IProgressRunner;
function progress(progress: IProgressRunner) {
progressRunner = progress;
static perform(edits: Edit[], textModelService: ITextModelService, fileService: IFileService, editor: ICodeEditor): TPromise<any> {
const edit = new BulkEdit(editor, null, textModelService, fileService);
edit.add(edits);
return edit.perform();
}
function add(edits: IResourceEdit[]): void {
all.push(...edits);
private _edits: Edit[] = [];
private _editor: ICodeEditor;
private _progress: IProgressRunner;
constructor(
editor: ICodeEditor,
progress: IProgressRunner,
@ITextModelService private _textModelService: ITextModelService,
@optional(IFileService) private _fileService: IFileService
) {
this._editor = editor;
this._progress = progress || emptyProgressRunner;
}
function getConcurrentEdits() {
let names: string[];
for (let edit of all) {
if (recording.hasChanged(edit.resource)) {
if (!names) {
names = [];
}
names.push(edit.resource.fsPath);
}
add(edits: Edit[] | Edit): void {
if (Array.isArray(edits)) {
this._edits.push(...edits);
} else {
this._edits.push(edits);
}
if (names) {
return nls.localize('conflict', "These files have changed in the meantime: {0}", names.join(', '));
}
return undefined;
}
function finish(): TPromise<ISelection> {
if (all.length === 0) {
return TPromise.as(undefined);
}
let concurrentEdits = getConcurrentEdits();
if (concurrentEdits) {
return TPromise.wrapError<ISelection>(new Error(concurrentEdits));
}
let uri: URI;
let selections: Selection[];
if (editor && editor.getModel()) {
uri = editor.getModel().uri;
selections = editor.getSelections();
}
const model = new BulkEditModel(textModelResolverService, uri, selections, all, progressRunner);
return model.prepare().then(_ => {
let concurrentEdits = getConcurrentEdits();
if (concurrentEdits) {
throw new Error(concurrentEdits);
}
recording.stop();
const result = model.apply();
model.dispose();
return result;
});
}
function ariaMessage(): string {
let editCount = all.length;
let resourceCount = size(groupBy(all, edit => edit.resource.toString()));
ariaMessage(): string {
const editCount = this._edits.reduce((prev, cur) => isResourceFileEdit(cur) ? prev : prev + cur.edits.length, 0);
const resourceCount = this._edits.length;
if (editCount === 0) {
return nls.localize('summary.0', "Made no edits");
} else if (editCount > 1 && resourceCount > 1) {
@@ -373,10 +298,84 @@ export function createBulkEdit(textModelResolverService: ITextModelService, edit
}
}
return {
progress,
add,
finish,
ariaMessage
};
async perform(): TPromise<Selection> {
let seen = new Set<string>();
let total = 0;
const groups: Edit[][] = [];
let group: Edit[];
for (const edit of this._edits) {
if (!group
|| (isResourceFileEdit(group[0]) && !isResourceFileEdit(edit))
|| (isResourceTextEdit(group[0]) && !isResourceTextEdit(edit))
) {
group = [];
groups.push(group);
}
group.push(edit);
if (isResourceFileEdit(edit)) {
total += 1;
} else if (!seen.has(edit.resource.toString())) {
seen.add(edit.resource.toString());
total += 2;
}
}
// define total work and progress callback
// for child operations
this._progress.total(total);
let progress: IProgress<void> = { report: _ => this._progress.worked(1) };
// do it. return the last selection computed
// by a text change (can be undefined then)
let res: Selection = undefined;
for (const group of groups) {
if (isResourceFileEdit(group[0])) {
await this._performFileEdits(<ResourceFileEdit[]>group, progress);
} else {
res = await this._performTextEdits(<ResourceTextEdit[]>group, progress) || res;
}
}
return res;
}
private async _performFileEdits(edits: ResourceFileEdit[], progress: IProgress<void>) {
for (const edit of edits) {
progress.report(undefined);
if (edit.newUri && edit.oldUri) {
await this._fileService.moveFile(edit.oldUri, edit.newUri, false);
} else if (!edit.newUri && edit.oldUri) {
await this._fileService.del(edit.oldUri, true);
} else if (edit.newUri && !edit.oldUri) {
await this._fileService.createFile(edit.newUri, undefined, { overwrite: false });
}
}
}
private async _performTextEdits(edits: ResourceTextEdit[], progress: IProgress<void>): TPromise<Selection> {
const recording = IRecording.start(this._fileService);
const model = new BulkEditModel(this._textModelService, this._editor, edits, progress);
await model.prepare();
const conflicts = edits
.filter(edit => recording.hasChanged(edit.resource))
.map(edit => getPathLabel(edit.resource));
recording.stop();
if (conflicts.length > 0) {
model.dispose();
throw new Error(nls.localize('conflict', "These files have changed in the meantime: {0}", conflicts.join(', ')));
}
const selection = await model.apply();
model.dispose();
return selection;
}
}
+5 -1
View File
@@ -57,12 +57,16 @@ export function createTextBufferFactory(text: string): model.ITextBufferFactory
return builder.finish();
}
export function createTextBufferFactoryFromStream(stream: IStringStream): TPromise<model.ITextBufferFactory> {
export function createTextBufferFactoryFromStream(stream: IStringStream, filter?: (chunk: string) => string): TPromise<model.ITextBufferFactory> {
return new TPromise<model.ITextBufferFactory>((c, e, p) => {
let done = false;
let builder = createTextBufferBuilder();
stream.on('data', (chunk) => {
if (filter) {
chunk = filter(chunk);
}
builder.acceptChunk(chunk);
});
+27 -5
View File
@@ -17,6 +17,7 @@ import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationReg
import { Color } from 'vs/base/common/color';
import { IMarkerData } from 'vs/platform/markers/common/markers';
import * as model from 'vs/editor/common/model';
import { isObject } from 'vs/base/common/types';
/**
* Open ended enum at runtime
@@ -825,15 +826,36 @@ export interface DocumentColorProvider {
provideColorPresentations(model: model.ITextModel, colorInfo: IColorInformation, token: CancellationToken): IColorPresentation[] | Thenable<IColorPresentation[]>;
}
export interface IResourceEdit {
/**
* @internal
*/
export function isResourceFileEdit(thing: any): thing is ResourceFileEdit {
return isObject(thing) && (Boolean((<ResourceFileEdit>thing).newUri) || Boolean((<ResourceFileEdit>thing).oldUri));
}
/**
* @internal
*/
export function isResourceTextEdit(thing: any): thing is ResourceTextEdit {
return isObject(thing) && (<ResourceTextEdit>thing).resource && Array.isArray((<ResourceTextEdit>thing).edits);
}
export interface ResourceFileEdit {
oldUri: URI;
newUri: URI;
}
export interface ResourceTextEdit {
resource: URI;
range: IRange;
newText: string;
modelVersionId?: number;
edits: TextEdit[];
}
export interface WorkspaceEdit {
edits: IResourceEdit[];
rejectReason?: string;
edits: Array<ResourceTextEdit | ResourceFileEdit>;
rejectReason?: string; // TODO@joh, move to rename
}
export interface RenameProvider {
provideRenameEdits(model: model.ITextModel, position: Position, newName: string, token: CancellationToken): WorkspaceEdit | Thenable<WorkspaceEdit>;
}
@@ -16,11 +16,14 @@ import { RunOnceScheduler } from 'vs/base/common/async';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { registerThemingParticipant, themeColorFromId } from 'vs/platform/theme/common/themeService';
import { editorBracketMatchBackground, editorBracketMatchBorder } from 'vs/editor/common/view/editorColorRegistry';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { TrackedRangeStickiness, IModelDeltaDecoration } from 'vs/editor/common/model';
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
import { TrackedRangeStickiness, IModelDeltaDecoration, OverviewRulerLane } from 'vs/editor/common/model';
const overviewRulerBracketMatchForeground = registerColor('editorOverviewRuler.bracketMatchForeground', { dark: '#A0A0A0', light: '#A0A0A0', hc: '#A0A0A0' }, nls.localize('overviewRulerBracketMatchForeground', 'Overview ruler marker color for matching brackets.'));
class JumpToBracketAction extends EditorAction {
constructor() {
@@ -214,7 +217,12 @@ export class BracketMatchingController extends Disposable implements editorCommo
private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
className: 'bracket-match'
className: 'bracket-match',
overviewRuler: {
color: themeColorFromId(overviewRulerBracketMatchForeground),
darkColor: themeColorFromId(overviewRulerBracketMatchForeground),
position: OverviewRulerLane.Center
}
});
private _updateBrackets(): void {
+1 -1
View File
@@ -396,7 +396,7 @@ export class FindModelBoundToEditorModel {
const findScope = this._decorations.getFindScope();
if (findScope === null && this._state.matchesCount >= MATCHES_LIMIT) {
// Doing a replace on the entire file that is over 1k matches
// Doing a replace on the entire file that is over ${MATCHES_LIMIT} matches
this._largeReplaceAll();
} else {
this._regularReplaceAll(findScope);
@@ -23,7 +23,7 @@ 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';
import { BulkEdit } from 'vs/editor/browser/services/bulkEdit';
import { IFileService } from 'vs/platform/files/common/files';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
@@ -129,9 +129,7 @@ export class QuickFixController implements IEditorContribution {
private async _onApplyCodeAction(action: CodeAction): TPromise<void> {
if (action.edit) {
const edit = createBulkEdit(this._textModelService, this._editor, this._fileService);
edit.add(action.edit.edits);
await edit.finish();
await BulkEdit.perform(action.edit.edits, this._textModelService, this._fileService, this._editor);
}
if (action.command) {
@@ -8,7 +8,7 @@ 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, CodeAction, CodeActionContext } from 'vs/editor/common/modes';
import { CodeActionProviderRegistry, LanguageIdentifier, CodeActionProvider, Command, WorkspaceEdit, ResourceTextEdit, CodeAction, CodeActionContext } from 'vs/editor/common/modes';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Range } from 'vs/editor/common/core/range';
import { getCodeActions } from 'vs/editor/contrib/quickFix/quickFix';
@@ -58,7 +58,7 @@ suite('QuickFix', () => {
bcd: {
diagnostics: [],
edit: new class implements WorkspaceEdit {
edits: IResourceEdit[];
edits: ResourceTextEdit[];
},
title: 'abc'
}
+3 -5
View File
@@ -18,7 +18,7 @@ import { registerEditorAction, registerEditorContribution, ServicesAccessor, Edi
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { createBulkEdit } from 'vs/editor/browser/services/bulkEdit';
import { BulkEdit } from 'vs/editor/browser/services/bulkEdit';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import RenameInputField from './renameInputField';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
@@ -147,9 +147,7 @@ class RenameController implements IEditorContribution {
this._renameInputVisible.reset();
this.editor.focus();
// start recording of file changes so that we can figure out if a file that
// is to be renamed conflicts with another (concurrent) modification
const edit = createBulkEdit(this._textModelResolverService, <ICodeEditor>this.editor, this._fileService);
const edit = new BulkEdit(this.editor, null, this._textModelResolverService, this._fileService);
const state = new EditorState(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection | CodeEditorStateFlag.Scroll);
const renameOperation = rename(this.editor.getModel(), this.editor.getPosition(), newName).then(result => {
@@ -163,7 +161,7 @@ class RenameController implements IEditorContribution {
}
edit.add(result.edits);
return edit.finish().then(selection => {
return edit.perform().then(selection => {
if (selection) {
this.editor.setSelection(selection);
}
@@ -42,38 +42,66 @@ for (let fileSize of fileSizes) {
iterations: 10
});
for (let i of [10, 100, 1000]) {
editsSuite.add({
name: `apply ${i} edits`,
buildBuffer: (textBufferBuilder: ITextBufferBuilder) => {
chunks.forEach(ck => textBufferBuilder.acceptChunk(ck));
return textBufferBuilder.finish();
},
preCycle: (textBuffer) => {
return textBuffer;
},
fn: (textBuffer) => {
// for line model, this loop doesn't reflect the real situation.
for (let k = 0; k < edits.length && k < i; k++) {
textBuffer.applyEdits([edits[k]], false);
}
editsSuite.add({
name: `apply 1000 edits`,
buildBuffer: (textBufferBuilder: ITextBufferBuilder) => {
chunks.forEach(ck => textBufferBuilder.acceptChunk(ck));
return textBufferBuilder.finish();
},
preCycle: (textBuffer) => {
return textBuffer;
},
fn: (textBuffer) => {
// for line model, this loop doesn't reflect the real situation.
for (let k = 0; k < edits.length; k++) {
textBuffer.applyEdits([edits[k]], false);
}
});
}
});
editsSuite.add({
name: `Read all lines after ${i} edits`,
buildBuffer: (textBufferBuilder: ITextBufferBuilder) => {
chunks.forEach(ck => textBufferBuilder.acceptChunk(ck));
return textBufferBuilder.finish();
},
preCycle: (textBuffer) => {
for (let k = 0; k < edits.length && k < i; k++) {
textBuffer.applyEdits([edits[k]], false);
}
return textBuffer;
},
fn: (textBuffer) => {
for (let j = 0, len = textBuffer.getLineCount(); j < len; j++) {
editsSuite.add({
name: `Read all lines after 1000 edits`,
buildBuffer: (textBufferBuilder: ITextBufferBuilder) => {
chunks.forEach(ck => textBufferBuilder.acceptChunk(ck));
return textBufferBuilder.finish();
},
preCycle: (textBuffer) => {
for (let k = 0; k < edits.length; k++) {
textBuffer.applyEdits([edits[k]], false);
}
return textBuffer;
},
fn: (textBuffer) => {
for (let j = 0, len = textBuffer.getLineCount(); j < len; j++) {
var str = textBuffer.getLineContent(j + 1);
let firstChar = str.charCodeAt(0);
let lastChar = str.charCodeAt(str.length - 1);
firstChar = firstChar - lastChar;
lastChar = firstChar + lastChar;
firstChar = lastChar - firstChar;
}
}
});
editsSuite.add({
name: `Read 10 random windows after 1000 edits`,
buildBuffer: (textBufferBuilder: ITextBufferBuilder) => {
chunks.forEach(ck => textBufferBuilder.acceptChunk(ck));
return textBufferBuilder.finish();
},
preCycle: (textBuffer) => {
for (let k = 0; k < edits.length; k++) {
textBuffer.applyEdits([edits[k]], false);
}
return textBuffer;
},
fn: (textBuffer) => {
for (let i = 0; i < 10; i++) {
let minLine = 1;
let maxLine = textBuffer.getLineCount();
let startLine = getRandomInt(minLine, Math.max(minLine, maxLine - 100));
let endLine = Math.min(maxLine, startLine + 100);
for (let j = startLine; j < endLine; j++) {
var str = textBuffer.getLineContent(j + 1);
let firstChar = str.charCodeAt(0);
let lastChar = str.charCodeAt(str.length - 1);
@@ -82,57 +110,27 @@ for (let fileSize of fileSizes) {
firstChar = lastChar - firstChar;
}
}
});
}
});
editsSuite.add({
name: `Read 10 random windows after ${i} edits`,
buildBuffer: (textBufferBuilder: ITextBufferBuilder) => {
chunks.forEach(ck => textBufferBuilder.acceptChunk(ck));
return textBufferBuilder.finish();
},
preCycle: (textBuffer) => {
for (let k = 0; k < edits.length && k < i; k++) {
textBuffer.applyEdits([edits[k]], false);
}
return textBuffer;
},
fn: (textBuffer) => {
for (let i = 0; i < 10; i++) {
let minLine = 1;
let maxLine = textBuffer.getLineCount();
let startLine = getRandomInt(minLine, Math.max(minLine, maxLine - 100));
let endLine = Math.min(maxLine, startLine + 100);
for (let j = startLine; j < endLine; j++) {
var str = textBuffer.getLineContent(j + 1);
let firstChar = str.charCodeAt(0);
let lastChar = str.charCodeAt(str.length - 1);
firstChar = firstChar - lastChar;
lastChar = firstChar + lastChar;
firstChar = lastChar - firstChar;
}
}
editsSuite.add({
name: `save file after 1000 edits`,
buildBuffer: (textBufferBuilder: ITextBufferBuilder) => {
chunks.forEach(ck => textBufferBuilder.acceptChunk(ck));
return textBufferBuilder.finish();
},
preCycle: (textBuffer) => {
for (let k = 0; k < edits.length; k++) {
textBuffer.applyEdits([edits[k]], false);
}
});
editsSuite.add({
name: `save file after ${i} edits`,
buildBuffer: (textBufferBuilder: ITextBufferBuilder) => {
chunks.forEach(ck => textBufferBuilder.acceptChunk(ck));
return textBufferBuilder.finish();
},
preCycle: (textBuffer) => {
for (let k = 0; k < edits.length && k < i; k++) {
textBuffer.applyEdits([edits[k]], false);
}
return textBuffer;
},
fn: (textBuffer) => {
const lineCount = textBuffer.getLineCount();
const fullModelRange = new Range(1, 1, lineCount, textBuffer.getLineLength(lineCount) + 1);
textBuffer.getValueInRange(fullModelRange, EndOfLinePreference.LF);
}
});
}
return textBuffer;
},
fn: (textBuffer) => {
const lineCount = textBuffer.getLineCount();
const fullModelRange = new Range(1, 1, lineCount, textBuffer.getLineLength(lineCount) + 1);
textBuffer.getValueInRange(fullModelRange, EndOfLinePreference.LF);
}
});
editsSuite.run();
}
+9 -4
View File
@@ -4913,14 +4913,19 @@ declare module monaco.languages {
provideColorPresentations(model: editor.ITextModel, colorInfo: IColorInformation, token: CancellationToken): IColorPresentation[] | Thenable<IColorPresentation[]>;
}
export interface IResourceEdit {
export interface ResourceFileEdit {
oldUri: Uri;
newUri: Uri;
}
export interface ResourceTextEdit {
resource: Uri;
range: IRange;
newText: string;
modelVersionId?: number;
edits: TextEdit[];
}
export interface WorkspaceEdit {
edits: IResourceEdit[];
edits: Array<ResourceTextEdit | ResourceFileEdit>;
rejectReason?: string;
}
@@ -34,7 +34,7 @@ suite('BackupMainService', () => {
class TestBackupMainService extends BackupMainService {
constructor(backupHome: string, backupWorkspacesPath: string, configService: TestConfigurationService) {
super(environmentService, configService, new ConsoleLogMainService(environmentService));
super(environmentService, configService, new ConsoleLogMainService());
this.backupHome = backupHome;
this.workspacesJsonPath = backupWorkspacesPath;
@@ -143,7 +143,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
}
public notifyConfigurationSchemaUpdated(configuration: IConfigurationNode) {
contributionRegistry.registerSchema(editorConfigurationSchemaId, this.editorConfigurationSchema);
contributionRegistry.notifySchemaChanged(editorConfigurationSchemaId);
}
public registerOverrideIdentifiers(overrideIdentifiers: string[]): void {
@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { LogLevel } from 'vs/platform/log/common/log';
export interface ParsedArgs {
[arg: string]: any;
@@ -117,7 +116,6 @@ export interface IEnvironmentService {
// logging
logsPath: string;
verbose: boolean;
logLevel: LogLevel;
skipGettingStarted: boolean | undefined;
skipReleaseNotes: boolean | undefined;
@@ -12,7 +12,6 @@ import URI from 'vs/base/common/uri';
import { memoize } from 'vs/base/common/decorators';
import pkg from 'vs/platform/node/package';
import product from 'vs/platform/node/product';
import { LogLevel } from 'vs/platform/log/common/log';
import { toLocalISOString } from 'vs/base/common/date';
import { isWindows, isLinux } from 'vs/base/common/platform';
@@ -152,33 +151,6 @@ export class EnvironmentService implements IEnvironmentService {
get isBuilt(): boolean { return !process.env['VSCODE_DEV']; }
get verbose(): boolean { return this._args.verbose; }
@memoize
get logLevel(): LogLevel {
if (this.verbose) {
return LogLevel.Trace;
}
if (typeof this._args.log === 'string') {
const logLevel = this._args.log.toLowerCase();
switch (logLevel) {
case 'trace':
return LogLevel.Trace;
case 'debug':
return LogLevel.Debug;
case 'info':
return LogLevel.Info;
case 'warn':
return LogLevel.Warning;
case 'error':
return LogLevel.Error;
case 'critical':
return LogLevel.Critical;
case 'off':
return LogLevel.Off;
}
}
return LogLevel.Info;
}
get wait(): boolean { return this._args.wait; }
get logExtensionHostCommunication(): boolean { return this._args.logExtensionHostCommunication; }
@@ -6,12 +6,15 @@
'use strict';
import { TPromise, Promise } from 'vs/base/common/winjs.base';
import { localize } from 'vs/nls';
import { IIssueService, IssueReporterStyles } from 'vs/platform/issue/common/issue';
import { BrowserWindow, ipcMain } from 'electron';
import { ILaunchService } from 'vs/code/electron-main/launch';
import { buildDiagnostics, DiagnosticInfo } from 'vs/code/electron-main/diagnostics';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
const DEFAULT_BACKGROUND_COLOR = '#1E1E1E';
export class IssueService implements IIssueService {
_serviceBrand: any;
_issueWindow: BrowserWindow;
@@ -39,8 +42,9 @@ export class IssueService implements IIssueService {
this._issueWindow = new BrowserWindow({
width: 800,
height: 900,
title: 'Issue Reporter',
alwaysOnTop: true
title: localize('issueReporter', "Issue Reporter"),
parent: BrowserWindow.getFocusedWindow(),
backgroundColor: theme && theme.backgroundColor || DEFAULT_BACKGROUND_COLOR
});
this._issueWindow.setMenuBarVisibility(false); // workaround for now, until a menu is implemented
@@ -25,6 +25,13 @@ export interface IJSONContributionRegistry {
*/
registerSchema(uri: string, unresolvedSchemaContent: IJSONSchema): void;
/**
* Notifies all listeneres that the content of the given schema has changed.
* @param uri The id of the schema
*/
notifySchemaChanged(uri: string): void;
/**
* Get all schemas
*/
@@ -60,6 +67,10 @@ class JSONContributionRegistry implements IJSONContributionRegistry {
this._onDidChangeSchema.fire(uri);
}
public notifySchemaChanged(uri: string): void {
this._onDidChangeSchema.fire(uri);
}
public getSchemaContributions(): ISchemaContributions {
return {
schemas: this.schemasById,
+3 -16
View File
@@ -5,7 +5,7 @@
'use strict';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { ILogService, LogLevel, AbstractLogService } from 'vs/platform/log/common/log';
interface ILog {
level: LogLevel;
@@ -24,17 +24,12 @@ function getLogFunction(logger: ILogService, level: LogLevel): Function {
}
}
export class BufferLogService implements ILogService {
export class BufferLogService extends AbstractLogService implements ILogService {
_serviceBrand: any;
private buffer: ILog[] = [];
private _logger: ILogService | undefined = undefined;
constructor(
private level: LogLevel = LogLevel.Error
) {
}
set logger(logger: ILogService) {
this._logger = logger;
@@ -46,19 +41,11 @@ export class BufferLogService implements ILogService {
this.buffer = [];
}
setLevel(logLevel: LogLevel): void {
this.level = logLevel;
}
getLevel(): LogLevel {
return this.level;
}
private _log(level: LogLevel, args: IArguments): void {
if (this._logger) {
const fn = getLogFunction(this._logger, level);
fn.apply(this._logger, args);
} else if (this.level <= level) {
} else if (this.getLevel() <= level) {
this.buffer.push({ level, args });
}
}
+121 -45
View File
@@ -5,10 +5,11 @@
'use strict';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { createDecorator as createServiceDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { isWindows } from 'vs/base/common/platform';
import Event, { Emitter } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
export const ILogService = createServiceDecorator<ILogService>('logService');
@@ -24,9 +25,10 @@ export enum LogLevel {
export interface ILogService extends IDisposable {
_serviceBrand: any;
onDidChangeLogLevel: Event<LogLevel>;
setLevel(level: LogLevel): void;
getLevel(): LogLevel;
setLevel(level: LogLevel): void;
trace(message: string, ...args: any[]): void;
debug(message: string, ...args: any[]): void;
info(message: string, ...args: any[]): void;
@@ -35,27 +37,37 @@ export interface ILogService extends IDisposable {
critical(message: string | Error, ...args: any[]): void;
}
export class ConsoleLogMainService implements ILogService {
export abstract class AbstractLogService extends Disposable {
_serviceBrand: any;
private level: LogLevel = LogLevel.Error;
private useColors: boolean;
constructor( @IEnvironmentService environmentService: IEnvironmentService) {
this.setLevel(environmentService.logLevel);
this.useColors = !isWindows;
}
private readonly _onDidChangeLogLevel: Emitter<LogLevel> = this._register(new Emitter<LogLevel>());
readonly onDidChangeLogLevel: Event<LogLevel> = this._onDidChangeLogLevel.event;
setLevel(level: LogLevel): void {
this.level = level;
if (this.level !== level) {
this.level = level;
this._onDidChangeLogLevel.fire(this.level);
}
}
getLevel(): LogLevel {
return this.level;
}
}
export class ConsoleLogMainService extends AbstractLogService implements ILogService {
_serviceBrand: any;
private useColors: boolean;
constructor(logLevel: LogLevel = LogLevel.Error) {
super();
this.setLevel(logLevel);
this.useColors = !isWindows;
}
trace(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Trace) {
if (this.getLevel() <= LogLevel.Trace) {
if (this.useColors) {
console.log(`\x1b[90m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
} else {
@@ -65,7 +77,7 @@ export class ConsoleLogMainService implements ILogService {
}
debug(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Debug) {
if (this.getLevel() <= LogLevel.Debug) {
if (this.useColors) {
console.log(`\x1b[90m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
} else {
@@ -75,7 +87,7 @@ export class ConsoleLogMainService implements ILogService {
}
info(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Info) {
if (this.getLevel() <= LogLevel.Info) {
if (this.useColors) {
console.log(`\x1b[90m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
} else {
@@ -85,7 +97,7 @@ export class ConsoleLogMainService implements ILogService {
}
warn(message: string | Error, ...args: any[]): void {
if (this.level <= LogLevel.Warning) {
if (this.getLevel() <= LogLevel.Warning) {
if (this.useColors) {
console.warn(`\x1b[93m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
} else {
@@ -95,7 +107,7 @@ export class ConsoleLogMainService implements ILogService {
}
error(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Error) {
if (this.getLevel() <= LogLevel.Error) {
if (this.useColors) {
console.error(`\x1b[91m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
} else {
@@ -105,7 +117,7 @@ export class ConsoleLogMainService implements ILogService {
}
critical(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Critical) {
if (this.getLevel() <= LogLevel.Critical) {
if (this.useColors) {
console.error(`\x1b[90m[main ${new Date().toLocaleTimeString()}]\x1b[0m`, message, ...args);
} else {
@@ -119,55 +131,47 @@ export class ConsoleLogMainService implements ILogService {
}
}
export class ConsoleLogService implements ILogService {
export class ConsoleLogService extends AbstractLogService implements ILogService {
_serviceBrand: any;
private level: LogLevel = LogLevel.Error;
constructor( @IEnvironmentService environmentService: IEnvironmentService) {
this.setLevel(environmentService.logLevel);
}
setLevel(level: LogLevel): void {
this.level = level;
}
getLevel(): LogLevel {
return this.level;
constructor(logLevel: LogLevel = LogLevel.Error) {
super();
this.setLevel(logLevel);
}
trace(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Trace) {
if (this.getLevel() <= LogLevel.Trace) {
console.log('%cTRACE', 'color: #888', message, ...args);
}
}
debug(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Debug) {
if (this.getLevel() <= LogLevel.Debug) {
console.log('%cDEBUG', 'background: #eee; color: #888', message, ...args);
}
}
info(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Info) {
if (this.getLevel() <= LogLevel.Info) {
console.log('%c INFO', 'color: #33f', message, ...args);
}
}
warn(message: string | Error, ...args: any[]): void {
if (this.level <= LogLevel.Warning) {
if (this.getLevel() <= LogLevel.Warning) {
console.log('%c WARN', 'color: #993', message, ...args);
}
}
error(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Error) {
if (this.getLevel() <= LogLevel.Error) {
console.log('%c ERR', 'color: #f33', message, ...args);
}
}
critical(message: string, ...args: any[]): void {
if (this.level <= LogLevel.Critical) {
if (this.getLevel() <= LogLevel.Critical) {
console.log('%cCRITI', 'background: #f33; color: white', message, ...args);
}
}
@@ -175,22 +179,21 @@ export class ConsoleLogService implements ILogService {
dispose(): void { }
}
export class MultiplexLogService implements ILogService {
export class MultiplexLogService extends AbstractLogService implements ILogService {
_serviceBrand: any;
constructor(private logServices: ILogService[]) { }
constructor(private logServices: ILogService[]) {
super();
if (logServices.length) {
this.setLevel(logServices[0].getLevel());
}
}
setLevel(level: LogLevel): void {
for (const logService of this.logServices) {
logService.setLevel(level);
}
}
getLevel(): LogLevel {
for (const logService of this.logServices) {
return logService.getLevel();
}
return LogLevel.Info;
super.setLevel(level);
}
trace(message: string, ...args: any[]): void {
@@ -236,8 +239,54 @@ export class MultiplexLogService implements ILogService {
}
}
export class DelegatedLogService extends Disposable implements ILogService {
_serviceBrand: any;
constructor(private logService: ILogService) {
super();
this._register(logService);
}
get onDidChangeLogLevel(): Event<LogLevel> {
return this.logService.onDidChangeLogLevel;
}
setLevel(level: LogLevel): void {
this.logService.setLevel(level);
}
getLevel(): LogLevel {
return this.logService.getLevel();
}
trace(message: string, ...args: any[]): void {
this.logService.trace(message, ...args);
}
debug(message: string, ...args: any[]): void {
this.logService.debug(message, ...args);
}
info(message: string, ...args: any[]): void {
this.logService.info(message, ...args);
}
warn(message: string, ...args: any[]): void {
this.logService.warn(message, ...args);
}
error(message: string | Error, ...args: any[]): void {
this.logService.error(message, ...args);
}
critical(message: string | Error, ...args: any[]): void {
this.logService.critical(message, ...args);
}
}
export class NullLogService implements ILogService {
_serviceBrand: any;
readonly onDidChangeLogLevel: Event<LogLevel> = new Emitter<LogLevel>().event;
setLevel(level: LogLevel): void { }
getLevel(): LogLevel { return LogLevel.Info; }
trace(message: string, ...args: any[]): void { }
@@ -248,3 +297,30 @@ export class NullLogService implements ILogService {
critical(message: string | Error, ...args: any[]): void { }
dispose(): void { }
}
export function getLogLevel(environmentService: IEnvironmentService): LogLevel {
if (environmentService.verbose) {
return LogLevel.Trace;
}
if (typeof environmentService.args.log === 'string') {
const logLevel = environmentService.args.log.toLowerCase();
switch (logLevel) {
case 'trace':
return LogLevel.Trace;
case 'debug':
return LogLevel.Debug;
case 'info':
return LogLevel.Info;
case 'warn':
return LogLevel.Warning;
case 'error':
return LogLevel.Error;
case 'critical':
return LogLevel.Critical;
case 'off':
return LogLevel.Off;
}
}
return LogLevel.Info;
}
+56
View File
@@ -0,0 +1,56 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
import { TPromise } from 'vs/base/common/winjs.base';
import { LogLevel, ILogService, DelegatedLogService } from 'vs/platform/log/common/log';
import Event, { buffer } from 'vs/base/common/event';
export interface ILogLevelSetterChannel extends IChannel {
call(command: 'event:onDidChangeLogLevel'): TPromise<LogLevel>;
call(command: 'setLevel', logLevel: LogLevel): TPromise<void>;
}
export class LogLevelSetterChannel implements ILogLevelSetterChannel {
onDidChangeLogLevel: Event<LogLevel>;
constructor(private service: ILogService) {
this.onDidChangeLogLevel = buffer(service.onDidChangeLogLevel, true);
}
call(command: string, arg?: any): TPromise<any> {
switch (command) {
case 'event:onDidChangeLogLevel': return eventToCall(this.onDidChangeLogLevel);
case 'setLevel': this.service.setLevel(arg); return TPromise.as(null);
}
return undefined;
}
}
export class LogLevelSetterChannelClient {
constructor(private channel: ILogLevelSetterChannel) { }
private _onDidChangeLogLevel = eventFromCall<LogLevel>(this.channel, 'event:onDidChangeLogLevel');
get onDidChangeLogLevel(): Event<LogLevel> { return this._onDidChangeLogLevel; }
setLevel(level: LogLevel): TPromise<void> {
return this.channel.call('setLevel', level);
}
}
export class FollowerLogService extends DelegatedLogService implements ILogService {
_serviceBrand: any;
constructor(private master: LogLevelSetterChannelClient, logService: ILogService) {
super(logService);
this._register(master.onDidChangeLogLevel(level => logService.setLevel(level)));
}
setLevel(level: LogLevel): void {
this.master.setLevel(level);
}
}
+14 -21
View File
@@ -6,69 +6,62 @@
'use strict';
import * as path from 'path';
import { ILogService, LogLevel, NullLogService } from 'vs/platform/log/common/log';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ILogService, LogLevel, NullLogService, AbstractLogService } from 'vs/platform/log/common/log';
import { RotatingLogger, setAsyncMode } from 'spdlog';
export function createSpdLogService(processName: string, environmentService: IEnvironmentService, logsSubfolder?: string): ILogService {
export function createSpdLogService(processName: string, logLevel: LogLevel, logsFolder: string, logsSubfolder?: string): ILogService {
try {
setAsyncMode(8192, 2000);
const logsDirPath = logsSubfolder ? path.join(environmentService.logsPath, logsSubfolder) : environmentService.logsPath;
const logsDirPath = logsSubfolder ? path.join(logsFolder, logsSubfolder) : logsFolder;
const logfilePath = path.join(logsDirPath, `${processName}.log`);
const logger = new RotatingLogger(processName, logfilePath, 1024 * 1024 * 5, 6);
logger.setLevel(0);
return new SpdLogService(logger, environmentService.logLevel);
return new SpdLogService(logger, logLevel);
} catch (e) {
console.error(e);
}
return new NullLogService();
}
class SpdLogService implements ILogService {
class SpdLogService extends AbstractLogService implements ILogService {
_serviceBrand: any;
constructor(
private readonly logger: RotatingLogger,
private level: LogLevel = LogLevel.Error
level: LogLevel = LogLevel.Error
) {
}
setLevel(logLevel: LogLevel): void {
this.level = logLevel;
}
getLevel(): LogLevel {
return this.level;
super();
this.setLevel(level);
}
trace(): void {
if (this.level <= LogLevel.Trace) {
if (this.getLevel() <= LogLevel.Trace) {
this.logger.trace(this.format(arguments));
}
}
debug(): void {
if (this.level <= LogLevel.Debug) {
if (this.getLevel() <= LogLevel.Debug) {
this.logger.debug(this.format(arguments));
}
}
info(): void {
if (this.level <= LogLevel.Info) {
if (this.getLevel() <= LogLevel.Info) {
this.logger.info(this.format(arguments));
}
}
warn(): void {
if (this.level <= LogLevel.Warning) {
if (this.getLevel() <= LogLevel.Warning) {
this.logger.warn(this.format(arguments));
}
}
error(): void {
if (this.level <= LogLevel.Error) {
if (this.getLevel() <= LogLevel.Error) {
const arg = arguments[0];
if (arg instanceof Error) {
@@ -82,7 +75,7 @@ class SpdLogService implements ILogService {
}
critical(): void {
if (this.level <= LogLevel.Critical) {
if (this.getLevel() <= LogLevel.Critical) {
this.logger.critical(this.format(arguments));
}
}
@@ -31,6 +31,12 @@ export interface IProgressRunner {
done(): void;
}
export const emptyProgressRunner: IProgressRunner = Object.freeze({
total() { },
worked() { },
done() { }
});
export interface IProgress<T> {
report(item: T): void;
}
@@ -19,8 +19,10 @@ export function resolveCommonProperties(commit: string, version: string, machine
result['commitHash'] = commit;
// __GDPR__COMMON__ "version" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['version'] = version;
// __GDPR__COMMON__ "common.platformVersion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.platformVersion'] = (os.release() || '').replace(/^(\d+)(\.\d+)?(\.\d+)?(.*)/, '$1$2$3');
// __GDPR__COMMON__ "common.osVersion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.osVersion'] = os.release();
result['common.osVersion'] = result['common.platformVersion']; // TODO: Drop this after the move to Nova
// __GDPR__COMMON__ "common.platform" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.platform'] = Platform.Platform[Platform.platform];
// __GDPR__COMMON__ "common.nodePlatform" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
@@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as os from 'os';
import { TPromise } from 'vs/base/common/winjs.base';
import * as uuid from 'vs/base/common/uuid';
import { IStorageService } from 'vs/platform/storage/common/storage';
@@ -15,8 +14,6 @@ export function resolveWorkbenchCommonProperties(storageService: IStorageService
result['common.version.shell'] = process.versions && (<any>process).versions['electron'];
// __GDPR__COMMON__ "common.version.renderer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.version.renderer'] = process.versions && (<any>process).versions['chrome'];
// __GDPR__COMMON__ "common.osVersion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.osVersion'] = os.release();
const lastSessionDate = storageService.get('telemetry.lastSessionDate');
const firstSessionDate = storageService.get('telemetry.firstSessionDate') || new Date().toUTCString();
@@ -49,6 +49,7 @@ suite('Telemetry - common properties', function () {
// assert.ok('common.version.shell' in first.data); // only when running on electron
// assert.ok('common.version.renderer' in first.data);
assert.ok('common.osVersion' in props, 'osVersion');
assert.ok('common.platformVersion' in props, 'platformVersion');
assert.ok('version' in props);
assert.equal(props['common.source'], 'my.install.source');
@@ -15,6 +15,7 @@ import { IWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/
import { IRecentlyOpened } from 'vs/platform/history/common/history';
import { ICommandAction } from 'vs/platform/actions/common/actions';
import { PerformanceEntry } from 'vs/base/common/performance';
import { LogLevel } from 'vs/platform/log/common/log';
export const IWindowsService = createDecorator<IWindowsService>('windowsService');
@@ -297,6 +298,7 @@ export interface IAddFoldersRequest {
export interface IWindowConfiguration extends ParsedArgs, IOpenFileRequest {
machineId: string;
windowId: number;
logLevel: LogLevel;
appRoot: string;
execPath: string;
@@ -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 };
}
}
@@ -48,7 +48,7 @@ suite('WorkspacesMainService', () => {
}
const environmentService = new TestEnvironmentService(parseArgs(process.argv), process.execPath);
const logService = new ConsoleLogMainService(environmentService);
const logService = new ConsoleLogMainService();
let service: TestWorkspacesMainService;
+43 -2
View File
@@ -2495,7 +2495,8 @@ declare module 'vscode' {
}
/**
* A workspace edit represents textual changes for many documents.
* A workspace edit represents textual and files changes for
* multiple resources and documents.
*/
export class WorkspaceEdit {
@@ -2556,9 +2557,49 @@ declare module 'vscode' {
/**
* Get all text edits grouped by resource.
*
* @return An array of `[Uri, TextEdit[]]`-tuples.
* @return A shallow copy of `[Uri, TextEdit[]]`-tuples.
*/
entries(): [Uri, TextEdit[]][];
/**
* Renames a given resource in the workspace.
*
* @param from Uri of current resource.
* @param to Uri of renamed resource.
*/
renameResource(from: Uri, to: Uri): void;
/**
* Create a new resource in the workspace.
*
* @param uri Uri of resource to create.
*/
createResource(uri: Uri): void;
/**
* Delete a given resource in the workspace.
*
* @param uri Uri of resource to delete.
*/
deleteResource(uri: Uri): void;
/**
* Get the resource edits for this workspace edit.
*
* @returns A shallow copy of uri-tuples in which a rename-edit
* is represented as `[from, to]`, a delete-operation as `[from, null]`,
* and a create-operation as `[null, to]`;
*/
resourceEdits(): [Uri, Uri][];
/**
* Get all edits, textual changes and file changes. The order is the order
* in which edits have been added to this workspace edits. Textuals edits
* are grouped and the first textual edit for a resource matters.
*
* @returns A shallow copy of all changes.
*/
allEntries(): ([Uri, TextEdit[]] | [Uri, Uri])[];
}
/**
@@ -46,6 +46,7 @@ import './mainThreadTask';
import './mainThreadTelemetry';
import './mainThreadTerminalService';
import './mainThreadTreeViews';
import './mainThreadLogService';
import './mainThreadWindow';
import './mainThreadWorkspace';
@@ -15,17 +15,18 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { Position as EditorPosition, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { MainThreadTextEditor } from './mainThreadEditor';
import { ITextEditorConfigurationUpdate, TextEditorRevealType, IApplyEditsOptions, IUndoStopOptions } from 'vs/workbench/api/node/extHost.protocol';
import { ITextEditorConfigurationUpdate, TextEditorRevealType, IApplyEditsOptions, IUndoStopOptions, WorkspaceEditDto, reviveWorkspaceEditDto } from 'vs/workbench/api/node/extHost.protocol';
import { MainThreadDocumentsAndEditors } from './mainThreadDocumentsAndEditors';
import { equals as objectEquals } from 'vs/base/common/objects';
import { ExtHostContext, MainThreadEditorsShape, ExtHostEditorsShape, ITextDocumentShowOptions, ITextEditorPositionData, IExtHostContext, IWorkspaceResourceEdit } from '../node/extHost.protocol';
import { ExtHostContext, MainThreadEditorsShape, ExtHostEditorsShape, ITextDocumentShowOptions, ITextEditorPositionData, IExtHostContext } from '../node/extHost.protocol';
import { IRange } from 'vs/editor/common/core/range';
import { ISelection } from 'vs/editor/common/core/selection';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { IFileService } from 'vs/platform/files/common/files';
import { bulkEdit, IResourceEdit } from 'vs/editor/browser/services/bulkEdit';
import { BulkEdit } from 'vs/editor/browser/services/bulkEdit';
import { IModelService } from 'vs/editor/common/services/modelService';
import { isCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { isResourceFileEdit } from 'vs/editor/common/modes';
export class MainThreadEditors implements MainThreadEditorsShape {
@@ -210,40 +211,22 @@ export class MainThreadEditors implements MainThreadEditorsShape {
return TPromise.as(this._documentsAndEditors.getEditor(id).applyEdits(modelVersionId, edits, opts));
}
$tryApplyWorkspaceEdit(workspaceResourceEdits: IWorkspaceResourceEdit[]): TPromise<boolean> {
$tryApplyWorkspaceEdit(dto: WorkspaceEditDto): TPromise<boolean> {
const { edits } = reviveWorkspaceEditDto(dto);
// First check if loaded models were not changed in the meantime
for (let i = 0, len = workspaceResourceEdits.length; i < len; i++) {
const workspaceResourceEdit = workspaceResourceEdits[i];
if (workspaceResourceEdit.modelVersionId) {
const uri = URI.revive(workspaceResourceEdit.resource);
let model = this._modelService.getModel(uri);
if (model && model.getVersionId() !== workspaceResourceEdit.modelVersionId) {
for (let i = 0, len = edits.length; i < len; i++) {
const edit = edits[i];
if (!isResourceFileEdit(edit) && edit.modelVersionId) {
let model = this._modelService.getModel(edit.resource);
if (model && model.getVersionId() !== edit.modelVersionId) {
// model changed in the meantime
return TPromise.as(false);
}
}
}
// Convert to shape expected by bulkEdit below
let resourceEdits: IResourceEdit[] = [];
for (let i = 0, len = workspaceResourceEdits.length; i < len; i++) {
const workspaceResourceEdit = workspaceResourceEdits[i];
const uri = URI.revive(workspaceResourceEdit.resource);
const edits = workspaceResourceEdit.edits;
for (let j = 0, lenJ = edits.length; j < lenJ; j++) {
const edit = edits[j];
resourceEdits.push({
resource: uri,
newText: edit.newText,
newEol: edit.newEol,
range: edit.range
});
}
}
let codeEditor: ICodeEditor;
let editor = this._workbenchEditorService.getActiveEditor();
if (editor) {
@@ -253,8 +236,7 @@ export class MainThreadEditors implements MainThreadEditorsShape {
}
}
return bulkEdit(this._textModelResolverService, codeEditor, resourceEdits, this._fileService)
.then(() => true);
return BulkEdit.perform(edits, this._textModelResolverService, this._fileService, codeEditor).then(() => true);
}
$tryInsertSnippet(id: string, template: string, ranges: IRange[], opts: IUndoStopOptions): TPromise<boolean> {
@@ -15,7 +15,7 @@ import { wireCancellationToken } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Position as EditorPosition } from 'vs/editor/common/core/position';
import { Range as EditorRange } from 'vs/editor/common/core/range';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, SymbolInformationDto, WorkspaceEditDto, ResourceEditDto, CodeActionDto } from '../node/extHost.protocol';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, SymbolInformationDto, CodeActionDto, reviveWorkspaceEditDto } from '../node/extHost.protocol';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration';
import { IHeapService } from './mainThreadHeapService';
@@ -86,21 +86,9 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
}
}
private static _reviveResourceEditDto(data: ResourceEditDto): modes.IResourceEdit {
data.resource = URI.revive(data.resource);
return <modes.IResourceEdit>data;
}
private static _reviveWorkspaceEditDto(data: WorkspaceEditDto): modes.WorkspaceEdit {
if (data && data.edits) {
data.edits.forEach(MainThreadLanguageFeatures._reviveResourceEditDto);
}
return <modes.WorkspaceEdit>data;
}
private static _reviveCodeActionDto(data: CodeActionDto[]): modes.CodeAction[] {
if (data) {
data.forEach(code => MainThreadLanguageFeatures._reviveWorkspaceEditDto(code.edit));
data.forEach(code => reviveWorkspaceEditDto(code.edit));
}
return <modes.CodeAction[]>data;
}
@@ -266,7 +254,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
$registerRenameSupport(handle: number, selector: vscode.DocumentSelector): void {
this._registrations[handle] = modes.RenameProviderRegistry.register(toLanguageSelector(selector), <modes.RenameProvider>{
provideRenameEdits: (model: ITextModel, position: EditorPosition, newName: string, token: CancellationToken): Thenable<modes.WorkspaceEdit> => {
return wireCancellationToken(token, this._proxy.$provideRenameEdits(handle, model.uri, position, newName)).then(MainThreadLanguageFeatures._reviveWorkspaceEditDto);
return wireCancellationToken(token, this._proxy.$provideRenameEdits(handle, model.uri, position, newName)).then(reviveWorkspaceEditDto);
}
});
}
@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* 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 { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { ILogService } from 'vs/platform/log/common/log';
import { Disposable } from 'vs/base/common/lifecycle';
import { IExtHostContext, ExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
@extHostCustomer
export class MainThreadLogService extends Disposable {
constructor(
extHostContext: IExtHostContext,
@ILogService logService: ILogService,
) {
super();
this._register(logService.onDidChangeLogLevel(level => extHostContext.getProxy(ExtHostContext.ExtHostLogService).$setLevel(level)));
}
}
@@ -27,6 +27,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress';
import { localize } from 'vs/nls';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { ILogService } from 'vs/platform/log/common/log';
export interface ISaveParticipantParticipant extends ISaveParticipant {
// progressMessage: string;
@@ -279,8 +280,9 @@ export class SaveParticipant implements ISaveParticipant {
constructor(
extHostContext: IExtHostContext,
@IInstantiationService instantiationService: IInstantiationService,
@IProgressService2 private _progressService: IProgressService2,
@IInstantiationService instantiationService: IInstantiationService
@ILogService private _logService: ILogService
) {
this._saveParticipants = [
instantiationService.createInstance(TrimWhitespaceParticipant),
@@ -303,7 +305,7 @@ export class SaveParticipant implements ISaveParticipant {
const promiseFactory = this._saveParticipants.map(p => () => {
return Promise.resolve(p.participate(model, env));
});
return sequence(promiseFactory).then(() => { });
return sequence(promiseFactory).then(() => { }, err => this._logService.error(err));
});
}
}
@@ -55,8 +55,8 @@ import { ExtHostDecorations } from 'vs/workbench/api/node/extHostDecorations';
import { toGlobPattern, toLanguageSelector } from 'vs/workbench/api/node/extHostTypeConverters';
import { ExtensionActivatedByAPI } from 'vs/workbench/api/node/extHostExtensionActivator';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { ILogService } from 'vs/platform/log/common/log';
import { OverviewRulerLane } from 'vs/editor/common/model';
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
export interface IExtensionApiFactory {
(extension: IExtensionDescription): typeof vscode;
@@ -89,18 +89,19 @@ export function createApiFactory(
extHostWorkspace: ExtHostWorkspace,
extHostConfiguration: ExtHostConfiguration,
extensionService: ExtHostExtensionService,
logService: ILogService
extHostLogService: ExtHostLogService
): IExtensionApiFactory {
// Addressable instances
rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService);
const extHostHeapService = rpcProtocol.set(ExtHostContext.ExtHostHeapService, new ExtHostHeapService());
const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, new ExtHostDecorations(rpcProtocol));
const extHostDocumentsAndEditors = rpcProtocol.set(ExtHostContext.ExtHostDocumentsAndEditors, new ExtHostDocumentsAndEditors(rpcProtocol));
const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));
const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors));
const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(logService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadEditors)));
const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadEditors)));
const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors));
const extHostCommands = rpcProtocol.set(ExtHostContext.ExtHostCommands, new ExtHostCommands(rpcProtocol, extHostHeapService, logService));
const extHostCommands = rpcProtocol.set(ExtHostContext.ExtHostCommands, new ExtHostCommands(rpcProtocol, extHostHeapService, extHostLogService));
const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands));
rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace);
const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(rpcProtocol, extHostWorkspace));
@@ -111,7 +112,7 @@ export function createApiFactory(
const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService());
const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands));
const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol));
const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, logService));
const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService));
const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace));
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService);
+37 -21
View File
@@ -14,7 +14,7 @@ import {
import * as vscode from 'vscode';
import { UriComponents } from 'vs/base/common/uri';
import URI, { UriComponents } from 'vs/base/common/uri';
import Severity from 'vs/base/common/severity';
import { TPromise } from 'vs/base/common/winjs.base';
@@ -52,8 +52,9 @@ import { IStat, FileChangeType } from 'vs/platform/files/common/files';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { CommentRule, CharacterPair, EnterAction } from 'vs/editor/common/modes/languageConfiguration';
import { EndOfLineSequence, ISingleEditOperation } from 'vs/editor/common/model';
import { ISingleEditOperation } from 'vs/editor/common/model';
import { ILineMatch, IPatternInfo } from 'vs/platform/search/common/search';
import { LogLevel } from 'vs/platform/log/common/log';
export interface IEnvironment {
isExtensionDevelopmentDebug: boolean;
@@ -71,6 +72,7 @@ export interface IWorkspaceData {
id: string;
name: string;
folders: { uri: UriComponents, name: string, index: number }[];
configuration?: UriComponents;
}
export interface IInitData {
@@ -83,6 +85,7 @@ export interface IInitData {
windowId: number;
args: ParsedArgs;
execPath: string;
logLevel: LogLevel;
}
export interface IConfigurationInitData extends IConfigurationData {
@@ -192,8 +195,6 @@ export interface IApplyEditsOptions extends IUndoStopOptions {
setEndOfLine: EndOfLine;
}
export interface ITextDocumentShowOptions {
position?: EditorPosition;
preserveFocus?: boolean;
@@ -201,16 +202,6 @@ export interface ITextDocumentShowOptions {
selection?: IRange;
}
export interface IWorkspaceResourceEdit {
resource: UriComponents;
modelVersionId?: number;
edits: {
range?: IRange;
newText: string;
newEol?: EndOfLineSequence;
}[];
}
export interface MainThreadEditorsShape extends IDisposable {
$tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): TPromise<string>;
$registerTextEditorDecorationType(key: string, options: editorCommon.IDecorationRenderOptions): void;
@@ -223,7 +214,7 @@ export interface MainThreadEditorsShape extends IDisposable {
$tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): TPromise<void>;
$trySetSelections(id: string, selections: ISelection[]): TPromise<void>;
$tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], opts: IApplyEditsOptions): TPromise<boolean>;
$tryApplyWorkspaceEdit(workspaceResourceEdits: IWorkspaceResourceEdit[]): TPromise<boolean>;
$tryApplyWorkspaceEdit(workspaceEditDto: WorkspaceEditDto): TPromise<boolean>;
$tryInsertSnippet(id: string, template: string, selections: IRange[], opts: IUndoStopOptions): TPromise<boolean>;
$getDiffInformation(id: string): TPromise<editorCommon.ILineChange[]>;
}
@@ -624,23 +615,44 @@ export interface WorkspaceSymbolsDto extends IdObject {
symbols: SymbolInformationDto[];
}
export interface ResourceEditDto {
export interface ResourceFileEditDto {
oldUri: UriComponents;
newUri: UriComponents;
}
export interface ResourceTextEditDto {
resource: UriComponents;
range: IRange;
newText: string;
modelVersionId?: number;
edits: modes.TextEdit[];
}
export interface WorkspaceEditDto {
edits: ResourceEditDto[];
edits: (ResourceFileEditDto | ResourceTextEditDto)[];
// todo@joh reject should go into rename
rejectReason?: string;
}
export function reviveWorkspaceEditDto(data: WorkspaceEditDto): modes.WorkspaceEdit {
if (data && data.edits) {
for (const edit of data.edits) {
if (typeof (<ResourceTextEditDto>edit).resource === 'object') {
(<ResourceTextEditDto>edit).resource = URI.revive((<ResourceTextEditDto>edit).resource);
} else {
(<ResourceFileEditDto>edit).newUri = URI.revive((<ResourceFileEditDto>edit).newUri);
(<ResourceFileEditDto>edit).oldUri = URI.revive((<ResourceFileEditDto>edit).oldUri);
}
}
}
return <modes.WorkspaceEdit>data;
}
export interface CodeActionDto {
title: string;
edit?: WorkspaceEditDto;
diagnostics?: IMarkerData[];
command?: modes.Command;
scope?: string;
kind?: string;
}
export interface ExtHostLanguageFeaturesShape {
@@ -745,6 +757,10 @@ export interface ExtHostWindowShape {
$onDidChangeWindowFocus(value: boolean): void;
}
export interface ExtHostLogServiceShape {
$setLevel(level: LogLevel);
}
// --- proxy identifiers
export const MainContext = {
@@ -795,7 +811,7 @@ export const ExtHostContext = {
ExtHostLanguageFeatures: createExtId<ExtHostLanguageFeaturesShape>('ExtHostLanguageFeatures'),
ExtHostQuickOpen: createExtId<ExtHostQuickOpenShape>('ExtHostQuickOpen'),
ExtHostExtensionService: createExtId<ExtHostExtensionServiceShape>('ExtHostExtensionService'),
// ExtHostLogService: createExtId<ExtHostLogServiceShape>('ExtHostLogService'),
ExtHostLogService: createExtId<ExtHostLogServiceShape>('ExtHostLogService'),
ExtHostTerminalService: createExtId<ExtHostTerminalServiceShape>('ExtHostTerminalService'),
ExtHostSCM: createExtId<ExtHostSCMShape>('ExtHostSCM'),
ExtHostTask: createExtId<ExtHostTaskShape>('ExtHostTask', ProxyType.CustomMarshaller),
@@ -353,11 +353,7 @@ export class ExtHostApiCommands {
if (value.rejectReason) {
return TPromise.wrapError<types.WorkspaceEdit>(new Error(value.rejectReason));
}
let workspaceEdit = new types.WorkspaceEdit();
for (let edit of value.edits) {
workspaceEdit.replace(edit.resource, typeConverters.toRange(edit.range), edit.newText);
}
return workspaceEdit;
return typeConverters.WorkspaceEdit.to(value);
});
}
@@ -8,7 +8,7 @@ import Event from 'vs/base/common/event';
import URI, { UriComponents } from 'vs/base/common/uri';
import { sequence, always } from 'vs/base/common/async';
import { illegalState } from 'vs/base/common/errors';
import { ExtHostDocumentSaveParticipantShape, MainThreadEditorsShape, IWorkspaceResourceEdit } from 'vs/workbench/api/node/extHost.protocol';
import { ExtHostDocumentSaveParticipantShape, MainThreadEditorsShape, ResourceTextEditDto } from 'vs/workbench/api/node/extHost.protocol';
import { TextEdit } from 'vs/workbench/api/node/extHostTypes';
import { fromRange, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/node/extHostTypeConverters';
import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
@@ -142,7 +142,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic
}).then(values => {
let workspaceResourceEdit: IWorkspaceResourceEdit = {
const resourceEdit: ResourceTextEditDto = {
resource: document.uri,
edits: []
};
@@ -150,10 +150,10 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic
for (const value of values) {
if (Array.isArray(value) && (<vscode.TextEdit[]>value).every(e => e instanceof TextEdit)) {
for (const { newText, newEol, range } of value) {
workspaceResourceEdit.edits.push({
resourceEdit.edits.push({
range: range && fromRange(range),
newText,
newEol: EndOfLine.from(newEol)
text: newText,
eol: EndOfLine.from(newEol)
});
}
}
@@ -161,12 +161,12 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic
// apply edits if any and if document
// didn't change somehow in the meantime
if (workspaceResourceEdit.edits.length === 0) {
if (resourceEdit.edits.length === 0) {
return undefined;
}
if (version === document.version) {
return this._mainThreadEditors.$tryApplyWorkspaceEdit([workspaceResourceEdit]);
return this._mainThreadEditors.$tryApplyWorkspaceEdit({ edits: [resourceEdit] });
}
// TODO@joh bubble this to listener?
@@ -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: this._workspace.configuration && URI.revive(this._workspace.configuration).toString(),
name: this._workspace.name
}, undefined, 2)
);
@@ -128,7 +130,6 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
private readonly _storage: ExtHostStorage;
private readonly _storagePath: ExtensionStoragePath;
private readonly _proxy: MainThreadExtensionServiceShape;
private readonly _logService: ILogService;
private readonly _extHostLogService: ExtHostLogService;
private _activator: ExtensionsActivator;
private _extensionPathIndex: TPromise<TernarySearchTree<IExtensionDescription>>;
@@ -139,21 +140,20 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
extHostContext: IExtHostContext,
extHostWorkspace: ExtHostWorkspace,
extHostConfiguration: ExtHostConfiguration,
logService: ILogService,
extHostLogService: ExtHostLogService,
environmentService: IEnvironmentService
) {
this._barrier = new Barrier();
this._registry = new ExtensionDescriptionRegistry(initData.extensions);
this._logService = logService;
this._extHostLogService = extHostLogService;
this._mainThreadTelemetry = extHostContext.getProxy(MainContext.MainThreadTelemetry);
this._storage = new ExtHostStorage(extHostContext);
this._storagePath = new ExtensionStoragePath(initData.workspace, initData.environment);
this._proxy = extHostContext.getProxy(MainContext.MainThreadExtensionService);
this._activator = null;
this._extHostLogService = new ExtHostLogService(environmentService);
// initialize API first (i.e. do not release barrier until the API is initialized)
const apiFactory = createApiFactory(initData, extHostContext, extHostWorkspace, extHostConfiguration, this, logService);
const apiFactory = createApiFactory(initData, extHostContext, extHostWorkspace, extHostConfiguration, this, this._extHostLogService);
initializeExtensionApi(this, apiFactory).then(() => {
@@ -314,14 +314,14 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
return TPromise.as(new EmptyExtension(ExtensionActivationTimes.NONE));
}
this._logService.info(`ExtensionService#_doActivateExtension ${extensionDescription.id} ${JSON.stringify(reason)}`);
this._extHostLogService.info(`ExtensionService#_doActivateExtension ${extensionDescription.id} ${JSON.stringify(reason)}`);
const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
return TPromise.join<any>([
loadCommonJSModule(this._logService, extensionDescription.main, activationTimesBuilder),
loadCommonJSModule(this._extHostLogService, extensionDescription.main, activationTimesBuilder),
this._loadExtensionContext(extensionDescription)
]).then(values => {
return ExtHostExtensionService._callActivate(this._logService, extensionDescription.id, <IExtensionModule>values[0], <IExtensionContext>values[1], activationTimesBuilder);
return ExtHostExtensionService._callActivate(this._extHostLogService, extensionDescription.id, <IExtensionModule>values[0], <IExtensionContext>values[1], activationTimesBuilder);
}, (errors: any[]) => {
// Avoid failing with an array of errors, fail with a single error
if (errors[0]) {
@@ -339,7 +339,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
let globalState = new ExtensionMemento(extensionDescription.id, true, this._storage);
let workspaceState = new ExtensionMemento(extensionDescription.id, false, this._storage);
this._logService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.id}`);
this._extHostLogService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.id}`);
return TPromise.join([
globalState.whenReady,
workspaceState.whenReady,
@@ -17,7 +17,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands';
import { ExtHostDiagnostics, DiagnosticCollection } from 'vs/workbench/api/node/extHostDiagnostics';
import { asWinJsPromise } from 'vs/base/common/async';
import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, SymbolInformationDto, SuggestResultDto, WorkspaceSymbolsDto, SuggestionDto } from './extHost.protocol';
import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, SymbolInformationDto, SuggestResultDto, WorkspaceSymbolsDto, SuggestionDto, CodeActionDto } from './extHost.protocol';
import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings';
import { IPosition } from 'vs/editor/common/core/position';
import { IRange } from 'vs/editor/common/core/range';
@@ -255,7 +255,7 @@ class ReferenceAdapter {
}
}
export interface CustomCodeAction extends modes.CodeAction {
export interface CustomCodeAction extends CodeActionDto {
_isSynthetic?: boolean;
}
@@ -273,7 +273,8 @@ class CodeActionAdapter {
this._provider = provider;
}
provideCodeActions(resource: URI, range: IRange, context: modes.CodeActionContext): TPromise<modes.CodeAction[]> {
provideCodeActions(resource: URI, range: IRange, context: modes.CodeActionContext): TPromise<CodeActionDto[]> {
const doc = this._documents.getDocumentData(resource).document;
const ran = <vscode.Range>TypeConverters.toRange(range);
@@ -948,7 +949,8 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
return this._createDisposable(handle);
}
$provideCodeActions(handle: number, resource: UriComponents, range: IRange, context: modes.CodeActionContext): TPromise<modes.CodeAction[]> {
$provideCodeActions(handle: number, resource: UriComponents, range: IRange, context: modes.CodeActionContext): TPromise<CodeActionDto[]> {
return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(URI.revive(resource), range, context));
}
+31 -17
View File
@@ -8,47 +8,61 @@ import * as path from 'path';
import * as vscode from 'vscode';
import { TPromise } from 'vs/base/common/winjs.base';
import { mkdirp, dirExists } from 'vs/base/node/pfs';
import Event, { Emitter } from 'vs/base/common/event';
import Event from 'vs/base/common/event';
import { LogLevel } from 'vs/workbench/api/node/extHostTypes';
import { ILogService } from 'vs/platform/log/common/log';
import { ILogService, DelegatedLogService } from 'vs/platform/log/common/log';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { memoize } from 'vs/base/common/decorators';
import { ExtHostLogServiceShape } from 'vs/workbench/api/node/extHost.protocol';
export class ExtHostLogService extends DelegatedLogService implements ILogService, ExtHostLogServiceShape {
export class ExtHostLogService {
private _loggers: Map<string, ExtHostLogger> = new Map();
constructor(private _environmentService: IEnvironmentService) {
constructor(
windowId: number,
logLevel: LogLevel,
private _environmentService: IEnvironmentService
) {
super(createSpdLogService(`exthost${windowId}`, logLevel, _environmentService.logsPath));
}
$setLevel(level: LogLevel): void {
this.setLevel(level);
}
getExtLogger(extensionID: string): ExtHostLogger {
if (!this._loggers.has(extensionID)) {
const logService = createSpdLogService(extensionID, this._environmentService, extensionID);
const logsDirPath = path.join(this._environmentService.logsPath, extensionID);
this._loggers.set(extensionID, new ExtHostLogger(logService, logsDirPath));
let logger = this._loggers.get(extensionID);
if (!logger) {
logger = this.createLogger(extensionID);
this._loggers.set(extensionID, logger);
}
return logger;
}
return this._loggers.get(extensionID);
private createLogger(extensionID: string): ExtHostLogger {
const logService = createSpdLogService(extensionID, this.getLevel(), this._environmentService.logsPath, extensionID);
const logsDirPath = path.join(this._environmentService.logsPath, extensionID);
this._register(this.onDidChangeLogLevel(level => logService.setLevel(level)));
return new ExtHostLogger(logService, logsDirPath);
}
}
export class ExtHostLogger implements vscode.Logger {
private _currentLevel: LogLevel;
private _onDidChangeLogLevel: Emitter<LogLevel>;
constructor(
private readonly _logService: ILogService,
private readonly _logDirectory: string
) {
this._currentLevel = this._logService.getLevel();
this._onDidChangeLogLevel = new Emitter<LogLevel>();
this.onDidChangeLogLevel = this._onDidChangeLogLevel.event;
}
// TODO
readonly onDidChangeLogLevel: Event<LogLevel>;
get onDidChangeLogLevel(): Event<LogLevel> {
return this._logService.onDidChangeLogLevel;
}
get currentLevel(): LogLevel { return this._currentLevel; }
get currentLevel(): LogLevel { return this._logService.getLevel(); }
@memoize
get logDirectory(): TPromise<string> {
+13 -26
View File
@@ -12,7 +12,7 @@ import * as TypeConverters from './extHostTypeConverters';
import { TextEditorDecorationType, ExtHostTextEditor } from './extHostTextEditor';
import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors';
import { Position as EditorPosition } from 'vs/platform/editor/common/editor';
import { MainContext, MainThreadEditorsShape, ExtHostEditorsShape, ITextDocumentShowOptions, ITextEditorPositionData, IResolvedTextEditorConfiguration, ISelectionChangeEvent, IMainContext, IWorkspaceResourceEdit } from './extHost.protocol';
import { MainContext, MainThreadEditorsShape, ExtHostEditorsShape, ITextDocumentShowOptions, ITextEditorPositionData, IResolvedTextEditorConfiguration, ISelectionChangeEvent, IMainContext, WorkspaceEditDto } from './extHost.protocol';
import * as vscode from 'vscode';
export class ExtHostEditors implements ExtHostEditorsShape {
@@ -92,36 +92,23 @@ export class ExtHostEditors implements ExtHostEditorsShape {
applyWorkspaceEdit(edit: vscode.WorkspaceEdit): TPromise<boolean> {
let workspaceResourceEdits: IWorkspaceResourceEdit[] = [];
const dto: WorkspaceEditDto = { edits: [] };
let entries = edit.entries();
for (let entry of entries) {
let [uri, edits] = entry;
let doc = this._extHostDocumentsAndEditors.getDocument(uri.toString());
let docVersion: number = undefined;
if (doc) {
docVersion = doc.version;
}
let workspaceResourceEdit: IWorkspaceResourceEdit = {
resource: uri,
modelVersionId: docVersion,
edits: []
};
for (let edit of edits) {
workspaceResourceEdit.edits.push({
newText: edit.newText,
newEol: TypeConverters.EndOfLine.from(edit.newEol),
range: edit.range && TypeConverters.fromRange(edit.range)
for (let entry of edit.allEntries()) {
let [uri, uriOrEdits] = entry;
if (Array.isArray(uriOrEdits)) {
let doc = this._extHostDocumentsAndEditors.getDocument(uri.toString());
dto.edits.push({
resource: uri,
modelVersionId: doc && doc.version,
edits: uriOrEdits.map(TypeConverters.TextEdit.from)
});
} else {
dto.edits.push({ oldUri: uri, newUri: uriOrEdits });
}
workspaceResourceEdits.push(workspaceResourceEdit);
}
return this._proxy.$tryApplyWorkspaceEdit(workspaceResourceEdits);
return this._proxy.$tryApplyWorkspaceEdit(dto);
}
// --- called from main thread
@@ -20,6 +20,7 @@ import { ISelection } from 'vs/editor/common/core/selection';
import * as htmlContent from 'vs/base/common/htmlContent';
import { IRelativePattern } from 'vs/base/common/glob';
import { LanguageSelector, LanguageFilter } from 'vs/editor/common/modes/languageSelector';
import { WorkspaceEditDto, ResourceTextEditDto, ResourceFileEditDto } from 'vs/workbench/api/node/extHost.protocol';
export interface PositionLike {
line: number;
@@ -228,24 +229,36 @@ export const TextEdit = {
export namespace WorkspaceEdit {
export function from(value: vscode.WorkspaceEdit): modes.WorkspaceEdit {
const result: modes.WorkspaceEdit = { edits: [] };
for (let entry of value.entries()) {
let [uri, textEdits] = entry;
for (let textEdit of textEdits) {
result.edits.push({
resource: uri,
newText: textEdit.newText,
range: fromRange(textEdit.range)
});
const result: modes.WorkspaceEdit = {
edits: []
};
for (const entry of value.allEntries()) {
const [uri, uriOrEdits] = entry;
if (Array.isArray(uriOrEdits)) {
// text edits
result.edits.push({ resource: uri, edits: uriOrEdits.map(TextEdit.from) });
} else {
// resource edits
result.edits.push({ oldUri: uri, newUri: uriOrEdits });
}
}
return result;
}
export function to(value: modes.WorkspaceEdit) {
export function to(value: WorkspaceEditDto) {
const result = new types.WorkspaceEdit();
for (const edit of value.edits) {
result.replace(edit.resource, toRange(edit.range), edit.newText);
if (Array.isArray((<ResourceTextEditDto>edit).edits)) {
result.set(
URI.revive((<ResourceTextEditDto>edit).resource),
<types.TextEdit[]>(<ResourceTextEditDto>edit).edits.map(TextEdit.to)
);
} else {
result.renameResource(
URI.revive((<ResourceFileEditDto>edit).oldUri),
URI.revive((<ResourceFileEditDto>edit).newUri)
);
}
}
return result;
}
+58 -15
View File
@@ -492,10 +492,28 @@ export class TextEdit {
}
}
export class WorkspaceEdit {
export class WorkspaceEdit implements vscode.WorkspaceEdit {
private _values: [URI, TextEdit[]][] = [];
private _index = new Map<string, number>();
private _seqPool: number = 0;
private _resourceEdits: { seq: number, from: URI, to: URI }[] = [];
private _textEdits = new Map<string, { seq: number, uri: URI, edits: TextEdit[] }>();
createResource(uri: vscode.Uri): void {
this.renameResource(undefined, uri);
}
deleteResource(uri: vscode.Uri): void {
this.renameResource(uri, undefined);
}
renameResource(from: vscode.Uri, to: vscode.Uri): void {
this._resourceEdits.push({ seq: this._seqPool++, from, to });
}
resourceEdits(): [vscode.Uri, vscode.Uri][] {
return this._resourceEdits.map(({ from, to }) => (<[vscode.Uri, vscode.Uri]>[from, to]));
}
replace(uri: URI, range: Range, newText: string): void {
let edit = new TextEdit(range, newText);
@@ -503,8 +521,9 @@ export class WorkspaceEdit {
if (array) {
array.push(edit);
} else {
this.set(uri, [edit]);
array = [edit];
}
this.set(uri, array);
}
insert(resource: URI, position: Position, newText: string): void {
@@ -516,34 +535,58 @@ export class WorkspaceEdit {
}
has(uri: URI): boolean {
return this._index.has(uri.toString());
return this._textEdits.has(uri.toString());
}
set(uri: URI, edits: TextEdit[]): void {
const idx = this._index.get(uri.toString());
if (typeof idx === 'undefined') {
let newLen = this._values.push([uri, edits]);
this._index.set(uri.toString(), newLen - 1);
let data = this._textEdits.get(uri.toString());
if (!data) {
data = { seq: this._seqPool++, uri, edits: [] };
this._textEdits.set(uri.toString(), data);
}
if (!edits) {
data.edits = undefined;
} else {
this._values[idx][1] = edits;
data.edits = edits.slice(0);
}
}
get(uri: URI): TextEdit[] {
let idx = this._index.get(uri.toString());
return typeof idx !== 'undefined' && this._values[idx][1];
if (!this._textEdits.has(uri.toString())) {
return undefined;
}
const { edits } = this._textEdits.get(uri.toString());
return edits ? edits.slice() : undefined;
}
entries(): [URI, TextEdit[]][] {
return this._values;
const res: [URI, TextEdit[]][] = [];
this._textEdits.forEach(value => res.push([value.uri, value.edits]));
return res.slice();
}
allEntries(): ([URI, TextEdit[]] | [URI, URI])[] {
// use the 'seq' the we have assigned when inserting
// the operation and use that order in the resulting
// array
const res: ([URI, TextEdit[]] | [URI, URI])[] = [];
this._textEdits.forEach(value => {
const { seq, uri, edits } = value;
res[seq] = [uri, edits];
});
this._resourceEdits.forEach(value => {
const { seq, from, to } = value;
res[seq] = [from, to];
});
return res;
}
get size(): number {
return this._values.length;
return this._textEdits.size + this._resourceEdits.length;
}
toJSON(): any {
return this._values;
return this.entries();
}
}
@@ -12,7 +12,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
import URI from 'vs/base/common/uri';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { BACKUP_FILE_RESOLVE_OPTIONS, IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { TPromise } from 'vs/base/common/winjs.base';
import { Schemas } from 'vs/base/common/network';
@@ -20,6 +20,8 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Position } from 'vs/platform/editor/common/editor';
import { onUnexpectedError } from 'vs/base/common/errors';
import { DefaultEndOfLine } from 'vs/editor/common/model';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
/**
* Shared function across some editor components to handle drag & drop of external resources. E.g. of folders and workspace files
@@ -37,6 +39,7 @@ export class EditorAreaDropHandler {
@IEditorGroupService private groupService: IEditorGroupService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IConfigurationService private configurationService: IConfigurationService
) {
}
@@ -107,13 +110,22 @@ export class EditorAreaDropHandler {
}
// Resolve the contents of the dropped dirty resource from source
return this.textFileService.resolveTextContent(droppedDirtyEditor.backupResource, BACKUP_FILE_RESOLVE_OPTIONS).then(content => {
return this.backupFileService.resolveBackupContent(droppedDirtyEditor.backupResource).then(content => {
// Set the contents of to the resource to the target
return this.backupFileService.backupResource(droppedDirtyEditor.resource, this.backupFileService.parseBackupContent(content.value));
return this.backupFileService.backupResource(droppedDirtyEditor.resource, content.create(this.getDefaultEOL()).createSnapshot(true));
}).then(() => false, () => false /* ignore any error */);
}
private getDefaultEOL(): DefaultEndOfLine {
const eol = this.configurationService.getValue('files.eol');
if (eol === '\r\n') {
return DefaultEndOfLine.CRLF;
}
return DefaultEndOfLine.LF;
}
private handleWorkspaceFileDrop(resources: (IDraggedResource | IDraggedEditor)[]): TPromise<boolean> {
const externalResources = resources.filter(d => d.isExternal).map(d => d.resource);
@@ -67,13 +67,13 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd
/**
* Creates the text editor model with the provided value, modeId (can be comma separated for multiple values) and optional resource URL.
*/
protected createTextEditorModel(value: string | ITextBufferFactory, resource?: URI, modeId?: string): TPromise<EditorModel> {
protected createTextEditorModel(value: ITextBufferFactory, resource?: URI, modeId?: string): TPromise<EditorModel> {
const firstLineText = this.getFirstLineText(value);
const mode = this.getOrCreateMode(this.modeService, modeId, firstLineText);
return TPromise.as(this.doCreateTextEditorModel(value, mode, resource));
}
private doCreateTextEditorModel(value: string | ITextBufferFactory, mode: TPromise<IMode>, resource: URI): EditorModel {
private doCreateTextEditorModel(value: ITextBufferFactory, mode: TPromise<IMode>, resource: URI): EditorModel {
let model = resource && this.modelService.getModel(resource);
if (!model) {
model = this.modelService.createModel(value, mode, resource);
@@ -91,24 +91,7 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd
return this;
}
protected getFirstLineText(value: string | ITextBufferFactory | ITextSnapshot): string {
// string
if (typeof value === 'string') {
const firstLineText = value.substr(0, 100);
let crIndex = firstLineText.indexOf('\r');
if (crIndex < 0) {
crIndex = firstLineText.length;
}
let lfIndex = firstLineText.indexOf('\n');
if (lfIndex < 0) {
lfIndex = firstLineText.length;
}
return firstLineText.substr(0, Math.min(crIndex, lfIndex));
}
protected getFirstLineText(value: ITextBufferFactory | ITextSnapshot): string {
// text buffer factory
const textBufferFactory = value as ITextBufferFactory;
@@ -118,7 +101,19 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd
// text snapshot
const textSnapshot = value as ITextSnapshot;
return this.getFirstLineText(textSnapshot.read() || '');
const firstLineText = textSnapshot.read().substr(0, 100);
let crIndex = firstLineText.indexOf('\r');
if (crIndex < 0) {
crIndex = firstLineText.length;
}
let lfIndex = firstLineText.indexOf('\n');
if (lfIndex < 0) {
lfIndex = firstLineText.length;
}
return firstLineText.substr(0, Math.min(crIndex, lfIndex));
}
/**
@@ -133,7 +128,7 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd
/**
* Updates the text editor model with the provided value. If the value is the same as the model has, this is a no-op.
*/
protected updateTextEditorModel(newValue: string | ITextBufferFactory): void {
protected updateTextEditorModel(newValue: ITextBufferFactory): void {
if (!this.textEditorModel) {
return;
}
@@ -16,9 +16,10 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import { IMode } from 'vs/editor/common/modes';
import Event, { Emitter } from 'vs/base/common/event';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IBackupFileService, BACKUP_FILE_RESOLVE_OPTIONS } from 'vs/workbench/services/backup/common/backup';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { ITextBufferFactory } from 'vs/editor/common/model';
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
export class UntitledEditorModel extends BaseTextEditorModel implements IEncodingSupport {
@@ -46,7 +47,6 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin
@IModeService modeService: IModeService,
@IModelService modelService: IModelService,
@IBackupFileService private backupFileService: IBackupFileService,
@ITextFileService private textFileService: ITextFileService,
@ITextResourceConfigurationService private configurationService: ITextResourceConfigurationService
) {
super(modelService, modeService);
@@ -163,18 +163,24 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin
// Check for backups first
return this.backupFileService.loadBackupResource(this.resource).then(backupResource => {
if (backupResource) {
return this.textFileService.resolveTextContent(backupResource, BACKUP_FILE_RESOLVE_OPTIONS).then(rawTextContent => {
return this.backupFileService.parseBackupContent(rawTextContent.value);
});
return this.backupFileService.resolveBackupContent(backupResource);
}
return null;
}).then(backupContent => {
}).then(backupTextBufferFactory => {
const hasBackup = !!backupTextBufferFactory;
// untitled associated to file path are dirty right away as well as untitled with content
this.setDirty(this.hasAssociatedFilePath || !!backupContent);
this.setDirty(this.hasAssociatedFilePath || hasBackup);
return this.doLoad(backupContent || this.initialValue || '').then(model => {
let untitledContents: ITextBufferFactory;
if (backupTextBufferFactory) {
untitledContents = backupTextBufferFactory;
} else {
untitledContents = createTextBufferFactory(this.initialValue || '');
}
return this.doLoad(untitledContents).then(model => {
// Encoding
this.configuredEncoding = this.configurationService.getValue<string>(this.resource, 'files.encoding');
@@ -189,7 +195,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin
});
}
private doLoad(content: string): TPromise<UntitledEditorModel> {
private doLoad(content: ITextBufferFactory): TPromise<UntitledEditorModel> {
// Create text editor model if not yet done
if (!this.textEditorModel) {
+11 -5
View File
@@ -43,9 +43,10 @@ import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import fs = require('fs');
import { ConsoleLogService, MultiplexLogService } from 'vs/platform/log/common/log';
import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log';
import { IssueChannelClient } from 'vs/platform/issue/common/issueIpc';
import { IIssueService } from 'vs/platform/issue/common/issue';
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
gracefulFs.gracefulify(fs); // enable gracefulFs
export function startup(configuration: IWindowConfiguration): TPromise<void> {
@@ -75,10 +76,7 @@ function openWorkbench(configuration: IWindowConfiguration): TPromise<void> {
const mainServices = createMainProcessServices(mainProcessClient, configuration);
const environmentService = new EnvironmentService(configuration, configuration.execPath);
const spdlogService = createSpdLogService(`renderer${configuration.windowId}`, environmentService);
const consoleLogService = new ConsoleLogService(environmentService);
const logService = new MultiplexLogService([consoleLogService, spdlogService]);
const logService = createLogService(mainProcessClient, configuration, environmentService);
logService.trace('openWorkbench configuration', JSON.stringify(configuration));
// Since the configuration service is one of the core services that is used in so many places, we initialize it
@@ -200,6 +198,14 @@ function createStorageService(workspaceService: IWorkspaceContextService, enviro
return new StorageService(storage, storage, workspaceId, secondaryWorkspaceId);
}
function createLogService(mainProcessClient: ElectronIPCClient, configuration: IWindowConfiguration, environmentService: IEnvironmentService): ILogService {
const spdlogService = createSpdLogService(`renderer${configuration.windowId}`, configuration.logLevel, environmentService.logsPath);
const consoleLogService = new ConsoleLogService(configuration.logLevel);
const logService = new MultiplexLogService([consoleLogService, spdlogService]);
const logLevelClient = new LogLevelSetterChannelClient(mainProcessClient.getChannel('loglevel'));
return new FollowerLogService(logLevelClient, logService);
}
function createMainProcessServices(mainProcessClient: ElectronIPCClient, configuration: IWindowConfiguration): ServiceCollection {
const serviceCollection = new ServiceCollection();
+8 -9
View File
@@ -21,12 +21,11 @@ import * as watchdog from 'native-watchdog';
import * as glob from 'vs/base/common/glob';
import { ExtensionActivatedByEvent } from 'vs/workbench/api/node/extHostExtensionActivator';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol';
import URI from 'vs/base/common/uri';
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
// const nativeExit = process.exit.bind(process);
function patchProcess(allowExit: boolean) {
@@ -78,7 +77,7 @@ export class ExtensionHostMain {
private _environment: IEnvironment;
private _extensionService: ExtHostExtensionService;
private _extHostConfiguration: ExtHostConfiguration;
private _logService: ILogService;
private _extHostLogService: ExtHostLogService;
private disposables: IDisposable[] = [];
constructor(protocol: IMessagePassingProtocol, initData: IInitData) {
@@ -92,14 +91,14 @@ export class ExtensionHostMain {
const rpcProtocol = new RPCProtocol(protocol);
const extHostWorkspace = new ExtHostWorkspace(rpcProtocol, initData.workspace);
const environmentService = new EnvironmentService(initData.args, initData.execPath);
this._logService = createSpdLogService(`exthost${initData.windowId}`, environmentService);
this.disposables.push(this._logService);
this._extHostLogService = new ExtHostLogService(initData.windowId, initData.logLevel, environmentService);
this.disposables.push(this._extHostLogService);
this._logService.info('extension host started');
this._logService.trace('initData', initData);
this._extHostLogService.info('extension host started');
this._extHostLogService.trace('initData', initData);
this._extHostConfiguration = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace, initData.configuration);
this._extensionService = new ExtHostExtensionService(initData, rpcProtocol, extHostWorkspace, this._extHostConfiguration, this._logService, environmentService);
this._extensionService = new ExtHostExtensionService(initData, rpcProtocol, extHostWorkspace, this._extHostConfiguration, this._extHostLogService, environmentService);
// error forwarding and stack trace scanning
const extensionErrors = new WeakMap<Error, IExtensionDescription>();
@@ -143,7 +142,7 @@ export class ExtensionHostMain {
.then(() => this.handleEagerExtensions())
.then(() => this.handleExtensionTests())
.then(() => {
this._logService.info(`eager extensions activated`);
this._extHostLogService.info(`eager extensions activated`);
});
}
+1 -1
View File
@@ -460,7 +460,7 @@ export interface ILaunch {
* Returns the names of all configurations and compounds.
* Ignores configurations which are invalid.
*/
getConfigurationNames(): string[];
getConfigurationNames(includeCompounds?: boolean): string[];
/**
* Returns the resolved configuration.
@@ -187,7 +187,12 @@ const schema: IJSONSchema = {
type: 'array',
default: [],
items: {
type: 'string'
oneOf: [{
enum: [],
description: nls.localize('useUniqueNames', "Please use unique configuration names.")
}, {
type: 'object'
}]
},
description: nls.localize('app.launch.json.compounds.configurations', "Names of configurations that will be started as part of this compound.")
}
@@ -311,6 +316,8 @@ export class ConfigurationManager implements IConfigurationManager {
items.defaultSnippets.push(...configurationSnippets);
}
});
this.setCompoundSchemaValues();
});
breakpointsExtPoint.setHandler(extensions => {
@@ -328,6 +335,7 @@ export class ConfigurationManager implements IConfigurationManager {
this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('launch')) {
this.selectConfiguration();
this.setCompoundSchemaValues();
}
}));
@@ -345,6 +353,13 @@ export class ConfigurationManager implements IConfigurationManager {
}
}
private setCompoundSchemaValues(): void {
const compoundConfigurationsSchema = (<IJSONSchema>schema.properties['compounds'].items).properties['configurations'];
(<IJSONSchema>compoundConfigurationsSchema.items).oneOf[0].enum = this.launches.map(l =>
l.getConfigurationNames(false)).reduce((first, second) => first.concat(second), []);
jsonRegistry.registerSchema(launchSchemaId, schema);
}
public getLaunches(): ILaunch[] {
return this.launches;
}
@@ -495,13 +510,13 @@ class Launch implements ILaunch {
return config.compounds.filter(compound => compound.name === name).pop();
}
public getConfigurationNames(): string[] {
public getConfigurationNames(includeCompounds = true): string[] {
const config = this.getConfig();
if (!config || !config.configurations || !Array.isArray(config.configurations)) {
return [];
} else {
const names = config.configurations.filter(cfg => cfg && typeof cfg.name === 'string').map(cfg => cfg.name);
if (names.length > 0 && config.compounds) {
if (includeCompounds && names.length > 0 && config.compounds) {
if (config.compounds) {
names.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length)
.map(compound => compound.name));
@@ -699,12 +699,9 @@ export class DebugService implements debug.IDebugService {
}
let rootForName = root;
if (launch === this.configurationManager.getWorkspaceLaunch()) {
// For workspace launches allow comound referencing configurations across folder
const launchContainingName = this.configurationManager.getLaunches().filter(l => !!l.getConfiguration(name)).pop();
if (launchContainingName) {
rootForName = launchContainingName.workspace;
}
const launchesContainingName = this.configurationManager.getLaunches().filter(l => !!l.getConfiguration(name));
if (launchesContainingName && launchesContainingName.length === 1) {
rootForName = launchesContainingName[0].workspace;
}
return this.startDebugging(rootForName, name, noDebug, topCompoundName || compound.name);
@@ -30,7 +30,7 @@ import { ResolvedKeybinding, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput';
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, IExtensionDependencies } from 'vs/workbench/parts/extensions/common/extensions';
import { Renderer, DataSource, Controller } from 'vs/workbench/parts/extensions/browser/dependenciesViewer';
import { RatingsWidget, InstallWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
import { RatingsWidget, InstallCountWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
import { EditorOptions } from 'vs/workbench/common/editor';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, BuiltinStatusLabelAction, ReloadAction } from 'vs/workbench/parts/extensions/browser/extensionsActions';
@@ -337,7 +337,7 @@ export class ExtensionEditor extends BaseEditor {
this.repository.style.display = 'none';
}
const install = this.instantiationService.createInstance(InstallWidget, this.installCount, { extension });
const install = this.instantiationService.createInstance(InstallCountWidget, this.installCount, { extension });
this.transientDisposables.push(install);
const ratings = this.instantiationService.createInstance(RatingsWidget, this.rating, { extension });
@@ -18,7 +18,7 @@ import { domEvent } from 'vs/base/browser/event';
import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions';
import { InstallAction, UpdateAction, BuiltinStatusLabelAction, ManageExtensionAction, ReloadAction, extensionButtonProminentBackground, extensionButtonProminentForeground } from 'vs/workbench/parts/extensions/browser/extensionsActions';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { Label, RatingsWidget, InstallWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
import { Label, RatingsWidget, InstallCountWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@@ -93,7 +93,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
actionbar.onDidRun(({ error }) => error && this.messageService.show(Severity.Error, error));
const versionWidget = this.instantiationService.createInstance(Label, version, (e: IExtension) => e.version);
const installCountWidget = this.instantiationService.createInstance(InstallWidget, installCount, { small: true });
const installCountWidget = this.instantiationService.createInstance(InstallCountWidget, installCount, { small: true });
const ratingsWidget = this.instantiationService.createInstance(RatingsWidget, ratings, { small: true });
const builtinStatusAction = this.instantiationService.createInstance(BuiltinStatusLabelAction);
@@ -42,7 +42,7 @@ export class Label implements IDisposable {
}
}
export class InstallWidget implements IDisposable {
export class InstallCountWidget implements IDisposable {
private disposables: IDisposable[] = [];
private _extension: IExtension;
@@ -58,6 +58,7 @@ import { ConfigurationTarget } from 'vs/platform/configuration/common/configurat
import { IHashService } from 'vs/workbench/services/hash/common/hashService';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IStringDictionary } from 'vs/base/common/collections';
import { IProgressService } from 'vs/platform/progress/common/progress';
export class PreferencesEditorInput extends SideBySideEditorInput {
public static ID: string = 'workbench.editorinputs.preferencesEditorInput';
@@ -123,7 +124,8 @@ export class PreferencesEditor extends BaseEditor {
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IProgressService private progressService: IProgressService
) {
super(PreferencesEditor.ID, telemetryService, themeService);
this.defaultSettingsEditorContextKey = CONTEXT_SETTINGS_EDITOR.bindTo(this.contextKeyService);
@@ -240,10 +242,10 @@ export class PreferencesEditor extends BaseEditor {
private onInputChanged(): void {
const query = this.searchWidget.getValue().trim();
this.delayedFilterLogging.cancel();
TPromise.join([
this.progressService.showWhile(TPromise.join([
this.preferencesRenderers.localFilterPreferences(query),
this.triggerThrottledSearch(query)
]).then(() => {
]), 250).then(() => {
const result = this.preferencesRenderers.lastFilterResult;
if (result) {
this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(
@@ -285,29 +287,33 @@ export class PreferencesEditor extends BaseEditor {
if (count === 0) {
this.searchWidget.showMessage(nls.localize('noSettingsFound', "No Results"), count);
} else if (count === 1) {
this.searchWidget.showMessage(nls.localize('oneSettingFound', "1 Setting matched"), count);
this.searchWidget.showMessage(nls.localize('oneSettingFound', "1 Setting Found"), count);
} else {
this.searchWidget.showMessage(nls.localize('settingsFound', "{0} Settings matched", count), count);
this.searchWidget.showMessage(nls.localize('settingsFound', "{0} Settings Found", count), count);
}
} else {
this.searchWidget.showMessage(nls.localize('totalSettingsMessage', "Total {0} Settings", count), count);
}
}
private reportFilteringUsed(filter: string, counts: IStringDictionary<number>, metadata?: IFilterMetadata): void {
private reportFilteringUsed(filter: string, counts: IStringDictionary<number>, metadata?: IStringDictionary<IFilterMetadata>): void {
if (filter && filter !== this._lastReportedFilter) {
let durations: any;
if (metadata) {
durations = Object.create(null);
Object.keys(metadata).forEach(key => durations[key] = metadata[key].duration);
}
let data = {
filter,
duration: metadata ? metadata.duration : undefined,
context: metadata ? metadata.context : undefined,
durations,
counts
};
/* __GDPR__
"defaultSettings.filter" : {
"filter": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"context" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"durations" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"counts" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
@@ -352,7 +358,7 @@ class SettingsNavigator implements INavigator<ISetting> {
interface IFilterOrSearchResult {
defaultSettingsGroupCounts: IStringDictionary<number>;
metadata: IFilterMetadata;
metadata: IStringDictionary<IFilterMetadata>;
}
class PreferencesRenderersController extends Disposable {
@@ -434,7 +440,7 @@ class PreferencesRenderersController extends Disposable {
this._currentNewExtensionsSearchProvider = (updateCurrentResults && this._currentNewExtensionsSearchProvider) || this.preferencesSearchService.getRemoteSearchProvider(query, true);
this._remoteFilterInProgress = this.filterOrSearchPreferences(query, this._currentRemoteSearchProvider, 'nlpResult', nls.localize('nlpResult', "Natural Language Results"), 1)
.then(result => this.filterOrSearchPreferences(query, this._currentNewExtensionsSearchProvider, 'newExtensionsResult', nls.localize('newExtensionsResult', "Other Extension Results"), 2));
.then(result => this.filterOrSearchPreferences(query, this._currentNewExtensionsSearchProvider, 'newExtensionsResult', nls.localize('newExtensionsResult', "Marketplace Extension Results"), 2));
return this._remoteFilterInProgress.then(() => {
this._remoteFilterInProgress = null;
@@ -113,7 +113,6 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend
if (this.filterResult) {
data['query'] = this.filterResult.query;
data['duration'] = this.filterResult.metadata && this.filterResult.metadata.duration;
data['index'] = source.index;
data['groupId'] = source.groupId;
data['editableSide'] = !!fromEditableSettings;
@@ -123,9 +122,8 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend
"defaultSettingsActions.copySetting" : {
"userConfigurationKeys" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"query" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"groupId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"index" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"groupId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"editableSide" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
@@ -622,31 +620,35 @@ export class FeedbackWidgetRenderer extends Disposable {
}
const result = this._currentResult;
const actualResults = result.metadata.scoredResults;
const metadata = result.metadata['nlpResult']; // Feedback only on nlpResult set for now
const marketplaceExtensionsResults = result.metadata['newExtensionsResult'] && result.metadata['newExtensionsResult'].scoredResults;
const actualResults = metadata ? metadata.scoredResults : {};
const actualResultIds = Object.keys(actualResults);
const feedbackQuery: any = {};
feedbackQuery['comment'] = FeedbackWidgetRenderer.DEFAULT_COMMENT_TEXT;
feedbackQuery['queryString'] = result.query;
feedbackQuery['resultScores'] = {};
feedbackQuery['resultScores'] = [];
actualResultIds.forEach(settingId => {
const outputKey = actualResults[settingId].key;
feedbackQuery['resultScores'][outputKey] = 10;
feedbackQuery['resultScores'].push({
packageID: actualResults[settingId].packageId,
key: actualResults[settingId].key,
score: 10
});
});
feedbackQuery['alts'] = [];
const contents = FeedbackWidgetRenderer.INSTRUCTION_TEXT + '\n' +
JSON.stringify(feedbackQuery, undefined, ' ') + '\n\n' +
actualResultIds.map(name => {
return `// ${actualResults[name].key}: ${actualResults[name].score}`;
}).join('\n');
this.getScoreText(actualResults) + '\n\n' +
this.getScoreText(marketplaceExtensionsResults) + '\n';
this.editorService.openEditor({ contents, language: 'jsonc' }, /*sideBySide=*/true).then(feedbackEditor => {
const sendFeedbackWidget = this._register(this.instantiationService.createInstance(FloatingClickWidget, feedbackEditor.getControl(), 'Send feedback', null));
sendFeedbackWidget.render();
this._register(sendFeedbackWidget.onClick(() => {
this.sendFeedback(feedbackEditor.getControl() as ICodeEditor, result, result.metadata.scoredResults).then(() => {
this.sendFeedback(feedbackEditor.getControl() as ICodeEditor, result, metadata.scoredResults).then(() => {
sendFeedbackWidget.dispose();
this.messageService.show(Severity.Info, 'Feedback sent successfully');
}, err => {
@@ -656,6 +658,17 @@ export class FeedbackWidgetRenderer extends Disposable {
});
}
private getScoreText(results?: IScoredResults): string {
if (!results) {
return '';
}
return Object.keys(results)
.map(name => {
return `// ${results[name].key}: ${results[name].score}`;
}).join('\n');
}
private sendFeedback(feedbackEditor: ICodeEditor, result: IFilterResult, actualResults: IScoredResults): TPromise<void> {
const model = feedbackEditor.getModel();
const expectedQueryLines = model.getLinesContent()
@@ -685,11 +698,9 @@ export class FeedbackWidgetRenderer extends Disposable {
"settingsSearchResultFeedback" : {
"query" : { "classification": "CustomContent", "purpose": "FeatureInsight" },
"userComment" : { "classification": "CustomerContent", "purpose": "FeatureInsight" },
"actualResults" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"expectedResults" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"url" : { "classification": "CustomerContent", "purpose": "FeatureInsight" },
"actualResults" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"timestamp" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
return this.telemetryService.publicLog('settingsSearchResultFeedback', {
@@ -697,9 +708,7 @@ export class FeedbackWidgetRenderer extends Disposable {
userComment,
actualResults,
expectedResults: expectedQuery.resultScores,
url: result.metadata.remoteUrl,
duration: result.metadata.duration,
timestamp: result.metadata.timestamp,
duration: result.metadata['nlpResult'].duration,
buildNumber: this.environmentService.settingsSearchBuildId,
alts,
autoIngest
@@ -877,7 +886,7 @@ export class ExtensionCodelensRenderer extends Disposable implements CodeLensPro
const extId = s.extensionPublisher + '.' + s.extensionName;
return <ICodeLensSymbol>{
command: {
title: nls.localize('newExtensionLabel', "View \"{0}\"", extId),
title: nls.localize('newExtensionLabel', "Show Extension \"{0}\"", extId),
id: 'workbench.extensions.action.showExtensionsWithId',
arguments: [extId.toLowerCase()]
},
@@ -16,6 +16,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { join } from 'vs/base/common/paths';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import Event from 'vs/base/common/event';
import { IStringDictionary } from 'vs/base/common/collections';
export interface IWorkbenchSettingsConfiguration {
workbench: {
@@ -79,7 +80,7 @@ export interface IFilterResult {
filteredGroups: ISettingsGroup[];
allGroups: ISettingsGroup[];
matches: IRange[];
metadata?: IFilterMetadata;
metadata?: IStringDictionary<IFilterMetadata>;
}
export interface ISettingMatch {
@@ -104,7 +105,8 @@ export interface IRemoteSetting {
}
export interface IFilterMetadata {
remoteUrl: string;
requestUrl: string;
requestBody: string;
timestamp: number;
duration: number;
scoredResults: IScoredResults;
@@ -6,7 +6,7 @@
import * as nls from 'vs/nls';
import { assign } from 'vs/base/common/objects';
import * as map from 'vs/base/common/map';
import { tail, flatten, first } from 'vs/base/common/arrays';
import { tail, flatten } from 'vs/base/common/arrays';
import URI from 'vs/base/common/uri';
import { IReference, Disposable } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
@@ -15,12 +15,13 @@ import { visit, JSONVisitor } from 'vs/base/common/json';
import { ITextModel, IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
import { EditorModel } from 'vs/workbench/common/editor';
import { IConfigurationNode, IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN, IConfigurationPropertySchema, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { ISettingsEditorModel, IKeybindingsEditorModel, ISettingsGroup, ISetting, IFilterResult, IGroupFilter, ISettingMatcher, ISettingMatch, ISearchResultGroup } from 'vs/workbench/parts/preferences/common/preferences';
import { ISettingsEditorModel, IKeybindingsEditorModel, ISettingsGroup, ISetting, IFilterResult, IGroupFilter, ISettingMatcher, ISettingMatch, ISearchResultGroup, IFilterMetadata } from 'vs/workbench/parts/preferences/common/preferences';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { IRange, Range } from 'vs/editor/common/core/range';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { Selection } from 'vs/editor/common/core/selection';
import { IStringDictionary } from 'vs/base/common/collections';
export abstract class AbstractSettingsModel extends EditorModel {
@@ -114,6 +115,19 @@ export abstract class AbstractSettingsModel extends EditorModel {
};
}
protected collectMetadata(groups: ISearchResultGroup[]): IStringDictionary<IFilterMetadata> {
const metadata = Object.create(null);
let hasMetadata = false;
groups.forEach(g => {
if (g.result.metadata) {
metadata[g.id] = g.result.metadata;
hasMetadata = true;
}
});
return hasMetadata ? metadata : null;
}
protected get filterGroups(): ISettingsGroup[] {
return this.settingsGroups;
@@ -205,12 +219,12 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti
};
}
const groupWithMetadata = first(resultGroups, group => !!group.result.metadata);
const metadata = this.collectMetadata(resultGroups);
return <IFilterResult>{
allGroups: this.settingsGroups,
filteredGroups: filteredGroup ? [filteredGroup] : [],
matches,
metadata: groupWithMetadata && groupWithMetadata.result.metadata
metadata
};
}
}
@@ -648,13 +662,13 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
const startLine = tail(this.settingsGroups).range.endLineNumber + 2;
const { settingsGroups: filteredGroups, matches } = this.writeResultGroups(nonEmptyResultGroups, startLine);
const groupWithMetadata = first(resultGroups, group => !!group.result.metadata);
const metadata = this.collectMetadata(resultGroups);
return resultGroups.length ?
<IFilterResult>{
allGroups: this.settingsGroups,
filteredGroups,
matches,
metadata: groupWithMetadata && groupWithMetadata.result.metadata
metadata
} :
null;
}
@@ -215,6 +215,9 @@ class RemoteSearchProvider implements ISearchProvider {
const packageId = r['packageid'];
const id = getSettingKey(key, packageId);
const value = r['value'];
const defaultValue = value ? JSON.parse(value) : value;
const packageName = r['packagename'];
let extensionName: string;
let extensionPublisher: string;
@@ -225,7 +228,7 @@ class RemoteSearchProvider implements ISearchProvider {
return <IRemoteSetting>{
key,
id,
defaultValue: r['value'],
defaultValue,
score: r['@search.score'],
description: JSON.parse(r['details']),
packageId,
@@ -240,7 +243,8 @@ class RemoteSearchProvider implements ISearchProvider {
});
return <IFilterMetadata>{
remoteUrl: details.url, // telemetry for filter text?
requestUrl: details.url,
requestBody: details.body,
duration,
timestamp,
scoredResults,
@@ -334,7 +338,7 @@ class RemoteSearchProvider implements ISearchProvider {
function getSettingKey(name: string, packageId?: string): string {
return packageId ?
packageId + '_' + name :
packageId + '##' + name :
name;
}
@@ -11,8 +11,8 @@ import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel
import { QuickOpenModel, QuickOpenEntryGroup, QuickOpenEntry } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { QuickOpenHandler, QuickOpenAction } from 'vs/workbench/browser/quickopen';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IOutputService, OUTPUT_PANEL_ID } from 'vs/workbench/parts/output/common/output';
import { ITerminalService, TERMINAL_PANEL_ID } from 'vs/workbench/parts/terminal/common/terminal';
import { IOutputService } from 'vs/workbench/parts/output/common/output';
import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { Action } from 'vs/base/common/actions';
@@ -138,40 +138,27 @@ export class ViewPickerHandler extends QuickOpenHandler {
// Viewlets
const viewlets = this.viewletService.getViewlets();
viewlets.forEach((viewlet, index) => viewEntries.push(new ViewEntry(viewlet.name, nls.localize('views', "Views"), () => this.viewletService.openViewlet(viewlet.id, true).done(null, errors.onUnexpectedError))));
// Panels
const panels = this.panelService.getPanels();
panels.forEach((panel, index) => viewEntries.push(new ViewEntry(panel.name, nls.localize('panels', "Panels"), () => this.panelService.openPanel(panel.id, true).done(null, errors.onUnexpectedError))));
// Views
viewlets.forEach((viewlet, index) => {
const viewLocation: ViewLocation = viewlet.id === EXPLORER_VIEWLET_ID ? ViewLocation.Explorer
: viewlet.id === DEBUG_VIEWLET_ID ? ViewLocation.Debug
: viewlet.id === EXTENSIONS_VIEWLET_ID ? ViewLocation.Extensions
: null;
const viewEntriesForViewlet: ViewEntry[] = viewLocation ? getViewEntriesForViewlet(viewlet, viewLocation)
: [new ViewEntry(viewlet.name, nls.localize('views', "Views"), () => this.viewletService.openViewlet(viewlet.id, true).done(null, errors.onUnexpectedError))];
viewEntries.push(...viewEntriesForViewlet);
});
const terminals = this.terminalService.terminalInstances;
// Panels
const panels = this.panelService.getPanels().filter(p => {
if (p.id === OUTPUT_PANEL_ID) {
return false; // since we already show output channels below
if (viewLocation) {
const viewEntriesForViewlet: ViewEntry[] = getViewEntriesForViewlet(viewlet, viewLocation);
viewEntries.push(...viewEntriesForViewlet);
}
if (p.id === TERMINAL_PANEL_ID && terminals.length > 0) {
return false; // since we already show terminal instances below
}
return true;
});
panels.forEach((panel, index) => {
const panelsCategory = nls.localize('panels', "Panels");
const entry = new ViewEntry(panel.name, panelsCategory, () => this.panelService.openPanel(panel.id, true).done(null, errors.onUnexpectedError));
viewEntries.push(entry);
});
// Terminals
const terminals = this.terminalService.terminalInstances;
terminals.forEach((terminal, index) => {
const terminalsCategory = nls.localize('terminals', "Terminal");
const entry = new ViewEntry(nls.localize('terminalTitle', "{0}: {1}", index + 1, terminal.title), terminalsCategory, () => {
@@ -15,7 +15,7 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { Match, FileMatch, FileMatchOrMatch, ISearchWorkbenchService } from 'vs/workbench/parts/search/common/searchModel';
import { BulkEdit, IResourceEdit, createBulkEdit } from 'vs/editor/browser/services/bulkEdit';
import { BulkEdit } from 'vs/editor/browser/services/bulkEdit';
import { IProgressRunner } from 'vs/platform/progress/common/progress';
import { IDiffEditor } from 'vs/editor/browser/editorBrowser';
import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
@@ -24,6 +24,7 @@ import { ScrollType } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IFileService } from 'vs/platform/files/common/files';
import { ResourceTextEdit } from 'vs/editor/common/modes';
import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel';
const REPLACE_PREVIEW = 'replacePreview';
@@ -104,8 +105,7 @@ export class ReplaceService implements IReplaceService {
public replace(match: FileMatchOrMatch, progress?: IProgressRunner, resource?: URI): TPromise<any>;
public replace(arg: any, progress: IProgressRunner = null, resource: URI = null): TPromise<any> {
let bulkEdit: BulkEdit = createBulkEdit(this.textModelResolverService, null, this.fileService);
bulkEdit.progress(progress);
let bulkEdit = new BulkEdit(null, progress, this.textModelResolverService, this.fileService);
if (arg instanceof Match) {
let match = <Match>arg;
@@ -127,7 +127,7 @@ export class ReplaceService implements IReplaceService {
});
}
return bulkEdit.finish();
return bulkEdit.perform();
}
public openReplacePreview(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
@@ -175,12 +175,14 @@ export class ReplaceService implements IReplaceService {
});
}
private createEdit(match: Match, text: string, resource: URI = null): IResourceEdit {
private createEdit(match: Match, text: string, resource: URI = null): ResourceTextEdit {
let fileMatch: FileMatch = match.parent();
let resourceEdit: IResourceEdit = {
let resourceEdit: ResourceTextEdit = {
resource: resource !== null ? resource : fileMatch.resource(),
range: match.range(),
newText: text
edits: [{
range: match.range(),
text: text
}]
};
return resourceEdit;
}
@@ -49,6 +49,8 @@ export const TerminalCursorStyle = {
export const TERMINAL_CONFIG_SECTION = 'terminal.integrated';
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
export interface ITerminalConfiguration {
shell: {
linux: string;
@@ -60,12 +62,13 @@ export interface ITerminalConfiguration {
osx: string[];
windows: string[];
};
enableBold: boolean;
macOptionIsMeta: boolean;
rightClickCopyPaste: boolean;
cursorBlinking: boolean;
cursorStyle: string;
fontFamily: string;
fontWeight: FontWeight;
fontWeightBold: FontWeight;
// fontLigatures: boolean;
fontSize: number;
lineHeight: number;
@@ -150,10 +150,17 @@ configurationRegistry.registerConfiguration({
'type': 'number',
'default': 1
},
'terminal.integrated.enableBold': {
'type': 'boolean',
'description': nls.localize('terminal.integrated.enableBold', "Whether to enable bold text within the terminal, note that this requires support from the terminal shell."),
'default': true
'terminal.integrated.fontWeight': {
'type': 'string',
'enum': ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
'description': nls.localize('terminal.integrated.fontWeight', "The font weight to use within the termianl for non-bold text."),
'default': 'normal'
},
'terminal.integrated.fontWeightBold': {
'type': 'string',
'enum': ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
'description': nls.localize('terminal.integrated.fontWeightBold', "The font weight to use within the termianl for bold text."),
'default': 'bold'
},
'terminal.integrated.cursorBlinking': {
'description': nls.localize('terminal.integrated.cursorBlinking', "Controls whether the terminal cursor blinks."),
@@ -278,9 +278,10 @@ export class TerminalInstance implements ITerminalInstance {
scrollback: this._configHelper.config.scrollback,
theme: this._getXtermTheme(),
fontFamily: font.fontFamily,
fontWeight: this._configHelper.config.fontWeight,
fontWeightBold: this._configHelper.config.fontWeightBold,
fontSize: font.fontSize,
lineHeight: font.lineHeight,
enableBold: this._configHelper.config.enableBold,
bellStyle: this._configHelper.config.enableBell ? 'sound' : 'none',
screenReaderMode: accessibilitySupport === 'on',
macOptionIsMeta: this._configHelper.config.macOptionIsMeta
@@ -1056,8 +1057,11 @@ export class TerminalInstance implements ITerminalInstance {
if (this._xterm.getOption('fontFamily') !== font.fontFamily) {
this._xterm.setOption('fontFamily', font.fontFamily);
}
if (this._xterm.getOption('enableBold') !== this._configHelper.config.enableBold) {
this._xterm.setOption('enableBold', this._configHelper.config.enableBold);
if (this._xterm.getOption('fontWeight') !== this._configHelper.config.fontWeight) {
this._xterm.setOption('fontWeight', this._configHelper.config.fontWeight);
}
if (this._xterm.getOption('fontWeightBold') !== this._configHelper.config.fontWeightBold) {
this._xterm.setOption('fontWeightBold', this._configHelper.config.fontWeightBold);
}
}
@@ -52,10 +52,10 @@ export interface IBackupFileService {
* Backs up a resource.
*
* @param resource The resource to back up.
* @param content The content of the resource as value or snapshot.
* @param content The content of the resource as snapshot.
* @param versionId The version id of the resource to backup.
*/
backupResource(resource: Uri, content: string | ITextSnapshot, versionId?: number): TPromise<void>;
backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): TPromise<void>;
/**
* Gets a list of file backups for the current workspace.
@@ -65,13 +65,12 @@ export interface IBackupFileService {
getWorkspaceFileBackups(): TPromise<Uri[]>;
/**
* Parses backup raw text content into the content, removing the metadata that is also stored
* in the file.
* Resolves the backup for the given resource.
*
* @param textBufferFactory The ITextBufferFactory from a backup resource.
* @return The backup file's backed up content.
* @param value The contents from a backup resource as stream.
* @return The backup file's backed up content as text buffer factory.
*/
parseBackupContent(textBufferFactory: ITextBufferFactory): string;
resolveBackupContent(backup: Uri): TPromise<ITextBufferFactory>;
/**
* Discards the backup associated with a resource if it exists..
@@ -10,12 +10,12 @@ import * as crypto from 'crypto';
import * as pfs from 'vs/base/node/pfs';
import Uri from 'vs/base/common/uri';
import { ResourceQueue } from 'vs/base/common/async';
import { IBackupFileService, BACKUP_FILE_UPDATE_OPTIONS } from 'vs/workbench/services/backup/common/backup';
import { IFileService, ITextSnapshot, IFileStat } from 'vs/platform/files/common/files';
import { IBackupFileService, BACKUP_FILE_UPDATE_OPTIONS, BACKUP_FILE_RESOLVE_OPTIONS } from 'vs/workbench/services/backup/common/backup';
import { IFileService, ITextSnapshot } from 'vs/platform/files/common/files';
import { TPromise } from 'vs/base/common/winjs.base';
import { readToMatchingString } from 'vs/base/node/stream';
import { Range } from 'vs/editor/common/core/range';
import { DefaultEndOfLine, ITextBufferFactory, EndOfLinePreference } from 'vs/editor/common/model';
import { ITextBufferFactory } from 'vs/editor/common/model';
import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel';
export interface IBackupFilesModel {
resolve(backupRoot: string): TPromise<IBackupFilesModel>;
@@ -171,7 +171,7 @@ export class BackupFileService implements IBackupFileService {
});
}
public backupResource(resource: Uri, content: string | ITextSnapshot, versionId?: number): TPromise<void> {
public backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): TPromise<void> {
if (this.isShuttingDown) {
return TPromise.as(void 0);
}
@@ -190,17 +190,7 @@ export class BackupFileService implements IBackupFileService {
const preamble = `${resource.toString()}${BackupFileService.META_MARKER}`;
// Update content with value
let updateContentPromise: TPromise<IFileStat>;
if (typeof content === 'string') {
updateContentPromise = this.fileService.updateContent(backupResource, `${preamble}${content}`, BACKUP_FILE_UPDATE_OPTIONS);
}
// Update content with snapshot
else {
updateContentPromise = this.fileService.updateContent(backupResource, new BackupSnapshot(content, preamble), BACKUP_FILE_UPDATE_OPTIONS);
}
return updateContentPromise.then(() => model.add(backupResource, versionId));
return this.fileService.updateContent(backupResource, new BackupSnapshot(content, preamble), BACKUP_FILE_UPDATE_OPTIONS).then(() => model.add(backupResource, versionId));
});
});
}
@@ -245,12 +235,27 @@ export class BackupFileService implements IBackupFileService {
});
}
public parseBackupContent(textBufferFactory: ITextBufferFactory): string {
// The first line of a backup text file is the file name
const textBuffer = textBufferFactory.create(DefaultEndOfLine.LF);
const lineCount = textBuffer.getLineCount();
const range = new Range(2, 1, lineCount, textBuffer.getLineLength(lineCount) + 1);
return textBuffer.getValueInRange(range, EndOfLinePreference.TextDefined);
public resolveBackupContent(backup: Uri): TPromise<ITextBufferFactory> {
return this.fileService.resolveStreamContent(backup, BACKUP_FILE_RESOLVE_OPTIONS).then(content => {
// Add a filter method to filter out everything until the meta marker
let metaFound = false;
const metaPreambleFilter = (chunk: string) => {
if (!metaFound && chunk) {
const metaIndex = chunk.indexOf(BackupFileService.META_MARKER);
if (metaIndex === -1) {
return ''; // meta not yet found, return empty string
}
metaFound = true;
return chunk.substr(metaIndex + 1); // meta found, return everything after
}
return chunk;
};
return createTextBufferFactoryFromStream(content.value, metaPreambleFilter);
});
}
public toBackupResource(resource: Uri): Uri {

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