mirror of
https://github.com/Microsoft/vscode
synced 2024-10-12 06:17:18 +00:00
Merge remote-tracking branch 'origin/master' into alex/asar
This commit is contained in:
commit
2970cf5517
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
|
@ -81,12 +81,26 @@
|
|||
{
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"name": "VS Code API Tests",
|
||||
"name": "VS Code API Tests (single folder)",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"${workspaceFolder}/extensions/vscode-api-tests/testWorkspace",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extensions/vscode-api-tests",
|
||||
"--extensionTestsPath=${workspaceFolder}/extensions/vscode-api-tests/out"
|
||||
"--extensionTestsPath=${workspaceFolder}/extensions/vscode-api-tests/out/singlefolder-tests"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"name": "VS Code API Tests (workspace)",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"${workspaceFolder}/extensions/vscode-api-tests/testWorkspace.code-workspace",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extensions/vscode-api-tests",
|
||||
"--extensionTestsPath=${workspaceFolder}/extensions/vscode-api-tests/out/workspace-tests"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[
|
||||
{
|
||||
"name": "ms-vscode.node-debug",
|
||||
"version": "1.20.4",
|
||||
"version": "1.20.6",
|
||||
"repo": "https://github.com/Microsoft/vscode-node-debug"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -77,7 +77,8 @@ const indentationFilter = [
|
|||
'!extensions/**/syntaxes/**',
|
||||
'!extensions/**/themes/**',
|
||||
'!extensions/**/colorize-fixtures/**',
|
||||
'!extensions/vscode-api-tests/testWorkspace/**'
|
||||
'!extensions/vscode-api-tests/testWorkspace/**',
|
||||
'!extensions/vscode-api-tests/testWorkspace2/**'
|
||||
];
|
||||
|
||||
const copyrightFilter = [
|
||||
|
@ -95,6 +96,7 @@ const copyrightFilter = [
|
|||
'!**/*.xpm',
|
||||
'!**/*.opts',
|
||||
'!**/*.disabled',
|
||||
'!**/*.code-workspace',
|
||||
'!build/**/*.init',
|
||||
'!resources/linux/snap/snapcraft.yaml',
|
||||
'!resources/win32/bin/code.js',
|
||||
|
@ -124,6 +126,7 @@ const tslintFilter = [
|
|||
'!**/node_modules/**',
|
||||
'!extensions/typescript/test/colorize-fixtures/**',
|
||||
'!extensions/vscode-api-tests/testWorkspace/**',
|
||||
'!extensions/vscode-api-tests/testWorkspace2/**',
|
||||
'!extensions/**/*.test.ts',
|
||||
'!extensions/html/server/lib/jquery.d.ts'
|
||||
];
|
||||
|
|
|
@ -401,7 +401,7 @@ const apiHostname = process.env.TRANSIFEX_API_URL;
|
|||
const apiName = process.env.TRANSIFEX_API_NAME;
|
||||
const apiToken = process.env.TRANSIFEX_API_TOKEN;
|
||||
|
||||
gulp.task('vscode-translations-push', ['optimize-vscode'], function () {
|
||||
gulp.task('vscode-translations-push', function () {
|
||||
const pathToMetadata = './out-vscode/nls.metadata.json';
|
||||
const pathToExtensions = './extensions/*';
|
||||
const pathToSetup = 'build/win32/**/{Default.isl,messages.en.isl}';
|
||||
|
@ -410,6 +410,7 @@ gulp.task('vscode-translations-push', ['optimize-vscode'], function () {
|
|||
gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()),
|
||||
gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()),
|
||||
gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions())
|
||||
).pipe(i18n.findObsoleteResources(apiHostname, apiName, apiToken)
|
||||
).pipe(i18n.pushXlfFiles(apiHostname, apiName, apiToken));
|
||||
});
|
||||
|
||||
|
@ -422,12 +423,13 @@ gulp.task('vscode-translations-push-test', function () {
|
|||
gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()),
|
||||
gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()),
|
||||
gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions())
|
||||
).pipe(i18n.findObsoleteResources(apiHostname, apiName, apiToken)
|
||||
).pipe(vfs.dest('../vscode-transifex-input'));
|
||||
});
|
||||
|
||||
gulp.task('vscode-translations-pull', function () {
|
||||
[...i18n.defaultLanguages, ...i18n.extraLanguages].forEach(language => {
|
||||
i18n.pullBuildXlfFiles(apiHostname, apiName, apiToken, language).pipe(vfs.dest(`../vscode-localization/${language.id}/build`));
|
||||
i18n.pullCoreAndExtensionsXlfFiles(apiHostname, apiName, apiToken, language).pipe(vfs.dest(`../vscode-localization/${language.id}/build`));
|
||||
|
||||
let includeDefault = !!innoSetupConfig[language.id].defaultInfo;
|
||||
i18n.pullSetupXlfFiles(apiHostname, apiName, apiToken, language, includeDefault).pipe(vfs.dest(`../vscode-localization/${language.id}/setup`));
|
||||
|
|
|
@ -15,7 +15,7 @@ var https = require("https");
|
|||
var gulp = require("gulp");
|
||||
var util = require('gulp-util');
|
||||
var iconv = require('iconv-lite');
|
||||
var NUMBER_OF_CONCURRENT_DOWNLOADS = 1;
|
||||
var NUMBER_OF_CONCURRENT_DOWNLOADS = 4;
|
||||
function log(message) {
|
||||
var rest = [];
|
||||
for (var _i = 1; _i < arguments.length; _i++) {
|
||||
|
@ -41,6 +41,14 @@ exports.extraLanguages = [
|
|||
{ id: 'tr', folderName: 'trk' }
|
||||
];
|
||||
exports.pseudoLanguage = { id: 'pseudo', folderName: 'pseudo', transifexId: 'pseudo' };
|
||||
// non built-in extensions also that are transifex and need to be part of the language packs
|
||||
var externalExtensionsWithTranslations = [
|
||||
//"azure-account",
|
||||
"vscode-chrome-debug",
|
||||
"vscode-chrome-debug-core",
|
||||
"vscode-node-debug",
|
||||
"vscode-node-debug2"
|
||||
];
|
||||
var LocalizeInfo;
|
||||
(function (LocalizeInfo) {
|
||||
function is(value) {
|
||||
|
@ -121,6 +129,7 @@ var XLF = /** @class */ (function () {
|
|||
this.project = project;
|
||||
this.buffer = [];
|
||||
this.files = Object.create(null);
|
||||
this.numberOfMessages = 0;
|
||||
}
|
||||
XLF.prototype.toString = function () {
|
||||
this.appendHeader();
|
||||
|
@ -139,6 +148,7 @@ var XLF = /** @class */ (function () {
|
|||
if (keys.length !== messages.length) {
|
||||
throw new Error("Unmatching keys(" + keys.length + ") and messages(" + messages.length + ").");
|
||||
}
|
||||
this.numberOfMessages += keys.length;
|
||||
this.files[original] = [];
|
||||
var existingKeys = new Set();
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
|
@ -701,6 +711,88 @@ function pushXlfFiles(apiHostname, username, password) {
|
|||
});
|
||||
}
|
||||
exports.pushXlfFiles = pushXlfFiles;
|
||||
function getAllResources(project, apiHostname, username, password) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var credentials = username + ":" + password;
|
||||
var options = {
|
||||
hostname: apiHostname,
|
||||
path: "/api/2/project/" + project + "/resources",
|
||||
auth: credentials,
|
||||
method: 'GET'
|
||||
};
|
||||
var request = https.request(options, function (res) {
|
||||
var buffer = [];
|
||||
res.on('data', function (chunk) { return buffer.push(chunk); });
|
||||
res.on('end', function () {
|
||||
if (res.statusCode === 200) {
|
||||
var json = JSON.parse(Buffer.concat(buffer).toString());
|
||||
if (Array.isArray(json)) {
|
||||
resolve(json.map(function (o) { return o.slug; }));
|
||||
return;
|
||||
}
|
||||
reject("Unexpected data format. Response code: " + res.statusCode + ".");
|
||||
}
|
||||
else {
|
||||
reject("No resources in " + project + " returned no data. Response code: " + res.statusCode + ".");
|
||||
}
|
||||
});
|
||||
});
|
||||
request.on('error', function (err) {
|
||||
reject("Failed to query resources in " + project + " with the following error: " + err + ". " + options.path);
|
||||
});
|
||||
request.end();
|
||||
});
|
||||
}
|
||||
function findObsoleteResources(apiHostname, username, password) {
|
||||
var resourcesByProject = Object.create(null);
|
||||
resourcesByProject[extensionsProject] = [].concat(externalExtensionsWithTranslations); // clone
|
||||
return event_stream_1.through(function (file) {
|
||||
var project = path.dirname(file.relative);
|
||||
var fileName = path.basename(file.path);
|
||||
var slug = fileName.substr(0, fileName.length - '.xlf'.length);
|
||||
var slugs = resourcesByProject[project];
|
||||
if (!slugs) {
|
||||
resourcesByProject[project] = slugs = [];
|
||||
}
|
||||
slugs.push(slug);
|
||||
this.push(file);
|
||||
}, function () {
|
||||
var _this = this;
|
||||
var json = JSON.parse(fs.readFileSync('./build/lib/i18n.resources.json', 'utf8'));
|
||||
var i18Resources = json.editor.concat(json.workbench).map(function (r) { return r.project + '/' + r.name.replace(/\//g, '_'); });
|
||||
var extractedResources = [];
|
||||
for (var _i = 0, _a = [workbenchProject, editorProject]; _i < _a.length; _i++) {
|
||||
var project = _a[_i];
|
||||
for (var _b = 0, _c = resourcesByProject[project]; _b < _c.length; _b++) {
|
||||
var resource = _c[_b];
|
||||
if (resource !== 'setup_messages') {
|
||||
extractedResources.push(project + '/' + resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i18Resources.length !== extractedResources.length) {
|
||||
console.log("[i18n] Obsolete resources in file 'build/lib/i18n.resources.json': JSON.stringify(" + i18Resources.filter(function (p) { return extractedResources.indexOf(p) === -1; }) + ")");
|
||||
console.log("[i18n] Missing resources in file 'build/lib/i18n.resources.json': JSON.stringify(" + extractedResources.filter(function (p) { return i18Resources.indexOf(p) === -1; }) + ")");
|
||||
}
|
||||
var promises = [];
|
||||
var _loop_1 = function (project) {
|
||||
promises.push(getAllResources(project, apiHostname, username, password).then(function (resources) {
|
||||
var expectedResources = resourcesByProject[project];
|
||||
var unusedResources = resources.filter(function (resource) { return resource && expectedResources.indexOf(resource) === -1; });
|
||||
if (unusedResources.length) {
|
||||
console.log("[transifex] Obsolete resources in project '" + project + "': " + unusedResources.join(', '));
|
||||
}
|
||||
}));
|
||||
};
|
||||
for (var project in resourcesByProject) {
|
||||
_loop_1(project);
|
||||
}
|
||||
return Promise.all(promises).then(function (_) {
|
||||
_this.push(null);
|
||||
}).catch(function (reason) { throw new Error(reason); });
|
||||
});
|
||||
}
|
||||
exports.findObsoleteResources = findObsoleteResources;
|
||||
function tryGetResource(project, slug, apiHostname, credentials) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var options = {
|
||||
|
@ -801,29 +893,35 @@ function updateResource(project, slug, xlfFile, apiHostname, credentials) {
|
|||
});
|
||||
}
|
||||
// cache resources
|
||||
var _buildResources;
|
||||
function pullBuildXlfFiles(apiHostname, username, password, language) {
|
||||
if (!_buildResources) {
|
||||
_buildResources = [];
|
||||
var _coreAndExtensionResources;
|
||||
function pullCoreAndExtensionsXlfFiles(apiHostname, username, password, language, includeExternalExtensions) {
|
||||
if (!_coreAndExtensionResources) {
|
||||
_coreAndExtensionResources = [];
|
||||
// editor and workbench
|
||||
var json = JSON.parse(fs.readFileSync('./build/lib/i18n.resources.json', 'utf8'));
|
||||
_buildResources.push.apply(_buildResources, json.editor);
|
||||
_buildResources.push.apply(_buildResources, json.workbench);
|
||||
_coreAndExtensionResources.push.apply(_coreAndExtensionResources, json.editor);
|
||||
_coreAndExtensionResources.push.apply(_coreAndExtensionResources, json.workbench);
|
||||
// extensions
|
||||
var extensionsToLocalize_1 = Object.create(null);
|
||||
glob.sync('./extensions/**/*.nls.json').forEach(function (extension) { return extensionsToLocalize_1[extension.split('/')[2]] = true; });
|
||||
glob.sync('./extensions/*/node_modules/vscode-nls').forEach(function (extension) { return extensionsToLocalize_1[extension.split('/')[2]] = true; });
|
||||
if (includeExternalExtensions) {
|
||||
for (var _i = 0, externalExtensionsWithTranslations_1 = externalExtensionsWithTranslations; _i < externalExtensionsWithTranslations_1.length; _i++) {
|
||||
var extension = externalExtensionsWithTranslations_1[_i];
|
||||
extensionsToLocalize_1[extension] = true;
|
||||
}
|
||||
}
|
||||
Object.keys(extensionsToLocalize_1).forEach(function (extension) {
|
||||
_buildResources.push({ name: extension, project: 'vscode-extensions' });
|
||||
_coreAndExtensionResources.push({ name: extension, project: extensionsProject });
|
||||
});
|
||||
}
|
||||
return pullXlfFiles(apiHostname, username, password, language, _buildResources);
|
||||
return pullXlfFiles(apiHostname, username, password, language, _coreAndExtensionResources);
|
||||
}
|
||||
exports.pullBuildXlfFiles = pullBuildXlfFiles;
|
||||
exports.pullCoreAndExtensionsXlfFiles = pullCoreAndExtensionsXlfFiles;
|
||||
function pullSetupXlfFiles(apiHostname, username, password, language, includeDefault) {
|
||||
var setupResources = [{ name: 'setup_messages', project: 'vscode-workbench' }];
|
||||
var setupResources = [{ name: 'setup_messages', project: workbenchProject }];
|
||||
if (includeDefault) {
|
||||
setupResources.push({ name: 'setup_default', project: 'vscode-setup' });
|
||||
setupResources.push({ name: 'setup_default', project: setupProject });
|
||||
}
|
||||
return pullXlfFiles(apiHostname, username, password, language, setupResources);
|
||||
}
|
||||
|
@ -865,6 +963,7 @@ function retrieveResource(language, resource, apiHostname, credentials) {
|
|||
port: 443,
|
||||
method: 'GET'
|
||||
};
|
||||
console.log('Fetching ' + options.path);
|
||||
var request = https.request(options, function (res) {
|
||||
var xlfBuffer = [];
|
||||
res.on('data', function (chunk) { return xlfBuffer.push(chunk); });
|
||||
|
@ -928,7 +1027,7 @@ function createI18nFile(originalFilePath, messages) {
|
|||
}
|
||||
var i18nPackVersion = "1.0.0";
|
||||
function pullI18nPackFiles(apiHostname, username, password, language) {
|
||||
return pullBuildXlfFiles(apiHostname, username, password, language).pipe(prepareI18nPackFiles());
|
||||
return pullCoreAndExtensionsXlfFiles(apiHostname, username, password, language, true).pipe(prepareI18nPackFiles());
|
||||
}
|
||||
exports.pullI18nPackFiles = pullI18nPackFiles;
|
||||
function prepareI18nPackFiles() {
|
||||
|
@ -937,32 +1036,26 @@ function prepareI18nPackFiles() {
|
|||
var extensionsPacks = {};
|
||||
return event_stream_1.through(function (xlf) {
|
||||
var stream = this;
|
||||
var project = path.dirname(xlf.path);
|
||||
var resource = path.basename(xlf.path, '.xlf');
|
||||
console.log(resource);
|
||||
var parsePromise = XLF.parse(xlf.contents.toString());
|
||||
parsePromises.push(parsePromise);
|
||||
parsePromise.then(function (resolvedFiles) {
|
||||
resolvedFiles.forEach(function (file) {
|
||||
var path = file.originalFilePath;
|
||||
var firstSlash = path.indexOf('/');
|
||||
var firstSegment = path.substr(0, firstSlash);
|
||||
if (firstSegment === 'src') {
|
||||
mainPack.contents[path.substr(firstSlash + 1)] = file.messages;
|
||||
}
|
||||
else if (firstSegment === 'extensions') {
|
||||
if (project === extensionsProject) {
|
||||
var extPack = extensionsPacks[resource];
|
||||
if (!extPack) {
|
||||
extPack = extensionsPacks[resource] = { version: i18nPackVersion, contents: {} };
|
||||
}
|
||||
var secondSlash = path.indexOf('/', firstSlash + 1);
|
||||
var secondSegment = path.substring(firstSlash + 1, secondSlash);
|
||||
if (secondSegment) {
|
||||
var extPack = extensionsPacks[secondSegment];
|
||||
if (!extPack) {
|
||||
extPack = extensionsPacks[secondSegment] = { version: i18nPackVersion, contents: {} };
|
||||
}
|
||||
extPack.contents[path.substr(secondSlash + 1)] = file.messages;
|
||||
}
|
||||
else {
|
||||
console.log('Unknown second segment ' + path);
|
||||
}
|
||||
var key = externalExtensionsWithTranslations.indexOf(resource) !== -1 ? path : path.substr(secondSlash + 1);
|
||||
extPack.contents[key] = file.messages;
|
||||
}
|
||||
else {
|
||||
console.log('Unknown first segment ' + path);
|
||||
mainPack.contents[path.substr(firstSlash + 1)] = file.messages;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -46,10 +46,6 @@
|
|||
"name": "vs/workbench/parts/execution",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/parts/explorers",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/parts/extensions",
|
||||
"project": "vscode-workbench"
|
||||
|
@ -74,10 +70,6 @@
|
|||
"name": "vs/workbench/parts/logs",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/parts/nps",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/parts/output",
|
||||
"project": "vscode-workbench"
|
||||
|
|
|
@ -17,7 +17,7 @@ import * as gulp from 'gulp';
|
|||
var util = require('gulp-util');
|
||||
var iconv = require('iconv-lite');
|
||||
|
||||
const NUMBER_OF_CONCURRENT_DOWNLOADS = 1;
|
||||
const NUMBER_OF_CONCURRENT_DOWNLOADS = 4;
|
||||
|
||||
function log(message: any, ...rest: any[]): void {
|
||||
util.log(util.colors.green('[i18n]'), message, ...rest);
|
||||
|
@ -58,6 +58,15 @@ export const extraLanguages: Language[] = [
|
|||
|
||||
export const pseudoLanguage: Language = { id: 'pseudo', folderName: 'pseudo', transifexId: 'pseudo' };
|
||||
|
||||
// non built-in extensions also that are transifex and need to be part of the language packs
|
||||
const externalExtensionsWithTranslations = [
|
||||
//"azure-account",
|
||||
"vscode-chrome-debug",
|
||||
"vscode-chrome-debug-core",
|
||||
"vscode-node-debug",
|
||||
"vscode-node-debug2"
|
||||
];
|
||||
|
||||
interface Map<V> {
|
||||
[key: string]: V;
|
||||
}
|
||||
|
@ -193,10 +202,12 @@ class TextModel {
|
|||
export class XLF {
|
||||
private buffer: string[];
|
||||
private files: Map<Item[]>;
|
||||
public numberOfMessages: number;
|
||||
|
||||
constructor(public project: string) {
|
||||
this.buffer = [];
|
||||
this.files = Object.create(null);
|
||||
this.numberOfMessages = 0;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
|
@ -218,6 +229,7 @@ export class XLF {
|
|||
if (keys.length !== messages.length) {
|
||||
throw new Error(`Unmatching keys(${keys.length}) and messages(${messages.length}).`);
|
||||
}
|
||||
this.numberOfMessages += keys.length;
|
||||
this.files[original] = [];
|
||||
let existingKeys = new Set<string>();
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
|
@ -808,6 +820,89 @@ export function pushXlfFiles(apiHostname: string, username: string, password: st
|
|||
});
|
||||
}
|
||||
|
||||
function getAllResources(project: string, apiHostname: string, username: string, password: string): Promise<string[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const credentials = `${username}:${password}`;
|
||||
const options = {
|
||||
hostname: apiHostname,
|
||||
path: `/api/2/project/${project}/resources`,
|
||||
auth: credentials,
|
||||
method: 'GET'
|
||||
};
|
||||
|
||||
const request = https.request(options, (res) => {
|
||||
let buffer: Buffer[] = [];
|
||||
res.on('data', (chunk: Buffer) => buffer.push(chunk));
|
||||
res.on('end', () => {
|
||||
if (res.statusCode === 200) {
|
||||
let json = JSON.parse(Buffer.concat(buffer).toString());
|
||||
if (Array.isArray(json)) {
|
||||
resolve(json.map(o => o.slug));
|
||||
return;
|
||||
}
|
||||
reject(`Unexpected data format. Response code: ${res.statusCode}.`);
|
||||
} else {
|
||||
reject(`No resources in ${project} returned no data. Response code: ${res.statusCode}.`);
|
||||
}
|
||||
});
|
||||
});
|
||||
request.on('error', (err) => {
|
||||
reject(`Failed to query resources in ${project} with the following error: ${err}. ${options.path}`);
|
||||
});
|
||||
request.end();
|
||||
});
|
||||
}
|
||||
|
||||
export function findObsoleteResources(apiHostname: string, username: string, password: string): ThroughStream {
|
||||
let resourcesByProject: Map<string[]> = Object.create(null);
|
||||
resourcesByProject[extensionsProject] = [].concat(externalExtensionsWithTranslations); // clone
|
||||
|
||||
return through(function (this: ThroughStream, file: File) {
|
||||
const project = path.dirname(file.relative);
|
||||
const fileName = path.basename(file.path);
|
||||
const slug = fileName.substr(0, fileName.length - '.xlf'.length);
|
||||
|
||||
let slugs = resourcesByProject[project];
|
||||
if (!slugs) {
|
||||
resourcesByProject[project] = slugs = [];
|
||||
}
|
||||
slugs.push(slug);
|
||||
this.push(file);
|
||||
}, function () {
|
||||
|
||||
const json = JSON.parse(fs.readFileSync('./build/lib/i18n.resources.json', 'utf8'));
|
||||
let i18Resources = [...json.editor, ...json.workbench].map((r: Resource) => r.project + '/' + r.name.replace(/\//g, '_'));
|
||||
let extractedResources = [];
|
||||
for (let project of [workbenchProject, editorProject]) {
|
||||
for (let resource of resourcesByProject[project]) {
|
||||
if (resource !== 'setup_messages') {
|
||||
extractedResources.push(project + '/' + resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i18Resources.length !== extractedResources.length) {
|
||||
console.log(`[i18n] Obsolete resources in file 'build/lib/i18n.resources.json': JSON.stringify(${i18Resources.filter(p => extractedResources.indexOf(p) === -1)})`);
|
||||
console.log(`[i18n] Missing resources in file 'build/lib/i18n.resources.json': JSON.stringify(${extractedResources.filter(p => i18Resources.indexOf(p) === -1)})`);
|
||||
}
|
||||
|
||||
let promises = [];
|
||||
for (let project in resourcesByProject) {
|
||||
promises.push(
|
||||
getAllResources(project, apiHostname, username, password).then(resources => {
|
||||
let expectedResources = resourcesByProject[project];
|
||||
let unusedResources = resources.filter(resource => resource && expectedResources.indexOf(resource) === -1);
|
||||
if (unusedResources.length) {
|
||||
console.log(`[transifex] Obsolete resources in project '${project}': ${unusedResources.join(', ')}`);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
return Promise.all(promises).then(_ => {
|
||||
this.push(null);
|
||||
}).catch((reason) => { throw new Error(reason); });
|
||||
});
|
||||
}
|
||||
|
||||
function tryGetResource(project: string, slug: string, apiHostname: string, credentials: string): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
|
@ -914,32 +1009,38 @@ function updateResource(project: string, slug: string, xlfFile: File, apiHostnam
|
|||
}
|
||||
|
||||
// cache resources
|
||||
let _buildResources: Resource[];
|
||||
let _coreAndExtensionResources: Resource[];
|
||||
|
||||
export function pullBuildXlfFiles(apiHostname: string, username: string, password: string, language: Language): NodeJS.ReadableStream {
|
||||
if (!_buildResources) {
|
||||
_buildResources = [];
|
||||
export function pullCoreAndExtensionsXlfFiles(apiHostname: string, username: string, password: string, language: Language, includeExternalExtensions?: boolean): NodeJS.ReadableStream {
|
||||
if (!_coreAndExtensionResources) {
|
||||
_coreAndExtensionResources = [];
|
||||
// editor and workbench
|
||||
const json = JSON.parse(fs.readFileSync('./build/lib/i18n.resources.json', 'utf8'));
|
||||
_buildResources.push(...json.editor);
|
||||
_buildResources.push(...json.workbench);
|
||||
_coreAndExtensionResources.push(...json.editor);
|
||||
_coreAndExtensionResources.push(...json.workbench);
|
||||
|
||||
// extensions
|
||||
let extensionsToLocalize = Object.create(null);
|
||||
glob.sync('./extensions/**/*.nls.json', ).forEach(extension => extensionsToLocalize[extension.split('/')[2]] = true);
|
||||
glob.sync('./extensions/*/node_modules/vscode-nls', ).forEach(extension => extensionsToLocalize[extension.split('/')[2]] = true);
|
||||
|
||||
if (includeExternalExtensions) {
|
||||
for (let extension of externalExtensionsWithTranslations) {
|
||||
extensionsToLocalize[extension] = true;
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(extensionsToLocalize).forEach(extension => {
|
||||
_buildResources.push({ name: extension, project: 'vscode-extensions' });
|
||||
_coreAndExtensionResources.push({ name: extension, project: extensionsProject });
|
||||
});
|
||||
}
|
||||
return pullXlfFiles(apiHostname, username, password, language, _buildResources);
|
||||
return pullXlfFiles(apiHostname, username, password, language, _coreAndExtensionResources);
|
||||
}
|
||||
|
||||
export function pullSetupXlfFiles(apiHostname: string, username: string, password: string, language: Language, includeDefault: boolean): NodeJS.ReadableStream {
|
||||
let setupResources = [{ name: 'setup_messages', project: 'vscode-workbench' }];
|
||||
let setupResources = [{ name: 'setup_messages', project: workbenchProject }];
|
||||
if (includeDefault) {
|
||||
setupResources.push({ name: 'setup_default', project: 'vscode-setup' });
|
||||
setupResources.push({ name: 'setup_default', project: setupProject });
|
||||
}
|
||||
return pullXlfFiles(apiHostname, username, password, language, setupResources);
|
||||
}
|
||||
|
@ -985,6 +1086,7 @@ function retrieveResource(language: Language, resource: Resource, apiHostname, c
|
|||
port: 443,
|
||||
method: 'GET'
|
||||
};
|
||||
console.log('Fetching ' + options.path);
|
||||
|
||||
let request = https.request(options, (res) => {
|
||||
let xlfBuffer: Buffer[] = [];
|
||||
|
@ -1059,7 +1161,7 @@ interface I18nPack {
|
|||
const i18nPackVersion = "1.0.0";
|
||||
|
||||
export function pullI18nPackFiles(apiHostname: string, username: string, password: string, language: Language): NodeJS.ReadableStream {
|
||||
return pullBuildXlfFiles(apiHostname, username, password, language).pipe(prepareI18nPackFiles());
|
||||
return pullCoreAndExtensionsXlfFiles(apiHostname, username, password, language, true).pipe(prepareI18nPackFiles());
|
||||
}
|
||||
|
||||
export function prepareI18nPackFiles() {
|
||||
|
@ -1068,6 +1170,9 @@ export function prepareI18nPackFiles() {
|
|||
let extensionsPacks: Map<I18nPack> = {};
|
||||
return through(function (this: ThroughStream, xlf: File) {
|
||||
let stream = this;
|
||||
let project = path.dirname(xlf.path);
|
||||
let resource = path.basename(xlf.path, '.xlf');
|
||||
console.log(resource);
|
||||
let parsePromise = XLF.parse(xlf.contents.toString());
|
||||
parsePromises.push(parsePromise);
|
||||
parsePromise.then(
|
||||
|
@ -1075,23 +1180,17 @@ export function prepareI18nPackFiles() {
|
|||
resolvedFiles.forEach(file => {
|
||||
const path = file.originalFilePath;
|
||||
const firstSlash = path.indexOf('/');
|
||||
const firstSegment = path.substr(0, firstSlash);
|
||||
if (firstSegment === 'src') {
|
||||
mainPack.contents[path.substr(firstSlash + 1)] = file.messages;
|
||||
} else if (firstSegment === 'extensions') {
|
||||
const secondSlash = path.indexOf('/', firstSlash + 1);
|
||||
const secondSegment = path.substring(firstSlash + 1, secondSlash);
|
||||
if (secondSegment) {
|
||||
let extPack = extensionsPacks[secondSegment];
|
||||
if (!extPack) {
|
||||
extPack = extensionsPacks[secondSegment] = { version: i18nPackVersion, contents: {} };
|
||||
}
|
||||
extPack.contents[path.substr(secondSlash + 1)] = file.messages;
|
||||
} else {
|
||||
console.log('Unknown second segment ' + path);
|
||||
|
||||
if (project === extensionsProject) {
|
||||
let extPack = extensionsPacks[resource];
|
||||
if (!extPack) {
|
||||
extPack = extensionsPacks[resource] = { version: i18nPackVersion, contents: {} };
|
||||
}
|
||||
const secondSlash = path.indexOf('/', firstSlash + 1);
|
||||
let key = externalExtensionsWithTranslations.indexOf(resource) !== -1 ? path: path.substr(secondSlash + 1);
|
||||
extPack.contents[key] = file.messages;
|
||||
} else {
|
||||
console.log('Unknown first segment ' + path);
|
||||
mainPack.contents[path.substr(firstSlash + 1)] = file.messages;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ function update(idOrPath) {
|
|||
}
|
||||
let locExtFolder = idOrPath;
|
||||
if (/^\w{2}(-\w+)?$/.test(idOrPath)) {
|
||||
locExtFolder = '../vscode-localization-' + idOrPath;
|
||||
locExtFolder = '../vscode-language-pack-' + idOrPath;
|
||||
}
|
||||
let locExtStat = fs.statSync(locExtFolder);
|
||||
if (!locExtStat || !locExtStat.isDirectory) {
|
||||
|
|
|
@ -60,26 +60,26 @@ export class SettingsDocument {
|
|||
private provideFilesAssociationsCompletionItems(location: Location, range: vscode.Range): vscode.ProviderResult<vscode.CompletionItem[]> {
|
||||
const completions: vscode.CompletionItem[] = [];
|
||||
|
||||
// Key
|
||||
if (location.path.length === 1) {
|
||||
completions.push(this.newSnippetCompletionItem({
|
||||
label: localize('assocLabelFile', "Files with Extension"),
|
||||
documentation: localize('assocDescriptionFile', "Map all files matching the glob pattern in their filename to the language with the given identifier."),
|
||||
snippet: location.isAtPropertyKey ? '"*.${1:extension}": "${2:language}"' : '{ "*.${1:extension}": "${2:language}" }',
|
||||
range
|
||||
}));
|
||||
if (location.path.length === 2) {
|
||||
// Key
|
||||
if (!location.isAtPropertyKey || location.path[1] === '') {
|
||||
completions.push(this.newSnippetCompletionItem({
|
||||
label: localize('assocLabelFile', "Files with Extension"),
|
||||
documentation: localize('assocDescriptionFile', "Map all files matching the glob pattern in their filename to the language with the given identifier."),
|
||||
snippet: location.isAtPropertyKey ? '"*.${1:extension}": "${2:language}"' : '{ "*.${1:extension}": "${2:language}" }',
|
||||
range
|
||||
}));
|
||||
|
||||
completions.push(this.newSnippetCompletionItem({
|
||||
label: localize('assocLabelPath', "Files with Path"),
|
||||
documentation: localize('assocDescriptionPath', "Map all files matching the absolute path glob pattern in their path to the language with the given identifier."),
|
||||
snippet: location.isAtPropertyKey ? '"/${1:path to file}/*.${2:extension}": "${3:language}"' : '{ "/${1:path to file}/*.${2:extension}": "${3:language}" }',
|
||||
range
|
||||
}));
|
||||
}
|
||||
|
||||
// Value
|
||||
else if (location.path.length === 2 && !location.isAtPropertyKey) {
|
||||
return this.provideLanguageCompletionItems(location, range);
|
||||
completions.push(this.newSnippetCompletionItem({
|
||||
label: localize('assocLabelPath', "Files with Path"),
|
||||
documentation: localize('assocDescriptionPath', "Map all files matching the absolute path glob pattern in their path to the language with the given identifier."),
|
||||
snippet: location.isAtPropertyKey ? '"/${1:path to file}/*.${2:extension}": "${3:language}"' : '{ "/${1:path to file}/*.${2:extension}": "${3:language}" }',
|
||||
range
|
||||
}));
|
||||
} else {
|
||||
// Value
|
||||
return this.provideLanguageCompletionItems(location, range);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(completions);
|
||||
|
|
|
@ -726,7 +726,7 @@
|
|||
},
|
||||
{
|
||||
"command": "git.openFile2",
|
||||
"when": "scmProvider == git && scmResourceGroup == merge",
|
||||
"when": "scmProvider == git && scmResourceGroup == merge && config.git.showInlineOpenFileAction",
|
||||
"group": "inline0"
|
||||
},
|
||||
{
|
||||
|
@ -756,7 +756,7 @@
|
|||
},
|
||||
{
|
||||
"command": "git.openFile2",
|
||||
"when": "scmProvider == git && scmResourceGroup == index",
|
||||
"when": "scmProvider == git && scmResourceGroup == index && config.git.showInlineOpenFileAction",
|
||||
"group": "inline0"
|
||||
},
|
||||
{
|
||||
|
@ -796,7 +796,7 @@
|
|||
},
|
||||
{
|
||||
"command": "git.openFile2",
|
||||
"when": "scmProvider == git && scmResourceGroup == workingTree",
|
||||
"when": "scmProvider == git && scmResourceGroup == workingTree && config.git.showInlineOpenFileAction",
|
||||
"group": "inline0"
|
||||
},
|
||||
{
|
||||
|
@ -936,6 +936,16 @@
|
|||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%config.decorations.enabled%"
|
||||
},
|
||||
"git.promptToSaveFilesBeforeCommit": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%config.promptToSaveFilesBeforeCommit%"
|
||||
},
|
||||
"git.showInlineOpenFileAction": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%config.showInlineOpenFileAction%"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1012,4 +1022,4 @@
|
|||
"@types/which": "^1.0.28",
|
||||
"mocha": "^3.2.0"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -64,10 +64,12 @@
|
|||
"config.enableCommitSigning": "Enables commit signing with GPG.",
|
||||
"config.discardAllScope": "Controls what changes are discarded by the `Discard all changes` command. `all` discards all changes. `tracked` discards only tracked files. `prompt` shows a prompt dialog every time the action is run.",
|
||||
"config.decorations.enabled": "Controls if Git contributes colors and badges to the explorer and the open editors view.",
|
||||
"config.promptToSaveFilesBeforeCommit": "Controls whether Git should check for unsaved files before committing.",
|
||||
"config.showInlineOpenFileAction": "Controls whether to show an inline Open File action in the Git changes view.",
|
||||
"colors.modified": "Color for modified resources.",
|
||||
"colors.deleted": "Color for deleted resources.",
|
||||
"colors.untracked": "Color for untracked resources.",
|
||||
"colors.ignored": "Color for ignored resources.",
|
||||
"colors.conflict": "Color for resources with conflicts.",
|
||||
"colors.submodule": "Color for submodule resources."
|
||||
}
|
||||
}
|
|
@ -963,26 +963,30 @@ export class CommandCenter {
|
|||
getCommitMessage: () => Promise<string | undefined>,
|
||||
opts?: CommitOptions
|
||||
): Promise<boolean> {
|
||||
const unsavedTextDocuments = workspace.textDocuments
|
||||
.filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath));
|
||||
const config = workspace.getConfiguration('git');
|
||||
const promptToSaveFilesBeforeCommit = config.get<boolean>('promptToSaveFilesBeforeCommit') === true;
|
||||
|
||||
if (unsavedTextDocuments.length > 0) {
|
||||
const message = unsavedTextDocuments.length === 1
|
||||
? localize('unsaved files single', "The following file is unsaved: {0}.\n\nWould you like to save it before comitting?", path.basename(unsavedTextDocuments[0].uri.fsPath))
|
||||
: localize('unsaved files', "There are {0} unsaved files.\n\nWould you like to save them before comitting?", unsavedTextDocuments.length);
|
||||
const saveAndCommit = localize('save and commit', "Save All & Commit");
|
||||
const commit = localize('commit', "Commit Anyway");
|
||||
const pick = await window.showWarningMessage(message, { modal: true }, saveAndCommit, commit);
|
||||
if (promptToSaveFilesBeforeCommit) {
|
||||
const unsavedTextDocuments = workspace.textDocuments
|
||||
.filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath));
|
||||
|
||||
if (pick === saveAndCommit) {
|
||||
await Promise.all(unsavedTextDocuments.map(d => d.save()));
|
||||
await repository.status();
|
||||
} else if (pick !== commit) {
|
||||
return false; // do not commit on cancel
|
||||
if (unsavedTextDocuments.length > 0) {
|
||||
const message = unsavedTextDocuments.length === 1
|
||||
? localize('unsaved files single', "The following file is unsaved: {0}.\n\nWould you like to save it before comitting?", path.basename(unsavedTextDocuments[0].uri.fsPath))
|
||||
: localize('unsaved files', "There are {0} unsaved files.\n\nWould you like to save them before comitting?", unsavedTextDocuments.length);
|
||||
const saveAndCommit = localize('save and commit', "Save All & Commit");
|
||||
const commit = localize('commit', "Commit Anyway");
|
||||
const pick = await window.showWarningMessage(message, { modal: true }, saveAndCommit, commit);
|
||||
|
||||
if (pick === saveAndCommit) {
|
||||
await Promise.all(unsavedTextDocuments.map(d => d.save()));
|
||||
await repository.status();
|
||||
} else if (pick !== commit) {
|
||||
return false; // do not commit on cancel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const config = workspace.getConfiguration('git');
|
||||
const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
|
||||
const enableCommitSigning = config.get<boolean>('enableCommitSigning') === true;
|
||||
const noStagedChanges = repository.indexGroup.resourceStates.length === 0;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import * as assert from 'assert';
|
||||
import { workspace, window, Position, Range, commands, TextEditor, TextDocument, TextEditorCursorStyle, TextEditorLineNumbersStyle, SnippetString, Selection } from 'vscode';
|
||||
import { createRandomFile, deleteFile, closeAllEditors } from './utils';
|
||||
import { createRandomFile, deleteFile, closeAllEditors } from '../utils';
|
||||
|
||||
suite('editor tests', () => {
|
||||
|
|
@ -21,7 +21,7 @@ suite('languages namespace tests', () => {
|
|||
constructor() {
|
||||
super(new Range(0, 2, 0, 7), 'sonntag');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let diag1 = new Diagnostic(new Range(0, 0, 0, 5), 'montag');
|
||||
let diag2 = new D2();
|
|
@ -8,7 +8,7 @@
|
|||
import * as assert from 'assert';
|
||||
import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind } from 'vscode';
|
||||
import { join } from 'path';
|
||||
import { closeAllEditors, pathEquals, createRandomFile } from './utils';
|
||||
import { closeAllEditors, pathEquals, createRandomFile } from '../utils';
|
||||
|
||||
suite('window namespace tests', () => {
|
||||
|
|
@ -7,10 +7,9 @@
|
|||
|
||||
import * as assert from 'assert';
|
||||
import * as vscode from 'vscode';
|
||||
import { createRandomFile, deleteFile, closeAllEditors, pathEquals } from './utils';
|
||||
import { createRandomFile, deleteFile, closeAllEditors, pathEquals } from '../utils';
|
||||
import { join, basename } from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { Uri } from 'vscode';
|
||||
|
||||
suite('workspace-namespace', () => {
|
||||
|
||||
|
@ -39,11 +38,27 @@ suite('workspace-namespace', () => {
|
|||
|
||||
test('rootPath', () => {
|
||||
if (vscode.workspace.rootPath) {
|
||||
assert.ok(pathEquals(vscode.workspace.rootPath, join(__dirname, '../testWorkspace')));
|
||||
assert.ok(pathEquals(vscode.workspace.rootPath, join(__dirname, '../../testWorkspace')));
|
||||
}
|
||||
assert.throws(() => vscode.workspace.rootPath = 'farboo');
|
||||
});
|
||||
|
||||
test('workspaceFolders', () => {
|
||||
if (vscode.workspace.workspaceFolders) {
|
||||
assert.equal(vscode.workspace.workspaceFolders.length, 1);
|
||||
assert.ok(pathEquals(vscode.workspace.workspaceFolders[0].uri.fsPath, join(__dirname, '../../testWorkspace')));
|
||||
}
|
||||
});
|
||||
|
||||
test('getWorkspaceFolder', () => {
|
||||
const folder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(join(__dirname, '../../testWorkspace/far.js')));
|
||||
assert.ok(!!folder);
|
||||
|
||||
if (folder) {
|
||||
assert.ok(pathEquals(folder.uri.fsPath, join(__dirname, '../../testWorkspace')));
|
||||
}
|
||||
});
|
||||
|
||||
test('openTextDocument', () => {
|
||||
let len = vscode.workspace.textDocuments.length;
|
||||
return vscode.workspace.openTextDocument(join(vscode.workspace.rootPath || '', './simple.txt')).then(doc => {
|
||||
|
@ -536,7 +551,7 @@ suite('workspace-namespace', () => {
|
|||
|
||||
test('applyEdit should fail when editing renamed from resource', async () => {
|
||||
const resource = await createRandomFile();
|
||||
const newResource = Uri.parse(resource.fsPath + '.1');
|
||||
const newResource = vscode.Uri.parse(resource.fsPath + '.1');
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
edit.renameResource(resource, newResource);
|
||||
try {
|
28
extensions/vscode-api-tests/src/workspace-tests/index.ts
Normal file
28
extensions/vscode-api-tests/src/workspace-tests/index.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//
|
||||
// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
|
||||
//
|
||||
// This file is providing the test runner to use when running extension tests.
|
||||
// By default the test runner in use is Mocha based.
|
||||
//
|
||||
// You can provide your own test runner if you want to override it by exporting
|
||||
// a function run(testRoot: string, clb: (error:Error) => void) that the extension
|
||||
// host can call to run the tests. The test runner is expected to use console.log
|
||||
// to report the results back to the caller. When the tests are finished, return
|
||||
// a possible error to the callback or null if none.
|
||||
|
||||
const testRunner = require('vscode/lib/testrunner');
|
||||
|
||||
// You can directly control Mocha options by uncommenting the following lines
|
||||
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
|
||||
testRunner.configure({
|
||||
ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
|
||||
useColors: process.platform !== 'win32', // colored output from test results (only windows cannot handle)
|
||||
timeout: 60000
|
||||
});
|
||||
|
||||
export = testRunner;
|
|
@ -0,0 +1,40 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as vscode from 'vscode';
|
||||
import { closeAllEditors, pathEquals } from '../utils';
|
||||
import { join } from 'path';
|
||||
|
||||
suite('workspace-namespace', () => {
|
||||
|
||||
teardown(closeAllEditors);
|
||||
|
||||
test('rootPath', () => {
|
||||
if (vscode.workspace.rootPath) {
|
||||
assert.ok(pathEquals(vscode.workspace.rootPath, join(__dirname, '../../testWorkspace')));
|
||||
}
|
||||
});
|
||||
|
||||
test('workspaceFolders', () => {
|
||||
if (vscode.workspace.workspaceFolders) {
|
||||
assert.equal(vscode.workspace.workspaceFolders.length, 2);
|
||||
assert.ok(pathEquals(vscode.workspace.workspaceFolders[0].uri.fsPath, join(__dirname, '../../testWorkspace')));
|
||||
assert.ok(pathEquals(vscode.workspace.workspaceFolders[1].uri.fsPath, join(__dirname, '../../testWorkspace2')));
|
||||
assert.ok(pathEquals(vscode.workspace.workspaceFolders[1].name, 'Test Workspace 2'));
|
||||
}
|
||||
});
|
||||
|
||||
test('getWorkspaceFolder', () => {
|
||||
const folder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(join(__dirname, '../../testWorkspace2/far.js')));
|
||||
assert.ok(!!folder);
|
||||
|
||||
if (folder) {
|
||||
assert.ok(pathEquals(folder.uri.fsPath, join(__dirname, '../../testWorkspace2')));
|
||||
}
|
||||
});
|
||||
});
|
1
extensions/vscode-api-tests/testWorkspace2/simple.txt
Normal file
1
extensions/vscode-api-tests/testWorkspace2/simple.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Just a simple file...
|
11
extensions/vscode-api-tests/testworkspace.code-workspace
Normal file
11
extensions/vscode-api-tests/testworkspace.code-workspace
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "testWorkspace"
|
||||
},
|
||||
{
|
||||
"path": "testWorkspace2",
|
||||
"name": "Test Workspace 2"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -39,7 +39,7 @@
|
|||
"node-pty": "0.7.4",
|
||||
"nsfw": "1.0.16",
|
||||
"semver": "4.3.6",
|
||||
"spdlog": "0.5.0",
|
||||
"spdlog": "0.6.0",
|
||||
"sudo-prompt": "^8.0.0",
|
||||
"v8-inspect-profiler": "^0.0.7",
|
||||
"vscode-chokidar": "1.6.2",
|
||||
|
|
|
@ -9,7 +9,10 @@ if not "%APPVEYOR%" == "" (
|
|||
set VSCODEUSERDATADIR=%TMP%\vscodeuserfolder-%RANDOM%-%TIME:~6,5%
|
||||
|
||||
:: Tests in the extension host
|
||||
call .\scripts\code.bat %~dp0\..\extensions\vscode-api-tests\testWorkspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
|
||||
call .\scripts\code.bat %~dp0\..\extensions\vscode-api-tests\testWorkspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
call .\scripts\code.bat %~dp0\..\extensions\vscode-api-tests\testWorkspace.code-workspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
call .\scripts\code.bat %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
|
||||
|
|
|
@ -13,7 +13,8 @@ fi
|
|||
cd $ROOT
|
||||
|
||||
# Tests in the extension host
|
||||
./scripts/code.sh $ROOT/extensions/vscode-api-tests/testWorkspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
|
||||
./scripts/code.sh $ROOT/extensions/vscode-api-tests/testWorkspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
|
||||
./scripts/code.sh $ROOT/extensions/vscode-api-tests/testWorkspace.code-workspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
|
||||
./scripts/code.sh $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
|
||||
./scripts/code.sh $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
|
||||
|
||||
|
|
|
@ -468,6 +468,17 @@ const editorConfiguration: IConfigurationNode = {
|
|||
'default': EDITOR_DEFAULTS.contribInfo.wordBasedSuggestions,
|
||||
'description': nls.localize('wordBasedSuggestions', "Controls whether completions should be computed based on words in the document.")
|
||||
},
|
||||
'editor.selectSuggestions': {
|
||||
'type': 'string',
|
||||
'enum': ['never', 'byRecency', 'byPrefix'],
|
||||
'enumDescriptions': [
|
||||
nls.localize('selectSuggestions.never', "Do not remember suggestions and always select the first."),
|
||||
nls.localize('selectSuggestions.byRecency', "Select recent suggestions unless further typing selects one, e.g. `console.| -> console.log`"),
|
||||
nls.localize('selectSuggestions.byPrefix', "Select suggestions based on previous prefixes that have completed those suggestions, e.g. `co -> console` and `con -> const`"),
|
||||
],
|
||||
'default': 'byRecency',
|
||||
'description': nls.localize('selectSuggestions', "Controls if accepting suggestions changes how future suggestions are pre-selected.")
|
||||
},
|
||||
'editor.suggestFontSize': {
|
||||
'type': 'integer',
|
||||
'default': 0,
|
||||
|
|
|
@ -457,6 +457,10 @@ export interface IEditorOptions {
|
|||
* Enable word based suggestions. Defaults to 'true'
|
||||
*/
|
||||
wordBasedSuggestions?: boolean;
|
||||
/**
|
||||
* The history mode for suggestions.
|
||||
*/
|
||||
selectSuggestions?: string;
|
||||
/**
|
||||
* The font size for the suggest widget.
|
||||
* Defaults to the editor font size.
|
||||
|
@ -827,6 +831,7 @@ export interface EditorContribOptions {
|
|||
readonly acceptSuggestionOnCommitCharacter: boolean;
|
||||
readonly snippetSuggestions: 'top' | 'bottom' | 'inline' | 'none';
|
||||
readonly wordBasedSuggestions: boolean;
|
||||
readonly selectSuggestions: 'never' | 'byRecency' | 'byPrefix';
|
||||
readonly suggestFontSize: number;
|
||||
readonly suggestLineHeight: number;
|
||||
readonly selectionHighlight: boolean;
|
||||
|
@ -1176,6 +1181,7 @@ export class InternalEditorOptions {
|
|||
&& a.acceptSuggestionOnCommitCharacter === b.acceptSuggestionOnCommitCharacter
|
||||
&& a.snippetSuggestions === b.snippetSuggestions
|
||||
&& a.wordBasedSuggestions === b.wordBasedSuggestions
|
||||
&& a.selectSuggestions === b.selectSuggestions
|
||||
&& a.suggestFontSize === b.suggestFontSize
|
||||
&& a.suggestLineHeight === b.suggestLineHeight
|
||||
&& a.selectionHighlight === b.selectionHighlight
|
||||
|
@ -1706,6 +1712,7 @@ export class EditorOptionsValidator {
|
|||
acceptSuggestionOnCommitCharacter: _boolean(opts.acceptSuggestionOnCommitCharacter, defaults.acceptSuggestionOnCommitCharacter),
|
||||
snippetSuggestions: _stringSet<'top' | 'bottom' | 'inline' | 'none'>(opts.snippetSuggestions, defaults.snippetSuggestions, ['top', 'bottom', 'inline', 'none']),
|
||||
wordBasedSuggestions: _boolean(opts.wordBasedSuggestions, defaults.wordBasedSuggestions),
|
||||
selectSuggestions: _stringSet<'never' | 'byRecency' | 'byPrefix'>(opts.selectSuggestions, defaults.selectSuggestions, ['never', 'byRecency', 'byPrefix']),
|
||||
suggestFontSize: _clampedInt(opts.suggestFontSize, defaults.suggestFontSize, 0, 1000),
|
||||
suggestLineHeight: _clampedInt(opts.suggestLineHeight, defaults.suggestLineHeight, 0, 1000),
|
||||
selectionHighlight: _boolean(opts.selectionHighlight, defaults.selectionHighlight),
|
||||
|
@ -1806,6 +1813,7 @@ export class InternalEditorOptionsFactory {
|
|||
acceptSuggestionOnCommitCharacter: opts.contribInfo.acceptSuggestionOnCommitCharacter,
|
||||
snippetSuggestions: opts.contribInfo.snippetSuggestions,
|
||||
wordBasedSuggestions: opts.contribInfo.wordBasedSuggestions,
|
||||
selectSuggestions: opts.contribInfo.selectSuggestions,
|
||||
suggestFontSize: opts.contribInfo.suggestFontSize,
|
||||
suggestLineHeight: opts.contribInfo.suggestLineHeight,
|
||||
selectionHighlight: (accessibilityIsOn ? false : opts.contribInfo.selectionHighlight), // DISABLED WHEN SCREEN READER IS ATTACHED
|
||||
|
@ -2260,6 +2268,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = {
|
|||
acceptSuggestionOnCommitCharacter: true,
|
||||
snippetSuggestions: 'inline',
|
||||
wordBasedSuggestions: true,
|
||||
selectSuggestions: 'byRecency',
|
||||
suggestFontSize: 0,
|
||||
suggestLineHeight: 0,
|
||||
selectionHighlight: true,
|
||||
|
|
|
@ -856,8 +856,14 @@ export interface WorkspaceEdit {
|
|||
rejectReason?: string; // TODO@joh, move to rename
|
||||
}
|
||||
|
||||
export interface RenameInitialValue {
|
||||
range: IRange;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export interface RenameProvider {
|
||||
provideRenameEdits(model: model.ITextModel, position: Position, newName: string, token: CancellationToken): WorkspaceEdit | Thenable<WorkspaceEdit>;
|
||||
resolveInitialRenameValue?(model: model.ITextModel, position: Position, token: CancellationToken): RenameInitialValue | Thenable<RenameInitialValue>;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { isPromiseCanceledError, illegalArgument } from 'vs/base/common/errors';
|
||||
import { isPromiseCanceledError, illegalArgument, onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
@ -25,7 +25,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
|||
import { optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { sequence, asWinJsPromise } from 'vs/base/common/async';
|
||||
import { WorkspaceEdit, RenameProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { WorkspaceEdit, RenameProviderRegistry, RenameInitialValue } from 'vs/editor/common/modes';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
|
@ -79,6 +79,19 @@ export function rename(model: ITextModel, position: Position, newName: string):
|
|||
});
|
||||
}
|
||||
|
||||
function resolveInitialRenameValue(model: ITextModel, position: Position): TPromise<RenameInitialValue> {
|
||||
const supports = RenameProviderRegistry.ordered(model);
|
||||
return asWinJsPromise((token) =>
|
||||
supports.length > 0
|
||||
? supports[0].resolveInitialRenameValue(model, position, token) //Use first rename provider so that we always use the same for resolving the location and for the actual rename
|
||||
: undefined
|
||||
).then(result => {
|
||||
return !result ? undefined : result;
|
||||
}, err => {
|
||||
onUnexpectedExternalError(err);
|
||||
return TPromise.wrapError<RenameInitialValue>(new Error('provider failed'));
|
||||
});
|
||||
}
|
||||
|
||||
// --- register actions and commands
|
||||
|
||||
|
@ -116,34 +129,64 @@ class RenameController implements IEditorContribution {
|
|||
return RenameController.ID;
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
|
||||
const selection = this.editor.getSelection(),
|
||||
word = this.editor.getModel().getWordAtPosition(selection.getStartPosition());
|
||||
|
||||
if (!word) {
|
||||
return undefined;
|
||||
}
|
||||
public async run(): TPromise<void> {
|
||||
const selection = this.editor.getSelection();
|
||||
|
||||
let lineNumber = selection.startLineNumber,
|
||||
selectionStart = 0,
|
||||
selectionEnd = word.word.length,
|
||||
wordRange: Range;
|
||||
selectionEnd = 0,
|
||||
wordRange: Range,
|
||||
word: string;
|
||||
|
||||
wordRange = new Range(
|
||||
lineNumber,
|
||||
word.startColumn,
|
||||
lineNumber,
|
||||
word.endColumn
|
||||
);
|
||||
let initialValue = await resolveInitialRenameValue(this.editor.getModel(), this.editor.getPosition());
|
||||
|
||||
if (!selection.isEmpty() && selection.startLineNumber === selection.endLineNumber) {
|
||||
selectionStart = Math.max(0, selection.startColumn - word.startColumn);
|
||||
selectionEnd = Math.min(word.endColumn, selection.endColumn) - word.startColumn;
|
||||
if(initialValue) {
|
||||
lineNumber = initialValue.range.startLineNumber;
|
||||
if(initialValue.text) {
|
||||
word = initialValue.text;
|
||||
}
|
||||
else {
|
||||
word = this.editor.getModel().getValueInRange(initialValue.range);
|
||||
}
|
||||
selectionEnd = word.length;
|
||||
|
||||
if (!selection.isEmpty() && selection.startLineNumber === selection.endLineNumber) {
|
||||
selectionStart = Math.max(0, selection.startColumn - initialValue.range.startColumn);
|
||||
selectionEnd = Math.min(initialValue.range.endColumn, selection.endColumn) - initialValue.range.startColumn;
|
||||
}
|
||||
|
||||
wordRange = new Range(
|
||||
lineNumber,
|
||||
initialValue.range.startColumn,
|
||||
lineNumber,
|
||||
initialValue.range.endColumn
|
||||
);
|
||||
|
||||
}
|
||||
else {
|
||||
const wordAtPosition = this.editor.getModel().getWordAtPosition(selection.getStartPosition());
|
||||
|
||||
if (!wordAtPosition) {
|
||||
return undefined;
|
||||
}
|
||||
word = wordAtPosition.word;
|
||||
selectionEnd = word.length;
|
||||
|
||||
if (!selection.isEmpty() && selection.startLineNumber === selection.endLineNumber) {
|
||||
selectionStart = Math.max(0, selection.startColumn - wordAtPosition.startColumn);
|
||||
selectionEnd = Math.min(wordAtPosition.endColumn, selection.endColumn) - wordAtPosition.startColumn;
|
||||
}
|
||||
|
||||
wordRange = new Range(
|
||||
lineNumber,
|
||||
wordAtPosition.startColumn,
|
||||
lineNumber,
|
||||
wordAtPosition.endColumn
|
||||
);
|
||||
}
|
||||
|
||||
this._renameInputVisible.set(true);
|
||||
return this._renameInputField.getInput(wordRange, word.word, selectionStart, selectionEnd).then(newName => {
|
||||
return this._renameInputField.getInput(wordRange, word, selectionStart, selectionEnd).then(newName => {
|
||||
this._renameInputVisible.reset();
|
||||
this.editor.focus();
|
||||
|
||||
|
@ -166,7 +209,7 @@ class RenameController implements IEditorContribution {
|
|||
this.editor.setSelection(selection);
|
||||
}
|
||||
// alert
|
||||
alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", word.word, newName, edit.ariaMessage()));
|
||||
alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", word, newName, edit.ariaMessage()));
|
||||
});
|
||||
|
||||
}, err => {
|
||||
|
|
|
@ -95,7 +95,7 @@ export class SuggestController implements IEditorContribution {
|
|||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
) {
|
||||
this._model = new SuggestModel(this._editor);
|
||||
this._memory = _instantiationService.createInstance(SuggestMemories);
|
||||
this._memory = _instantiationService.createInstance(SuggestMemories, this._editor.getConfiguration().contribInfo.selectSuggestions);
|
||||
|
||||
this._toDispose.push(this._model.onDidTrigger(e => {
|
||||
if (!this._widget) {
|
||||
|
@ -103,29 +103,22 @@ export class SuggestController implements IEditorContribution {
|
|||
}
|
||||
this._widget.showTriggered(e.auto);
|
||||
}));
|
||||
let lastSelectedItem: ICompletionItem;
|
||||
this._toDispose.push(this._model.onDidSuggest(e => {
|
||||
let index = this._memory.select(this._editor.getModel().getLanguageIdentifier(), e.completionModel.items, lastSelectedItem);
|
||||
if (index >= 0) {
|
||||
lastSelectedItem = e.completionModel.items[index];
|
||||
} else {
|
||||
index = 0;
|
||||
lastSelectedItem = undefined;
|
||||
}
|
||||
let index = this._memory.select(this._editor.getModel(), this._editor.getPosition(), e.completionModel.items);
|
||||
this._widget.showSuggestions(e.completionModel, index, e.isFrozen, e.auto);
|
||||
}));
|
||||
this._toDispose.push(this._model.onDidCancel(e => {
|
||||
if (this._widget && !e.retrigger) {
|
||||
this._widget.hideWidget();
|
||||
lastSelectedItem = undefined;
|
||||
}
|
||||
}));
|
||||
|
||||
// Manage the acceptSuggestionsOnEnter context key
|
||||
let acceptSuggestionsOnEnter = SuggestContext.AcceptSuggestionsOnEnter.bindTo(_contextKeyService);
|
||||
let updateFromConfig = () => {
|
||||
const { acceptSuggestionOnEnter } = this._editor.getConfiguration().contribInfo;
|
||||
const { acceptSuggestionOnEnter, selectSuggestions } = this._editor.getConfiguration().contribInfo;
|
||||
acceptSuggestionsOnEnter.set(acceptSuggestionOnEnter === 'on' || acceptSuggestionOnEnter === 'smart');
|
||||
this._memory.setMode(selectSuggestions);
|
||||
};
|
||||
this._toDispose.push(this._editor.onDidChangeConfiguration((e) => updateFromConfig()));
|
||||
updateFromConfig();
|
||||
|
@ -209,12 +202,8 @@ export class SuggestController implements IEditorContribution {
|
|||
this._editor.pushUndoStop();
|
||||
}
|
||||
|
||||
// remember this suggestion for future invocations
|
||||
// when it wasn't the first suggestion but from the group
|
||||
// of top suggestions (cons -> const, console, constructor)
|
||||
if (event.model.items[0].score === event.item.score) {
|
||||
this._memory.remember(this._editor.getModel().getLanguageIdentifier(), event.item);
|
||||
}
|
||||
// keep item in memory
|
||||
this._memory.memorize(this._editor.getModel(), this._editor.getPosition(), event.item);
|
||||
|
||||
let { insertText } = suggestion;
|
||||
if (suggestion.snippetType !== 'textmate') {
|
||||
|
|
|
@ -5,119 +5,210 @@
|
|||
'use strict';
|
||||
|
||||
import { ICompletionItem } from 'vs/editor/contrib/suggest/completionModel';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { LRUCache, TernarySearchTree } from 'vs/base/common/map';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
|
||||
export class SuggestMemories {
|
||||
export abstract class Memory {
|
||||
|
||||
private readonly _storagePrefix = 'suggest/memories';
|
||||
private readonly _data = new Map<string, SuggestMemory>();
|
||||
abstract memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void;
|
||||
|
||||
constructor(
|
||||
@IStorageService private _storageService: IStorageService
|
||||
) {
|
||||
abstract select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number;
|
||||
|
||||
abstract toJSON(): object;
|
||||
|
||||
abstract fromJSON(data: object): void;
|
||||
}
|
||||
|
||||
export class NoMemory extends Memory {
|
||||
|
||||
memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void {
|
||||
// no-op
|
||||
}
|
||||
|
||||
select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
fromJSON() {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
remember({ language }: LanguageIdentifier, item: ICompletionItem): void {
|
||||
let memory = this._data.get(language);
|
||||
if (!memory) {
|
||||
memory = new SuggestMemory();
|
||||
this._data.set(language, memory);
|
||||
}
|
||||
memory.remember(item);
|
||||
this._storageService.store(`${this._storagePrefix}/${language}`, JSON.stringify(memory), StorageScope.WORKSPACE);
|
||||
export interface MemItem {
|
||||
type: string;
|
||||
insertText: string;
|
||||
touch: number;
|
||||
}
|
||||
|
||||
export class LRUMemory extends Memory {
|
||||
|
||||
private _cache = new LRUCache<string, MemItem>(300, .66);
|
||||
private _seq = 0;
|
||||
|
||||
memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void {
|
||||
const { label } = item.suggestion;
|
||||
const key = `${model.getLanguageIdentifier().language}/${label}`;
|
||||
this._cache.set(key, {
|
||||
touch: this._seq++,
|
||||
type: item.suggestion.type,
|
||||
insertText: undefined
|
||||
});
|
||||
}
|
||||
|
||||
select({ language }: LanguageIdentifier, items: ICompletionItem[], last: ICompletionItem): number {
|
||||
let memory = this._data.get(language);
|
||||
if (!memory) {
|
||||
const key: string = `${this._storagePrefix}/${language}`;
|
||||
const raw = this._storageService.get(key, StorageScope.WORKSPACE);
|
||||
if (raw) {
|
||||
try {
|
||||
const tuples = <[string, MemoryItem][]>JSON.parse(raw);
|
||||
memory = new SuggestMemory(tuples);
|
||||
last = undefined;
|
||||
this._data.set(language, memory);
|
||||
} catch (e) {
|
||||
this._storageService.remove(key, StorageScope.WORKSPACE);
|
||||
select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number {
|
||||
// in order of completions, select the first
|
||||
// that has been used in the past
|
||||
let { word } = model.getWordUntilPosition(pos);
|
||||
|
||||
let res = 0;
|
||||
let seq = -1;
|
||||
if (word.length === 0) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const { suggestion } = items[i];
|
||||
const key = `${model.getLanguageIdentifier().language}/${suggestion.label}`;
|
||||
const item = this._cache.get(key);
|
||||
if (item && item.touch > seq && item.type === suggestion.type) {
|
||||
seq = item.touch;
|
||||
res = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (memory) {
|
||||
return memory.select(items, last);
|
||||
} else {
|
||||
return -1;
|
||||
return res;
|
||||
}
|
||||
|
||||
toJSON(): object {
|
||||
let data: [string, MemItem][] = [];
|
||||
this._cache.forEach((value, key) => {
|
||||
data.push([key, value]);
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
fromJSON(data: [string, MemItem][]): void {
|
||||
this._cache.clear();
|
||||
let seq = 0;
|
||||
for (const [key, value] of data) {
|
||||
value.touch = seq;
|
||||
this._cache.set(key, value);
|
||||
}
|
||||
this._seq = this._cache.size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export interface MemoryItem {
|
||||
type: string;
|
||||
insertText: string;
|
||||
}
|
||||
export class PrefixMemory extends Memory {
|
||||
|
||||
export class SuggestMemory {
|
||||
private _trie = TernarySearchTree.forStrings<MemItem>();
|
||||
private _seq = 0;
|
||||
|
||||
private readonly _memory = new LRUCache<string, MemoryItem>(400, 0.75);
|
||||
|
||||
constructor(tuples?: [string, MemoryItem][]) {
|
||||
if (tuples) {
|
||||
for (const [word, item] of tuples) {
|
||||
this._memory.set(word, item);
|
||||
}
|
||||
}
|
||||
memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void {
|
||||
const { word } = model.getWordUntilPosition(pos);
|
||||
const key = `${model.getLanguageIdentifier().language}/${word}`;
|
||||
this._trie.set(key, {
|
||||
type: item.suggestion.type,
|
||||
insertText: item.suggestion.insertText,
|
||||
touch: this._seq++
|
||||
});
|
||||
}
|
||||
|
||||
remember(item: ICompletionItem): void {
|
||||
if (item.word) {
|
||||
this._memory.set(item.word, { insertText: item.suggestion.insertText, type: item.suggestion.type });
|
||||
select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number {
|
||||
let { word } = model.getWordUntilPosition(pos);
|
||||
if (!word) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
select(items: ICompletionItem[], last: ICompletionItem): number {
|
||||
|
||||
if (items.length === 0) {
|
||||
return -1;
|
||||
let key = `${model.getLanguageIdentifier().language}/${word}`;
|
||||
let item = this._trie.get(key);
|
||||
if (!item) {
|
||||
item = this._trie.findSubstr(key);
|
||||
}
|
||||
|
||||
const topScore = items[0].score;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
|
||||
if (topScore !== items[i].score) {
|
||||
// we only take a look at the bucket
|
||||
// of top matches, hence we return
|
||||
// as soon as we see an item that
|
||||
// hasn't the top score anymore
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (items[i] === last) {
|
||||
// prefer the last selected item when
|
||||
// there is one
|
||||
return i;
|
||||
}
|
||||
if (items[i].word) {
|
||||
const item = this._memory.get(items[i].word);
|
||||
if (this._matches(item, items[i])) {
|
||||
if (item) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let { type, insertText } = items[i].suggestion;
|
||||
if (type === item.type && insertText === item.insertText) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private _matches(item: MemoryItem, candidate: ICompletionItem): boolean {
|
||||
return item && item.insertText === candidate.suggestion.insertText && item.type === candidate.suggestion.type;
|
||||
toJSON(): object {
|
||||
|
||||
let entries: [string, MemItem][] = [];
|
||||
this._trie.forEach((value, key) => entries.push([key, value]));
|
||||
|
||||
// sort by last recently used (touch), then
|
||||
// take the top 200 item and normalize their
|
||||
// touch
|
||||
entries
|
||||
.sort((a, b) => -(a[1].touch - b[1].touch))
|
||||
.forEach((value, i) => value[1].touch = i);
|
||||
|
||||
return entries.slice(0, 200);
|
||||
}
|
||||
|
||||
toJSON(): [string, MemoryItem][] {
|
||||
const tuples: [string, MemoryItem][] = [];
|
||||
this._memory.forEach((value, key) => tuples.push([key, value]));
|
||||
return tuples;
|
||||
fromJSON(data: [string, MemItem][]): void {
|
||||
this._trie.clear();
|
||||
if (data.length > 0) {
|
||||
this._seq = data[0][1].touch + 1;
|
||||
for (const [key, value] of data) {
|
||||
this._trie.set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type MemMode = 'never' | 'byRecency' | 'byPrefix';
|
||||
|
||||
export class SuggestMemories {
|
||||
|
||||
private readonly _storagePrefix = 'suggest/memories';
|
||||
|
||||
private _mode: MemMode;
|
||||
private _strategy: Memory;
|
||||
private _persistSoon: RunOnceScheduler;
|
||||
|
||||
constructor(
|
||||
mode: MemMode,
|
||||
@IStorageService private _storageService: IStorageService
|
||||
) {
|
||||
this._persistSoon = new RunOnceScheduler(() => this._flush(), 3000);
|
||||
this.setMode(mode);
|
||||
}
|
||||
|
||||
setMode(mode: MemMode): void {
|
||||
if (this._mode === mode) {
|
||||
return;
|
||||
}
|
||||
this._mode = mode;
|
||||
this._strategy = mode === 'byPrefix' ? new PrefixMemory() : mode === 'byRecency' ? new LRUMemory() : new NoMemory();
|
||||
|
||||
try {
|
||||
const raw = this._storageService.get(`${this._storagePrefix}/${this._mode}`, StorageScope.WORKSPACE);
|
||||
this._strategy.fromJSON(JSON.parse(raw));
|
||||
} catch (e) {
|
||||
// things can go wrong with JSON...
|
||||
}
|
||||
}
|
||||
|
||||
memorize(model: ITextModel, pos: IPosition, item: ICompletionItem): void {
|
||||
this._strategy.memorize(model, pos, item);
|
||||
this._persistSoon.schedule();
|
||||
}
|
||||
|
||||
select(model: ITextModel, pos: IPosition, items: ICompletionItem[]): number {
|
||||
return this._strategy.select(model, pos, items);
|
||||
}
|
||||
|
||||
private _flush() {
|
||||
const raw = JSON.stringify(this._strategy);
|
||||
this._storageService.store(`${this._storagePrefix}/${this._mode}`, raw, StorageScope.WORKSPACE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,37 +11,37 @@ import { CompletionModel } from 'vs/editor/contrib/suggest/completionModel';
|
|||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
suite('CompletionModel', function () {
|
||||
export function createSuggestItem(label: string, overwriteBefore: number, type: SuggestionType = 'property', incomplete: boolean = false, position: IPosition = { lineNumber: 1, column: 1 }): ISuggestionItem {
|
||||
|
||||
function createSuggestItem(label: string, overwriteBefore: number, type: SuggestionType = 'property', incomplete: boolean = false, position: IPosition = { lineNumber: 1, column: 1 }): ISuggestionItem {
|
||||
return new class implements ISuggestionItem {
|
||||
|
||||
return new class implements ISuggestionItem {
|
||||
position = position;
|
||||
|
||||
position = position;
|
||||
suggestion: ISuggestion = {
|
||||
label,
|
||||
overwriteBefore,
|
||||
insertText: label,
|
||||
type
|
||||
};
|
||||
|
||||
suggestion: ISuggestion = {
|
||||
label,
|
||||
overwriteBefore,
|
||||
insertText: label,
|
||||
type
|
||||
};
|
||||
container: ISuggestResult = {
|
||||
incomplete,
|
||||
suggestions: [this.suggestion]
|
||||
};
|
||||
|
||||
container: ISuggestResult = {
|
||||
incomplete,
|
||||
suggestions: [this.suggestion]
|
||||
};
|
||||
|
||||
support: ISuggestSupport = {
|
||||
provideCompletionItems(): any {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
resolve(): TPromise<void> {
|
||||
return null;
|
||||
support: ISuggestSupport = {
|
||||
provideCompletionItems(): any {
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
resolve(): TPromise<void> {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
suite('CompletionModel', function () {
|
||||
|
||||
|
||||
let model: CompletionModel;
|
||||
|
||||
|
|
86
src/vs/editor/contrib/suggest/test/suggestMemory.test.ts
Normal file
86
src/vs/editor/contrib/suggest/test/suggestMemory.test.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { LRUMemory, NoMemory, PrefixMemory } from 'vs/editor/contrib/suggest/suggestMemory';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { ICompletionItem } from 'vs/editor/contrib/suggest/completionModel';
|
||||
import { createSuggestItem } from 'vs/editor/contrib/suggest/test/completionModel.test';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
|
||||
suite('SuggestMemories', function () {
|
||||
|
||||
let pos: IPosition;
|
||||
let buffer: ITextModel;
|
||||
let items: ICompletionItem[];
|
||||
|
||||
setup(function () {
|
||||
pos = { lineNumber: 1, column: 1 };
|
||||
buffer = TextModel.createFromString('This is some text');
|
||||
items = [
|
||||
createSuggestItem('foo', 0),
|
||||
createSuggestItem('bar', 0)
|
||||
];
|
||||
});
|
||||
|
||||
test('NoMemory', function () {
|
||||
|
||||
const mem = new NoMemory();
|
||||
|
||||
assert.equal(mem.select(buffer, pos, items), 0);
|
||||
assert.equal(mem.select(buffer, pos, []), 0);
|
||||
|
||||
mem.memorize(buffer, pos, items[0]);
|
||||
mem.memorize(buffer, pos, null);
|
||||
});
|
||||
|
||||
test('ShyMemories', function () {
|
||||
|
||||
const mem = new LRUMemory();
|
||||
mem.memorize(buffer, pos, items[1]);
|
||||
|
||||
assert.equal(mem.select(buffer, pos, items), 1);
|
||||
assert.equal(mem.select(buffer, { lineNumber: 1, column: 3 }, items), 0);
|
||||
|
||||
mem.memorize(buffer, pos, items[0]);
|
||||
assert.equal(mem.select(buffer, pos, items), 0);
|
||||
|
||||
assert.equal(mem.select(buffer, pos, [
|
||||
createSuggestItem('new', 0),
|
||||
createSuggestItem('bar', 0)
|
||||
]), 1);
|
||||
|
||||
assert.equal(mem.select(buffer, pos, [
|
||||
createSuggestItem('new1', 0),
|
||||
createSuggestItem('new2', 0)
|
||||
]), 0);
|
||||
|
||||
});
|
||||
|
||||
test('PrefixMemory', function () {
|
||||
|
||||
const mem = new PrefixMemory();
|
||||
buffer.setValue('constructor');
|
||||
const item0 = createSuggestItem('console', 0);
|
||||
const item1 = createSuggestItem('const', 0);
|
||||
const item2 = createSuggestItem('constructor', 0);
|
||||
const item3 = createSuggestItem('constant', 0);
|
||||
const items = [item0, item1, item2, item3];
|
||||
|
||||
mem.memorize(buffer, { lineNumber: 1, column: 2 }, item1); // c -> const
|
||||
mem.memorize(buffer, { lineNumber: 1, column: 3 }, item0); // co -> console
|
||||
mem.memorize(buffer, { lineNumber: 1, column: 4 }, item2); // con -> constructor
|
||||
|
||||
assert.equal(mem.select(buffer, { lineNumber: 1, column: 1 }, items), 0);
|
||||
assert.equal(mem.select(buffer, { lineNumber: 1, column: 2 }, items), 1);
|
||||
assert.equal(mem.select(buffer, { lineNumber: 1, column: 3 }, items), 0);
|
||||
assert.equal(mem.select(buffer, { lineNumber: 1, column: 4 }, items), 2);
|
||||
assert.equal(mem.select(buffer, { lineNumber: 1, column: 7 }, items), 2); // find substr
|
||||
});
|
||||
|
||||
});
|
11
src/vs/monaco.d.ts
vendored
11
src/vs/monaco.d.ts
vendored
|
@ -2742,6 +2742,10 @@ declare module monaco.editor {
|
|||
* Enable word based suggestions. Defaults to 'true'
|
||||
*/
|
||||
wordBasedSuggestions?: boolean;
|
||||
/**
|
||||
* The history mode for suggestions.
|
||||
*/
|
||||
selectSuggestions?: string;
|
||||
/**
|
||||
* The font size for the suggest widget.
|
||||
* Defaults to the editor font size.
|
||||
|
@ -3048,6 +3052,7 @@ declare module monaco.editor {
|
|||
readonly acceptSuggestionOnCommitCharacter: boolean;
|
||||
readonly snippetSuggestions: 'top' | 'bottom' | 'inline' | 'none';
|
||||
readonly wordBasedSuggestions: boolean;
|
||||
readonly selectSuggestions: 'never' | 'byRecency' | 'byPrefix';
|
||||
readonly suggestFontSize: number;
|
||||
readonly suggestLineHeight: number;
|
||||
readonly selectionHighlight: boolean;
|
||||
|
@ -4951,8 +4956,14 @@ declare module monaco.languages {
|
|||
rejectReason?: string;
|
||||
}
|
||||
|
||||
export interface RenameInitialValue {
|
||||
range: IRange;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export interface RenameProvider {
|
||||
provideRenameEdits(model: editor.ITextModel, position: Position, newName: string, token: CancellationToken): WorkspaceEdit | Thenable<WorkspaceEdit>;
|
||||
resolveInitialRenameValue?(model: editor.ITextModel, position: Position, token: CancellationToken): RenameInitialValue | Thenable<RenameInitialValue>;
|
||||
}
|
||||
|
||||
export interface Command {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { distinct, coalesce } from 'vs/base/common/arrays';
|
|||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, ILocalExtension, isIExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { adoptToGalleryExtensionId, getIdFromLocalExtensionId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { adoptToGalleryExtensionId, getIdFromLocalExtensionId, areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
@ -89,7 +89,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
|
|||
if (!this.canChangeEnablement(arg)) {
|
||||
return TPromise.wrap(false);
|
||||
}
|
||||
identifier = { id: getIdFromLocalExtensionId(arg.identifier.id), uuid: arg.identifier.uuid };
|
||||
identifier = { id: getGalleryExtensionIdFromLocal(arg), uuid: arg.identifier.uuid };
|
||||
}
|
||||
|
||||
const workspace = newState === EnablementState.WorkspaceDisabled || newState === EnablementState.WorkspaceEnabled;
|
||||
|
|
|
@ -712,7 +712,7 @@ export class ExtensionManagementService implements IExtensionManagementService {
|
|||
if (manifest.extensionDependencies) {
|
||||
manifest.extensionDependencies = manifest.extensionDependencies.map(id => adoptToGalleryExtensionId(id));
|
||||
}
|
||||
const identifier = { id, uuid: metadata ? metadata.id : null };
|
||||
const identifier = { id: type === LocalExtensionType.System ? id : getLocalExtensionIdFromManifest(manifest), uuid: metadata ? metadata.id : null };
|
||||
return { type, identifier, manifest, metadata, path: extensionPath, readmeUrl, changelogUrl };
|
||||
});
|
||||
}).then(null, () => null);
|
||||
|
|
|
@ -331,9 +331,12 @@ suite('ExtensionEnablementService Test', () => {
|
|||
});
|
||||
|
||||
function aLocalExtension(id: string, contributes?: IExtensionContributions): ILocalExtension {
|
||||
const [publisher, name] = id.split('.');
|
||||
return <ILocalExtension>Object.create({
|
||||
identifier: { id },
|
||||
manifest: {
|
||||
name,
|
||||
publisher,
|
||||
contributes
|
||||
}
|
||||
});
|
||||
|
|
|
@ -23,6 +23,8 @@ export enum LogLevel {
|
|||
Off
|
||||
}
|
||||
|
||||
export const DEFAULT_LOG_LEVEL: LogLevel = LogLevel.Info;
|
||||
|
||||
export interface ILogService extends IDisposable {
|
||||
_serviceBrand: any;
|
||||
onDidChangeLogLevel: Event<LogLevel>;
|
||||
|
@ -39,7 +41,7 @@ export interface ILogService extends IDisposable {
|
|||
|
||||
export abstract class AbstractLogService extends Disposable {
|
||||
|
||||
private level: LogLevel = LogLevel.Error;
|
||||
private level: LogLevel = DEFAULT_LOG_LEVEL;
|
||||
private readonly _onDidChangeLogLevel: Emitter<LogLevel> = this._register(new Emitter<LogLevel>());
|
||||
readonly onDidChangeLogLevel: Event<LogLevel> = this._onDidChangeLogLevel.event;
|
||||
|
||||
|
@ -60,7 +62,7 @@ export class ConsoleLogMainService extends AbstractLogService implements ILogSer
|
|||
_serviceBrand: any;
|
||||
private useColors: boolean;
|
||||
|
||||
constructor(logLevel: LogLevel = LogLevel.Error) {
|
||||
constructor(logLevel: LogLevel = DEFAULT_LOG_LEVEL) {
|
||||
super();
|
||||
this.setLevel(logLevel);
|
||||
this.useColors = !isWindows;
|
||||
|
@ -135,7 +137,7 @@ export class ConsoleLogService extends AbstractLogService implements ILogService
|
|||
|
||||
_serviceBrand: any;
|
||||
|
||||
constructor(logLevel: LogLevel = LogLevel.Error) {
|
||||
constructor(logLevel: LogLevel = DEFAULT_LOG_LEVEL) {
|
||||
super();
|
||||
this.setLevel(logLevel);
|
||||
}
|
||||
|
@ -322,5 +324,5 @@ export function getLogLevel(environmentService: IEnvironmentService): LogLevel {
|
|||
return LogLevel.Off;
|
||||
}
|
||||
}
|
||||
return LogLevel.Info;
|
||||
return DEFAULT_LOG_LEVEL;
|
||||
}
|
|
@ -9,11 +9,10 @@ import * as path from 'path';
|
|||
import { ILogService, LogLevel, NullLogService, AbstractLogService } from 'vs/platform/log/common/log';
|
||||
import { RotatingLogger, setAsyncMode } from 'spdlog';
|
||||
|
||||
export function createSpdLogService(processName: string, logLevel: LogLevel, logsFolder: string, logsSubfolder?: string): ILogService {
|
||||
export function createSpdLogService(processName: string, logLevel: LogLevel, logsFolder: string): ILogService {
|
||||
try {
|
||||
setAsyncMode(8192, 2000);
|
||||
const logsDirPath = logsSubfolder ? path.join(logsFolder, logsSubfolder) : logsFolder;
|
||||
const logfilePath = path.join(logsDirPath, `${processName}.log`);
|
||||
const logfilePath = path.join(logsFolder, `${processName}.log`);
|
||||
const logger = new RotatingLogger(processName, logfilePath, 1024 * 1024 * 5, 6);
|
||||
logger.setLevel(0);
|
||||
|
||||
|
|
|
@ -98,6 +98,7 @@ const configurationValueWhitelist = [
|
|||
'editor.snippetSuggestions',
|
||||
'editor.emptySelectionClipboard',
|
||||
'editor.wordBasedSuggestions',
|
||||
'editor.selectSuggestions',
|
||||
'editor.suggestFontSize',
|
||||
'editor.suggestLineHeight',
|
||||
'editor.selectionHighlight',
|
||||
|
|
|
@ -205,8 +205,8 @@ export class Workspace implements IWorkspace {
|
|||
export class WorkspaceFolder implements IWorkspaceFolder {
|
||||
|
||||
readonly uri: URI;
|
||||
readonly name: string;
|
||||
readonly index: number;
|
||||
name: string;
|
||||
index: number;
|
||||
|
||||
constructor(data: IWorkspaceFolderData,
|
||||
readonly raw?: IStoredWorkspaceFolder) {
|
||||
|
|
7
src/vs/vscode.d.ts
vendored
7
src/vs/vscode.d.ts
vendored
|
@ -2664,6 +2664,11 @@ declare module 'vscode' {
|
|||
appendVariable(name: string, defaultValue: string | ((snippet: SnippetString) => any)): SnippetString;
|
||||
}
|
||||
|
||||
export interface RenameInitialValue {
|
||||
range: Range
|
||||
text?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The rename provider interface defines the contract between extensions and
|
||||
* the [rename](https://code.visualstudio.com/docs/editor/editingevolved#_rename-symbol)-feature.
|
||||
|
@ -2682,6 +2687,8 @@ declare module 'vscode' {
|
|||
* signaled by returning `undefined` or `null`.
|
||||
*/
|
||||
provideRenameEdits(document: TextDocument, position: Position, newName: string, token: CancellationToken): ProviderResult<WorkspaceEdit>;
|
||||
|
||||
resolveInitialRenameValue?(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<RenameInitialValue>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
44
src/vs/vscode.proposed.d.ts
vendored
44
src/vs/vscode.proposed.d.ts
vendored
|
@ -157,6 +157,46 @@ declare module 'vscode' {
|
|||
|
||||
export namespace workspace {
|
||||
export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider): Disposable;
|
||||
|
||||
/**
|
||||
* Updates the workspace folders of the currently opened workspace. This method allows to add, remove
|
||||
* and change workspace folders a the same time. Use the [onDidChangeWorkspaceFolders()](#onDidChangeWorkspaceFolders)
|
||||
* event to get notified when the workspace folders have been updated.
|
||||
*
|
||||
* **Example:** adding a new workspace folder at the end of workspace folders
|
||||
* ```typescript
|
||||
* workspace.updateWorkspaceFolders(workspace.workspaceFolders ? workspace.workspaceFolders.length : 0, null, { uri: ...});
|
||||
* ```
|
||||
*
|
||||
* **Example:** removing the first workspace folder
|
||||
* ```typescript
|
||||
* workspace.updateWorkspaceFolders(0, 1);
|
||||
* ```
|
||||
*
|
||||
* **Example:** replacing an existing workspace folder with a new one
|
||||
* ```typescript
|
||||
* workspace.updateWorkspaceFolders(0, 1, { uri: ...});
|
||||
* ```
|
||||
*
|
||||
* It is valid to remove an existing workspace folder and add it again with a different name
|
||||
* to rename that folder.
|
||||
*
|
||||
* Note: if the first workspace folder is added, removed or changed, all extensions will be restarted
|
||||
* so that the (deprecated) `rootPath` property is updated to point to the first workspace
|
||||
* folder.
|
||||
*
|
||||
* Note: it is not valid to call [updateWorkspaceFolders()](#updateWorkspaceFolders) multiple times
|
||||
* without waiting for the [onDidChangeWorkspaceFolders()](#onDidChangeWorkspaceFolders) to fire.
|
||||
*
|
||||
* @param start the zero-based location in the list of currently opened [workspace folders](#WorkspaceFolder)
|
||||
* from which to start deleting workspace folders.
|
||||
* @param deleteCount the optional number of workspace folders to remove.
|
||||
* @param workspaceFoldersToAdd the optional variable set of workspace folders to add in place of the deleted ones.
|
||||
* Each workspace is identified with a mandatory URI and an optional name.
|
||||
* @return true if the operation was successfully started and false otherwise if arguments were used that would result
|
||||
* in invalid workspace folder state (e.g. 2 folders with the same URI).
|
||||
*/
|
||||
export function updateWorkspaceFolders(start: number, deleteCount: number, ...workspaceFoldersToAdd: { uri: Uri, name?: string }[]): boolean;
|
||||
}
|
||||
|
||||
export namespace window {
|
||||
|
@ -240,13 +280,13 @@ declare module 'vscode' {
|
|||
* Add breakpoints.
|
||||
* @param breakpoints The breakpoints to add.
|
||||
*/
|
||||
export function addBreakpoints(breakpoints: Breakpoint[]): Thenable<void>;
|
||||
export function addBreakpoints(breakpoints: Breakpoint[]): void;
|
||||
|
||||
/**
|
||||
* Remove breakpoints.
|
||||
* @param breakpoints The breakpoints to remove.
|
||||
*/
|
||||
export function removeBreakpoints(breakpoints: Breakpoint[]): Thenable<void>;
|
||||
export function removeBreakpoints(breakpoints: Breakpoint[]): void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -11,7 +11,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
|||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import {
|
||||
ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext,
|
||||
IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IBreakpointIndexDto
|
||||
IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto
|
||||
} from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import severity from 'vs/base/common/severity';
|
||||
|
@ -92,14 +92,13 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape {
|
|||
return TPromise.wrap<void>(undefined);
|
||||
}
|
||||
|
||||
public async $registerBreakpoints(DTOs: (ISourceMultiBreakpointDto | IFunctionBreakpointDto)[]): TPromise<IBreakpointIndexDto[]> {
|
||||
public $registerBreakpoints(DTOs: (ISourceMultiBreakpointDto | IFunctionBreakpointDto)[]): TPromise<void> {
|
||||
|
||||
const result: IBreakpointIndexDto[] = [];
|
||||
for (let dto of DTOs) {
|
||||
if (dto.type === 'sourceMulti') {
|
||||
const sdto = <ISourceMultiBreakpointDto>dto;
|
||||
const rawbps = dto.lines.map(l =>
|
||||
<IRawBreakpoint>{
|
||||
id: l.id,
|
||||
enabled: l.enabled,
|
||||
lineNumber: l.line + 1,
|
||||
column: l.character > 0 ? l.character + 1 : 0,
|
||||
|
@ -107,20 +106,12 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape {
|
|||
hitCondition: l.hitCondition
|
||||
}
|
||||
);
|
||||
const bps = await this.debugService.addBreakpoints(uri.revive(dto.uri), rawbps);
|
||||
bps.forEach((bp, ix) => result.push({
|
||||
index: sdto.lines[ix].index,
|
||||
id: bp.getId()
|
||||
}));
|
||||
this.debugService.addBreakpoints(uri.revive(dto.uri), rawbps);
|
||||
} else if (dto.type === 'function') {
|
||||
const fbs = this.debugService.addFunctionBreakpoint(dto.functionName);
|
||||
result.push({
|
||||
index: dto.index,
|
||||
id: fbs.getId()
|
||||
});
|
||||
this.debugService.addFunctionBreakpoint(dto.functionName, dto.id);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return void 0;
|
||||
}
|
||||
|
||||
public $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[]): TPromise<void> {
|
||||
|
|
|
@ -251,11 +251,14 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
|||
|
||||
// --- rename
|
||||
|
||||
$registerRenameSupport(handle: number, selector: vscode.DocumentSelector): void {
|
||||
$registerRenameSupport(handle: number, selector: vscode.DocumentSelector, supportsResolveInitialValues: boolean): 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(reviveWorkspaceEditDto);
|
||||
}
|
||||
},
|
||||
resolveInitialRenameValue: supportsResolveInitialValues
|
||||
? (model: ITextModel, position: EditorPosition, token: CancellationToken): Thenable<modes.RenameInitialValue> => wireCancellationToken(token, this._proxy.$resolveInitialRenameValue(handle, model.uri, position))
|
||||
: undefined
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
'use strict';
|
||||
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { ISearchService, QueryType, ISearchQuery, IFolderQuery, ISearchConfiguration } from 'vs/platform/search/common/search';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
|
@ -14,6 +14,9 @@ import { MainThreadWorkspaceShape, ExtHostWorkspaceShape, ExtHostContext, MainCo
|
|||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadWorkspace)
|
||||
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
|
@ -27,7 +30,9 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
|||
@ISearchService private readonly _searchService: ISearchService,
|
||||
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
|
||||
@ITextFileService private readonly _textFileService: ITextFileService,
|
||||
@IConfigurationService private _configurationService: IConfigurationService
|
||||
@IConfigurationService private _configurationService: IConfigurationService,
|
||||
@IWorkspaceEditingService private _workspaceEditingService: IWorkspaceEditingService,
|
||||
@IStatusbarService private _statusbarService: IStatusbarService
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace);
|
||||
this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose);
|
||||
|
@ -45,6 +50,47 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
|||
|
||||
// --- workspace ---
|
||||
|
||||
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, foldersToAdd: { uri: UriComponents, name?: string }[]): Thenable<void> {
|
||||
const workspaceFoldersToAdd = foldersToAdd.map(f => ({ uri: URI.revive(f.uri), name: f.name }));
|
||||
|
||||
// Indicate in status message
|
||||
this._statusbarService.setStatusMessage(this.getStatusMessage(extensionName, workspaceFoldersToAdd.length, deleteCount), 10 * 1000 /* 10s */);
|
||||
|
||||
return this._workspaceEditingService.updateFolders(index, deleteCount, workspaceFoldersToAdd, true);
|
||||
}
|
||||
|
||||
private getStatusMessage(extensionName, addCount: number, removeCount: number): string {
|
||||
let message: string;
|
||||
|
||||
const wantsToAdd = addCount > 0;
|
||||
const wantsToDelete = removeCount > 0;
|
||||
|
||||
// Add Folders
|
||||
if (wantsToAdd && !wantsToDelete) {
|
||||
if (addCount === 1) {
|
||||
message = localize('folderStatusMessageAddSingleFolder', "Extension '{0}' added 1 folder to the workspace", extensionName);
|
||||
} else {
|
||||
message = localize('folderStatusMessageAddMultipleFolders', "Extension '{0}' added {1} folders to the workspace", extensionName, addCount);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete Folders
|
||||
else if (wantsToDelete && !wantsToAdd) {
|
||||
if (removeCount === 1) {
|
||||
message = localize('folderStatusMessageRemoveSingleFolder', "Extension '{0}' removed 1 folder from the workspace", extensionName);
|
||||
} else {
|
||||
message = localize('folderStatusMessageRemoveMultipleFolders', "Extension '{0}' removed {1} folders from the workspace", extensionName, removeCount);
|
||||
}
|
||||
}
|
||||
|
||||
// Change Folders
|
||||
else {
|
||||
message = localize('folderStatusChangeFolder', "Extension '{0}' changed folders of the workspace", extensionName);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private _onDidChangeWorkspace(): void {
|
||||
this._proxy.$acceptWorkspaceData(this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : this._contextService.getWorkspace());
|
||||
}
|
||||
|
@ -122,4 +168,4 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
|||
return result.results.every(each => each.success === true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -419,6 +419,9 @@ export function createApiFactory(
|
|||
set name(value) {
|
||||
throw errors.readonly();
|
||||
},
|
||||
updateWorkspaceFolders: proposedApiFunction(extension, (index, deleteCount, ...workspaceFoldersToAdd) => {
|
||||
return extHostWorkspace.updateWorkspaceFolders(extension, index, deleteCount, ...workspaceFoldersToAdd);
|
||||
}),
|
||||
onDidChangeWorkspaceFolders: function (listener, thisArgs?, disposables?) {
|
||||
return extHostWorkspace.onDidChangeWorkspace(listener, thisArgs, disposables);
|
||||
},
|
||||
|
|
|
@ -277,7 +277,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
|
|||
$registerRangeFormattingSupport(handle: number, selector: vscode.DocumentSelector): void;
|
||||
$registerOnTypeFormattingSupport(handle: number, selector: vscode.DocumentSelector, autoFormatTriggerCharacters: string[]): void;
|
||||
$registerNavigateTypeSupport(handle: number): void;
|
||||
$registerRenameSupport(handle: number, selector: vscode.DocumentSelector): void;
|
||||
$registerRenameSupport(handle: number, selector: vscode.DocumentSelector, supportsResolveInitialValues: boolean): void;
|
||||
$registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[], supportsResolveDetails: boolean): void;
|
||||
$registerSignatureHelpProvider(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): void;
|
||||
$registerDocumentLinkProvider(handle: number, selector: vscode.DocumentSelector): void;
|
||||
|
@ -349,6 +349,7 @@ export interface MainThreadWorkspaceShape extends IDisposable {
|
|||
$startSearch(includePattern: string, includeFolder: string, excludePattern: string, maxResults: number, requestId: number): Thenable<UriComponents[]>;
|
||||
$cancelSearch(requestId: number): Thenable<boolean>;
|
||||
$saveAll(includeUntitled?: boolean): Thenable<boolean>;
|
||||
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string }[]): Thenable<void>;
|
||||
}
|
||||
|
||||
export interface IFileChangeDto {
|
||||
|
@ -442,7 +443,7 @@ export interface MainThreadDebugServiceShape extends IDisposable {
|
|||
$customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): TPromise<any>;
|
||||
$appendDebugConsole(value: string): TPromise<any>;
|
||||
$startBreakpointEvents(): TPromise<any>;
|
||||
$registerBreakpoints(breakpoints: (ISourceMultiBreakpointDto | IFunctionBreakpointDto)[]): TPromise<IBreakpointIndexDto[]>;
|
||||
$registerBreakpoints(breakpoints: (ISourceMultiBreakpointDto | IFunctionBreakpointDto)[]): TPromise<void>;
|
||||
$unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[]): TPromise<void>;
|
||||
}
|
||||
|
||||
|
@ -668,6 +669,7 @@ export interface ExtHostLanguageFeaturesShape {
|
|||
$resolveWorkspaceSymbol(handle: number, symbol: SymbolInformationDto): TPromise<SymbolInformationDto>;
|
||||
$releaseWorkspaceSymbols(handle: number, id: number): void;
|
||||
$provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string): TPromise<WorkspaceEditDto>;
|
||||
$resolveInitialRenameValue(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.RenameInitialValue>;
|
||||
$provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.SuggestContext): TPromise<SuggestResultDto>;
|
||||
$resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, suggestion: modes.ISuggestion): TPromise<modes.ISuggestion>;
|
||||
$releaseCompletionItems(handle: number, id: number): void;
|
||||
|
@ -700,7 +702,6 @@ export interface ExtHostTaskShape {
|
|||
|
||||
export interface IFunctionBreakpointDto {
|
||||
type: 'function';
|
||||
index: number;
|
||||
id?: string;
|
||||
enabled: boolean;
|
||||
condition?: string;
|
||||
|
@ -729,7 +730,7 @@ export interface ISourceMultiBreakpointDto {
|
|||
type: 'sourceMulti';
|
||||
uri: UriComponents;
|
||||
lines: {
|
||||
index: number;
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
condition?: string;
|
||||
hitCondition?: string;
|
||||
|
@ -738,11 +739,6 @@ export interface ISourceMultiBreakpointDto {
|
|||
}[];
|
||||
}
|
||||
|
||||
export interface IBreakpointIndexDto {
|
||||
index: number;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface ExtHostDebugServiceShape {
|
||||
$resolveDebugConfiguration(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig): TPromise<IConfig>;
|
||||
$provideDebugConfigurations(handle: number, folder: UriComponents | undefined): TPromise<IConfig[]>;
|
||||
|
|
|
@ -16,6 +16,7 @@ import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
|||
import * as vscode from 'vscode';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
|
||||
export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
|
@ -106,16 +107,22 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
|||
|
||||
if (delta.added) {
|
||||
for (const bpd of delta.added) {
|
||||
let bp: vscode.Breakpoint;
|
||||
if (bpd.type === 'function') {
|
||||
bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition);
|
||||
} else {
|
||||
const uri = URI.revive(bpd.uri);
|
||||
bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition);
|
||||
|
||||
if (!this._breakpoints.has(bpd.id)) {
|
||||
let bp: vscode.Breakpoint;
|
||||
if (bpd.type === 'function') {
|
||||
bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition);
|
||||
} else {
|
||||
const uri = URI.revive(bpd.uri);
|
||||
bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition);
|
||||
}
|
||||
bp['_id'] = bpd.id;
|
||||
this._breakpoints.set(bpd.id, bp);
|
||||
a.push(bp);
|
||||
|
||||
}
|
||||
bp['_id'] = bpd.id;
|
||||
this._breakpoints.set(bpd.id, bp);
|
||||
a.push(bp);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,25 +164,31 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
|||
|
||||
this.startBreakpoints();
|
||||
|
||||
// assign temporary ids for brand new breakpoints
|
||||
// assign uuids for brand new breakpoints
|
||||
const breakpoints: vscode.Breakpoint[] = [];
|
||||
for (const bp of breakpoints0) {
|
||||
let id = bp['_id'];
|
||||
if (id) { // has already id
|
||||
if (!this._breakpoints.has(id)) {
|
||||
if (this._breakpoints.has(id)) {
|
||||
// already there
|
||||
} else {
|
||||
breakpoints.push(bp);
|
||||
}
|
||||
} else {
|
||||
// no id -> assign temp id
|
||||
id = generateUuid();
|
||||
bp['_id'] = id;
|
||||
this._breakpoints.set(id, bp);
|
||||
breakpoints.push(bp);
|
||||
}
|
||||
}
|
||||
|
||||
// convert to DTOs
|
||||
// send notification for added breakpoints
|
||||
this.fireBreakpointChanges(breakpoints, [], []);
|
||||
|
||||
// convert added breakpoints to DTOs
|
||||
const dtos: (ISourceMultiBreakpointDto | IFunctionBreakpointDto)[] = [];
|
||||
const map = new Map<string, ISourceMultiBreakpointDto>();
|
||||
for (let i = 0; i < breakpoints.length; i++) {
|
||||
const bp = breakpoints[i];
|
||||
for (const bp of breakpoints) {
|
||||
if (bp instanceof SourceBreakpoint) {
|
||||
let dto = map.get(bp.location.uri.toString());
|
||||
if (!dto) {
|
||||
|
@ -188,7 +201,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
|||
dtos.push(dto);
|
||||
}
|
||||
dto.lines.push({
|
||||
index: i,
|
||||
id: bp['_id'],
|
||||
enabled: bp.enabled,
|
||||
condition: bp.condition,
|
||||
hitCondition: bp.hitCondition,
|
||||
|
@ -198,7 +211,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
|||
} else if (bp instanceof FunctionBreakpoint) {
|
||||
dtos.push({
|
||||
type: 'function',
|
||||
index: i,
|
||||
id: bp['_id'],
|
||||
enabled: bp.enabled,
|
||||
functionName: bp.functionName,
|
||||
hitCondition: bp.hitCondition,
|
||||
|
@ -207,21 +220,8 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
|||
}
|
||||
}
|
||||
|
||||
// register with VS Code
|
||||
return this._debugServiceProxy.$registerBreakpoints(dtos).then(ids => {
|
||||
|
||||
// assign VS Code ids to breakpoints and store them in map
|
||||
ids.forEach(id => {
|
||||
const bp = breakpoints[id.index];
|
||||
bp['_id'] = id.id;
|
||||
this._breakpoints.set(id.id, bp);
|
||||
});
|
||||
|
||||
// send notification
|
||||
this.fireBreakpointChanges(breakpoints, [], []);
|
||||
|
||||
return void 0;
|
||||
});
|
||||
// send DTOs to VS Code
|
||||
return this._debugServiceProxy.$registerBreakpoints(dtos);
|
||||
}
|
||||
|
||||
public removeBreakpoints(breakpoints0: vscode.Breakpoint[]): TPromise<void> {
|
||||
|
|
|
@ -470,6 +470,10 @@ class NavigateTypeAdapter {
|
|||
|
||||
class RenameAdapter {
|
||||
|
||||
static supportsResolving(provider: vscode.RenameProvider): boolean {
|
||||
return typeof provider.resolveInitialRenameValue === 'function';
|
||||
}
|
||||
|
||||
private _documents: ExtHostDocuments;
|
||||
private _provider: vscode.RenameProvider;
|
||||
|
||||
|
@ -505,6 +509,22 @@ class RenameAdapter {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
resolveInitialRenameValue(resource: URI, position: IPosition) : TPromise<modes.RenameInitialValue> {
|
||||
if (typeof this._provider.resolveInitialRenameValue !== 'function') {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
let doc = this._documents.getDocumentData(resource).document;
|
||||
let pos = TypeConverters.toPosition(position);
|
||||
|
||||
return asWinJsPromise(token => this._provider.resolveInitialRenameValue(doc, pos, token)).then((value) => {
|
||||
return <modes.RenameInitialValue> {
|
||||
range: TypeConverters.fromRange(value.range),
|
||||
text: value.text
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1010,7 +1030,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
|||
|
||||
registerRenameProvider(selector: vscode.DocumentSelector, provider: vscode.RenameProvider): vscode.Disposable {
|
||||
const handle = this._addNewAdapter(new RenameAdapter(this._documents, provider));
|
||||
this._proxy.$registerRenameSupport(handle, selector);
|
||||
this._proxy.$registerRenameSupport(handle, selector, RenameAdapter.supportsResolving(provider));
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
|
@ -1018,6 +1038,10 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
|||
return this._withAdapter(handle, RenameAdapter, adapter => adapter.provideRenameEdits(URI.revive(resource), position, newName));
|
||||
}
|
||||
|
||||
$resolveInitialRenameValue(handle: number, resource: URI, position: IPosition): TPromise<modes.RenameInitialValue> {
|
||||
return this._withAdapter(handle, RenameAdapter, adapter => adapter.resolveInitialRenameValue(resource, position));
|
||||
}
|
||||
|
||||
// --- suggestion
|
||||
|
||||
registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable {
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { join } from 'vs/base/common/paths';
|
||||
import { mkdirp, dirExists } from 'vs/base/node/pfs';
|
||||
import Event from 'vs/base/common/event';
|
||||
import { LogLevel } from 'vs/workbench/api/node/extHostTypes';
|
||||
|
@ -43,8 +43,8 @@ export class ExtHostLogService extends DelegatedLogService implements ILogServic
|
|||
}
|
||||
|
||||
private createLogger(extensionID: string): ExtHostLogger {
|
||||
const logService = createSpdLogService(extensionID, this.getLevel(), this._environmentService.logsPath, extensionID);
|
||||
const logsDirPath = path.join(this._environmentService.logsPath, extensionID);
|
||||
const logsDirPath = join(this._environmentService.logsPath, extensionID);
|
||||
const logService = createSpdLogService(extensionID, this.getLevel(), logsDirPath);
|
||||
this._register(this.onDidChangeLogLevel(level => logService.setLevel(level)));
|
||||
return new ExtHostLogger(logService, logsDirPath);
|
||||
}
|
||||
|
|
|
@ -7,40 +7,109 @@
|
|||
import URI from 'vs/base/common/uri';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { normalize } from 'vs/base/common/paths';
|
||||
import { delta } from 'vs/base/common/arrays';
|
||||
import { delta as arrayDelta } from 'vs/base/common/arrays';
|
||||
import { relative, dirname } from 'path';
|
||||
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkspaceData, ExtHostWorkspaceShape, MainContext, MainThreadWorkspaceShape, IMainContext } from './extHost.protocol';
|
||||
import { IWorkspaceData, ExtHostWorkspaceShape, MainContext, MainThreadWorkspaceShape, IMainContext, MainThreadMessageServiceShape } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { basenameOrAuthority, isEqual } from 'vs/base/common/resources';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { Severity } from 'vs/platform/message/common/message';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
class Workspace2 extends Workspace {
|
||||
function isFolderEqual(folderA: URI, folderB: URI): boolean {
|
||||
return isEqual(folderA, folderB, !isLinux);
|
||||
}
|
||||
|
||||
static fromData(data: IWorkspaceData) {
|
||||
function compareWorkspaceFolderByUri(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder): number {
|
||||
return isFolderEqual(a.uri, b.uri) ? 0 : compare(a.uri.toString(), b.uri.toString());
|
||||
}
|
||||
|
||||
function compareWorkspaceFolderByUriAndNameAndIndex(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder): number {
|
||||
if (a.index !== b.index) {
|
||||
return a.index < b.index ? -1 : 1;
|
||||
}
|
||||
|
||||
return isFolderEqual(a.uri, b.uri) ? compare(a.name, b.name) : compare(a.uri.toString(), b.uri.toString());
|
||||
}
|
||||
|
||||
function delta(oldFolders: vscode.WorkspaceFolder[], newFolders: vscode.WorkspaceFolder[], compare: (a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder) => number): { removed: vscode.WorkspaceFolder[], added: vscode.WorkspaceFolder[] } {
|
||||
const oldSortedFolders = oldFolders.slice(0).sort(compare);
|
||||
const newSortedFolders = newFolders.slice(0).sort(compare);
|
||||
|
||||
return arrayDelta(oldSortedFolders, newSortedFolders, compare);
|
||||
}
|
||||
|
||||
interface MutableWorkspaceFolder extends vscode.WorkspaceFolder {
|
||||
name: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
class ExtHostWorkspaceImpl extends Workspace {
|
||||
|
||||
static toExtHostWorkspace(data: IWorkspaceData, previousConfirmedWorkspace?: ExtHostWorkspaceImpl, previousUnconfirmedWorkspace?: ExtHostWorkspaceImpl): { workspace: ExtHostWorkspaceImpl, added: vscode.WorkspaceFolder[], removed: vscode.WorkspaceFolder[] } {
|
||||
if (!data) {
|
||||
return null;
|
||||
} else {
|
||||
const { id, name, folders } = data;
|
||||
return new Workspace2(
|
||||
id,
|
||||
name,
|
||||
folders.map(({ uri, name, index }) => new WorkspaceFolder({ name, index, uri: URI.revive(uri) }))
|
||||
);
|
||||
return { workspace: null, added: [], removed: [] };
|
||||
}
|
||||
|
||||
const { id, name, folders } = data;
|
||||
const newWorkspaceFolders: vscode.WorkspaceFolder[] = [];
|
||||
|
||||
// If we have an existing workspace, we try to find the folders that match our
|
||||
// data and update their properties. It could be that an extension stored them
|
||||
// for later use and we want to keep them "live" if they are still present.
|
||||
const oldWorkspace = previousConfirmedWorkspace;
|
||||
if (oldWorkspace) {
|
||||
folders.forEach((folderData, index) => {
|
||||
const folderUri = URI.revive(folderData.uri);
|
||||
const existingFolder = ExtHostWorkspaceImpl._findFolder(previousUnconfirmedWorkspace || previousConfirmedWorkspace, folderUri);
|
||||
|
||||
if (existingFolder) {
|
||||
existingFolder.name = folderData.name;
|
||||
existingFolder.index = folderData.index;
|
||||
|
||||
newWorkspaceFolders.push(existingFolder);
|
||||
} else {
|
||||
newWorkspaceFolders.push({ uri: folderUri, name: folderData.name, index });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
newWorkspaceFolders.push(...folders.map(({ uri, name, index }) => ({ uri: URI.revive(uri), name, index })));
|
||||
}
|
||||
|
||||
// make sure to restore sort order based on index
|
||||
newWorkspaceFolders.sort((f1, f2) => f1.index < f2.index ? -1 : 1);
|
||||
|
||||
const workspace = new ExtHostWorkspaceImpl(id, name, newWorkspaceFolders);
|
||||
const { added, removed } = delta(oldWorkspace ? oldWorkspace.workspaceFolders : [], workspace.workspaceFolders, compareWorkspaceFolderByUri);
|
||||
|
||||
return { workspace, added, removed };
|
||||
}
|
||||
|
||||
private static _findFolder(workspace: ExtHostWorkspaceImpl, folderUriToFind: URI): MutableWorkspaceFolder {
|
||||
for (let i = 0; i < workspace.folders.length; i++) {
|
||||
const folder = workspace.workspaceFolders[i];
|
||||
if (isFolderEqual(folder.uri, folderUriToFind)) {
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private readonly _workspaceFolders: vscode.WorkspaceFolder[] = [];
|
||||
private readonly _structure = TernarySearchTree.forPaths<vscode.WorkspaceFolder>();
|
||||
|
||||
private constructor(id: string, name: string, folders: WorkspaceFolder[]) {
|
||||
super(id, name, folders);
|
||||
private constructor(id: string, name: string, folders: vscode.WorkspaceFolder[]) {
|
||||
super(id, name, folders.map(f => new WorkspaceFolder(f)));
|
||||
|
||||
// setup the workspace folder data structure
|
||||
this.folders.forEach(({ name, uri, index }) => {
|
||||
const workspaceFolder = { name, uri, index };
|
||||
this._workspaceFolders.push(workspaceFolder);
|
||||
this._structure.set(workspaceFolder.uri.toString(), workspaceFolder);
|
||||
folders.forEach(folder => {
|
||||
this._workspaceFolders.push(folder);
|
||||
this._structure.set(folder.uri.toString(), folder);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -63,44 +132,116 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
|||
|
||||
private readonly _onDidChangeWorkspace = new Emitter<vscode.WorkspaceFoldersChangeEvent>();
|
||||
private readonly _proxy: MainThreadWorkspaceShape;
|
||||
private _workspace: Workspace2;
|
||||
|
||||
private _confirmedWorkspace: ExtHostWorkspaceImpl;
|
||||
private _unconfirmedWorkspace: ExtHostWorkspaceImpl;
|
||||
|
||||
private _messageService: MainThreadMessageServiceShape;
|
||||
|
||||
readonly onDidChangeWorkspace: Event<vscode.WorkspaceFoldersChangeEvent> = this._onDidChangeWorkspace.event;
|
||||
|
||||
constructor(mainContext: IMainContext, data: IWorkspaceData) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadWorkspace);
|
||||
this._workspace = Workspace2.fromData(data);
|
||||
this._messageService = mainContext.getProxy(MainContext.MainThreadMessageService);
|
||||
this._confirmedWorkspace = ExtHostWorkspaceImpl.toExtHostWorkspace(data).workspace;
|
||||
}
|
||||
|
||||
// --- workspace ---
|
||||
|
||||
get workspace(): Workspace {
|
||||
return this._workspace;
|
||||
return this._actualWorkspace;
|
||||
}
|
||||
|
||||
private get _actualWorkspace(): ExtHostWorkspaceImpl {
|
||||
return this._unconfirmedWorkspace || this._confirmedWorkspace;
|
||||
}
|
||||
|
||||
getWorkspaceFolders(): vscode.WorkspaceFolder[] {
|
||||
if (!this._workspace) {
|
||||
if (!this._actualWorkspace) {
|
||||
return undefined;
|
||||
} else {
|
||||
return this._workspace.workspaceFolders.slice(0);
|
||||
}
|
||||
return this._actualWorkspace.workspaceFolders.slice(0);
|
||||
}
|
||||
|
||||
updateWorkspaceFolders(extension: IExtensionDescription, index: number, deleteCount: number, ...workspaceFoldersToAdd: { uri: vscode.Uri, name?: string }[]): boolean {
|
||||
const validatedDistinctWorkspaceFoldersToAdd: { uri: vscode.Uri, name?: string }[] = [];
|
||||
if (Array.isArray(workspaceFoldersToAdd)) {
|
||||
workspaceFoldersToAdd.forEach(folderToAdd => {
|
||||
if (URI.isUri(folderToAdd.uri) && !validatedDistinctWorkspaceFoldersToAdd.some(f => isFolderEqual(f.uri, folderToAdd.uri))) {
|
||||
validatedDistinctWorkspaceFoldersToAdd.push({ uri: folderToAdd.uri, name: folderToAdd.name || basenameOrAuthority(folderToAdd.uri) });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!!this._unconfirmedWorkspace) {
|
||||
return false; // prevent accumulated calls without a confirmed workspace
|
||||
}
|
||||
|
||||
if ([index, deleteCount].some(i => typeof i !== 'number' || i < 0)) {
|
||||
return false; // validate numbers
|
||||
}
|
||||
|
||||
if (deleteCount === 0 && validatedDistinctWorkspaceFoldersToAdd.length === 0) {
|
||||
return false; // nothing to delete or add
|
||||
}
|
||||
|
||||
const currentWorkspaceFolders: MutableWorkspaceFolder[] = this._actualWorkspace ? this._actualWorkspace.workspaceFolders : [];
|
||||
if (index + deleteCount > currentWorkspaceFolders.length) {
|
||||
return false; // cannot delete more than we have
|
||||
}
|
||||
|
||||
// Simulate the updateWorkspaceFolders method on our data to do more validation
|
||||
const newWorkspaceFolders = currentWorkspaceFolders.slice(0);
|
||||
newWorkspaceFolders.splice(index, deleteCount, ...validatedDistinctWorkspaceFoldersToAdd.map(f => ({ uri: f.uri, name: f.name || basenameOrAuthority(f.uri) })));
|
||||
|
||||
for (let i = 0; i < newWorkspaceFolders.length; i++) {
|
||||
const folder = newWorkspaceFolders[i];
|
||||
if (newWorkspaceFolders.some((otherFolder, index) => index !== i && isFolderEqual(folder.uri, otherFolder.uri))) {
|
||||
return false; // cannot add the same folder multiple times
|
||||
}
|
||||
}
|
||||
|
||||
newWorkspaceFolders.forEach((f, index) => f.index = index); // fix index
|
||||
const { added, removed } = delta(currentWorkspaceFolders, newWorkspaceFolders, compareWorkspaceFolderByUriAndNameAndIndex);
|
||||
if (added.length === 0 && removed.length === 0) {
|
||||
return false; // nothing actually changed
|
||||
}
|
||||
|
||||
// Trigger on main side
|
||||
if (this._proxy) {
|
||||
const extName = extension.displayName || extension.name;
|
||||
this._proxy.$updateWorkspaceFolders(extName, index, deleteCount, validatedDistinctWorkspaceFoldersToAdd).then(null, error => {
|
||||
|
||||
// in case of an error, make sure to clear out the unconfirmed workspace
|
||||
// because we cannot expect the acknowledgement from the main side for this
|
||||
this._unconfirmedWorkspace = undefined;
|
||||
|
||||
// show error to user
|
||||
this._messageService.$showMessage(Severity.Error, localize('updateerror', "Extension '{0}' failed to update workspace folders: {1}", extName, error.toString()), { extension }, []);
|
||||
});
|
||||
}
|
||||
|
||||
// Try to accept directly
|
||||
return this.trySetWorkspaceFolders(newWorkspaceFolders);
|
||||
}
|
||||
|
||||
getWorkspaceFolder(uri: vscode.Uri, resolveParent?: boolean): vscode.WorkspaceFolder {
|
||||
if (!this._workspace) {
|
||||
if (!this._actualWorkspace) {
|
||||
return undefined;
|
||||
}
|
||||
return this._workspace.getWorkspaceFolder(uri, resolveParent);
|
||||
return this._actualWorkspace.getWorkspaceFolder(uri, resolveParent);
|
||||
}
|
||||
|
||||
getPath(): string {
|
||||
|
||||
// this is legacy from the days before having
|
||||
// multi-root and we keep it only alive if there
|
||||
// is just one workspace folder.
|
||||
if (!this._workspace) {
|
||||
if (!this._actualWorkspace) {
|
||||
return undefined;
|
||||
}
|
||||
const { folders } = this._workspace;
|
||||
|
||||
const { folders } = this._actualWorkspace;
|
||||
if (folders.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -130,7 +271,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
|||
}
|
||||
|
||||
if (typeof includeWorkspace === 'undefined') {
|
||||
includeWorkspace = this.workspace.folders.length > 1;
|
||||
includeWorkspace = this._actualWorkspace.folders.length > 1;
|
||||
}
|
||||
|
||||
let result = relative(folder.uri.fsPath, path);
|
||||
|
@ -140,27 +281,40 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
|||
return normalize(result, true);
|
||||
}
|
||||
|
||||
private trySetWorkspaceFolders(folders: vscode.WorkspaceFolder[]): boolean {
|
||||
|
||||
// Update directly here. The workspace is unconfirmed as long as we did not get an
|
||||
// acknowledgement from the main side (via $acceptWorkspaceData)
|
||||
if (this._actualWorkspace) {
|
||||
this._unconfirmedWorkspace = ExtHostWorkspaceImpl.toExtHostWorkspace({
|
||||
id: this._actualWorkspace.id,
|
||||
name: this._actualWorkspace.name,
|
||||
configuration: this._actualWorkspace.configuration,
|
||||
folders
|
||||
} as IWorkspaceData, this._actualWorkspace).workspace;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$acceptWorkspaceData(data: IWorkspaceData): void {
|
||||
|
||||
// keep old workspace folder, build new workspace, and
|
||||
// capture new workspace folders. Compute delta between
|
||||
// them send that as event
|
||||
const oldRoots = this._workspace ? this._workspace.workspaceFolders.sort(ExtHostWorkspace._compareWorkspaceFolder) : [];
|
||||
const { workspace, added, removed } = ExtHostWorkspaceImpl.toExtHostWorkspace(data, this._confirmedWorkspace, this._unconfirmedWorkspace);
|
||||
|
||||
this._workspace = Workspace2.fromData(data);
|
||||
const newRoots = this._workspace ? this._workspace.workspaceFolders.sort(ExtHostWorkspace._compareWorkspaceFolder) : [];
|
||||
// Update our workspace object. We have a confirmed workspace, so we drop our
|
||||
// unconfirmed workspace.
|
||||
this._confirmedWorkspace = workspace;
|
||||
this._unconfirmedWorkspace = undefined;
|
||||
|
||||
const { added, removed } = delta(oldRoots, newRoots, ExtHostWorkspace._compareWorkspaceFolder);
|
||||
// Events
|
||||
this._onDidChangeWorkspace.fire(Object.freeze({
|
||||
added: Object.freeze<vscode.WorkspaceFolder[]>(added),
|
||||
removed: Object.freeze<vscode.WorkspaceFolder[]>(removed)
|
||||
}));
|
||||
}
|
||||
|
||||
private static _compareWorkspaceFolder(a: vscode.WorkspaceFolder, b: vscode.WorkspaceFolder): number {
|
||||
return compare(a.uri.toString(), b.uri.toString());
|
||||
}
|
||||
|
||||
// --- search ---
|
||||
|
||||
findFiles(include: vscode.GlobPattern, exclude: vscode.GlobPattern, maxResults?: number, token?: vscode.CancellationToken): Thenable<vscode.Uri[]> {
|
||||
|
|
|
@ -223,6 +223,7 @@ export interface IEnablement extends ITreeElement {
|
|||
}
|
||||
|
||||
export interface IRawBreakpoint {
|
||||
id?: string;
|
||||
lineNumber: number;
|
||||
column?: number;
|
||||
enabled?: boolean;
|
||||
|
@ -523,7 +524,7 @@ export interface IDebugService {
|
|||
/**
|
||||
* Adds new breakpoints to the model for the file specified with the uri. Notifies debug adapter of breakpoint changes.
|
||||
*/
|
||||
addBreakpoints(uri: uri, rawBreakpoints: IRawBreakpoint[]): TPromise<IBreakpoint[]>;
|
||||
addBreakpoints(uri: uri, rawBreakpoints: IRawBreakpoint[]): TPromise<void>;
|
||||
|
||||
/**
|
||||
* Updates the breakpoints.
|
||||
|
@ -551,7 +552,7 @@ export interface IDebugService {
|
|||
/**
|
||||
* Adds a new function breakpoint for the given name.
|
||||
*/
|
||||
addFunctionBreakpoint(name?: string): IFunctionBreakpoint;
|
||||
addFunctionBreakpoint(name?: string, id?: string): void;
|
||||
|
||||
/**
|
||||
* Renames an already existing function breakpoint.
|
||||
|
|
|
@ -675,7 +675,6 @@ export class Breakpoint implements IBreakpoint {
|
|||
public message: string;
|
||||
public endLineNumber: number;
|
||||
public endColumn: number;
|
||||
private id: string;
|
||||
|
||||
constructor(
|
||||
public uri: uri,
|
||||
|
@ -684,13 +683,13 @@ export class Breakpoint implements IBreakpoint {
|
|||
public enabled: boolean,
|
||||
public condition: string,
|
||||
public hitCondition: string,
|
||||
public adapterData: any
|
||||
public adapterData: any,
|
||||
private id = generateUuid()
|
||||
) {
|
||||
if (enabled === undefined) {
|
||||
this.enabled = true;
|
||||
}
|
||||
this.verified = false;
|
||||
this.id = generateUuid();
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
|
@ -700,13 +699,11 @@ export class Breakpoint implements IBreakpoint {
|
|||
|
||||
export class FunctionBreakpoint implements IFunctionBreakpoint {
|
||||
|
||||
private id: string;
|
||||
public verified: boolean;
|
||||
public idFromAdapter: number;
|
||||
|
||||
constructor(public name: string, public enabled: boolean, public hitCondition: string) {
|
||||
constructor(public name: string, public enabled: boolean, public hitCondition: string, private id = generateUuid()) {
|
||||
this.verified = false;
|
||||
this.id = generateUuid();
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
|
@ -867,7 +864,7 @@ export class Model implements IModel {
|
|||
}
|
||||
|
||||
public addBreakpoints(uri: uri, rawData: IRawBreakpoint[], fireEvent = true): Breakpoint[] {
|
||||
const newBreakpoints = rawData.map(rawBp => new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled, rawBp.condition, rawBp.hitCondition, undefined));
|
||||
const newBreakpoints = rawData.map(rawBp => new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled, rawBp.condition, rawBp.hitCondition, undefined, rawBp.id));
|
||||
this.breakpoints = this.breakpoints.concat(newBreakpoints);
|
||||
this.breakpointsActivated = true;
|
||||
this.sortAndDeDup();
|
||||
|
@ -957,8 +954,8 @@ export class Model implements IModel {
|
|||
this._onDidChangeBreakpoints.fire({ changed: changed });
|
||||
}
|
||||
|
||||
public addFunctionBreakpoint(functionName: string): FunctionBreakpoint {
|
||||
const newFunctionBreakpoint = new FunctionBreakpoint(functionName, true, null);
|
||||
public addFunctionBreakpoint(functionName: string, id: string): FunctionBreakpoint {
|
||||
const newFunctionBreakpoint = new FunctionBreakpoint(functionName, true, null, id);
|
||||
this.functionBreakpoints.push(newFunctionBreakpoint);
|
||||
this._onDidChangeBreakpoints.fire({ added: [newFunctionBreakpoint] });
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import Event, { Emitter } from 'vs/base/common/event';
|
|||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { first } from 'vs/base/common/arrays';
|
||||
import severity from 'vs/base/common/severity';
|
||||
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import uri from 'vs/base/common/uri';
|
||||
|
@ -35,7 +34,6 @@ import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
|||
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
|
||||
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
|
||||
// debuggers extension point
|
||||
export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawAdapter[]>('debuggers', [], {
|
||||
|
@ -501,8 +499,7 @@ class Launch implements ILaunch {
|
|||
@IFileService private fileService: IFileService,
|
||||
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
|
||||
@IConfigurationService protected configurationService: IConfigurationService,
|
||||
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService,
|
||||
@IMessageService private messageService: IMessageService
|
||||
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
@ -579,7 +576,7 @@ class Launch implements ILaunch {
|
|||
|
||||
public openConfigFile(sideBySide: boolean, type?: string): TPromise<IEditor> {
|
||||
const resource = this.uri;
|
||||
let configFileCreated = false;
|
||||
let pinned = false;
|
||||
|
||||
return this.fileService.resolveContent(resource).then(content => content.value, err => {
|
||||
|
||||
|
@ -599,7 +596,7 @@ class Launch implements ILaunch {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
configFileCreated = true;
|
||||
pinned = true; // pin only if config file is created #8727
|
||||
return this.fileService.updateContent(resource, content).then(() => {
|
||||
// convert string into IContent; see #32135
|
||||
return content;
|
||||
|
@ -623,16 +620,10 @@ class Launch implements ILaunch {
|
|||
options: {
|
||||
forceOpen: true,
|
||||
selection,
|
||||
pinned: configFileCreated, // pin only if config file is created #8727
|
||||
pinned,
|
||||
revealIfVisible: true
|
||||
},
|
||||
}, sideBySide).then(editor => {
|
||||
if (configFileCreated) {
|
||||
this.messageService.show(severity.Info, nls.localize('NewLaunchConfig', "Please set up the launch configuration file for your application."));
|
||||
}
|
||||
|
||||
return editor;
|
||||
});
|
||||
}, sideBySide);
|
||||
}, (error) => {
|
||||
throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error));
|
||||
});
|
||||
|
@ -647,10 +638,9 @@ class WorkspaceLaunch extends Launch implements ILaunch {
|
|||
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IConfigurationResolverService configurationResolverService: IConfigurationResolverService,
|
||||
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
|
||||
@IMessageService messageService: IMessageService
|
||||
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService
|
||||
) {
|
||||
super(configurationManager, undefined, fileService, editorService, configurationService, configurationResolverService, messageService);
|
||||
super(configurationManager, undefined, fileService, editorService, configurationService, configurationResolverService);
|
||||
}
|
||||
|
||||
get uri(): uri {
|
||||
|
|
|
@ -580,13 +580,11 @@ export class DebugService implements debug.IDebugService {
|
|||
return this.sendAllBreakpoints();
|
||||
}
|
||||
|
||||
public addBreakpoints(uri: uri, rawBreakpoints: debug.IRawBreakpoint[]): TPromise<debug.IBreakpoint[]> {
|
||||
const bps = this.model.addBreakpoints(uri, rawBreakpoints);
|
||||
public addBreakpoints(uri: uri, rawBreakpoints: debug.IRawBreakpoint[]): TPromise<void> {
|
||||
this.model.addBreakpoints(uri, rawBreakpoints);
|
||||
rawBreakpoints.forEach(rbp => aria.status(nls.localize('breakpointAdded', "Added breakpoint, line {0}, file {1}", rbp.lineNumber, uri.fsPath)));
|
||||
|
||||
return this.sendBreakpoints(uri).then(_ => {
|
||||
return bps;
|
||||
});
|
||||
return this.sendBreakpoints(uri);
|
||||
}
|
||||
|
||||
public updateBreakpoints(uri: uri, data: { [id: string]: DebugProtocol.Breakpoint }): void {
|
||||
|
@ -609,10 +607,9 @@ export class DebugService implements debug.IDebugService {
|
|||
return this.sendAllBreakpoints();
|
||||
}
|
||||
|
||||
public addFunctionBreakpoint(name?: string): debug.IFunctionBreakpoint {
|
||||
const newFunctionBreakpoint = this.model.addFunctionBreakpoint(name || '');
|
||||
public addFunctionBreakpoint(name?: string, id?: string): void {
|
||||
const newFunctionBreakpoint = this.model.addFunctionBreakpoint(name || '', id);
|
||||
this.viewModel.setSelectedFunctionBreakpoint(newFunctionBreakpoint);
|
||||
return newFunctionBreakpoint;
|
||||
}
|
||||
|
||||
public renameFunctionBreakpoint(id: string, newFunctionName: string): TPromise<void> {
|
||||
|
|
|
@ -39,7 +39,7 @@ export class MockDebugService implements debug.IDebugService {
|
|||
public focusStackFrame(focusedStackFrame: debug.IStackFrame): void {
|
||||
}
|
||||
|
||||
public addBreakpoints(uri: uri, rawBreakpoints: debug.IRawBreakpoint[]): TPromise<debug.IBreakpoint[]> {
|
||||
public addBreakpoints(uri: uri, rawBreakpoints: debug.IRawBreakpoint[]): TPromise<void> {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
|
@ -57,9 +57,7 @@ export class MockDebugService implements debug.IDebugService {
|
|||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public addFunctionBreakpoint(): debug.IFunctionBreakpoint {
|
||||
return null;
|
||||
}
|
||||
public addFunctionBreakpoint(): void { }
|
||||
|
||||
public moveWatchExpression(id: string, position: number): void { }
|
||||
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import paths = require('vs/base/common/paths');
|
||||
import resources = require('vs/base/common/resources');
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { IFileStat, isParent } from 'vs/platform/files/common/files';
|
||||
import { IFileStat } from 'vs/platform/files/common/files';
|
||||
import { IEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IEditorGroup, toResource, IEditorIdentifier } from 'vs/workbench/common/editor';
|
||||
|
@ -130,7 +131,7 @@ export class FileStat implements IFileStat {
|
|||
// the folder is fully resolved if either it has a list of children or the client requested this by using the resolveTo
|
||||
// array of resource path to resolve.
|
||||
stat.isDirectoryResolved = !!raw.children || (!!resolveTo && resolveTo.some((r) => {
|
||||
return paths.isEqualOrParent(r.fsPath, stat.resource.fsPath, !isLinux /* ignorecase */);
|
||||
return resources.isEqualOrParent(r, stat.resource, !isLinux /* ignorecase */);
|
||||
}));
|
||||
|
||||
// Recurse into children
|
||||
|
@ -280,7 +281,7 @@ export class FileStat implements IFileStat {
|
|||
public find(resource: URI): FileStat {
|
||||
|
||||
// Return if path found
|
||||
if (paths.isEqual(resource.fsPath, this.resource.fsPath, !isLinux /* ignorecase */)) {
|
||||
if (resources.isEqual(resource, this.resource, !isLinux /* ignorecase */)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -292,11 +293,11 @@ export class FileStat implements IFileStat {
|
|||
for (let i = 0; i < this.children.length; i++) {
|
||||
const child = this.children[i];
|
||||
|
||||
if (paths.isEqual(resource.fsPath, child.resource.fsPath, !isLinux /* ignorecase */)) {
|
||||
if (resources.isEqual(resource, child.resource, !isLinux /* ignorecase */)) {
|
||||
return child;
|
||||
}
|
||||
|
||||
if (child.isDirectory && isParent(resource.fsPath, child.resource.fsPath, !isLinux /* ignorecase */)) {
|
||||
if (child.isDirectory && resources.isEqualOrParent(resource, child.resource, !isLinux /* ignorecase */)) {
|
||||
return child.find(resource);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -777,6 +777,16 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView
|
|||
isDirectory: true
|
||||
}, root);
|
||||
|
||||
const setInputAndExpand = (input: FileStat | Model, statsToExpand: FileStat[]) => {
|
||||
// Make sure to expand all folders that where expanded in the previous session
|
||||
// Special case: we are switching to multi workspace view, thus expand all the roots (they might just be added)
|
||||
if (input === this.model && statsToExpand.every(fs => !fs.isRoot)) {
|
||||
statsToExpand = this.model.roots.concat(statsToExpand);
|
||||
}
|
||||
|
||||
return this.explorerViewer.setInput(input).then(() => this.explorerViewer.expandAll(statsToExpand));
|
||||
};
|
||||
|
||||
if (targetsToResolve.every(t => t.root.resource.scheme === 'file')) {
|
||||
// All the roots are local, resolve them in parallel
|
||||
return this.fileService.resolveFiles(targetsToResolve).then(results => {
|
||||
|
@ -792,17 +802,11 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView
|
|||
modelStats.forEach((modelStat, index) => FileStat.mergeLocalWithDisk(modelStat, this.model.roots[index]));
|
||||
|
||||
const statsToExpand: FileStat[] = this.explorerViewer.getExpandedElements().concat(targetsToExpand.map(expand => this.model.findClosest(expand)));
|
||||
|
||||
if (input === this.explorerViewer.getInput()) {
|
||||
return this.explorerViewer.refresh().then(() => statsToExpand.length ? this.explorerViewer.expandAll(statsToExpand) : undefined);
|
||||
return this.explorerViewer.refresh().then(() => this.explorerViewer.expandAll(statsToExpand));
|
||||
}
|
||||
|
||||
// Make sure to expand all folders that where expanded in the previous session
|
||||
// Special case: there is nothing to expand, thus expand all the roots (they might just be added)
|
||||
if (statsToExpand.length === 0) {
|
||||
statsToExpand.push(...this.model.roots);
|
||||
}
|
||||
return this.explorerViewer.setInput(input).then(() => statsToExpand.length ? this.explorerViewer.expandAll(statsToExpand) : undefined);
|
||||
return setInputAndExpand(input, statsToExpand);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -829,13 +833,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView
|
|||
return delayerPromise;
|
||||
}
|
||||
|
||||
// Display roots only when multi folder workspace
|
||||
// Make sure to expand all folders that where expanded in the previous session
|
||||
if (input === this.model) {
|
||||
// We have transitioned into workspace view -> expand all roots
|
||||
toExpand = this.model.roots.concat(toExpand);
|
||||
}
|
||||
return this.explorerViewer.setInput(input).then(() => this.explorerViewer.expandAll(toExpand));
|
||||
return setInputAndExpand(input, statsToExpand);
|
||||
})));
|
||||
}
|
||||
|
||||
|
|
|
@ -466,7 +466,7 @@ export class FileController extends DefaultController implements IDisposable {
|
|||
getAnchor: () => anchor,
|
||||
getActions: () => {
|
||||
const actions = [];
|
||||
fillInActions(this.contributedContextMenu, { arg: stat instanceof FileStat ? stat.resource : undefined, shouldForwardArgs: true }, actions, this.contextMenuService);
|
||||
fillInActions(this.contributedContextMenu, { arg: stat instanceof FileStat ? stat.resource : {}, shouldForwardArgs: true }, actions, this.contextMenuService);
|
||||
return TPromise.as(actions);
|
||||
},
|
||||
onHide: (wasCancelled?: boolean) => {
|
||||
|
@ -474,7 +474,9 @@ export class FileController extends DefaultController implements IDisposable {
|
|||
tree.DOMFocus();
|
||||
}
|
||||
},
|
||||
getActionsContext: () => selection && selection.indexOf(stat) >= 0 ? selection.map((fs: FileStat) => fs.resource) : [stat]
|
||||
getActionsContext: () => selection && selection.indexOf(stat) >= 0
|
||||
? selection.map((fs: FileStat) => fs.resource)
|
||||
: stat instanceof FileStat ? [stat.resource] : []
|
||||
});
|
||||
|
||||
return true;
|
||||
|
|
|
@ -10,7 +10,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
|
|||
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
|
||||
import { ILogService, LogLevel, DEFAULT_LOG_LEVEL } from 'vs/platform/log/common/log';
|
||||
import { IOutputService, COMMAND_OPEN_LOG_VIEWER } from 'vs/workbench/parts/output/common/output';
|
||||
import * as Constants from 'vs/workbench/parts/logs/common/logConstants';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
|
@ -108,14 +108,15 @@ export class SetLogLevelAction extends Action {
|
|||
}
|
||||
|
||||
run(): TPromise<void> {
|
||||
const current = this.logService.getLevel();
|
||||
const entries = [
|
||||
{ label: nls.localize('trace', "Trace"), level: LogLevel.Trace },
|
||||
{ label: nls.localize('debug', "Debug"), level: LogLevel.Debug },
|
||||
{ label: nls.localize('info', "Info"), level: LogLevel.Info },
|
||||
{ label: nls.localize('warn', "Warning"), level: LogLevel.Warning },
|
||||
{ label: nls.localize('err', "Error"), level: LogLevel.Error },
|
||||
{ label: nls.localize('critical', "Critical"), level: LogLevel.Critical },
|
||||
{ label: nls.localize('off', "Off"), level: LogLevel.Off }
|
||||
{ label: nls.localize('trace', "Trace"), level: LogLevel.Trace, description: this.getDescription(LogLevel.Trace, current) },
|
||||
{ label: nls.localize('debug', "Debug"), level: LogLevel.Debug, description: this.getDescription(LogLevel.Debug, current) },
|
||||
{ label: nls.localize('info', "Info"), level: LogLevel.Info, description: this.getDescription(LogLevel.Info, current) },
|
||||
{ label: nls.localize('warn', "Warning"), level: LogLevel.Warning, description: this.getDescription(LogLevel.Warning, current) },
|
||||
{ label: nls.localize('err', "Error"), level: LogLevel.Error, description: this.getDescription(LogLevel.Error, current) },
|
||||
{ label: nls.localize('critical', "Critical"), level: LogLevel.Critical, description: this.getDescription(LogLevel.Critical, current) },
|
||||
{ label: nls.localize('off', "Off"), level: LogLevel.Off, description: this.getDescription(LogLevel.Off, current) },
|
||||
];
|
||||
|
||||
return this.quickOpenService.pick(entries, { placeHolder: nls.localize('selectLogLevel', "Select log level"), autoFocus: { autoFocusIndex: this.logService.getLevel() } }).then(entry => {
|
||||
|
@ -124,4 +125,17 @@ export class SetLogLevelAction extends Action {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getDescription(level: LogLevel, current: LogLevel): string {
|
||||
if (DEFAULT_LOG_LEVEL === level && current === level) {
|
||||
return nls.localize('default and current', "Default & Current");
|
||||
}
|
||||
if (DEFAULT_LOG_LEVEL === level) {
|
||||
return nls.localize('default', "Default");
|
||||
}
|
||||
if (current === level) {
|
||||
return nls.localize('current', "Current");
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
}
|
|
@ -40,6 +40,7 @@ import { binarySearch } from 'vs/base/common/arrays';
|
|||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel';
|
||||
|
||||
|
@ -225,20 +226,25 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannel implements Out
|
|||
private appendedMessage = '';
|
||||
private loadingFromFileInProgress: boolean = false;
|
||||
private resettingDelayer: ThrottledDelayer<void>;
|
||||
private readonly rotatingFilePath: string;
|
||||
|
||||
constructor(
|
||||
outputChannelIdentifier: IOutputChannelIdentifier,
|
||||
outputDir: string,
|
||||
modelUri: URI,
|
||||
@IFileService fileService: IFileService,
|
||||
@IModelService modelService: IModelService,
|
||||
@IModeService modeService: IModeService,
|
||||
@ILogService logService: ILogService
|
||||
@ILogService logService: ILogService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super(outputChannelIdentifier, modelUri, fileService, modelService, modeService);
|
||||
super({ ...outputChannelIdentifier, file: URI.file(paths.join(outputDir, `${outputChannelIdentifier.id}.log`)) }, modelUri, fileService, modelService, modeService);
|
||||
|
||||
// Use one rotating file to check for main file reset
|
||||
this.outputWriter = new RotatingLogger(this.id, this.file.fsPath, 1024 * 1024 * 30, 1);
|
||||
const threshold = configurationService.getValue('output.threshold');
|
||||
this.outputWriter = new RotatingLogger(this.id, this.file.fsPath, threshold && typeof threshold === 'number' ? threshold : 1024 * 1024 * 30, 1);
|
||||
this.outputWriter.clearFormatters();
|
||||
this.rotatingFilePath = `${outputChannelIdentifier.id}.1.log`;
|
||||
this._register(watchOutputDirectory(paths.dirname(this.file.fsPath), logService, (eventType, file) => this.onFileChangedInOutputDirector(eventType, file)));
|
||||
|
||||
this.resettingDelayer = new ThrottledDelayer<void>(50);
|
||||
|
@ -301,7 +307,7 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannel implements Out
|
|||
|
||||
private onFileChangedInOutputDirector(eventType: string, fileName: string): void {
|
||||
// Check if rotating file has changed. It changes only when the main file exceeds its limit.
|
||||
if (`${paths.basename(this.file.fsPath)}.1` === fileName) {
|
||||
if (this.rotatingFilePath === fileName) {
|
||||
this.resettingDelayer.trigger(() => this.resetModel());
|
||||
}
|
||||
}
|
||||
|
@ -554,9 +560,8 @@ export class OutputService extends Disposable implements IOutputService, ITextMo
|
|||
if (channelData && channelData.file) {
|
||||
return this.instantiationService.createInstance(FileOutputChannel, channelData, uri);
|
||||
}
|
||||
const file = URI.file(paths.join(this.outputDir, `${id}.log`));
|
||||
try {
|
||||
return this.instantiationService.createInstance(OutputChannelBackedByFile, { id, label: channelData ? channelData.label : '', file }, uri);
|
||||
return this.instantiationService.createInstance(OutputChannelBackedByFile, { id, label: channelData ? channelData.label : '' }, this.outputDir, uri);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
this.telemetryService.publicLog('output.used.bufferedChannel');
|
||||
|
|
|
@ -429,6 +429,7 @@ interface ResourceTemplate {
|
|||
fileLabel: FileLabel;
|
||||
decorationIcon: HTMLElement;
|
||||
actionBar: ActionBar;
|
||||
elementDisposable: IDisposable;
|
||||
dispose: () => void;
|
||||
}
|
||||
|
||||
|
@ -464,7 +465,8 @@ class ResourceRenderer implements IRenderer<ISCMResource, ResourceTemplate> {
|
|||
private actionItemProvider: IActionItemProvider,
|
||||
private getSelectedResources: () => ISCMResource[],
|
||||
@IThemeService private themeService: IThemeService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IConfigurationService private configurationService: IConfigurationService
|
||||
) { }
|
||||
|
||||
renderTemplate(container: HTMLElement): ResourceTemplate {
|
||||
|
@ -480,7 +482,7 @@ class ResourceRenderer implements IRenderer<ISCMResource, ResourceTemplate> {
|
|||
const decorationIcon = append(element, $('.decoration-icon'));
|
||||
|
||||
return {
|
||||
element, name, fileLabel, decorationIcon, actionBar, dispose: () => {
|
||||
element, name, fileLabel, decorationIcon, actionBar, elementDisposable: EmptyDisposable, dispose: () => {
|
||||
actionBar.dispose();
|
||||
fileLabel.dispose();
|
||||
}
|
||||
|
@ -488,14 +490,22 @@ class ResourceRenderer implements IRenderer<ISCMResource, ResourceTemplate> {
|
|||
}
|
||||
|
||||
renderElement(resource: ISCMResource, index: number, template: ResourceTemplate): void {
|
||||
template.elementDisposable.dispose();
|
||||
|
||||
const theme = this.themeService.getTheme();
|
||||
const icon = theme.type === LIGHT ? resource.decorations.icon : resource.decorations.iconDark;
|
||||
|
||||
template.fileLabel.setFile(resource.sourceUri, { fileDecorations: { colors: false, badges: !icon, data: resource.decorations } });
|
||||
template.actionBar.clear();
|
||||
template.actionBar.context = resource;
|
||||
template.actionBar.push(this.scmMenus.getResourceActions(resource), { icon: true, label: false });
|
||||
|
||||
const updateActions = () => {
|
||||
template.actionBar.clear();
|
||||
template.actionBar.push(this.scmMenus.getResourceActions(resource), { icon: true, label: false });
|
||||
};
|
||||
|
||||
template.elementDisposable = this.configurationService.onDidChangeConfiguration(updateActions);
|
||||
updateActions();
|
||||
|
||||
toggleClass(template.name, 'strike-through', resource.decorations.strikeThrough);
|
||||
toggleClass(template.element, 'faded', resource.decorations.faded);
|
||||
|
||||
|
@ -507,9 +517,12 @@ class ResourceRenderer implements IRenderer<ISCMResource, ResourceTemplate> {
|
|||
template.decorationIcon.style.display = 'none';
|
||||
template.decorationIcon.style.backgroundImage = '';
|
||||
}
|
||||
|
||||
template.element.setAttribute('data-tooltip', resource.decorations.tooltip);
|
||||
}
|
||||
|
||||
disposeTemplate(template: ResourceTemplate): void {
|
||||
template.elementDisposable.dispose();
|
||||
template.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import * as nls from 'vs/nls';
|
||||
import 'vs/css!./media/update.contribution';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { ReleaseNotesEditor } from 'vs/workbench/parts/update/electron-browser/releaseNotesEditor';
|
||||
|
@ -60,7 +61,7 @@ configurationRegistry.registerConfiguration({
|
|||
},
|
||||
'update.enableWindowsBackgroundUpdates': {
|
||||
'type': 'boolean',
|
||||
'default': false,
|
||||
'default': product.quality === 'insider',
|
||||
'description': nls.localize('enableWindowsBackgroundUpdates', "Enables Windows background updates.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,9 +110,9 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
|
|||
return this.workspace.getFolder(resource);
|
||||
}
|
||||
|
||||
public addFolders(foldersToAdd: IWorkspaceFolderCreationData[]): TPromise<void> {
|
||||
public addFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number): TPromise<void> {
|
||||
assert.ok(this.jsonEditingService, 'Workbench is not initialized yet');
|
||||
return this.workspaceEditingQueue.queue(() => this.doAddFolders(foldersToAdd));
|
||||
return this.workspaceEditingQueue.queue(() => this.doAddFolders(foldersToAdd, index));
|
||||
}
|
||||
|
||||
public removeFolders(foldersToRemove: URI[]): TPromise<void> {
|
||||
|
@ -134,7 +134,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
|
|||
return false;
|
||||
}
|
||||
|
||||
private doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[]): TPromise<void> {
|
||||
private doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number): TPromise<void> {
|
||||
if (this.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
|
||||
return TPromise.as(void 0); // we need a workspace to begin with
|
||||
}
|
||||
|
@ -176,7 +176,16 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
|
|||
});
|
||||
|
||||
if (storedFoldersToAdd.length > 0) {
|
||||
return this.setFolders([...currentStoredFolders, ...storedFoldersToAdd]);
|
||||
let newStoredWorkspaceFolders: IStoredWorkspaceFolder[] = [];
|
||||
|
||||
if (typeof index === 'number' && index >= 0 && index < currentStoredFolders.length) {
|
||||
newStoredWorkspaceFolders = currentStoredFolders.slice(0);
|
||||
newStoredWorkspaceFolders.splice(index, 0, ...storedFoldersToAdd);
|
||||
} else {
|
||||
newStoredWorkspaceFolders = [...currentStoredFolders, ...storedFoldersToAdd];
|
||||
}
|
||||
|
||||
return this.setFolders(newStoredWorkspaceFolders);
|
||||
}
|
||||
|
||||
return TPromise.as(void 0);
|
||||
|
|
|
@ -192,6 +192,34 @@ suite('WorkspaceContextService - Workspace', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('add folders (at specific index)', () => {
|
||||
const workspaceDir = path.dirname(testObject.getWorkspace().folders[0].uri.fsPath);
|
||||
return testObject.addFolders([{ uri: URI.file(path.join(workspaceDir, 'd')) }, { uri: URI.file(path.join(workspaceDir, 'c')) }], 0)
|
||||
.then(() => {
|
||||
const actual = testObject.getWorkspace().folders;
|
||||
|
||||
assert.equal(actual.length, 4);
|
||||
assert.equal(path.basename(actual[0].uri.fsPath), 'd');
|
||||
assert.equal(path.basename(actual[1].uri.fsPath), 'c');
|
||||
assert.equal(path.basename(actual[2].uri.fsPath), 'a');
|
||||
assert.equal(path.basename(actual[3].uri.fsPath), 'b');
|
||||
});
|
||||
});
|
||||
|
||||
test('add folders (at specific wrong index)', () => {
|
||||
const workspaceDir = path.dirname(testObject.getWorkspace().folders[0].uri.fsPath);
|
||||
return testObject.addFolders([{ uri: URI.file(path.join(workspaceDir, 'd')) }, { uri: URI.file(path.join(workspaceDir, 'c')) }], 10)
|
||||
.then(() => {
|
||||
const actual = testObject.getWorkspace().folders;
|
||||
|
||||
assert.equal(actual.length, 4);
|
||||
assert.equal(path.basename(actual[0].uri.fsPath), 'a');
|
||||
assert.equal(path.basename(actual[1].uri.fsPath), 'b');
|
||||
assert.equal(path.basename(actual[2].uri.fsPath), 'd');
|
||||
assert.equal(path.basename(actual[3].uri.fsPath), 'c');
|
||||
});
|
||||
});
|
||||
|
||||
test('add folders (with name)', () => {
|
||||
const workspaceDir = path.dirname(testObject.getWorkspace().folders[0].uri.fsPath);
|
||||
return testObject.addFolders([{ uri: URI.file(path.join(workspaceDir, 'd')), name: 'DDD' }, { uri: URI.file(path.join(workspaceDir, 'c')), name: 'CCC' }])
|
||||
|
|
|
@ -27,6 +27,12 @@ export interface IWorkspaceEditingService {
|
|||
*/
|
||||
removeFolders(folders: URI[], donotNotifyError?: boolean): TPromise<void>;
|
||||
|
||||
/**
|
||||
* Allows to add and remove folders to the existing workspace at once.
|
||||
* When `donotNotifyError` is `true`, error will be bubbled up otherwise, the service handles the error with proper message and action
|
||||
*/
|
||||
updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): TPromise<void>;
|
||||
|
||||
/**
|
||||
* creates a new workspace with the provided folders and opens it. if path is provided
|
||||
* the workspace will be saved into that location.
|
||||
|
|
|
@ -47,16 +47,56 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
|
|||
) {
|
||||
}
|
||||
|
||||
public updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): TPromise<void> {
|
||||
const folders = this.contextService.getWorkspace().folders;
|
||||
|
||||
let foldersToDelete: URI[] = [];
|
||||
if (typeof deleteCount === 'number') {
|
||||
foldersToDelete = folders.slice(index, index + deleteCount).map(f => f.uri);
|
||||
}
|
||||
|
||||
const wantsToDelete = foldersToDelete.length > 0;
|
||||
const wantsToAdd = Array.isArray(foldersToAdd) && foldersToAdd.length > 0;
|
||||
|
||||
if (!wantsToAdd && !wantsToDelete) {
|
||||
return TPromise.as(void 0); // return early if there is nothing to do
|
||||
}
|
||||
|
||||
// Add Folders
|
||||
if (wantsToAdd && !wantsToDelete) {
|
||||
return this.doAddFolders(foldersToAdd, index, donotNotifyError);
|
||||
}
|
||||
|
||||
// Delete Folders
|
||||
if (wantsToDelete && !wantsToAdd) {
|
||||
return this.removeFolders(foldersToDelete);
|
||||
}
|
||||
|
||||
// Add & Delete Folders
|
||||
if (this.includesSingleFolderWorkspace(foldersToDelete)) {
|
||||
// if we are in single-folder state and the folder is replaced with
|
||||
// other folders, we handle this specially and just enter workspace
|
||||
// mode with the folders that are being added.
|
||||
return this.createAndEnterWorkspace(foldersToAdd);
|
||||
}
|
||||
|
||||
// Make sure to first remove folders and then add them to account for folders being updated
|
||||
return this.removeFolders(foldersToDelete).then(() => this.doAddFolders(foldersToAdd, index, donotNotifyError));
|
||||
}
|
||||
|
||||
public addFolders(foldersToAdd: IWorkspaceFolderCreationData[], donotNotifyError: boolean = false): TPromise<void> {
|
||||
return this.doAddFolders(foldersToAdd, void 0, donotNotifyError);
|
||||
}
|
||||
|
||||
private doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number, donotNotifyError: boolean = false): TPromise<void> {
|
||||
const state = this.contextService.getWorkbenchState();
|
||||
|
||||
// If we are in no-workspace or single-folder workspace, adding folders has to
|
||||
// enter a workspace.
|
||||
if (state !== WorkbenchState.WORKSPACE) {
|
||||
const newWorkspaceFolders: IWorkspaceFolderCreationData[] = distinct([
|
||||
...this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri } as IWorkspaceFolderCreationData)),
|
||||
...foldersToAdd
|
||||
] as IWorkspaceFolderCreationData[], folder => isLinux ? folder.uri.toString() : folder.uri.toString().toLowerCase());
|
||||
let newWorkspaceFolders = this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri } as IWorkspaceFolderCreationData));
|
||||
newWorkspaceFolders.splice(typeof index === 'number' ? index : newWorkspaceFolders.length, 0, ...foldersToAdd);
|
||||
newWorkspaceFolders = distinct(newWorkspaceFolders, folder => isLinux ? folder.uri.toString() : folder.uri.toString().toLowerCase());
|
||||
|
||||
if (state === WorkbenchState.EMPTY && newWorkspaceFolders.length === 0 || state === WorkbenchState.FOLDER && newWorkspaceFolders.length === 1) {
|
||||
return TPromise.as(void 0); // return if the operation is a no-op for the current state
|
||||
|
@ -66,19 +106,16 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
|
|||
}
|
||||
|
||||
// Delegate addition of folders to workspace service otherwise
|
||||
return this.contextService.addFolders(foldersToAdd)
|
||||
return this.contextService.addFolders(foldersToAdd, index)
|
||||
.then(() => null, error => donotNotifyError ? TPromise.wrapError(error) : this.handleWorkspaceConfigurationEditingError(error));
|
||||
}
|
||||
|
||||
public removeFolders(foldersToRemove: URI[], donotNotifyError: boolean = false): TPromise<void> {
|
||||
|
||||
// If we are in single-folder state and the opened folder is to be removed,
|
||||
// we close the workspace and enter the empty workspace state for the window.
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
|
||||
const workspaceFolder = this.contextService.getWorkspace().folders[0];
|
||||
if (foldersToRemove.some(folder => isEqual(folder, workspaceFolder.uri, !isLinux))) {
|
||||
return this.windowService.closeWorkspace();
|
||||
}
|
||||
// we create an empty workspace and enter it.
|
||||
if (this.includesSingleFolderWorkspace(foldersToRemove)) {
|
||||
return this.createAndEnterWorkspace([]);
|
||||
}
|
||||
|
||||
// Delegate removal of folders to workspace service otherwise
|
||||
|
@ -86,6 +123,15 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
|
|||
.then(() => null, error => donotNotifyError ? TPromise.wrapError(error) : this.handleWorkspaceConfigurationEditingError(error));
|
||||
}
|
||||
|
||||
private includesSingleFolderWorkspace(folders: URI[]): boolean {
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
|
||||
const workspaceFolder = this.contextService.getWorkspace().folders[0];
|
||||
return (folders.some(folder => isEqual(folder, workspaceFolder.uri, !isLinux)));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public createAndEnterWorkspace(folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<void> {
|
||||
return this.doEnterWorkspace(() => this.windowService.createAndEnterWorkspace(folders, path));
|
||||
}
|
||||
|
|
|
@ -12,9 +12,21 @@ import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
|||
import { TestRPCProtocol } from './testRPCProtocol';
|
||||
import { normalize } from 'vs/base/common/paths';
|
||||
import { IWorkspaceFolderData } from 'vs/platform/workspace/common/workspace';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
suite('ExtHostWorkspace', function () {
|
||||
|
||||
const extensionDescriptor: IExtensionDescription = {
|
||||
id: 'nullExtensionDescription',
|
||||
name: 'ext',
|
||||
publisher: 'vscode',
|
||||
enableProposedApi: false,
|
||||
engines: undefined,
|
||||
extensionFolderPath: undefined,
|
||||
isBuiltin: false,
|
||||
version: undefined
|
||||
};
|
||||
|
||||
function assertAsRelativePath(workspace: ExtHostWorkspace, input: string, expected: string, includeWorkspace?: boolean) {
|
||||
const actual = workspace.getRelativePath(input, includeWorkspace);
|
||||
if (actual === expected) {
|
||||
|
@ -159,57 +171,370 @@ suite('ExtHostWorkspace', function () {
|
|||
assert.equal(folder.name, 'Two');
|
||||
});
|
||||
|
||||
test('Multiroot change event should have a delta, #29641', function () {
|
||||
test('Multiroot change event should have a delta, #29641', function (done) {
|
||||
let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] });
|
||||
|
||||
let finished = false;
|
||||
const finish = (error?) => {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
done(error);
|
||||
}
|
||||
};
|
||||
|
||||
let sub = ws.onDidChangeWorkspace(e => {
|
||||
assert.deepEqual(e.added, []);
|
||||
assert.deepEqual(e.removed, []);
|
||||
try {
|
||||
assert.deepEqual(e.added, []);
|
||||
assert.deepEqual(e.removed, []);
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [] });
|
||||
sub.dispose();
|
||||
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
assert.deepEqual(e.removed, []);
|
||||
assert.equal(e.added.length, 1);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar');
|
||||
try {
|
||||
assert.deepEqual(e.removed, []);
|
||||
assert.equal(e.added.length, 1);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar');
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] });
|
||||
sub.dispose();
|
||||
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
assert.deepEqual(e.removed, []);
|
||||
assert.equal(e.added.length, 1);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar2');
|
||||
try {
|
||||
assert.deepEqual(e.removed, []);
|
||||
assert.equal(e.added.length, 1);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar2');
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0), aWorkspaceFolderData(URI.parse('foo:bar2'), 1)] });
|
||||
sub.dispose();
|
||||
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
assert.equal(e.removed.length, 2);
|
||||
assert.equal(e.removed[0].uri.toString(), 'foo:bar');
|
||||
assert.equal(e.removed[1].uri.toString(), 'foo:bar2');
|
||||
try {
|
||||
assert.equal(e.removed.length, 2);
|
||||
assert.equal(e.removed[0].uri.toString(), 'foo:bar');
|
||||
assert.equal(e.removed[1].uri.toString(), 'foo:bar2');
|
||||
|
||||
assert.equal(e.added.length, 1);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar3');
|
||||
assert.equal(e.added.length, 1);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar3');
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0)] });
|
||||
sub.dispose();
|
||||
|
||||
finish();
|
||||
});
|
||||
|
||||
test('Multiroot change event is immutable', function () {
|
||||
test('Multiroot change keeps existing workspaces live', function () {
|
||||
let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] });
|
||||
|
||||
let firstFolder = ws.getWorkspaceFolders()[0];
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar2'), 0), aWorkspaceFolderData(URI.parse('foo:bar'), 1, 'renamed')] });
|
||||
|
||||
assert.equal(ws.getWorkspaceFolders()[1], firstFolder);
|
||||
assert.equal(firstFolder.index, 1);
|
||||
assert.equal(firstFolder.name, 'renamed');
|
||||
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0), aWorkspaceFolderData(URI.parse('foo:bar2'), 1), aWorkspaceFolderData(URI.parse('foo:bar'), 2)] });
|
||||
assert.equal(ws.getWorkspaceFolders()[2], firstFolder);
|
||||
assert.equal(firstFolder.index, 2);
|
||||
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0)] });
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0), aWorkspaceFolderData(URI.parse('foo:bar'), 1)] });
|
||||
|
||||
assert.notEqual(firstFolder, ws.workspace.folders[0]);
|
||||
});
|
||||
|
||||
test('updateWorkspaceFolders - invalid arguments', function () {
|
||||
let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] });
|
||||
|
||||
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, null, null));
|
||||
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 0, 0));
|
||||
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 0, 1));
|
||||
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 1, 0));
|
||||
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, -1, 0));
|
||||
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, -1, -1));
|
||||
|
||||
ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] });
|
||||
|
||||
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 1, 1));
|
||||
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 0, 2));
|
||||
assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 0, 1, asUpdateWorkspaceFolderData(URI.parse('foo:bar'))));
|
||||
});
|
||||
|
||||
test('updateWorkspaceFolders - valid arguments', function (done) {
|
||||
let finished = false;
|
||||
const finish = (error?) => {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
done(error);
|
||||
}
|
||||
};
|
||||
|
||||
const protocol = {
|
||||
getProxy: () => { return undefined; },
|
||||
set: undefined,
|
||||
assertRegistered: undefined
|
||||
};
|
||||
|
||||
const ws = new ExtHostWorkspace(protocol, { id: 'foo', name: 'Test', folders: [] });
|
||||
|
||||
//
|
||||
// Add one folder
|
||||
//
|
||||
|
||||
assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 0, 0, asUpdateWorkspaceFolderData(URI.parse('foo:bar'))));
|
||||
assert.equal(1, ws.workspace.folders.length);
|
||||
assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar').toString());
|
||||
|
||||
const firstAddedFolder = ws.getWorkspaceFolders()[0];
|
||||
|
||||
let gotEvent = false;
|
||||
let sub = ws.onDidChangeWorkspace(e => {
|
||||
try {
|
||||
assert.deepEqual(e.removed, []);
|
||||
assert.equal(e.added.length, 1);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar');
|
||||
assert.equal(e.added[0], firstAddedFolder); // verify object is still live
|
||||
gotEvent = true;
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }); // simulate acknowledgement from main side
|
||||
assert.equal(gotEvent, true);
|
||||
sub.dispose();
|
||||
assert.equal(ws.getWorkspaceFolders()[0], firstAddedFolder); // verify object is still live
|
||||
|
||||
//
|
||||
// Add two more folders
|
||||
//
|
||||
|
||||
assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 1, 0, asUpdateWorkspaceFolderData(URI.parse('foo:bar1')), asUpdateWorkspaceFolderData(URI.parse('foo:bar2'))));
|
||||
assert.equal(3, ws.workspace.folders.length);
|
||||
assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar').toString());
|
||||
assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar1').toString());
|
||||
assert.equal(ws.workspace.folders[2].uri.toString(), URI.parse('foo:bar2').toString());
|
||||
|
||||
const secondAddedFolder = ws.getWorkspaceFolders()[1];
|
||||
const thirdAddedFolder = ws.getWorkspaceFolders()[2];
|
||||
|
||||
gotEvent = false;
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
try {
|
||||
assert.deepEqual(e.removed, []);
|
||||
assert.equal(e.added.length, 2);
|
||||
assert.equal(e.added[0].uri.toString(), 'foo:bar1');
|
||||
assert.equal(e.added[1].uri.toString(), 'foo:bar2');
|
||||
assert.equal(e.added[0], secondAddedFolder);
|
||||
assert.equal(e.added[1], thirdAddedFolder);
|
||||
gotEvent = true;
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0), aWorkspaceFolderData(URI.parse('foo:bar1'), 1), aWorkspaceFolderData(URI.parse('foo:bar2'), 2)] }); // simulate acknowledgement from main side
|
||||
assert.equal(gotEvent, true);
|
||||
sub.dispose();
|
||||
assert.equal(ws.getWorkspaceFolders()[0], firstAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[1], secondAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[2], thirdAddedFolder); // verify object is still live
|
||||
|
||||
//
|
||||
// Remove one folder
|
||||
//
|
||||
|
||||
assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 2, 1));
|
||||
assert.equal(2, ws.workspace.folders.length);
|
||||
assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar').toString());
|
||||
assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar1').toString());
|
||||
|
||||
gotEvent = false;
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
try {
|
||||
assert.deepEqual(e.added, []);
|
||||
assert.equal(e.removed.length, 1);
|
||||
assert.equal(e.removed[0], thirdAddedFolder);
|
||||
gotEvent = true;
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0), aWorkspaceFolderData(URI.parse('foo:bar1'), 1)] }); // simulate acknowledgement from main side
|
||||
assert.equal(gotEvent, true);
|
||||
sub.dispose();
|
||||
assert.equal(ws.getWorkspaceFolders()[0], firstAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[1], secondAddedFolder); // verify object is still live
|
||||
|
||||
//
|
||||
// Rename folder
|
||||
//
|
||||
|
||||
assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 0, 2, asUpdateWorkspaceFolderData(URI.parse('foo:bar'), 'renamed 1'), asUpdateWorkspaceFolderData(URI.parse('foo:bar1'), 'renamed 2')));
|
||||
assert.equal(2, ws.workspace.folders.length);
|
||||
assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar').toString());
|
||||
assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar1').toString());
|
||||
assert.equal(ws.workspace.folders[0].name, 'renamed 1');
|
||||
assert.equal(ws.workspace.folders[1].name, 'renamed 2');
|
||||
assert.equal(ws.getWorkspaceFolders()[0].name, 'renamed 1');
|
||||
assert.equal(ws.getWorkspaceFolders()[1].name, 'renamed 2');
|
||||
|
||||
gotEvent = false;
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
try {
|
||||
assert.deepEqual(e.added, []);
|
||||
assert.equal(e.removed.length, []);
|
||||
gotEvent = true;
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0, 'renamed 1'), aWorkspaceFolderData(URI.parse('foo:bar1'), 1, 'renamed 2')] }); // simulate acknowledgement from main side
|
||||
assert.equal(gotEvent, true);
|
||||
sub.dispose();
|
||||
assert.equal(ws.getWorkspaceFolders()[0], firstAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[1], secondAddedFolder); // verify object is still live
|
||||
assert.equal(ws.workspace.folders[0].name, 'renamed 1');
|
||||
assert.equal(ws.workspace.folders[1].name, 'renamed 2');
|
||||
assert.equal(ws.getWorkspaceFolders()[0].name, 'renamed 1');
|
||||
assert.equal(ws.getWorkspaceFolders()[1].name, 'renamed 2');
|
||||
|
||||
//
|
||||
// Add and remove folders
|
||||
//
|
||||
|
||||
assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 0, 2, asUpdateWorkspaceFolderData(URI.parse('foo:bar3')), asUpdateWorkspaceFolderData(URI.parse('foo:bar4'))));
|
||||
assert.equal(2, ws.workspace.folders.length);
|
||||
assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar3').toString());
|
||||
assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar4').toString());
|
||||
|
||||
const fourthAddedFolder = ws.getWorkspaceFolders()[0];
|
||||
const fifthAddedFolder = ws.getWorkspaceFolders()[1];
|
||||
|
||||
gotEvent = false;
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
try {
|
||||
assert.equal(e.added.length, 2);
|
||||
assert.equal(e.added[0], fourthAddedFolder);
|
||||
assert.equal(e.added[1], fifthAddedFolder);
|
||||
assert.equal(e.removed.length, 2);
|
||||
assert.equal(e.removed[0], firstAddedFolder);
|
||||
assert.equal(e.removed[1], secondAddedFolder);
|
||||
gotEvent = true;
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0), aWorkspaceFolderData(URI.parse('foo:bar4'), 1)] }); // simulate acknowledgement from main side
|
||||
assert.equal(gotEvent, true);
|
||||
sub.dispose();
|
||||
assert.equal(ws.getWorkspaceFolders()[0], fourthAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[1], fifthAddedFolder); // verify object is still live
|
||||
|
||||
//
|
||||
// Swap folders
|
||||
//
|
||||
|
||||
assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 0, 2, asUpdateWorkspaceFolderData(URI.parse('foo:bar4')), asUpdateWorkspaceFolderData(URI.parse('foo:bar3'))));
|
||||
assert.equal(2, ws.workspace.folders.length);
|
||||
assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar4').toString());
|
||||
assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar3').toString());
|
||||
|
||||
assert.equal(ws.getWorkspaceFolders()[0], fifthAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[1], fourthAddedFolder); // verify object is still live
|
||||
|
||||
gotEvent = false;
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
try {
|
||||
assert.equal(e.added.length, 0);
|
||||
assert.equal(e.removed.length, 0);
|
||||
gotEvent = true;
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar4'), 0), aWorkspaceFolderData(URI.parse('foo:bar3'), 1)] }); // simulate acknowledgement from main side
|
||||
assert.equal(gotEvent, true);
|
||||
sub.dispose();
|
||||
assert.equal(ws.getWorkspaceFolders()[0], fifthAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[1], fourthAddedFolder); // verify object is still live
|
||||
assert.equal(fifthAddedFolder.index, 0);
|
||||
assert.equal(fourthAddedFolder.index, 1);
|
||||
|
||||
//
|
||||
// Add one folder after the other without waiting for confirmation (not supported currently)
|
||||
//
|
||||
|
||||
assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 2, 0, asUpdateWorkspaceFolderData(URI.parse('foo:bar5'))));
|
||||
|
||||
assert.equal(3, ws.workspace.folders.length);
|
||||
assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar4').toString());
|
||||
assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar3').toString());
|
||||
assert.equal(ws.workspace.folders[2].uri.toString(), URI.parse('foo:bar5').toString());
|
||||
|
||||
const sixthAddedFolder = ws.getWorkspaceFolders()[2];
|
||||
|
||||
gotEvent = false;
|
||||
sub = ws.onDidChangeWorkspace(e => {
|
||||
try {
|
||||
assert.equal(e.added.length, 1);
|
||||
assert.equal(e.added[0], sixthAddedFolder);
|
||||
gotEvent = true;
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({
|
||||
id: 'foo', name: 'Test', folders: [
|
||||
aWorkspaceFolderData(URI.parse('foo:bar4'), 0),
|
||||
aWorkspaceFolderData(URI.parse('foo:bar3'), 1),
|
||||
aWorkspaceFolderData(URI.parse('foo:bar5'), 2)
|
||||
]
|
||||
}); // simulate acknowledgement from main side
|
||||
assert.equal(gotEvent, true);
|
||||
sub.dispose();
|
||||
|
||||
assert.equal(ws.getWorkspaceFolders()[0], fifthAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[1], fourthAddedFolder); // verify object is still live
|
||||
assert.equal(ws.getWorkspaceFolders()[2], sixthAddedFolder); // verify object is still live
|
||||
|
||||
finish();
|
||||
});
|
||||
|
||||
test('Multiroot change event is immutable', function (done) {
|
||||
let finished = false;
|
||||
const finish = (error?) => {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
done(error);
|
||||
}
|
||||
};
|
||||
|
||||
let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] });
|
||||
let sub = ws.onDidChangeWorkspace(e => {
|
||||
assert.throws(() => {
|
||||
(<any>e).added = [];
|
||||
});
|
||||
assert.throws(() => {
|
||||
(<any>e.added)[0] = null;
|
||||
});
|
||||
try {
|
||||
assert.throws(() => {
|
||||
(<any>e).added = [];
|
||||
});
|
||||
assert.throws(() => {
|
||||
(<any>e.added)[0] = null;
|
||||
});
|
||||
} catch (error) {
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [] });
|
||||
sub.dispose();
|
||||
finish();
|
||||
});
|
||||
|
||||
test('`vscode.workspace.getWorkspaceFolder(file)` don\'t return workspace folder when file open from command line. #36221', function () {
|
||||
|
@ -230,4 +555,8 @@ suite('ExtHostWorkspace', function () {
|
|||
name: name || basename(uri.path)
|
||||
};
|
||||
}
|
||||
|
||||
function asUpdateWorkspaceFolderData(uri: URI, name?: string): { uri: URI, name?: string } {
|
||||
return { uri, name };
|
||||
}
|
||||
});
|
||||
|
|
|
@ -5,54 +5,19 @@
|
|||
|
||||
import * as assert from 'assert';
|
||||
import * as http from 'http';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as stripJsonComments from 'strip-json-comments';
|
||||
import { SpectronApplication, Quality } from '../../spectron/application';
|
||||
import { SpectronApplication } from '../../spectron/application';
|
||||
|
||||
export function setup() {
|
||||
describe('Debug', () => {
|
||||
let skip = false;
|
||||
|
||||
before(async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
|
||||
if (app.quality === Quality.Dev) {
|
||||
const extensionsPath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions');
|
||||
|
||||
const debugPath = path.join(extensionsPath, 'vscode-node-debug');
|
||||
const debugExists = fs.existsSync(debugPath);
|
||||
|
||||
const debug2Path = path.join(extensionsPath, 'vscode-node-debug2');
|
||||
const debug2Exists = fs.existsSync(debug2Path);
|
||||
|
||||
if (!debugExists) {
|
||||
console.warn(`Skipping debug tests because vscode-node-debug extension was not found in ${extensionsPath}`);
|
||||
skip = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!debug2Exists) {
|
||||
console.warn(`Skipping debug tests because vscode-node-debug2 extension was not found in ${extensionsPath}`);
|
||||
skip = true;
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise((c, e) => fs.symlink(debugPath, path.join(app.extensionsPath, 'vscode-node-debug'), err => err ? e(err) : c()));
|
||||
await new Promise((c, e) => fs.symlink(debug2Path, path.join(app.extensionsPath, 'vscode-node-debug2'), err => err ? e(err) : c()));
|
||||
await app.reload();
|
||||
}
|
||||
|
||||
this.app.suiteName = 'Debug';
|
||||
app.suiteName = 'Debug';
|
||||
});
|
||||
|
||||
it('configure launch json', async function () {
|
||||
if (skip) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const app = this.app as SpectronApplication;
|
||||
|
||||
await app.workbench.debug.openDebugViewlet();
|
||||
|
@ -78,11 +43,6 @@ export function setup() {
|
|||
});
|
||||
|
||||
it('breakpoints', async function () {
|
||||
if (skip) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const app = this.app as SpectronApplication;
|
||||
|
||||
await app.workbench.quickopen.openFile('index.js');
|
||||
|
@ -92,11 +52,6 @@ export function setup() {
|
|||
|
||||
let port: number;
|
||||
it('start debugging', async function () {
|
||||
if (skip) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const app = this.app as SpectronApplication;
|
||||
|
||||
port = await app.workbench.debug.startDebugging();
|
||||
|
@ -112,11 +67,6 @@ export function setup() {
|
|||
});
|
||||
|
||||
it('focus stack frames and variables', async function () {
|
||||
if (skip) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const app = this.app as SpectronApplication;
|
||||
|
||||
await app.client.waitFor(() => app.workbench.debug.getLocalVariableCount(), c => c === 4, 'there should be 4 local variables');
|
||||
|
@ -132,11 +82,6 @@ export function setup() {
|
|||
});
|
||||
|
||||
it('stepOver, stepIn, stepOut', async function () {
|
||||
if (skip) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const app = this.app as SpectronApplication;
|
||||
|
||||
await app.workbench.debug.stepIn();
|
||||
|
@ -154,11 +99,6 @@ export function setup() {
|
|||
});
|
||||
|
||||
it('continue', async function () {
|
||||
if (skip) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const app = this.app as SpectronApplication;
|
||||
|
||||
await app.workbench.debug.continue();
|
||||
|
@ -174,22 +114,12 @@ export function setup() {
|
|||
});
|
||||
|
||||
it('debug console', async function () {
|
||||
if (skip) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const app = this.app as SpectronApplication;
|
||||
|
||||
await app.workbench.debug.waitForReplCommand('2 + 2', r => r === '4');
|
||||
});
|
||||
|
||||
it('stop debugging', async function () {
|
||||
if (skip) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const app = this.app as SpectronApplication;
|
||||
|
||||
await app.workbench.debug.stopDebugging();
|
||||
|
|
|
@ -12,8 +12,9 @@ const SYNC_STATUSBAR = 'div[id="workbench.parts.statusbar"] .statusbar-entry a[t
|
|||
|
||||
export function setup() {
|
||||
describe('Git', () => {
|
||||
before(function () {
|
||||
this.app.suiteName = 'Git';
|
||||
before(async function () {
|
||||
const app = this.app as SpectronApplication;
|
||||
app.suiteName = 'Git';
|
||||
});
|
||||
|
||||
it('reflects working tree changes', async function () {
|
||||
|
|
|
@ -13,7 +13,7 @@ const SCM_RESOURCE = `${VIEWLET} .monaco-list-row > .resource`;
|
|||
const SCM_RESOURCE_GROUP = `${VIEWLET} .monaco-list-row > .resource-group`;
|
||||
const REFRESH_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[title="Refresh"]`;
|
||||
const COMMIT_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[title="Commit"]`;
|
||||
const SCM_RESOURCE_CLICK = name => `${SCM_RESOURCE} .monaco-icon-label[title*="${name}"]`;
|
||||
const SCM_RESOURCE_CLICK = name => `${SCM_RESOURCE} .monaco-icon-label[title*="${name}"] .label-name`;
|
||||
const SCM_RESOURCE_GROUP_COMMAND_CLICK = name => `${SCM_RESOURCE_GROUP} .actions .action-label[title="${name}"]`;
|
||||
|
||||
export interface Change {
|
||||
|
@ -49,7 +49,7 @@ export class SCM extends Viewlet {
|
|||
const result = await this.spectron.webclient.selectorExecute(SCM_RESOURCE,
|
||||
div => (Array.isArray(div) ? div : [div]).map(element => {
|
||||
const name = element.querySelector('.label-name') as HTMLElement;
|
||||
const icon = element.querySelector('.decoration-icon') as HTMLElement;
|
||||
const type = element.getAttribute('data-tooltip') || '';
|
||||
const actionElementList = element.querySelectorAll('.actions .action-label');
|
||||
const actionElements: any[] = [];
|
||||
|
||||
|
@ -60,7 +60,7 @@ export class SCM extends Viewlet {
|
|||
|
||||
return {
|
||||
name: name.textContent,
|
||||
type: (icon.title || ''),
|
||||
type,
|
||||
element,
|
||||
actionElements
|
||||
};
|
||||
|
|
|
@ -51,14 +51,8 @@ export class Workbench {
|
|||
}
|
||||
|
||||
public async saveOpenedFile(): Promise<any> {
|
||||
try {
|
||||
await this.spectron.client.waitForElement('.tabs-container div.tab.active.dirty');
|
||||
} catch (e) {
|
||||
// ignore if there is no dirty file
|
||||
return Promise.resolve();
|
||||
}
|
||||
await this.spectron.runCommand('workbench.action.files.save');
|
||||
return this.spectron.client.waitForElement('.tabs-container div.tab.active.dirty', element => !element);
|
||||
await this.spectron.client.waitForElement('.tabs-container div.tab.active.dirty');
|
||||
await this.spectron.workbench.quickopen.runCommand('File: Save');
|
||||
}
|
||||
|
||||
public async selectTab(tabName: string, untitled: boolean = false): Promise<void> {
|
||||
|
|
|
@ -220,7 +220,7 @@ console.warn = function suppressWebdriverWarnings(message) {
|
|||
};
|
||||
|
||||
function createApp(quality: Quality): SpectronApplication | null {
|
||||
const path = quality === Quality.Insiders ? electronPath : stablePath;
|
||||
const path = quality === Quality.Stable ? stablePath : electronPath;
|
||||
|
||||
if (!path) {
|
||||
return null;
|
||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -5213,9 +5213,9 @@ sparkles@^1.0.0:
|
|||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3"
|
||||
|
||||
spdlog@0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.5.0.tgz#5ec92c34e59f29328f4e19dfab17a1ba51cc0573"
|
||||
spdlog@0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.6.0.tgz#20632ed4f1558ffa46e8a5827a5e97c61e0fa9ed"
|
||||
dependencies:
|
||||
bindings "^1.3.0"
|
||||
mkdirp "^0.5.1"
|
||||
|
@ -5977,8 +5977,8 @@ vscode-fsevents@0.3.8:
|
|||
nan "^2.3.0"
|
||||
|
||||
vscode-nls-dev@^3.0.5:
|
||||
version "3.0.5"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls-dev/-/vscode-nls-dev-3.0.5.tgz#f3ffd04852abfba7db9a615697084023fbceceb2"
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls-dev/-/vscode-nls-dev-3.0.6.tgz#8d07a74b09763df0cf10175b8588ed0d7aa52664"
|
||||
dependencies:
|
||||
clone "^2.1.1"
|
||||
event-stream "^3.3.4"
|
||||
|
|
Loading…
Reference in a new issue