diff --git a/.gitignore b/.gitignore index 0b257508fe925..08adb4af663a6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ node_modules/ out/ out-build/ out-editor/ +out-editor-src/ +out-editor-build/ out-editor-esm/ out-editor-min/ out-monaco-editor-core/ diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index 1a2d3b971f604..670f09e16625e 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -6,7 +6,7 @@ }, { "name": "ms-vscode.node-debug2", - "version": "1.26.3", + "version": "1.26.4", "repo": "https://github.com/Microsoft/vscode-node-debug2" } ] diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index abfeb7e4a4be3..562ee1dd7540e 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -12,6 +12,9 @@ const File = require('vinyl'); const i18n = require('./lib/i18n'); const standalone = require('./lib/standalone'); const cp = require('child_process'); +const compilation = require('./lib/compilation'); +const monacoapi = require('./monaco/api'); +const fs = require('fs'); var root = path.dirname(__dirname); var sha1 = util.getVersion(root); @@ -58,29 +61,56 @@ var BUNDLED_FILE_HEADER = [ '' ].join('\n'); -function editorLoaderConfig() { - var result = common.loaderConfig(); - - // never ship octicons in editor - result.paths['vs/base/browser/ui/octiconLabel/octiconLabel'] = 'out-build/vs/base/browser/ui/octiconLabel/octiconLabel.mock'; - - // force css inlining to use base64 -- see https://github.com/Microsoft/monaco-editor/issues/148 - result['vs/css'] = { - inlineResources: 'base64', - inlineResourcesLimit: 3000 // see https://github.com/Microsoft/monaco-editor/issues/336 - }; +const languages = i18n.defaultLanguages.concat([]); // i18n.defaultLanguages.concat(process.env.VSCODE_QUALITY !== 'stable' ? i18n.extraLanguages : []); - return result; -} +gulp.task('clean-editor-src', util.rimraf('out-editor-src')); +gulp.task('extract-editor-src', ['clean-editor-src'], function () { + console.log(`If the build fails, consider tweaking shakeLevel below to a lower value.`); + const apiusages = monacoapi.execute().usageContent; + const extrausages = fs.readFileSync(path.join(root, 'build', 'monaco', 'monaco.usage.recipe')).toString(); + standalone.extractEditor({ + sourcesRoot: path.join(root, 'src'), + entryPoints: [ + 'vs/editor/editor.main', + 'vs/editor/editor.worker', + 'vs/base/worker/workerMain', + ], + inlineEntryPoints: [ + apiusages, + extrausages + ], + libs: [ + `lib.d.ts`, + `lib.es2015.collection.d.ts` + ], + redirects: { + 'vs/base/browser/ui/octiconLabel/octiconLabel': 'vs/base/browser/ui/octiconLabel/octiconLabel.mock', + }, + compilerOptions: { + module: 2, // ModuleKind.AMD + }, + shakeLevel: 2, // 0-Files, 1-InnerFile, 2-ClassMembers + importIgnorePattern: /^vs\/css!/, + destRoot: path.join(root, 'out-editor-src') + }); +}); -const languages = i18n.defaultLanguages.concat([]); // i18n.defaultLanguages.concat(process.env.VSCODE_QUALITY !== 'stable' ? i18n.extraLanguages : []); +// Full compile, including nls and inline sources in sourcemaps, for build +gulp.task('clean-editor-build', util.rimraf('out-editor-build')); +gulp.task('compile-editor-build', ['clean-editor-build', 'extract-editor-src'], compilation.compileTask('out-editor-src', 'out-editor-build', true)); gulp.task('clean-optimized-editor', util.rimraf('out-editor')); -gulp.task('optimize-editor', ['clean-optimized-editor', 'compile-client-build'], common.optimizeTask({ +gulp.task('optimize-editor', ['clean-optimized-editor', 'compile-editor-build'], common.optimizeTask({ + src: 'out-editor-build', entryPoints: editorEntryPoints, otherSources: editorOtherSources, resources: editorResources, - loaderConfig: editorLoaderConfig(), + loaderConfig: { + paths: { + 'vs': 'out-editor-build/vs', + 'vscode': 'empty:' + } + }, bundleLoader: false, header: BUNDLED_FILE_HEADER, bundleInfo: true, @@ -229,7 +259,7 @@ gulp.task('editor-distro', ['clean-editor-distro', 'compile-editor-esm', 'minify }); gulp.task('analyze-editor-distro', function () { - // @ts-ignore + // @ts-ignore var bundleInfo = require('../out-editor/bundleInfo.json'); var graph = bundleInfo.graph; var bundles = bundleInfo.bundles; diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 71c9530041be5..090db00ffebba 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -95,6 +95,7 @@ const BUNDLED_FILE_HEADER = [ gulp.task('clean-optimized-vscode', util.rimraf('out-vscode')); gulp.task('optimize-vscode', ['clean-optimized-vscode', 'compile-build', 'compile-extensions-build'], common.optimizeTask({ + src: 'out-build', entryPoints: vscodeEntryPoints, otherSources: [], resources: vscodeResources, diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 998ebb4f37933..ad73e2dcf7309 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -18,18 +18,21 @@ var _ = require("underscore"); var monacodts = require("../monaco/api"); var fs = require("fs"); var reporter = reporter_1.createReporter(); -var rootDir = path.join(__dirname, '../../src'); -var options = require('../../src/tsconfig.json').compilerOptions; -options.verbose = false; -options.sourceMap = true; -if (process.env['VSCODE_NO_SOURCEMAP']) { // To be used by developers in a hurry - options.sourceMap = false; +function getTypeScriptCompilerOptions(src) { + var rootDir = path.join(__dirname, "../../" + src); + var options = require("../../" + src + "/tsconfig.json").compilerOptions; + options.verbose = false; + options.sourceMap = true; + if (process.env['VSCODE_NO_SOURCEMAP']) { // To be used by developers in a hurry + options.sourceMap = false; + } + options.rootDir = rootDir; + options.sourceRoot = util.toFileUri(rootDir); + options.newLine = /\r\n/.test(fs.readFileSync(__filename, 'utf8')) ? 'CRLF' : 'LF'; + return options; } -options.rootDir = rootDir; -options.sourceRoot = util.toFileUri(rootDir); -options.newLine = /\r\n/.test(fs.readFileSync(__filename, 'utf8')) ? 'CRLF' : 'LF'; -function createCompile(build, emitError) { - var opts = _.clone(options); +function createCompile(src, build, emitError) { + var opts = _.clone(getTypeScriptCompilerOptions(src)); opts.inlineSources = !!build; opts.noFilesystemLookup = true; var ts = tsb.create(opts, null, null, function (err) { return reporter(err.toString()); }); @@ -51,31 +54,31 @@ function createCompile(build, emitError) { .pipe(sourcemaps.write('.', { addComment: false, includeContent: !!build, - sourceRoot: options.sourceRoot + sourceRoot: opts.sourceRoot })) .pipe(tsFilter.restore) .pipe(reporter.end(emitError)); return es.duplex(input, output); }; } -function compileTask(out, build) { +function compileTask(src, out, build) { return function () { - var compile = createCompile(build, true); - var src = es.merge(gulp.src('src/**', { base: 'src' }), gulp.src('node_modules/typescript/lib/lib.d.ts')); + var compile = createCompile(src, build, true); + var srcPipe = es.merge(gulp.src(src + "/**", { base: "" + src }), gulp.src('node_modules/typescript/lib/lib.d.ts')); // Do not write .d.ts files to disk, as they are not needed there. var dtsFilter = util.filter(function (data) { return !/\.d\.ts$/.test(data.path); }); - return src + return srcPipe .pipe(compile()) .pipe(dtsFilter) .pipe(gulp.dest(out)) .pipe(dtsFilter.restore) - .pipe(monacodtsTask(out, false)); + .pipe(src !== 'src' ? es.through() : monacodtsTask(out, false)); }; } exports.compileTask = compileTask; function watchTask(out, build) { return function () { - var compile = createCompile(build); + var compile = createCompile('src', build); var src = es.merge(gulp.src('src/**', { base: 'src' }), gulp.src('node_modules/typescript/lib/lib.d.ts')); var watchSrc = watch('src/**', { base: 'src' }); // Do not write .d.ts files to disk, as they are not needed there. @@ -122,6 +125,7 @@ function monacodtsTask(out, isWatch) { fs.writeFileSync(result.filePath, result.content); } else { + fs.writeFileSync(result.filePath, result.content); resultStream.emit('error', 'monaco.d.ts is no longer up to date. Please run gulp watch and commit the new file.'); } } diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index cedcb4155b678..33d8c11169093 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -21,19 +21,22 @@ import * as fs from 'fs'; const reporter = createReporter(); -const rootDir = path.join(__dirname, '../../src'); -const options = require('../../src/tsconfig.json').compilerOptions; -options.verbose = false; -options.sourceMap = true; -if (process.env['VSCODE_NO_SOURCEMAP']) { // To be used by developers in a hurry - options.sourceMap = false; +function getTypeScriptCompilerOptions(src: string) { + const rootDir = path.join(__dirname, `../../${src}`); + const options = require(`../../${src}/tsconfig.json`).compilerOptions; + options.verbose = false; + options.sourceMap = true; + if (process.env['VSCODE_NO_SOURCEMAP']) { // To be used by developers in a hurry + options.sourceMap = false; + } + options.rootDir = rootDir; + options.sourceRoot = util.toFileUri(rootDir); + options.newLine = /\r\n/.test(fs.readFileSync(__filename, 'utf8')) ? 'CRLF' : 'LF'; + return options; } -options.rootDir = rootDir; -options.sourceRoot = util.toFileUri(rootDir); -options.newLine = /\r\n/.test(fs.readFileSync(__filename, 'utf8')) ? 'CRLF' : 'LF'; -function createCompile(build: boolean, emitError?: boolean): (token?: util.ICancellationToken) => NodeJS.ReadWriteStream { - const opts = _.clone(options); +function createCompile(src: string, build: boolean, emitError?: boolean): (token?: util.ICancellationToken) => NodeJS.ReadWriteStream { + const opts = _.clone(getTypeScriptCompilerOptions(src)); opts.inlineSources = !!build; opts.noFilesystemLookup = true; @@ -59,7 +62,7 @@ function createCompile(build: boolean, emitError?: boolean): (token?: util.ICanc .pipe(sourcemaps.write('.', { addComment: false, includeContent: !!build, - sourceRoot: options.sourceRoot + sourceRoot: opts.sourceRoot })) .pipe(tsFilter.restore) .pipe(reporter.end(emitError)); @@ -68,32 +71,32 @@ function createCompile(build: boolean, emitError?: boolean): (token?: util.ICanc }; } -export function compileTask(out: string, build: boolean): () => NodeJS.ReadWriteStream { +export function compileTask(src: string, out: string, build: boolean): () => NodeJS.ReadWriteStream { return function () { - const compile = createCompile(build, true); + const compile = createCompile(src, build, true); - const src = es.merge( - gulp.src('src/**', { base: 'src' }), + const srcPipe = es.merge( + gulp.src(`${src}/**`, { base: `${src}` }), gulp.src('node_modules/typescript/lib/lib.d.ts'), ); // Do not write .d.ts files to disk, as they are not needed there. const dtsFilter = util.filter(data => !/\.d\.ts$/.test(data.path)); - return src + return srcPipe .pipe(compile()) .pipe(dtsFilter) .pipe(gulp.dest(out)) .pipe(dtsFilter.restore) - .pipe(monacodtsTask(out, false)); + .pipe(src !== 'src' ? es.through() : monacodtsTask(out, false)); }; } export function watchTask(out: string, build: boolean): () => NodeJS.ReadWriteStream { return function () { - const compile = createCompile(build); + const compile = createCompile('src', build); const src = es.merge( gulp.src('src/**', { base: 'src' }), @@ -150,6 +153,7 @@ function monacodtsTask(out: string, isWatch: boolean): NodeJS.ReadWriteStream { if (isWatch) { fs.writeFileSync(result.filePath, result.content); } else { + fs.writeFileSync(result.filePath, result.content); resultStream.emit('error', 'monaco.d.ts is no longer up to date. Please run gulp watch and commit the new file.'); } } diff --git a/build/lib/optimize.js b/build/lib/optimize.js index 3599086a4b43b..4fafbd800a98a 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -37,19 +37,19 @@ function loaderConfig(emptyPaths) { } exports.loaderConfig = loaderConfig; var IS_OUR_COPYRIGHT_REGEXP = /Copyright \(C\) Microsoft Corporation/i; -function loader(bundledFileHeader, bundleLoader) { +function loader(src, bundledFileHeader, bundleLoader) { var sources = [ - 'out-build/vs/loader.js' + src + "/vs/loader.js" ]; if (bundleLoader) { sources = sources.concat([ - 'out-build/vs/css.js', - 'out-build/vs/nls.js' + src + "/vs/css.js", + src + "/vs/nls.js" ]); } var isFirst = true; return (gulp - .src(sources, { base: 'out-build' }) + .src(sources, { base: "" + src }) .pipe(es.through(function (data) { if (isFirst) { isFirst = false; @@ -71,7 +71,7 @@ function loader(bundledFileHeader, bundleLoader) { return f; }))); } -function toConcatStream(bundledFileHeader, sources, dest) { +function toConcatStream(src, bundledFileHeader, sources, dest) { var useSourcemaps = /\.js$/.test(dest) && !/\.nls\.js$/.test(dest); // If a bundle ends up including in any of the sources our copyright, then // insert a fake source at the beginning of each bundle with our copyright @@ -91,7 +91,7 @@ function toConcatStream(bundledFileHeader, sources, dest) { } var treatedSources = sources.map(function (source) { var root = source.path ? REPO_ROOT_PATH.replace(/\\/g, '/') : ''; - var base = source.path ? root + '/out-build' : ''; + var base = source.path ? root + ("/" + src) : ''; return new VinylFile({ path: source.path ? root + '/' + source.path.replace(/\\/g, '/') : 'fake', base: base, @@ -102,12 +102,13 @@ function toConcatStream(bundledFileHeader, sources, dest) { .pipe(useSourcemaps ? util.loadSourcemaps() : es.through()) .pipe(concat(dest)); } -function toBundleStream(bundledFileHeader, bundles) { +function toBundleStream(src, bundledFileHeader, bundles) { return es.merge(bundles.map(function (bundle) { - return toConcatStream(bundledFileHeader, bundle.sources, bundle.dest); + return toConcatStream(src, bundledFileHeader, bundle.sources, bundle.dest); })); } function optimizeTask(opts) { + var src = opts.src; var entryPoints = opts.entryPoints; var otherSources = opts.otherSources; var resources = opts.resources; @@ -123,7 +124,7 @@ function optimizeTask(opts) { if (err) { return bundlesStream.emit('error', JSON.stringify(err)); } - toBundleStream(bundledFileHeader, result.files).pipe(bundlesStream); + toBundleStream(src, bundledFileHeader, result.files).pipe(bundlesStream); // Remove css inlined resources var filteredResources = resources.slice(); result.cssInlinedResources.forEach(function (resource) { @@ -132,7 +133,7 @@ function optimizeTask(opts) { } filteredResources.push('!' + resource); }); - gulp.src(filteredResources, { base: 'out-build' }).pipe(resourcesStream); + gulp.src(filteredResources, { base: "" + src }).pipe(resourcesStream); var bundleInfoArray = []; if (opts.bundleInfo) { bundleInfoArray.push(new VinylFile({ @@ -145,9 +146,9 @@ function optimizeTask(opts) { }); var otherSourcesStream = es.through(); var otherSourcesStreamArr = []; - gulp.src(otherSources, { base: 'out-build' }) + gulp.src(otherSources, { base: "" + src }) .pipe(es.through(function (data) { - otherSourcesStreamArr.push(toConcatStream(bundledFileHeader, [data], data.relative)); + otherSourcesStreamArr.push(toConcatStream(src, bundledFileHeader, [data], data.relative)); }, function () { if (!otherSourcesStreamArr.length) { setTimeout(function () { otherSourcesStream.emit('end'); }, 0); @@ -156,7 +157,7 @@ function optimizeTask(opts) { es.merge(otherSourcesStreamArr).pipe(otherSourcesStream); } })); - var result = es.merge(loader(bundledFileHeader, bundleLoader), bundlesStream, otherSourcesStream, resourcesStream, bundleInfoStream); + var result = es.merge(loader(src, bundledFileHeader, bundleLoader), bundlesStream, otherSourcesStream, resourcesStream, bundleInfoStream); return result .pipe(sourcemaps.write('./', { sourceRoot: null, diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index 86e8db56a7d6d..78328e3ce2cdb 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -50,21 +50,21 @@ declare class FileSourceMap extends VinylFile { public sourceMap: sm.RawSourceMap; } -function loader(bundledFileHeader: string, bundleLoader: boolean): NodeJS.ReadWriteStream { +function loader(src: string, bundledFileHeader: string, bundleLoader: boolean): NodeJS.ReadWriteStream { let sources = [ - 'out-build/vs/loader.js' + `${src}/vs/loader.js` ]; if (bundleLoader) { sources = sources.concat([ - 'out-build/vs/css.js', - 'out-build/vs/nls.js' + `${src}/vs/css.js`, + `${src}/vs/nls.js` ]); } let isFirst = true; return ( gulp - .src(sources, { base: 'out-build' }) + .src(sources, { base: `${src}` }) .pipe(es.through(function (data) { if (isFirst) { isFirst = false; @@ -87,7 +87,7 @@ function loader(bundledFileHeader: string, bundleLoader: boolean): NodeJS.ReadWr ); } -function toConcatStream(bundledFileHeader: string, sources: bundle.IFile[], dest: string): NodeJS.ReadWriteStream { +function toConcatStream(src: string, bundledFileHeader: string, sources: bundle.IFile[], dest: string): NodeJS.ReadWriteStream { const useSourcemaps = /\.js$/.test(dest) && !/\.nls\.js$/.test(dest); // If a bundle ends up including in any of the sources our copyright, then @@ -110,7 +110,7 @@ function toConcatStream(bundledFileHeader: string, sources: bundle.IFile[], dest const treatedSources = sources.map(function (source) { const root = source.path ? REPO_ROOT_PATH.replace(/\\/g, '/') : ''; - const base = source.path ? root + '/out-build' : ''; + const base = source.path ? root + `/${src}` : ''; return new VinylFile({ path: source.path ? root + '/' + source.path.replace(/\\/g, '/') : 'fake', @@ -124,13 +124,17 @@ function toConcatStream(bundledFileHeader: string, sources: bundle.IFile[], dest .pipe(concat(dest)); } -function toBundleStream(bundledFileHeader: string, bundles: bundle.IConcatFile[]): NodeJS.ReadWriteStream { +function toBundleStream(src:string, bundledFileHeader: string, bundles: bundle.IConcatFile[]): NodeJS.ReadWriteStream { return es.merge(bundles.map(function (bundle) { - return toConcatStream(bundledFileHeader, bundle.sources, bundle.dest); + return toConcatStream(src, bundledFileHeader, bundle.sources, bundle.dest); })); } export interface IOptimizeTaskOpts { + /** + * The folder to read files from. + */ + src: string; /** * (for AMD files, will get bundled and get Copyright treatment) */ @@ -167,6 +171,7 @@ export interface IOptimizeTaskOpts { } export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStream { + const src = opts.src; const entryPoints = opts.entryPoints; const otherSources = opts.otherSources; const resources = opts.resources; @@ -183,7 +188,7 @@ export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStr bundle.bundle(entryPoints, loaderConfig, function (err, result) { if (err) { return bundlesStream.emit('error', JSON.stringify(err)); } - toBundleStream(bundledFileHeader, result.files).pipe(bundlesStream); + toBundleStream(src, bundledFileHeader, result.files).pipe(bundlesStream); // Remove css inlined resources const filteredResources = resources.slice(); @@ -193,7 +198,7 @@ export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStr } filteredResources.push('!' + resource); }); - gulp.src(filteredResources, { base: 'out-build' }).pipe(resourcesStream); + gulp.src(filteredResources, { base: `${src}` }).pipe(resourcesStream); const bundleInfoArray: VinylFile[] = []; if (opts.bundleInfo) { @@ -209,9 +214,9 @@ export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStr const otherSourcesStream = es.through(); const otherSourcesStreamArr: NodeJS.ReadWriteStream[] = []; - gulp.src(otherSources, { base: 'out-build' }) + gulp.src(otherSources, { base: `${src}` }) .pipe(es.through(function (data) { - otherSourcesStreamArr.push(toConcatStream(bundledFileHeader, [data], data.relative)); + otherSourcesStreamArr.push(toConcatStream(src, bundledFileHeader, [data], data.relative)); }, function () { if (!otherSourcesStreamArr.length) { setTimeout(function () { otherSourcesStream.emit('end'); }, 0); @@ -221,7 +226,7 @@ export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStr })); const result = es.merge( - loader(bundledFileHeader, bundleLoader), + loader(src, bundledFileHeader, bundleLoader), bundlesStream, otherSourcesStream, resourcesStream, diff --git a/build/lib/standalone.js b/build/lib/standalone.js index 12511b01d36b4..885d3e789f5ba 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -7,9 +7,93 @@ Object.defineProperty(exports, "__esModule", { value: true }); var ts = require("typescript"); var fs = require("fs"); var path = require("path"); +var tss = require("./treeshaking"); var REPO_ROOT = path.join(__dirname, '../../'); var SRC_DIR = path.join(REPO_ROOT, 'src'); var OUT_EDITOR = path.join(REPO_ROOT, 'out-editor'); +var dirCache = {}; +function writeFile(filePath, contents) { + function ensureDirs(dirPath) { + if (dirCache[dirPath]) { + return; + } + dirCache[dirPath] = true; + ensureDirs(path.dirname(dirPath)); + if (fs.existsSync(dirPath)) { + return; + } + fs.mkdirSync(dirPath); + } + ensureDirs(path.dirname(filePath)); + fs.writeFileSync(filePath, contents); +} +function extractEditor(options) { + var result = tss.shake(options); + for (var fileName in result) { + if (result.hasOwnProperty(fileName)) { + writeFile(path.join(options.destRoot, fileName), result[fileName]); + } + } + var copied = {}; + var copyFile = function (fileName) { + if (copied[fileName]) { + return; + } + copied[fileName] = true; + var srcPath = path.join(options.sourcesRoot, fileName); + var dstPath = path.join(options.destRoot, fileName); + writeFile(dstPath, fs.readFileSync(srcPath)); + }; + var writeOutputFile = function (fileName, contents) { + writeFile(path.join(options.destRoot, fileName), contents); + }; + for (var fileName in result) { + if (result.hasOwnProperty(fileName)) { + var fileContents = result[fileName]; + var info = ts.preProcessFile(fileContents); + for (var i = info.importedFiles.length - 1; i >= 0; i--) { + var importedFileName = info.importedFiles[i].fileName; + var importedFilePath = void 0; + if (/^vs\/css!/.test(importedFileName)) { + importedFilePath = importedFileName.substr('vs/css!'.length) + '.css'; + } + else { + importedFilePath = importedFileName; + } + if (/(^\.\/)|(^\.\.\/)/.test(importedFilePath)) { + importedFilePath = path.join(path.dirname(fileName), importedFilePath); + } + if (/\.css$/.test(importedFilePath)) { + transportCSS(importedFilePath, copyFile, writeOutputFile); + } + else { + if (fs.existsSync(path.join(options.sourcesRoot, importedFilePath + '.js'))) { + copyFile(importedFilePath + '.js'); + } + } + } + } + } + var tsConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.json')).toString()); + tsConfig.compilerOptions.noUnusedLocals = false; + writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); + [ + 'vs/css.build.js', + 'vs/css.d.ts', + 'vs/css.js', + 'vs/loader.js', + 'vs/monaco.d.ts', + 'vs/nls.build.js', + 'vs/nls.d.ts', + 'vs/nls.js', + 'vs/nls.mock.ts', + 'typings/lib.ie11_safe_es6.d.ts', + 'typings/thenable.d.ts', + 'typings/es6-promise.d.ts', + 'typings/require.d.ts', + ].forEach(copyFile); +} +exports.extractEditor = extractEditor; function createESMSourcesAndResources(options) { var OUT_FOLDER = path.join(REPO_ROOT, options.outFolder); var OUT_RESOURCES_FOLDER = path.join(REPO_ROOT, options.outResourcesFolder); @@ -94,7 +178,7 @@ function createESMSourcesAndResources(options) { options.entryPoints.forEach(function (entryPoint) { return enqueue(entryPoint); }); while (queue.length > 0) { var module_1 = queue.shift(); - if (transportCSS(options, module_1, enqueue, write)) { + if (transportCSS(module_1, enqueue, write)) { continue; } if (transportResource(options, module_1, enqueue, write)) { @@ -171,7 +255,7 @@ function createESMSourcesAndResources(options) { fs.writeFileSync(path.join(OUT_FOLDER, 'vs/monaco.d.ts'), monacodts); } exports.createESMSourcesAndResources = createESMSourcesAndResources; -function transportCSS(options, module, enqueue, write) { +function transportCSS(module, enqueue, write) { if (!/\.css/.test(module)) { return false; } @@ -179,10 +263,10 @@ function transportCSS(options, module, enqueue, write) { var fileContents = fs.readFileSync(filename).toString(); var inlineResources = 'base64'; // see https://github.com/Microsoft/monaco-editor/issues/148 var inlineResourcesLimit = 300000; //3000; // see https://github.com/Microsoft/monaco-editor/issues/336 - var newContents = _rewriteOrInlineUrls(filename, fileContents, inlineResources === 'base64', inlineResourcesLimit); + var newContents = _rewriteOrInlineUrls(fileContents, inlineResources === 'base64', inlineResourcesLimit); write(module, newContents); return true; - function _rewriteOrInlineUrls(originalFileFSPath, contents, forceBase64, inlineByteLimit) { + function _rewriteOrInlineUrls(contents, forceBase64, inlineByteLimit) { return _replaceURL(contents, function (url) { var imagePath = path.join(path.dirname(module), url); var fileContents = fs.readFileSync(path.join(SRC_DIR, imagePath)); diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index a402cf6840564..7931737fadbb2 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -6,11 +6,101 @@ import * as ts from 'typescript'; import * as fs from 'fs'; import * as path from 'path'; +import * as tss from './treeshaking'; const REPO_ROOT = path.join(__dirname, '../../'); const SRC_DIR = path.join(REPO_ROOT, 'src'); const OUT_EDITOR = path.join(REPO_ROOT, 'out-editor'); +let dirCache: { [dir: string]: boolean; } = {}; + +function writeFile(filePath: string, contents: Buffer | string): void { + function ensureDirs(dirPath: string): void { + if (dirCache[dirPath]) { + return; + } + dirCache[dirPath] = true; + + ensureDirs(path.dirname(dirPath)); + if (fs.existsSync(dirPath)) { + return; + } + fs.mkdirSync(dirPath); + } + ensureDirs(path.dirname(filePath)); + fs.writeFileSync(filePath, contents); +} + +export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: string }): void { + let result = tss.shake(options); + for (let fileName in result) { + if (result.hasOwnProperty(fileName)) { + writeFile(path.join(options.destRoot, fileName), result[fileName]); + } + } + let copied: { [fileName:string]: boolean; } = {}; + const copyFile = (fileName: string) => { + if (copied[fileName]) { + return; + } + copied[fileName] = true; + const srcPath = path.join(options.sourcesRoot, fileName); + const dstPath = path.join(options.destRoot, fileName); + writeFile(dstPath, fs.readFileSync(srcPath)); + }; + const writeOutputFile = (fileName: string, contents: string) => { + writeFile(path.join(options.destRoot, fileName), contents); + }; + for (let fileName in result) { + if (result.hasOwnProperty(fileName)) { + const fileContents = result[fileName]; + const info = ts.preProcessFile(fileContents); + + for (let i = info.importedFiles.length - 1; i >= 0; i--) { + const importedFileName = info.importedFiles[i].fileName; + + let importedFilePath: string; + if (/^vs\/css!/.test(importedFileName)) { + importedFilePath = importedFileName.substr('vs/css!'.length) + '.css'; + } else { + importedFilePath = importedFileName; + } + if (/(^\.\/)|(^\.\.\/)/.test(importedFilePath)) { + importedFilePath = path.join(path.dirname(fileName), importedFilePath); + } + + if (/\.css$/.test(importedFilePath)) { + transportCSS(importedFilePath, copyFile, writeOutputFile); + } else { + if (fs.existsSync(path.join(options.sourcesRoot, importedFilePath + '.js'))) { + copyFile(importedFilePath + '.js'); + } + } + } + } + } + + const tsConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.json')).toString()); + tsConfig.compilerOptions.noUnusedLocals = false; + writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); + + [ + 'vs/css.build.js', + 'vs/css.d.ts', + 'vs/css.js', + 'vs/loader.js', + 'vs/monaco.d.ts', + 'vs/nls.build.js', + 'vs/nls.d.ts', + 'vs/nls.js', + 'vs/nls.mock.ts', + 'typings/lib.ie11_safe_es6.d.ts', + 'typings/thenable.d.ts', + 'typings/es6-promise.d.ts', + 'typings/require.d.ts', + ].forEach(copyFile); +} + export interface IOptions { entryPoints: string[]; outFolder: string; @@ -111,7 +201,7 @@ export function createESMSourcesAndResources(options: IOptions): void { while (queue.length > 0) { const module = queue.shift(); - if (transportCSS(options, module, enqueue, write)) { + if (transportCSS(module, enqueue, write)) { continue; } if (transportResource(options, module, enqueue, write)) { @@ -198,7 +288,7 @@ export function createESMSourcesAndResources(options: IOptions): void { } -function transportCSS(options: IOptions, module: string, enqueue: (module: string) => void, write: (path: string, contents: string | Buffer) => void): boolean { +function transportCSS(module: string, enqueue: (module: string) => void, write: (path: string, contents: string | Buffer) => void): boolean { if (!/\.css/.test(module)) { return false; @@ -209,11 +299,11 @@ function transportCSS(options: IOptions, module: string, enqueue: (module: strin const inlineResources = 'base64'; // see https://github.com/Microsoft/monaco-editor/issues/148 const inlineResourcesLimit = 300000;//3000; // see https://github.com/Microsoft/monaco-editor/issues/336 - const newContents = _rewriteOrInlineUrls(filename, fileContents, inlineResources === 'base64', inlineResourcesLimit); + const newContents = _rewriteOrInlineUrls(fileContents, inlineResources === 'base64', inlineResourcesLimit); write(module, newContents); return true; - function _rewriteOrInlineUrls(originalFileFSPath: string, contents: string, forceBase64: boolean, inlineByteLimit: number): string { + function _rewriteOrInlineUrls(contents: string, forceBase64: boolean, inlineByteLimit: number): string { return _replaceURL(contents, (url) => { let imagePath = path.join(path.dirname(module), url); let fileContents = fs.readFileSync(path.join(SRC_DIR, imagePath)); diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js new file mode 100644 index 0000000000000..c77fa12891254 --- /dev/null +++ b/build/lib/treeshaking.js @@ -0,0 +1,682 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; +Object.defineProperty(exports, "__esModule", { value: true }); +var fs = require("fs"); +var path = require("path"); +var ts = require("typescript"); +var TYPESCRIPT_LIB_FOLDER = path.dirname(require.resolve('typescript/lib/lib.d.ts')); +var ShakeLevel; +(function (ShakeLevel) { + ShakeLevel[ShakeLevel["Files"] = 0] = "Files"; + ShakeLevel[ShakeLevel["InnerFile"] = 1] = "InnerFile"; + ShakeLevel[ShakeLevel["ClassMembers"] = 2] = "ClassMembers"; +})(ShakeLevel = exports.ShakeLevel || (exports.ShakeLevel = {})); +function shake(options) { + var languageService = createTypeScriptLanguageService(options); + markNodes(languageService, options); + return generateResult(languageService, options.shakeLevel); +} +exports.shake = shake; +//#region Discovery, LanguageService & Setup +function createTypeScriptLanguageService(options) { + // Discover referenced files + var FILES = discoverAndReadFiles(options); + // Add fake usage files + options.inlineEntryPoints.forEach(function (inlineEntryPoint, index) { + FILES["inlineEntryPoint:" + index + ".ts"] = inlineEntryPoint; + }); + // Resolve libs + var RESOLVED_LIBS = {}; + options.libs.forEach(function (filename) { + var filepath = path.join(TYPESCRIPT_LIB_FOLDER, filename); + RESOLVED_LIBS["defaultLib:" + filename] = fs.readFileSync(filepath).toString(); + }); + var host = new TypeScriptLanguageServiceHost(RESOLVED_LIBS, FILES, options.compilerOptions); + return ts.createLanguageService(host); +} +/** + * Read imports and follow them until all files have been handled + */ +function discoverAndReadFiles(options) { + var FILES = {}; + var in_queue = Object.create(null); + var queue = []; + var enqueue = function (moduleId) { + if (in_queue[moduleId]) { + return; + } + in_queue[moduleId] = true; + queue.push(moduleId); + }; + options.entryPoints.forEach(function (entryPoint) { return enqueue(entryPoint); }); + while (queue.length > 0) { + var moduleId = queue.shift(); + var dts_filename = path.join(options.sourcesRoot, moduleId + '.d.ts'); + if (fs.existsSync(dts_filename)) { + var dts_filecontents = fs.readFileSync(dts_filename).toString(); + FILES[moduleId + '.d.ts'] = dts_filecontents; + continue; + } + var ts_filename = void 0; + if (options.redirects[moduleId]) { + ts_filename = path.join(options.sourcesRoot, options.redirects[moduleId] + '.ts'); + } + else { + ts_filename = path.join(options.sourcesRoot, moduleId + '.ts'); + } + var ts_filecontents = fs.readFileSync(ts_filename).toString(); + var info = ts.preProcessFile(ts_filecontents); + for (var i = info.importedFiles.length - 1; i >= 0; i--) { + var importedFileName = info.importedFiles[i].fileName; + if (options.importIgnorePattern.test(importedFileName)) { + // Ignore vs/css! imports + continue; + } + var importedModuleId = importedFileName; + if (/(^\.\/)|(^\.\.\/)/.test(importedModuleId)) { + importedModuleId = path.join(path.dirname(moduleId), importedModuleId); + } + enqueue(importedModuleId); + } + FILES[moduleId + '.ts'] = ts_filecontents; + } + return FILES; +} +/** + * A TypeScript language service host + */ +var TypeScriptLanguageServiceHost = /** @class */ (function () { + function TypeScriptLanguageServiceHost(libs, files, compilerOptions) { + this._libs = libs; + this._files = files; + this._compilerOptions = compilerOptions; + } + // --- language service host --------------- + TypeScriptLanguageServiceHost.prototype.getCompilationSettings = function () { + return this._compilerOptions; + }; + TypeScriptLanguageServiceHost.prototype.getScriptFileNames = function () { + return ([] + .concat(Object.keys(this._libs)) + .concat(Object.keys(this._files))); + }; + TypeScriptLanguageServiceHost.prototype.getScriptVersion = function (fileName) { + return '1'; + }; + TypeScriptLanguageServiceHost.prototype.getProjectVersion = function () { + return '1'; + }; + TypeScriptLanguageServiceHost.prototype.getScriptSnapshot = function (fileName) { + if (this._files.hasOwnProperty(fileName)) { + return ts.ScriptSnapshot.fromString(this._files[fileName]); + } + else if (this._libs.hasOwnProperty(fileName)) { + return ts.ScriptSnapshot.fromString(this._libs[fileName]); + } + else { + return ts.ScriptSnapshot.fromString(''); + } + }; + TypeScriptLanguageServiceHost.prototype.getScriptKind = function (fileName) { + return ts.ScriptKind.TS; + }; + TypeScriptLanguageServiceHost.prototype.getCurrentDirectory = function () { + return ''; + }; + TypeScriptLanguageServiceHost.prototype.getDefaultLibFileName = function (options) { + return 'defaultLib:lib.d.ts'; + }; + TypeScriptLanguageServiceHost.prototype.isDefaultLibFileName = function (fileName) { + return fileName === this.getDefaultLibFileName(this._compilerOptions); + }; + return TypeScriptLanguageServiceHost; +}()); +//#endregion +//#region Tree Shaking +var NodeColor; +(function (NodeColor) { + NodeColor[NodeColor["White"] = 0] = "White"; + NodeColor[NodeColor["Gray"] = 1] = "Gray"; + NodeColor[NodeColor["Black"] = 2] = "Black"; +})(NodeColor || (NodeColor = {})); +function getColor(node) { + return node.$$$color || 0 /* White */; +} +function setColor(node, color) { + node.$$$color = color; +} +function nodeOrParentIsBlack(node) { + while (node) { + var color = getColor(node); + if (color === 2 /* Black */) { + return true; + } + node = node.parent; + } + return false; +} +function nodeOrChildIsBlack(node) { + if (getColor(node) === 2 /* Black */) { + return true; + } + for (var _i = 0, _a = node.getChildren(); _i < _a.length; _i++) { + var child = _a[_i]; + if (nodeOrChildIsBlack(child)) { + return true; + } + } + return false; +} +function markNodes(languageService, options) { + var program = languageService.getProgram(); + if (options.shakeLevel === 0 /* Files */) { + // Mark all source files Black + program.getSourceFiles().forEach(function (sourceFile) { + setColor(sourceFile, 2 /* Black */); + }); + return; + } + var black_queue = []; + var gray_queue = []; + var sourceFilesLoaded = {}; + function enqueueTopLevelModuleStatements(sourceFile) { + sourceFile.forEachChild(function (node) { + if (ts.isImportDeclaration(node)) { + if (!node.importClause && ts.isStringLiteral(node.moduleSpecifier)) { + setColor(node, 2 /* Black */); + enqueueImport(node, node.moduleSpecifier.text); + } + return; + } + if (ts.isExportDeclaration(node)) { + if (ts.isStringLiteral(node.moduleSpecifier)) { + setColor(node, 2 /* Black */); + enqueueImport(node, node.moduleSpecifier.text); + } + return; + } + if (ts.isExpressionStatement(node) + || ts.isIfStatement(node) + || ts.isIterationStatement(node, true) + || ts.isExportAssignment(node)) { + enqueue_black(node); + } + if (ts.isImportEqualsDeclaration(node)) { + if (/export/.test(node.getFullText(sourceFile))) { + // e.g. "export import Severity = BaseSeverity;" + enqueue_black(node); + } + } + }); + } + function enqueue_gray(node) { + if (nodeOrParentIsBlack(node) || getColor(node) === 1 /* Gray */) { + return; + } + setColor(node, 1 /* Gray */); + gray_queue.push(node); + } + function enqueue_black(node) { + var previousColor = getColor(node); + if (previousColor === 2 /* Black */) { + return; + } + if (previousColor === 1 /* Gray */) { + // remove from gray queue + gray_queue.splice(gray_queue.indexOf(node), 1); + setColor(node, 0 /* White */); + // add to black queue + enqueue_black(node); + // // move from one queue to the other + // black_queue.push(node); + // setColor(node, NodeColor.Black); + return; + } + if (nodeOrParentIsBlack(node)) { + return; + } + var fileName = node.getSourceFile().fileName; + if (/^defaultLib:/.test(fileName) || /\.d\.ts$/.test(fileName)) { + setColor(node, 2 /* Black */); + return; + } + var sourceFile = node.getSourceFile(); + if (!sourceFilesLoaded[sourceFile.fileName]) { + sourceFilesLoaded[sourceFile.fileName] = true; + enqueueTopLevelModuleStatements(sourceFile); + } + if (ts.isSourceFile(node)) { + return; + } + setColor(node, 2 /* Black */); + black_queue.push(node); + if (options.shakeLevel === 2 /* ClassMembers */ && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { + var references = languageService.getReferencesAtPosition(node.getSourceFile().fileName, node.name.pos + node.name.getLeadingTriviaWidth()); + if (references) { + for (var i = 0, len = references.length; i < len; i++) { + var reference = references[i]; + var referenceSourceFile = program.getSourceFile(reference.fileName); + var referenceNode = getTokenAtPosition(referenceSourceFile, reference.textSpan.start, false, false); + if (ts.isMethodDeclaration(referenceNode.parent) + || ts.isPropertyDeclaration(referenceNode.parent) + || ts.isGetAccessor(referenceNode.parent) + || ts.isSetAccessor(referenceNode.parent)) { + enqueue_gray(referenceNode.parent); + } + } + } + } + } + function enqueueFile(filename) { + var sourceFile = program.getSourceFile(filename); + if (!sourceFile) { + console.warn("Cannot find source file " + filename); + return; + } + enqueue_black(sourceFile); + } + function enqueueImport(node, importText) { + if (options.importIgnorePattern.test(importText)) { + // this import should be ignored + return; + } + var nodeSourceFile = node.getSourceFile(); + var fullPath; + if (/(^\.\/)|(^\.\.\/)/.test(importText)) { + fullPath = path.join(path.dirname(nodeSourceFile.fileName), importText) + '.ts'; + } + else { + fullPath = importText + '.ts'; + } + enqueueFile(fullPath); + } + options.entryPoints.forEach(function (moduleId) { return enqueueFile(moduleId + '.ts'); }); + // Add fake usage files + options.inlineEntryPoints.forEach(function (_, index) { return enqueueFile("inlineEntryPoint:" + index + ".ts"); }); + var step = 0; + var checker = program.getTypeChecker(); + var _loop_1 = function () { + ++step; + var node = void 0; + if (step % 100 === 0) { + console.log(step + "/" + (step + black_queue.length + gray_queue.length) + " (" + black_queue.length + ", " + gray_queue.length + ")"); + } + if (black_queue.length === 0) { + for (var i = 0; i < gray_queue.length; i++) { + var node_1 = gray_queue[i]; + var nodeParent = node_1.parent; + if ((ts.isClassDeclaration(nodeParent) || ts.isInterfaceDeclaration(nodeParent)) && nodeOrChildIsBlack(nodeParent)) { + gray_queue.splice(i, 1); + black_queue.push(node_1); + setColor(node_1, 2 /* Black */); + i--; + } + } + } + if (black_queue.length > 0) { + node = black_queue.shift(); + } + else { + return "break"; + } + var nodeSourceFile = node.getSourceFile(); + var loop = function (node) { + var _a = getRealNodeSymbol(checker, node), symbol = _a[0], symbolImportNode = _a[1]; + if (symbolImportNode) { + setColor(symbolImportNode, 2 /* Black */); + } + if (symbol && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { + for (var i = 0, len = symbol.declarations.length; i < len; i++) { + var declaration = symbol.declarations[i]; + if (ts.isSourceFile(declaration)) { + // Do not enqueue full source files + // (they can be the declaration of a module import) + continue; + } + if (options.shakeLevel === 2 /* ClassMembers */ && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration))) { + enqueue_black(declaration.name); + for (var j = 0; j < declaration.members.length; j++) { + var member = declaration.members[j]; + var memberName = member.name ? member.name.getText() : null; + if (ts.isConstructorDeclaration(member) + || ts.isConstructSignatureDeclaration(member) + || ts.isIndexSignatureDeclaration(member) + || ts.isCallSignatureDeclaration(member) + || memberName === 'toJSON' + || memberName === 'toString' + || memberName === 'dispose' // TODO: keeping all `dispose` methods + ) { + enqueue_black(member); + } + } + // queue the heritage clauses + if (declaration.heritageClauses) { + for (var _i = 0, _b = declaration.heritageClauses; _i < _b.length; _i++) { + var heritageClause = _b[_i]; + enqueue_black(heritageClause); + } + } + } + else { + enqueue_black(declaration); + } + } + } + node.forEachChild(loop); + }; + node.forEachChild(loop); + }; + while (black_queue.length > 0 || gray_queue.length > 0) { + var state_1 = _loop_1(); + if (state_1 === "break") + break; + } +} +function nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol) { + for (var i = 0, len = symbol.declarations.length; i < len; i++) { + var declaration = symbol.declarations[i]; + var declarationSourceFile = declaration.getSourceFile(); + if (nodeSourceFile === declarationSourceFile) { + if (declaration.pos <= node.pos && node.end <= declaration.end) { + return true; + } + } + } + return false; +} +function generateResult(languageService, shakeLevel) { + var program = languageService.getProgram(); + var result = {}; + var writeFile = function (filePath, contents) { + result[filePath] = contents; + }; + program.getSourceFiles().forEach(function (sourceFile) { + var fileName = sourceFile.fileName; + if (/^defaultLib:/.test(fileName)) { + return; + } + var destination = fileName; + if (/\.d\.ts$/.test(fileName)) { + if (nodeOrChildIsBlack(sourceFile)) { + writeFile(destination, sourceFile.text); + } + return; + } + var text = sourceFile.text; + var result = ''; + function keep(node) { + result += text.substring(node.pos, node.end); + } + function write(data) { + result += data; + } + function writeMarkedNodes(node) { + if (getColor(node) === 2 /* Black */) { + return keep(node); + } + // Always keep certain top-level statements + if (ts.isSourceFile(node.parent)) { + if (ts.isExpressionStatement(node) && ts.isStringLiteral(node.expression) && node.expression.text === 'use strict') { + return keep(node); + } + if (ts.isVariableStatement(node) && nodeOrChildIsBlack(node)) { + return keep(node); + } + } + // Keep the entire import in import * as X cases + if (ts.isImportDeclaration(node)) { + if (node.importClause && node.importClause.namedBindings) { + if (ts.isNamespaceImport(node.importClause.namedBindings)) { + if (getColor(node.importClause.namedBindings) === 2 /* Black */) { + return keep(node); + } + } + else { + var survivingImports = []; + for (var i = 0; i < node.importClause.namedBindings.elements.length; i++) { + var importNode = node.importClause.namedBindings.elements[i]; + if (getColor(importNode) === 2 /* Black */) { + survivingImports.push(importNode.getFullText(sourceFile)); + } + } + var leadingTriviaWidth = node.getLeadingTriviaWidth(); + var leadingTrivia = sourceFile.text.substr(node.pos, leadingTriviaWidth); + if (survivingImports.length > 0) { + if (node.importClause && getColor(node.importClause) === 2 /* Black */) { + return write(leadingTrivia + "import " + node.importClause.name.text + ", {" + survivingImports.join(',') + " } from" + node.moduleSpecifier.getFullText(sourceFile) + ";"); + } + return write(leadingTrivia + "import {" + survivingImports.join(',') + " } from" + node.moduleSpecifier.getFullText(sourceFile) + ";"); + } + else { + if (node.importClause && getColor(node.importClause) === 2 /* Black */) { + return write(leadingTrivia + "import " + node.importClause.name.text + " from" + node.moduleSpecifier.getFullText(sourceFile) + ";"); + } + } + } + } + else { + if (node.importClause && getColor(node.importClause) === 2 /* Black */) { + return keep(node); + } + } + } + if (shakeLevel === 2 /* ClassMembers */ && (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) && nodeOrChildIsBlack(node)) { + var toWrite = node.getFullText(); + for (var i = node.members.length - 1; i >= 0; i--) { + var member = node.members[i]; + if (getColor(member) === 2 /* Black */) { + // keep method + continue; + } + if (/^_(.*)Brand$/.test(member.name.getText())) { + // TODO: keep all members ending with `Brand`... + continue; + } + var pos = member.pos - node.pos; + var end = member.end - node.pos; + toWrite = toWrite.substring(0, pos) + toWrite.substring(end); + } + return write(toWrite); + } + if (ts.isFunctionDeclaration(node)) { + // Do not go inside functions if they haven't been marked + return; + } + node.forEachChild(writeMarkedNodes); + } + if (getColor(sourceFile) !== 2 /* Black */) { + if (!nodeOrChildIsBlack(sourceFile)) { + // none of the elements are reachable => don't write this file at all! + return; + } + sourceFile.forEachChild(writeMarkedNodes); + result += sourceFile.endOfFileToken.getFullText(sourceFile); + } + else { + result = text; + } + writeFile(destination, result); + }); + return result; +} +//#endregion +//#region Utils +/** + * Returns the node's symbol and the `import` node (if the symbol resolved from a different module) + */ +function getRealNodeSymbol(checker, node) { + /** + * Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 } + */ + /* @internal */ + function getContainingObjectLiteralElement(node) { + switch (node.kind) { + case ts.SyntaxKind.StringLiteral: + case ts.SyntaxKind.NumericLiteral: + if (node.parent.kind === ts.SyntaxKind.ComputedPropertyName) { + return ts.isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined; + } + // falls through + case ts.SyntaxKind.Identifier: + return ts.isObjectLiteralElement(node.parent) && + (node.parent.parent.kind === ts.SyntaxKind.ObjectLiteralExpression || node.parent.parent.kind === ts.SyntaxKind.JsxAttributes) && + node.parent.name === node ? node.parent : undefined; + } + return undefined; + } + function getPropertySymbolsFromType(type, propName) { + function getTextOfPropertyName(name) { + function isStringOrNumericLiteral(node) { + var kind = node.kind; + return kind === ts.SyntaxKind.StringLiteral + || kind === ts.SyntaxKind.NumericLiteral; + } + switch (name.kind) { + case ts.SyntaxKind.Identifier: + return name.text; + case ts.SyntaxKind.StringLiteral: + case ts.SyntaxKind.NumericLiteral: + return name.text; + case ts.SyntaxKind.ComputedPropertyName: + return isStringOrNumericLiteral(name.expression) ? name.expression.text : undefined; + } + } + var name = getTextOfPropertyName(propName); + if (name && type) { + var result = []; + var symbol_1 = type.getProperty(name); + if (type.flags & ts.TypeFlags.Union) { + for (var _i = 0, _a = type.types; _i < _a.length; _i++) { + var t = _a[_i]; + var symbol_2 = t.getProperty(name); + if (symbol_2) { + result.push(symbol_2); + } + } + return result; + } + if (symbol_1) { + result.push(symbol_1); + return result; + } + } + return undefined; + } + function getPropertySymbolsFromContextualType(typeChecker, node) { + var objectLiteral = node.parent; + var contextualType = typeChecker.getContextualType(objectLiteral); + return getPropertySymbolsFromType(contextualType, node.name); + } + // Go to the original declaration for cases: + // + // (1) when the aliased symbol was declared in the location(parent). + // (2) when the aliased symbol is originating from an import. + // + function shouldSkipAlias(node, declaration) { + if (node.kind !== ts.SyntaxKind.Identifier) { + return false; + } + if (node.parent === declaration) { + return true; + } + switch (declaration.kind) { + case ts.SyntaxKind.ImportClause: + case ts.SyntaxKind.ImportEqualsDeclaration: + return true; + case ts.SyntaxKind.ImportSpecifier: + return declaration.parent.kind === ts.SyntaxKind.NamedImports; + default: + return false; + } + } + if (!ts.isShorthandPropertyAssignment(node)) { + if (node.getChildCount() !== 0) { + return [null, null]; + } + } + var symbol = checker.getSymbolAtLocation(node); + var importNode = null; + if (symbol && symbol.flags & ts.SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) { + var aliased = checker.getAliasedSymbol(symbol); + if (aliased.declarations) { + // We should mark the import as visited + importNode = symbol.declarations[0]; + symbol = aliased; + } + } + if (symbol) { + // Because name in short-hand property assignment has two different meanings: property name and property value, + // using go-to-definition at such position should go to the variable declaration of the property value rather than + // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition + // is performed at the location of property access, we would like to go to definition of the property in the short-hand + // assignment. This case and others are handled by the following code. + if (node.parent.kind === ts.SyntaxKind.ShorthandPropertyAssignment) { + symbol = checker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration); + } + // If the node is the name of a BindingElement within an ObjectBindingPattern instead of just returning the + // declaration the symbol (which is itself), we should try to get to the original type of the ObjectBindingPattern + // and return the property declaration for the referenced property. + // For example: + // import('./foo').then(({ b/*goto*/ar }) => undefined); => should get use to the declaration in file "./foo" + // + // function bar(onfulfilled: (value: T) => void) { //....} + // interface Test { + // pr/*destination*/op1: number + // } + // bar(({pr/*goto*/op1})=>{}); + if (ts.isPropertyName(node) && ts.isBindingElement(node.parent) && ts.isObjectBindingPattern(node.parent.parent) && + (node === (node.parent.propertyName || node.parent.name))) { + var type = checker.getTypeAtLocation(node.parent.parent); + if (type) { + var propSymbols = getPropertySymbolsFromType(type, node); + if (propSymbols) { + symbol = propSymbols[0]; + } + } + } + // If the current location we want to find its definition is in an object literal, try to get the contextual type for the + // object literal, lookup the property symbol in the contextual type, and use this for goto-definition. + // For example + // interface Props{ + // /*first*/prop1: number + // prop2: boolean + // } + // function Foo(arg: Props) {} + // Foo( { pr/*1*/op1: 10, prop2: false }) + var element = getContainingObjectLiteralElement(node); + if (element && checker.getContextualType(element.parent)) { + var propertySymbols = getPropertySymbolsFromContextualType(checker, element); + if (propertySymbols) { + symbol = propertySymbols[0]; + } + } + } + if (symbol && symbol.declarations) { + return [symbol, importNode]; + } + return [null, null]; +} +/** Get the token whose text contains the position */ +function getTokenAtPosition(sourceFile, position, allowPositionInLeadingTrivia, includeEndPosition) { + var current = sourceFile; + outer: while (true) { + // find the child that contains 'position' + for (var _i = 0, _a = current.getChildren(); _i < _a.length; _i++) { + var child = _a[_i]; + var start = allowPositionInLeadingTrivia ? child.getFullStart() : child.getStart(sourceFile, /*includeJsDoc*/ true); + if (start > position) { + // If this child begins after position, then all subsequent children will as well. + break; + } + var end = child.getEnd(); + if (position < end || (position === end && (child.kind === ts.SyntaxKind.EndOfFileToken || includeEndPosition))) { + current = child; + continue outer; + } + } + return current; + } +} diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts new file mode 100644 index 0000000000000..0527fe3ebce89 --- /dev/null +++ b/build/lib/treeshaking.ts @@ -0,0 +1,817 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as fs from 'fs'; +import * as path from 'path'; +import * as ts from 'typescript'; + +const TYPESCRIPT_LIB_FOLDER = path.dirname(require.resolve('typescript/lib/lib.d.ts')); + +export const enum ShakeLevel { + Files = 0, + InnerFile = 1, + ClassMembers = 2 +} + +export interface ITreeShakingOptions { + /** + * The full path to the root where sources are. + */ + sourcesRoot: string; + /** + * Module ids. + * e.g. `vs/editor/editor.main` or `index` + */ + entryPoints: string[]; + /** + * Inline usages. + */ + inlineEntryPoints: string[]; + /** + * TypeScript libs. + * e.g. `lib.d.ts`, `lib.es2015.collection.d.ts` + */ + libs: string[]; + /** + * TypeScript compiler options. + */ + compilerOptions: ts.CompilerOptions; + /** + * The shake level to perform. + */ + shakeLevel: ShakeLevel; + /** + * regex pattern to ignore certain imports e.g. `vs/css!` imports + */ + importIgnorePattern: RegExp; + + redirects: { [module: string]: string; }; +} + +export interface ITreeShakingResult { + [file: string]: string; +} + +export function shake(options: ITreeShakingOptions): ITreeShakingResult { + const languageService = createTypeScriptLanguageService(options); + + markNodes(languageService, options); + + return generateResult(languageService, options.shakeLevel); +} + +//#region Discovery, LanguageService & Setup +function createTypeScriptLanguageService(options: ITreeShakingOptions): ts.LanguageService { + // Discover referenced files + const FILES = discoverAndReadFiles(options); + + // Add fake usage files + options.inlineEntryPoints.forEach((inlineEntryPoint, index) => { + FILES[`inlineEntryPoint:${index}.ts`] = inlineEntryPoint; + }); + + // Resolve libs + const RESOLVED_LIBS: ILibMap = {}; + options.libs.forEach((filename) => { + const filepath = path.join(TYPESCRIPT_LIB_FOLDER, filename); + RESOLVED_LIBS[`defaultLib:${filename}`] = fs.readFileSync(filepath).toString(); + }); + + const host = new TypeScriptLanguageServiceHost(RESOLVED_LIBS, FILES, options.compilerOptions); + return ts.createLanguageService(host); +} + +/** + * Read imports and follow them until all files have been handled + */ +function discoverAndReadFiles(options: ITreeShakingOptions): IFileMap { + const FILES: IFileMap = {}; + + const in_queue: { [module: string]: boolean; } = Object.create(null); + const queue: string[] = []; + + const enqueue = (moduleId: string) => { + if (in_queue[moduleId]) { + return; + } + in_queue[moduleId] = true; + queue.push(moduleId); + }; + + options.entryPoints.forEach((entryPoint) => enqueue(entryPoint)); + + while (queue.length > 0) { + const moduleId = queue.shift(); + const dts_filename = path.join(options.sourcesRoot, moduleId + '.d.ts'); + if (fs.existsSync(dts_filename)) { + const dts_filecontents = fs.readFileSync(dts_filename).toString(); + FILES[moduleId + '.d.ts'] = dts_filecontents; + continue; + } + + let ts_filename: string; + if (options.redirects[moduleId]) { + ts_filename = path.join(options.sourcesRoot, options.redirects[moduleId] + '.ts'); + } else { + ts_filename = path.join(options.sourcesRoot, moduleId + '.ts'); + } + const ts_filecontents = fs.readFileSync(ts_filename).toString(); + const info = ts.preProcessFile(ts_filecontents); + for (let i = info.importedFiles.length - 1; i >= 0; i--) { + const importedFileName = info.importedFiles[i].fileName; + + if (options.importIgnorePattern.test(importedFileName)) { + // Ignore vs/css! imports + continue; + } + + let importedModuleId = importedFileName; + if (/(^\.\/)|(^\.\.\/)/.test(importedModuleId)) { + importedModuleId = path.join(path.dirname(moduleId), importedModuleId); + } + enqueue(importedModuleId); + } + + FILES[moduleId + '.ts'] = ts_filecontents; + } + + return FILES; +} + +interface ILibMap { [libName: string]: string; } +interface IFileMap { [fileName: string]: string; } + +/** + * A TypeScript language service host + */ +class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { + + private readonly _libs: ILibMap; + private readonly _files: IFileMap; + private readonly _compilerOptions: ts.CompilerOptions; + + constructor(libs: ILibMap, files: IFileMap, compilerOptions: ts.CompilerOptions) { + this._libs = libs; + this._files = files; + this._compilerOptions = compilerOptions; + } + + // --- language service host --------------- + + getCompilationSettings(): ts.CompilerOptions { + return this._compilerOptions; + } + getScriptFileNames(): string[] { + return ( + [] + .concat(Object.keys(this._libs)) + .concat(Object.keys(this._files)) + ); + } + getScriptVersion(fileName: string): string { + return '1'; + } + getProjectVersion(): string { + return '1'; + } + getScriptSnapshot(fileName: string): ts.IScriptSnapshot { + if (this._files.hasOwnProperty(fileName)) { + return ts.ScriptSnapshot.fromString(this._files[fileName]); + } else if (this._libs.hasOwnProperty(fileName)) { + return ts.ScriptSnapshot.fromString(this._libs[fileName]); + } else { + return ts.ScriptSnapshot.fromString(''); + } + } + getScriptKind(fileName: string): ts.ScriptKind { + return ts.ScriptKind.TS; + } + getCurrentDirectory(): string { + return ''; + } + getDefaultLibFileName(options: ts.CompilerOptions): string { + return 'defaultLib:lib.d.ts'; + } + isDefaultLibFileName(fileName: string): boolean { + return fileName === this.getDefaultLibFileName(this._compilerOptions); + } +} +//#endregion + +//#region Tree Shaking + +const enum NodeColor { + White = 0, + Gray = 1, + Black = 2 +} + +function getColor(node: ts.Node): NodeColor { + return (node).$$$color || NodeColor.White; +} +function setColor(node: ts.Node, color: NodeColor): void { + (node).$$$color = color; +} +function nodeOrParentIsBlack(node: ts.Node): boolean { + while (node) { + const color = getColor(node); + if (color === NodeColor.Black) { + return true; + } + node = node.parent; + } + return false; +} +function nodeOrChildIsBlack(node: ts.Node): boolean { + if (getColor(node) === NodeColor.Black) { + return true; + } + for (const child of node.getChildren()) { + if (nodeOrChildIsBlack(child)) { + return true; + } + } + return false; +} + +function markNodes(languageService: ts.LanguageService, options: ITreeShakingOptions) { + const program = languageService.getProgram(); + + if (options.shakeLevel === ShakeLevel.Files) { + // Mark all source files Black + program.getSourceFiles().forEach((sourceFile) => { + setColor(sourceFile, NodeColor.Black); + }); + return; + } + + const black_queue: ts.Node[] = []; + const gray_queue: ts.Node[] = []; + const sourceFilesLoaded: { [fileName: string]: boolean } = {}; + + function enqueueTopLevelModuleStatements(sourceFile: ts.SourceFile): void { + + sourceFile.forEachChild((node: ts.Node) => { + + if (ts.isImportDeclaration(node)) { + if (!node.importClause && ts.isStringLiteral(node.moduleSpecifier)) { + setColor(node, NodeColor.Black); + enqueueImport(node, node.moduleSpecifier.text); + } + return; + } + + if (ts.isExportDeclaration(node)) { + if (ts.isStringLiteral(node.moduleSpecifier)) { + setColor(node, NodeColor.Black); + enqueueImport(node, node.moduleSpecifier.text); + } + return; + } + + if ( + ts.isExpressionStatement(node) + || ts.isIfStatement(node) + || ts.isIterationStatement(node, true) + || ts.isExportAssignment(node) + ) { + enqueue_black(node); + } + + if (ts.isImportEqualsDeclaration(node)) { + if (/export/.test(node.getFullText(sourceFile))) { + // e.g. "export import Severity = BaseSeverity;" + enqueue_black(node); + } + } + + }); + } + + function enqueue_gray(node: ts.Node): void { + if (nodeOrParentIsBlack(node) || getColor(node) === NodeColor.Gray) { + return; + } + setColor(node, NodeColor.Gray); + gray_queue.push(node); + } + + function enqueue_black(node: ts.Node): void { + const previousColor = getColor(node); + + if (previousColor === NodeColor.Black) { + return; + } + + if (previousColor === NodeColor.Gray) { + // remove from gray queue + gray_queue.splice(gray_queue.indexOf(node), 1); + setColor(node, NodeColor.White); + + // add to black queue + enqueue_black(node); + + // // move from one queue to the other + // black_queue.push(node); + // setColor(node, NodeColor.Black); + return; + } + + if (nodeOrParentIsBlack(node)) { + return; + } + + const fileName = node.getSourceFile().fileName; + if (/^defaultLib:/.test(fileName) || /\.d\.ts$/.test(fileName)) { + setColor(node, NodeColor.Black); + return; + } + + const sourceFile = node.getSourceFile(); + if (!sourceFilesLoaded[sourceFile.fileName]) { + sourceFilesLoaded[sourceFile.fileName] = true; + enqueueTopLevelModuleStatements(sourceFile); + } + + if (ts.isSourceFile(node)) { + return; + } + + setColor(node, NodeColor.Black); + black_queue.push(node); + + if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { + const references = languageService.getReferencesAtPosition(node.getSourceFile().fileName, node.name.pos + node.name.getLeadingTriviaWidth()); + if (references) { + for (let i = 0, len = references.length; i < len; i++) { + const reference = references[i]; + const referenceSourceFile = program.getSourceFile(reference.fileName); + const referenceNode = getTokenAtPosition(referenceSourceFile, reference.textSpan.start, false, false); + if ( + ts.isMethodDeclaration(referenceNode.parent) + || ts.isPropertyDeclaration(referenceNode.parent) + || ts.isGetAccessor(referenceNode.parent) + || ts.isSetAccessor(referenceNode.parent) + ) { + enqueue_gray(referenceNode.parent); + } + } + } + } + } + + function enqueueFile(filename: string): void { + const sourceFile = program.getSourceFile(filename); + if (!sourceFile) { + console.warn(`Cannot find source file ${filename}`); + return; + } + enqueue_black(sourceFile); + } + + function enqueueImport(node: ts.Node, importText: string): void { + if (options.importIgnorePattern.test(importText)) { + // this import should be ignored + return; + } + + const nodeSourceFile = node.getSourceFile(); + let fullPath: string; + if (/(^\.\/)|(^\.\.\/)/.test(importText)) { + fullPath = path.join(path.dirname(nodeSourceFile.fileName), importText) + '.ts'; + } else { + fullPath = importText + '.ts'; + } + enqueueFile(fullPath); + } + + options.entryPoints.forEach(moduleId => enqueueFile(moduleId + '.ts')); + // Add fake usage files + options.inlineEntryPoints.forEach((_, index) => enqueueFile(`inlineEntryPoint:${index}.ts`)); + + let step = 0; + + const checker = program.getTypeChecker(); + while (black_queue.length > 0 || gray_queue.length > 0) { + ++step; + let node: ts.Node; + + if (step % 100 === 0) { + console.log(`${step}/${step+black_queue.length+gray_queue.length} (${black_queue.length}, ${gray_queue.length})`); + } + + if (black_queue.length === 0) { + for (let i = 0; i < gray_queue.length; i++) { + const node = gray_queue[i]; + const nodeParent = node.parent; + if ((ts.isClassDeclaration(nodeParent) || ts.isInterfaceDeclaration(nodeParent)) && nodeOrChildIsBlack(nodeParent)) { + gray_queue.splice(i, 1); + black_queue.push(node); + setColor(node, NodeColor.Black); + i--; + } + } + } + + if (black_queue.length > 0) { + node = black_queue.shift(); + } else { + // only gray nodes remaining... + break; + } + const nodeSourceFile = node.getSourceFile(); + + const loop = (node: ts.Node) => { + const [symbol, symbolImportNode] = getRealNodeSymbol(checker, node); + if (symbolImportNode) { + setColor(symbolImportNode, NodeColor.Black); + } + + if (symbol && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { + for (let i = 0, len = symbol.declarations.length; i < len; i++) { + const declaration = symbol.declarations[i]; + if (ts.isSourceFile(declaration)) { + // Do not enqueue full source files + // (they can be the declaration of a module import) + continue; + } + + if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration))) { + enqueue_black(declaration.name); + + for (let j = 0; j < declaration.members.length; j++) { + const member = declaration.members[j]; + const memberName = member.name ? member.name.getText() : null; + if ( + ts.isConstructorDeclaration(member) + || ts.isConstructSignatureDeclaration(member) + || ts.isIndexSignatureDeclaration(member) + || ts.isCallSignatureDeclaration(member) + || memberName === 'toJSON' + || memberName === 'toString' + || memberName === 'dispose'// TODO: keeping all `dispose` methods + ) { + enqueue_black(member); + } + } + + // queue the heritage clauses + if (declaration.heritageClauses) { + for (let heritageClause of declaration.heritageClauses) { + enqueue_black(heritageClause); + } + } + } else { + enqueue_black(declaration); + } + } + } + node.forEachChild(loop); + }; + node.forEachChild(loop); + } +} + +function nodeIsInItsOwnDeclaration(nodeSourceFile: ts.SourceFile, node: ts.Node, symbol: ts.Symbol): boolean { + for (let i = 0, len = symbol.declarations.length; i < len; i++) { + const declaration = symbol.declarations[i]; + const declarationSourceFile = declaration.getSourceFile(); + + if (nodeSourceFile === declarationSourceFile) { + if (declaration.pos <= node.pos && node.end <= declaration.end) { + return true; + } + } + } + + return false; +} + +function generateResult(languageService: ts.LanguageService, shakeLevel: ShakeLevel): ITreeShakingResult { + const program = languageService.getProgram(); + + let result: ITreeShakingResult = {}; + const writeFile = (filePath: string, contents: string): void => { + result[filePath] = contents; + }; + + program.getSourceFiles().forEach((sourceFile) => { + const fileName = sourceFile.fileName; + if (/^defaultLib:/.test(fileName)) { + return; + } + const destination = fileName; + if (/\.d\.ts$/.test(fileName)) { + if (nodeOrChildIsBlack(sourceFile)) { + writeFile(destination, sourceFile.text); + } + return; + } + + let text = sourceFile.text; + let result = ''; + + function keep(node: ts.Node): void { + result += text.substring(node.pos, node.end); + } + function write(data: string): void { + result += data; + } + + function writeMarkedNodes(node: ts.Node): void { + if (getColor(node) === NodeColor.Black) { + return keep(node); + } + + // Always keep certain top-level statements + if (ts.isSourceFile(node.parent)) { + if (ts.isExpressionStatement(node) && ts.isStringLiteral(node.expression) && node.expression.text === 'use strict') { + return keep(node); + } + + if (ts.isVariableStatement(node) && nodeOrChildIsBlack(node)) { + return keep(node); + } + } + + // Keep the entire import in import * as X cases + if (ts.isImportDeclaration(node)) { + if (node.importClause && node.importClause.namedBindings) { + if (ts.isNamespaceImport(node.importClause.namedBindings)) { + if (getColor(node.importClause.namedBindings) === NodeColor.Black) { + return keep(node); + } + } else { + let survivingImports: string[] = []; + for (let i = 0; i < node.importClause.namedBindings.elements.length; i++) { + const importNode = node.importClause.namedBindings.elements[i]; + if (getColor(importNode) === NodeColor.Black) { + survivingImports.push(importNode.getFullText(sourceFile)); + } + } + const leadingTriviaWidth = node.getLeadingTriviaWidth(); + const leadingTrivia = sourceFile.text.substr(node.pos, leadingTriviaWidth); + if (survivingImports.length > 0) { + if (node.importClause && getColor(node.importClause) === NodeColor.Black) { + return write(`${leadingTrivia}import ${node.importClause.name.text}, {${survivingImports.join(',')} } from${node.moduleSpecifier.getFullText(sourceFile)};`); + } + return write(`${leadingTrivia}import {${survivingImports.join(',')} } from${node.moduleSpecifier.getFullText(sourceFile)};`); + } else { + if (node.importClause && getColor(node.importClause) === NodeColor.Black) { + return write(`${leadingTrivia}import ${node.importClause.name.text} from${node.moduleSpecifier.getFullText(sourceFile)};`); + } + } + } + } else { + if (node.importClause && getColor(node.importClause) === NodeColor.Black) { + return keep(node); + } + } + } + + if (shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) && nodeOrChildIsBlack(node)) { + let toWrite = node.getFullText(); + for (let i = node.members.length - 1; i >= 0; i--) { + const member = node.members[i]; + if (getColor(member) === NodeColor.Black) { + // keep method + continue; + } + if (/^_(.*)Brand$/.test(member.name.getText())) { + // TODO: keep all members ending with `Brand`... + continue; + } + + let pos = member.pos - node.pos; + let end = member.end - node.pos; + toWrite = toWrite.substring(0, pos) + toWrite.substring(end); + } + return write(toWrite); + } + + if (ts.isFunctionDeclaration(node)) { + // Do not go inside functions if they haven't been marked + return; + } + + node.forEachChild(writeMarkedNodes); + } + + if (getColor(sourceFile) !== NodeColor.Black) { + if (!nodeOrChildIsBlack(sourceFile)) { + // none of the elements are reachable => don't write this file at all! + return; + } + sourceFile.forEachChild(writeMarkedNodes); + result += sourceFile.endOfFileToken.getFullText(sourceFile); + } else { + result = text; + } + + writeFile(destination, result); + }); + + return result; +} + +//#endregion + +//#region Utils + +/** + * Returns the node's symbol and the `import` node (if the symbol resolved from a different module) + */ +function getRealNodeSymbol(checker: ts.TypeChecker, node: ts.Node): [ts.Symbol, ts.Declaration] { + /** + * Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 } + */ + /* @internal */ + function getContainingObjectLiteralElement(node: ts.Node): ts.ObjectLiteralElement | undefined { + switch (node.kind) { + case ts.SyntaxKind.StringLiteral: + case ts.SyntaxKind.NumericLiteral: + if (node.parent.kind === ts.SyntaxKind.ComputedPropertyName) { + return ts.isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined; + } + // falls through + case ts.SyntaxKind.Identifier: + return ts.isObjectLiteralElement(node.parent) && + (node.parent.parent.kind === ts.SyntaxKind.ObjectLiteralExpression || node.parent.parent.kind === ts.SyntaxKind.JsxAttributes) && + node.parent.name === node ? node.parent : undefined; + } + return undefined; + } + + function getPropertySymbolsFromType(type: ts.Type, propName: ts.PropertyName) { + function getTextOfPropertyName(name: ts.PropertyName): string { + + function isStringOrNumericLiteral(node: ts.Node): node is ts.StringLiteral | ts.NumericLiteral { + const kind = node.kind; + return kind === ts.SyntaxKind.StringLiteral + || kind === ts.SyntaxKind.NumericLiteral; + } + + switch (name.kind) { + case ts.SyntaxKind.Identifier: + return name.text; + case ts.SyntaxKind.StringLiteral: + case ts.SyntaxKind.NumericLiteral: + return name.text; + case ts.SyntaxKind.ComputedPropertyName: + return isStringOrNumericLiteral(name.expression) ? name.expression.text : undefined!; + } + } + + const name = getTextOfPropertyName(propName); + if (name && type) { + const result: ts.Symbol[] = []; + const symbol = type.getProperty(name); + if (type.flags & ts.TypeFlags.Union) { + for (const t of (type).types) { + const symbol = t.getProperty(name); + if (symbol) { + result.push(symbol); + } + } + return result; + } + + if (symbol) { + result.push(symbol); + return result; + } + } + return undefined; + } + + function getPropertySymbolsFromContextualType(typeChecker: ts.TypeChecker, node: ts.ObjectLiteralElement): ts.Symbol[] { + const objectLiteral = node.parent; + const contextualType = typeChecker.getContextualType(objectLiteral)!; + return getPropertySymbolsFromType(contextualType, node.name!)!; + } + + // Go to the original declaration for cases: + // + // (1) when the aliased symbol was declared in the location(parent). + // (2) when the aliased symbol is originating from an import. + // + function shouldSkipAlias(node: ts.Node, declaration: ts.Node): boolean { + if (node.kind !== ts.SyntaxKind.Identifier) { + return false; + } + if (node.parent === declaration) { + return true; + } + switch (declaration.kind) { + case ts.SyntaxKind.ImportClause: + case ts.SyntaxKind.ImportEqualsDeclaration: + return true; + case ts.SyntaxKind.ImportSpecifier: + return declaration.parent.kind === ts.SyntaxKind.NamedImports; + default: + return false; + } + } + + if (!ts.isShorthandPropertyAssignment(node)) { + if (node.getChildCount() !== 0) { + return [null, null]; + } + } + + let symbol = checker.getSymbolAtLocation(node); + let importNode: ts.Declaration = null; + if (symbol && symbol.flags & ts.SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) { + const aliased = checker.getAliasedSymbol(symbol); + if (aliased.declarations) { + // We should mark the import as visited + importNode = symbol.declarations[0]; + symbol = aliased; + } + } + + if (symbol) { + // Because name in short-hand property assignment has two different meanings: property name and property value, + // using go-to-definition at such position should go to the variable declaration of the property value rather than + // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition + // is performed at the location of property access, we would like to go to definition of the property in the short-hand + // assignment. This case and others are handled by the following code. + if (node.parent.kind === ts.SyntaxKind.ShorthandPropertyAssignment) { + symbol = checker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration); + } + + // If the node is the name of a BindingElement within an ObjectBindingPattern instead of just returning the + // declaration the symbol (which is itself), we should try to get to the original type of the ObjectBindingPattern + // and return the property declaration for the referenced property. + // For example: + // import('./foo').then(({ b/*goto*/ar }) => undefined); => should get use to the declaration in file "./foo" + // + // function bar(onfulfilled: (value: T) => void) { //....} + // interface Test { + // pr/*destination*/op1: number + // } + // bar(({pr/*goto*/op1})=>{}); + if (ts.isPropertyName(node) && ts.isBindingElement(node.parent) && ts.isObjectBindingPattern(node.parent.parent) && + (node === (node.parent.propertyName || node.parent.name))) { + const type = checker.getTypeAtLocation(node.parent.parent); + if (type) { + const propSymbols = getPropertySymbolsFromType(type, node); + if (propSymbols) { + symbol = propSymbols[0]; + } + } + } + + // If the current location we want to find its definition is in an object literal, try to get the contextual type for the + // object literal, lookup the property symbol in the contextual type, and use this for goto-definition. + // For example + // interface Props{ + // /*first*/prop1: number + // prop2: boolean + // } + // function Foo(arg: Props) {} + // Foo( { pr/*1*/op1: 10, prop2: false }) + const element = getContainingObjectLiteralElement(node); + if (element && checker.getContextualType(element.parent as ts.Expression)) { + const propertySymbols = getPropertySymbolsFromContextualType(checker, element); + if (propertySymbols) { + symbol = propertySymbols[0]; + } + } + } + + if (symbol && symbol.declarations) { + return [symbol, importNode]; + } + + return [null, null]; +} + +/** Get the token whose text contains the position */ +function getTokenAtPosition(sourceFile: ts.SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includeEndPosition: boolean): ts.Node { + let current: ts.Node = sourceFile; + outer: while (true) { + // find the child that contains 'position' + for (const child of current.getChildren()) { + const start = allowPositionInLeadingTrivia ? child.getFullStart() : child.getStart(sourceFile, /*includeJsDoc*/ true); + if (start > position) { + // If this child begins after position, then all subsequent children will as well. + break; + } + + const end = child.getEnd(); + if (position < end || (position === end && (child.kind === ts.SyntaxKind.EndOfFileToken || includeEndPosition))) { + current = child; + continue outer; + } + } + + return current; + } +} + +//#endregion diff --git a/build/monaco/api.js b/build/monaco/api.js index 74e9273f35ee9..ae4a0d266164a 100644 --- a/build/monaco/api.js +++ b/build/monaco/api.js @@ -134,7 +134,25 @@ function getTopLevelDeclaration(sourceFile, typeName) { function getNodeText(sourceFile, node) { return sourceFile.getFullText().substring(node.pos, node.end); } -function getMassagedTopLevelDeclarationText(sourceFile, declaration) { +function hasModifier(modifiers, kind) { + if (modifiers) { + for (var i = 0; i < modifiers.length; i++) { + var mod = modifiers[i]; + if (mod.kind === kind) { + return true; + } + } + } + return false; +} +function isStatic(member) { + return hasModifier(member.modifiers, ts.SyntaxKind.StaticKeyword); +} +function isDefaultExport(declaration) { + return (hasModifier(declaration.modifiers, ts.SyntaxKind.DefaultKeyword) + && hasModifier(declaration.modifiers, ts.SyntaxKind.ExportKeyword)); +} +function getMassagedTopLevelDeclarationText(sourceFile, declaration, importName, usage) { var result = getNodeText(sourceFile, declaration); // if (result.indexOf('MonacoWorker') >= 0) { // console.log('here!'); @@ -142,6 +160,18 @@ function getMassagedTopLevelDeclarationText(sourceFile, declaration) { // } if (declaration.kind === ts.SyntaxKind.InterfaceDeclaration || declaration.kind === ts.SyntaxKind.ClassDeclaration) { var interfaceDeclaration = declaration; + var staticTypeName_1 = (isDefaultExport(interfaceDeclaration) + ? importName + ".default" + : importName + "." + declaration.name.text); + var instanceTypeName_1 = staticTypeName_1; + var typeParametersCnt = (interfaceDeclaration.typeParameters ? interfaceDeclaration.typeParameters.length : 0); + if (typeParametersCnt > 0) { + var arr = []; + for (var i = 0; i < typeParametersCnt; i++) { + arr.push('any'); + } + instanceTypeName_1 = instanceTypeName_1 + "<" + arr.join(',') + ">"; + } var members = interfaceDeclaration.members; members.forEach(function (member) { try { @@ -151,6 +181,15 @@ function getMassagedTopLevelDeclarationText(sourceFile, declaration) { result = result.replace(memberText, ''); // console.log('AFTER: ', result); } + else { + var memberName = member.name.text; + if (isStatic(member)) { + usage.push("a = " + staticTypeName_1 + "." + memberName + ";"); + } + else { + usage.push("a = (<" + instanceTypeName_1 + ">b)." + memberName + ";"); + } + } } catch (err) { // life.. @@ -211,6 +250,16 @@ function generateDeclarationFile(out, inputFiles, recipe) { var endl = /\r\n/.test(recipe) ? '\r\n' : '\n'; var lines = recipe.split(endl); var result = []; + var usageCounter = 0; + var usageImports = []; + var usage = []; + usage.push("var a;"); + usage.push("var b;"); + var generateUsageImport = function (moduleId) { + var importName = 'm' + (++usageCounter); + usageImports.push("import * as " + importName + " from '" + moduleId.replace(/\.d\.ts$/, '') + "';"); + return importName; + }; lines.forEach(function (line) { var m1 = line.match(/^\s*#include\(([^;)]*)(;[^)]*)?\)\:(.*)$/); if (m1) { @@ -220,6 +269,7 @@ function generateDeclarationFile(out, inputFiles, recipe) { if (!sourceFile_1) { return; } + var importName_1 = generateUsageImport(moduleId); var replacer_1 = createReplacer(m1[2]); var typeNames = m1[3].split(/,/); typeNames.forEach(function (typeName) { @@ -232,7 +282,7 @@ function generateDeclarationFile(out, inputFiles, recipe) { logErr('Cannot find type ' + typeName); return; } - result.push(replacer_1(getMassagedTopLevelDeclarationText(sourceFile_1, declaration))); + result.push(replacer_1(getMassagedTopLevelDeclarationText(sourceFile_1, declaration, importName_1, usage))); }); return; } @@ -244,6 +294,7 @@ function generateDeclarationFile(out, inputFiles, recipe) { if (!sourceFile_2) { return; } + var importName_2 = generateUsageImport(moduleId); var replacer_2 = createReplacer(m2[2]); var typeNames = m2[3].split(/,/); var typesToExcludeMap_1 = {}; @@ -271,7 +322,7 @@ function generateDeclarationFile(out, inputFiles, recipe) { } } } - result.push(replacer_2(getMassagedTopLevelDeclarationText(sourceFile_2, declaration))); + result.push(replacer_2(getMassagedTopLevelDeclarationText(sourceFile_2, declaration, importName_2, usage))); }); return; } @@ -282,9 +333,12 @@ function generateDeclarationFile(out, inputFiles, recipe) { resultTxt = resultTxt.replace(/\bEvent, kind: ts.SyntaxKind): boolean { + if (modifiers) { + for (let i = 0; i < modifiers.length; i++) { + let mod = modifiers[i]; + if (mod.kind === kind) { + return true; + } + } + } + return false; +} -function getMassagedTopLevelDeclarationText(sourceFile: ts.SourceFile, declaration: TSTopLevelDeclare): string { +function isStatic(member: ts.ClassElement | ts.TypeElement): boolean { + return hasModifier(member.modifiers, ts.SyntaxKind.StaticKeyword); +} + +function isDefaultExport(declaration: ts.InterfaceDeclaration | ts.ClassDeclaration): boolean { + return ( + hasModifier(declaration.modifiers, ts.SyntaxKind.DefaultKeyword) + && hasModifier(declaration.modifiers, ts.SyntaxKind.ExportKeyword) + ); +} + +function getMassagedTopLevelDeclarationText(sourceFile: ts.SourceFile, declaration: TSTopLevelDeclare, importName: string, usage: string[]): string { let result = getNodeText(sourceFile, declaration); // if (result.indexOf('MonacoWorker') >= 0) { // console.log('here!'); @@ -163,7 +185,23 @@ function getMassagedTopLevelDeclarationText(sourceFile: ts.SourceFile, declarati if (declaration.kind === ts.SyntaxKind.InterfaceDeclaration || declaration.kind === ts.SyntaxKind.ClassDeclaration) { let interfaceDeclaration = declaration; - let members: ts.NodeArray = interfaceDeclaration.members; + const staticTypeName = ( + isDefaultExport(interfaceDeclaration) + ? `${importName}.default` + : `${importName}.${declaration.name.text}` + ); + + let instanceTypeName = staticTypeName; + const typeParametersCnt = (interfaceDeclaration.typeParameters ? interfaceDeclaration.typeParameters.length : 0); + if (typeParametersCnt > 0) { + let arr: string[] = []; + for (let i = 0; i < typeParametersCnt; i++) { + arr.push('any'); + } + instanceTypeName = `${instanceTypeName}<${arr.join(',')}>`; + } + + const members: ts.NodeArray = interfaceDeclaration.members; members.forEach((member) => { try { let memberText = getNodeText(sourceFile, member); @@ -171,6 +209,13 @@ function getMassagedTopLevelDeclarationText(sourceFile: ts.SourceFile, declarati // console.log('BEFORE: ', result); result = result.replace(memberText, ''); // console.log('AFTER: ', result); + } else { + const memberName = (member.name).text; + if (isStatic(member)) { + usage.push(`a = ${staticTypeName}.${memberName};`); + } else { + usage.push(`a = (<${instanceTypeName}>b).${memberName};`); + } } } catch (err) { // life.. @@ -237,11 +282,24 @@ function createReplacer(data: string): (str: string) => string { }; } -function generateDeclarationFile(out: string, inputFiles: { [file: string]: string; }, recipe: string): string { +function generateDeclarationFile(out: string, inputFiles: { [file: string]: string; }, recipe: string): [string, string] { const endl = /\r\n/.test(recipe) ? '\r\n' : '\n'; let lines = recipe.split(endl); - let result = []; + let result: string[] = []; + + let usageCounter = 0; + let usageImports: string[] = []; + let usage: string[] = []; + + usage.push(`var a;`); + usage.push(`var b;`); + + const generateUsageImport = (moduleId: string) => { + let importName = 'm' + (++usageCounter); + usageImports.push(`import * as ${importName} from '${moduleId.replace(/\.d\.ts$/, '')}';`); + return importName; + }; lines.forEach(line => { @@ -254,6 +312,8 @@ function generateDeclarationFile(out: string, inputFiles: { [file: string]: stri return; } + const importName = generateUsageImport(moduleId); + let replacer = createReplacer(m1[2]); let typeNames = m1[3].split(/,/); @@ -267,7 +327,7 @@ function generateDeclarationFile(out: string, inputFiles: { [file: string]: stri logErr('Cannot find type ' + typeName); return; } - result.push(replacer(getMassagedTopLevelDeclarationText(sourceFile, declaration))); + result.push(replacer(getMassagedTopLevelDeclarationText(sourceFile, declaration, importName, usage))); }); return; } @@ -281,6 +341,8 @@ function generateDeclarationFile(out: string, inputFiles: { [file: string]: stri return; } + const importName = generateUsageImport(moduleId); + let replacer = createReplacer(m2[2]); let typeNames = m2[3].split(/,/); @@ -309,7 +371,7 @@ function generateDeclarationFile(out: string, inputFiles: { [file: string]: stri } } } - result.push(replacer(getMassagedTopLevelDeclarationText(sourceFile, declaration))); + result.push(replacer(getMassagedTopLevelDeclarationText(sourceFile, declaration, importName, usage))); }); return; } @@ -324,10 +386,13 @@ function generateDeclarationFile(out: string, inputFiles: { [file: string]: stri resultTxt = format(resultTxt); - return resultTxt; + return [ + resultTxt, + `${usageImports.join('\n')}\n\n${usage.join('\n')}` + ]; } -export function getFilesToWatch(out: string): string[] { +function getIncludesInRecipe(): string[] { let recipe = fs.readFileSync(RECIPE_PATH).toString(); let lines = recipe.split(/\r\n|\n|\r/); let result = []; @@ -337,14 +402,14 @@ export function getFilesToWatch(out: string): string[] { let m1 = line.match(/^\s*#include\(([^;)]*)(;[^)]*)?\)\:(.*)$/); if (m1) { let moduleId = m1[1]; - result.push(moduleIdToPath(out, moduleId)); + result.push(moduleId); return; } let m2 = line.match(/^\s*#includeAll\(([^;)]*)(;[^)]*)?\)\:(.*)$/); if (m2) { let moduleId = m2[1]; - result.push(moduleIdToPath(out, moduleId)); + result.push(moduleId); return; } }); @@ -352,8 +417,13 @@ export function getFilesToWatch(out: string): string[] { return result; } +export function getFilesToWatch(out: string): string[] { + return getIncludesInRecipe().map((moduleId) => moduleIdToPath(out, moduleId)); +} + export interface IMonacoDeclarationResult { content: string; + usageContent: string; filePath: string; isTheSame: boolean; } @@ -363,7 +433,7 @@ export function run(out: string, inputFiles: { [file: string]: string; }): IMona SOURCE_FILE_MAP = {}; let recipe = fs.readFileSync(RECIPE_PATH).toString(); - let result = generateDeclarationFile(out, inputFiles, recipe); + let [result, usageContent] = generateDeclarationFile(out, inputFiles, recipe); let currentContent = fs.readFileSync(DECLARATION_PATH).toString(); log('Finished monaco.d.ts generation'); @@ -374,6 +444,7 @@ export function run(out: string, inputFiles: { [file: string]: string; }): IMona return { content: result, + usageContent: usageContent, filePath: DECLARATION_PATH, isTheSame }; @@ -382,3 +453,96 @@ export function run(out: string, inputFiles: { [file: string]: string; }): IMona export function complainErrors() { logErr('Not running monaco.d.ts generation due to compile errors'); } + + + +interface ILibMap { [libName: string]: string; } +interface IFileMap { [fileName: string]: string; } + +class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { + + private readonly _libs: ILibMap; + private readonly _files: IFileMap; + private readonly _compilerOptions: ts.CompilerOptions; + + constructor(libs: ILibMap, files: IFileMap, compilerOptions: ts.CompilerOptions) { + this._libs = libs; + this._files = files; + this._compilerOptions = compilerOptions; + } + + // --- language service host --------------- + + getCompilationSettings(): ts.CompilerOptions { + return this._compilerOptions; + } + getScriptFileNames(): string[] { + return ( + [] + .concat(Object.keys(this._libs)) + .concat(Object.keys(this._files)) + ); + } + getScriptVersion(fileName: string): string { + return '1'; + } + getProjectVersion(): string { + return '1'; + } + getScriptSnapshot(fileName: string): ts.IScriptSnapshot { + if (this._files.hasOwnProperty(fileName)) { + return ts.ScriptSnapshot.fromString(this._files[fileName]); + } else if (this._libs.hasOwnProperty(fileName)) { + return ts.ScriptSnapshot.fromString(this._libs[fileName]); + } else { + return ts.ScriptSnapshot.fromString(''); + } + } + getScriptKind(fileName: string): ts.ScriptKind { + return ts.ScriptKind.TS; + } + getCurrentDirectory(): string { + return ''; + } + getDefaultLibFileName(options: ts.CompilerOptions): string { + return 'defaultLib:es5'; + } + isDefaultLibFileName(fileName: string): boolean { + return fileName === this.getDefaultLibFileName(this._compilerOptions); + } +} + +export function execute(): IMonacoDeclarationResult { + + const OUTPUT_FILES: { [file: string]: string; } = {}; + const SRC_FILES: IFileMap = {}; + const SRC_FILE_TO_EXPECTED_NAME: { [filename: string]: string; } = {}; + getIncludesInRecipe().forEach((moduleId) => { + if (/\.d\.ts$/.test(moduleId)) { + let fileName = path.join(SRC, moduleId); + OUTPUT_FILES[moduleIdToPath('src', moduleId)] = fs.readFileSync(fileName).toString(); + return; + } + + let fileName = path.join(SRC, moduleId) + '.ts'; + SRC_FILES[fileName] = fs.readFileSync(fileName).toString(); + SRC_FILE_TO_EXPECTED_NAME[fileName] = moduleIdToPath('src', moduleId); + }); + + const languageService = ts.createLanguageService(new TypeScriptLanguageServiceHost({}, SRC_FILES, {})); + + var t1 = Date.now(); + Object.keys(SRC_FILES).forEach((fileName) => { + var t = Date.now(); + const emitOutput = languageService.getEmitOutput(fileName, true); + OUTPUT_FILES[SRC_FILE_TO_EXPECTED_NAME[fileName]] = emitOutput.outputFiles[0].text; + // console.log(`Generating .d.ts for ${fileName} took ${Date.now() - t} ms`); + }); + console.log(`Generating .d.ts took ${Date.now() - t1} ms`); + + // console.log(result.filePath); + // fs.writeFileSync(result.filePath, result.content.replace(/\r\n/gm, '\n')); + // fs.writeFileSync(path.join(SRC, 'user.ts'), result.usageContent.replace(/\r\n/gm, '\n')); + + return run('src', OUTPUT_FILES); +} diff --git a/build/monaco/monaco.usage.recipe b/build/monaco/monaco.usage.recipe new file mode 100644 index 0000000000000..05377a19ba09f --- /dev/null +++ b/build/monaco/monaco.usage.recipe @@ -0,0 +1,82 @@ + +// This file is adding references to various symbols which should not be removed via tree shaking + +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IHighlight } from 'vs/base/parts/quickopen/browser/quickOpenModel'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; +import { SimpleWorkerClient, create as create1 } from 'vs/base/common/worker/simpleWorker'; +import { create as create2 } from 'vs/editor/common/services/editorSimpleWorker'; +import { QuickOpenWidget } from 'vs/base/parts/quickopen/browser/quickOpenWidget'; +import { SyncDescriptor0, SyncDescriptor1, SyncDescriptor2, SyncDescriptor3, SyncDescriptor4, SyncDescriptor5, SyncDescriptor6, SyncDescriptor7, SyncDescriptor8 } from 'vs/platform/instantiation/common/descriptors'; +import { PolyfillPromise } from 'vs/base/common/winjs.polyfill.promise'; +import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator'; +import * as editorAPI from 'vs/editor/editor.api'; + +(function () { + var a: any; + var b: any; + a = (b).layout; // IContextViewProvider + a = (b).getWorkspaceFolder; // IWorkspaceFolderProvider + a = (b).getWorkspace; // IWorkspaceFolderProvider + a = (b).style; // IThemable + a = (b).style; // IThemable + a = (b).userHome; // IUserHomeProvider + a = (b).previous; // IDiffNavigator + a = (>b).type; + a = (b).start; + a = (b).end; + a = (>b).getProxyObject; // IWorkerClient + a = create1; + a = create2; + + // promise polyfill + a = PolyfillPromise.all; + a = PolyfillPromise.race; + a = PolyfillPromise.resolve; + a = PolyfillPromise.reject; + a = (b).then; + a = (b).catch; + + // injection madness + a = (>b).ctor; + a = (>b).bind; + a = (>b).ctor; + a = (>b).bind; + a = (>b).ctor; + a = (>b).bind; + a = (>b).ctor; + a = (>b).bind; + a = (>b).ctor; + a = (>b).bind; + a = (>b).ctor; + a = (>b).bind; + a = (>b).ctor; + a = (>b).bind; + a = (>b).ctor; + a = (>b).bind; + a = (>b).ctor; + a = (>b).bind; + a = (>b).ctor; + a = (>b).bind; + + // exported API + a = editorAPI.CancellationTokenSource; + a = editorAPI.Emitter; + a = editorAPI.KeyCode; + a = editorAPI.KeyMod; + a = editorAPI.Position; + a = editorAPI.Range; + a = editorAPI.Selection; + a = editorAPI.SelectionDirection; + a = editorAPI.Severity; + a = editorAPI.MarkerSeverity; + a = editorAPI.MarkerTag; + a = editorAPI.Promise; + a = editorAPI.Uri; + a = editorAPI.Token; + a = editorAPI.editor; + a = editorAPI.languages; +})(); diff --git a/extensions/make/package.json b/extensions/make/package.json index d57f0077caada..f66762ba8001b 100644 --- a/extensions/make/package.json +++ b/extensions/make/package.json @@ -4,29 +4,47 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", - "engines": { "vscode": "*" }, + "engines": { + "vscode": "*" + }, "scripts": { "update-grammar": "node ../../build/npm/update-grammar.js fadeevab/make.tmbundle Syntaxes/Makefile.plist ./syntaxes/make.tmLanguage.json" }, "contributes": { - - "languages": [{ - "id": "makefile", - "aliases": ["Makefile", "makefile"], - "extensions": [ ".mk" ], - "filenames": [ "Makefile", "makefile", "GNUmakefile", "OCamlMakefile" ], - "firstLine": "^#!\\s*/usr/bin/make", - "configuration": "./language-configuration.json" - }], - "grammars": [{ - "language": "makefile", - "scopeName": "source.makefile", - "path": "./syntaxes/make.tmLanguage.json" - }], + "languages": [ + { + "id": "makefile", + "aliases": [ + "Makefile", + "makefile" + ], + "extensions": [ + ".mk" + ], + "filenames": [ + "Makefile", + "makefile", + "GNUmakefile", + "OCamlMakefile" + ], + "firstLine": "^#!\\s*/usr/bin/make", + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "makefile", + "scopeName": "source.makefile", + "path": "./syntaxes/make.tmLanguage.json", + "tokenTypes": { + "string.interpolated": "other" + } + } + ], "configurationDefaults": { "[makefile]": { "editor.insertSpaces": false } } } -} +} \ No newline at end of file diff --git a/extensions/markdown-basics/package.json b/extensions/markdown-basics/package.json index a091ad8ea291f..dd0b3f5a87d38 100644 --- a/extensions/markdown-basics/package.json +++ b/extensions/markdown-basics/package.json @@ -19,7 +19,8 @@ ".md", ".mdown", ".markdown", - ".markdn" + ".markdn", + ".workbook" ], "configuration": "./language-configuration.json" } diff --git a/extensions/markdown-language-features/src/slugify.ts b/extensions/markdown-language-features/src/slugify.ts index c3e167e89e07d..4bc3cee42ceff 100644 --- a/extensions/markdown-language-features/src/slugify.ts +++ b/extensions/markdown-language-features/src/slugify.ts @@ -23,7 +23,7 @@ export const githubSlugifier: Slugifier = new class implements Slugifier { heading.trim() .toLowerCase() .replace(/\s+/g, '-') // Replace whitespace with - - .replace(/[\]\[\!\'\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~\`]/g, '') // Remove known puctuators + .replace(/[\]\[\!\'\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~\`。,、;:?!…—·ˉ¨‘’“”々~‖∶"'`|〃〔〕〈〉《》「」『』.〖〗【】()[]{}]/g, '') // Remove known puctuators .replace(/^\-+/, '') // Remove leading - .replace(/\-+$/, '') // Remove trailing - ); diff --git a/extensions/npm/README.md b/extensions/npm/README.md index 007cb59abe6c4..c3dd943720362 100644 --- a/extensions/npm/README.md +++ b/extensions/npm/README.md @@ -15,11 +15,11 @@ For more information about auto detection of Tasks, see the [documentation](http ### Script Explorer -The Npm Script Explorer shows the npm scripts found in your workspace. The explorer view is enabled by the setting `npm.enableScriptExplorer`. +The Npm Script Explorer shows the npm scripts found in your workspace. The explorer view is enabled by the setting `npm.enableScriptExplorer`. A script can be opened, run, or debug from the explorer. ### Run Scripts from the Editor -The extension provides commands to run the script containing the selection. +The extension provides code lense actions to run or debug a script from the editor. ## Settings @@ -29,3 +29,5 @@ The extension provides commands to run the script containing the selection. - `npm.exclude` - Glob patterns for folders that should be excluded from automatic script detection. The pattern is matched against the **absolute path** of the package.json. For example, to exclude all test folders use '**/test/**'. - `npm.enableScriptExplorer` - Enable an explorer view for npm scripts. - `npm.scriptExplorerAction` - The default click action: `open` or `run`, the default is `open`. +- `npm.scriptCodeLens.enable` - Enable/disable the code lenses to run a script. + diff --git a/extensions/npm/package.json b/extensions/npm/package.json index 7856f52a62cd8..7f4b794ee0c57 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -59,10 +59,6 @@ "dark": "resources/dark/continue.svg" } }, - { - "command": "npm.runScriptFromSource", - "title": "%command.runScriptFromSource%" - }, { "command": "npm.debugScript", "title": "%command.debug%", @@ -98,10 +94,6 @@ "command": "npm.runScript", "when": "false" }, - { - "command": "npm.runScriptFromSource", - "when": "false" - }, { "command": "npm.debugScript", "when": "false" @@ -122,13 +114,6 @@ "group": "navigation" } ], - "editor/context": [ - { - "command": "npm.runScriptFromSource", - "when": "resourceFilename == 'package.json'", - "group": "navigation@+1" - } - ], "view/item/context": [ { "command": "npm.openScript", @@ -193,6 +178,12 @@ "scope": "resource", "description": "%config.npm.runSilent%" }, + "npm.scriptCodeLens.enable": { + "type": "boolean", + "default": true, + "scope": "resource", + "description": "%config.scriptCodeLens.enable%" + }, "npm.packageManager": { "scope": "resource", "type": "string", diff --git a/extensions/npm/package.nls.json b/extensions/npm/package.nls.json index 70e8880002c4f..92665d5f65a56 100644 --- a/extensions/npm/package.nls.json +++ b/extensions/npm/package.nls.json @@ -15,6 +15,5 @@ "command.run": "Run", "command.debug": "Debug", "command.openScript": "Open", - "command.runInstall": "Run Install", - "command.runScriptFromSource": "Run Script" + "command.runInstall": "Run Install" } diff --git a/extensions/npm/src/lenses.ts b/extensions/npm/src/lenses.ts new file mode 100644 index 0000000000000..6394fbad5b309 --- /dev/null +++ b/extensions/npm/src/lenses.ts @@ -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 { + ExtensionContext, CodeLensProvider, TextDocument, commands, ProviderResult, CodeLens, CancellationToken, + workspace, tasks, Range, Command, Event, EventEmitter +} from 'vscode'; +import { + createTask, startDebugging, findAllScriptRanges, extractDebugArgFromScript +} from './tasks'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); + +export class NpmLensProvider implements CodeLensProvider { + private extensionContext: ExtensionContext; + private _onDidChangeCodeLenses: EventEmitter = new EventEmitter(); + readonly onDidChangeCodeLenses: Event = this._onDidChangeCodeLenses.event; + + constructor(context: ExtensionContext) { + this.extensionContext = context; + context.subscriptions.push(commands.registerCommand('npm.runScriptFromLens', this.runScriptFromLens, this)); + context.subscriptions.push(commands.registerCommand('npm.debugScriptFromLens', this.debugScriptFromLens, this)); + } + + public provideCodeLenses(document: TextDocument, _token: CancellationToken): ProviderResult { + let result = findAllScriptRanges(document.getText()); + let folder = workspace.getWorkspaceFolder(document.uri); + let lenses: CodeLens[] = []; + + + if (folder && !workspace.getConfiguration('npm', folder.uri).get('scriptCodeLens.enable', 'true')) { + return lenses; + } + + result.forEach((value, key) => { + let start = document.positionAt(value[0]); + let end = document.positionAt(value[0] + value[1]); + let range = new Range(start, end); + + let command: Command = { + command: 'npm.runScriptFromLens', + title: localize('run', "Run"), + arguments: [document, key] + }; + let lens: CodeLens = new CodeLens(range, command); + lenses.push(lens); + + let debugArgs = extractDebugArgFromScript(value[2]); + if (debugArgs) { + command = { + command: 'npm.debugScriptFromLens', + title: localize('debug', "Debug"), + arguments: [document, key, debugArgs[0], debugArgs[1]] + }; + lens = new CodeLens(range, command); + lenses.push(lens); + } + }); + return lenses; + } + + public refresh() { + this._onDidChangeCodeLenses.fire(); + } + + public runScriptFromLens(document: TextDocument, script: string) { + let uri = document.uri; + let folder = workspace.getWorkspaceFolder(uri); + if (folder) { + let task = createTask(script, `run ${script}`, folder, uri); + tasks.executeTask(task); + } + } + + public debugScriptFromLens(document: TextDocument, script: string, protocol: string, port: number) { + let uri = document.uri; + let folder = workspace.getWorkspaceFolder(uri); + if (folder) { + startDebugging(script, protocol, port, folder); + } + } +} diff --git a/extensions/npm/src/main.ts b/extensions/npm/src/main.ts index 66c6cad1c2866..f2c80f5f47600 100644 --- a/extensions/npm/src/main.ts +++ b/extensions/npm/src/main.ts @@ -9,17 +9,14 @@ import * as vscode from 'vscode'; import { addJSONProviders } from './features/jsonContributions'; import { NpmScriptsTreeDataProvider } from './npmView'; -import { provideNpmScripts, invalidateScriptsCache, findScriptAtPosition, createTask } from './tasks'; - -import * as nls from 'vscode-nls'; - -let taskProvider: vscode.Disposable | undefined; - -const localize = nls.loadMessageBundle(); +import { invalidateScriptsCache, NpmTaskProvider } from './tasks'; +import { NpmLensProvider } from './lenses'; export async function activate(context: vscode.ExtensionContext): Promise { - taskProvider = registerTaskProvider(context); + const taskProvider = registerTaskProvider(context); const treeDataProvider = registerExplorer(context); + const lensProvider = registerLensProvider(context); + configureHttpRequest(); vscode.workspace.onDidChangeConfiguration((e) => { configureHttpRequest(); @@ -34,9 +31,13 @@ export async function activate(context: vscode.ExtensionContext): Promise treeDataProvider.refresh(); } } + if (e.affectsConfiguration('npm.scriptCodeLens.enable')) { + if (lensProvider) { + lensProvider.refresh(); + } + } }); context.subscriptions.push(addJSONProviders(httpRequest.xhr)); - context.subscriptions.push(vscode.commands.registerCommand('npm.runScriptFromSource', runScriptFromSource)); } function registerTaskProvider(context: vscode.ExtensionContext): vscode.Disposable | undefined { @@ -47,15 +48,10 @@ function registerTaskProvider(context: vscode.ExtensionContext): vscode.Disposab watcher.onDidCreate((_e) => invalidateScriptsCache()); context.subscriptions.push(watcher); - let provider: vscode.TaskProvider = { - provideTasks: async () => { - return provideNpmScripts(); - }, - resolveTask(_task: vscode.Task): vscode.Task | undefined { - return undefined; - } - }; - return vscode.workspace.registerTaskProvider('npm', provider); + let provider: vscode.TaskProvider = new NpmTaskProvider(context); + let disposable = vscode.workspace.registerTaskProvider('npm', provider); + context.subscriptions.push(disposable); + return disposable; } return undefined; } @@ -70,36 +66,24 @@ function registerExplorer(context: vscode.ExtensionContext): NpmScriptsTreeDataP return undefined; } +function registerLensProvider(context: vscode.ExtensionContext): NpmLensProvider | undefined { + if (vscode.workspace.workspaceFolders) { + let npmSelector: vscode.DocumentSelector = { + language: 'json', + scheme: 'file', + pattern: '**/package.json' + }; + let provider = new NpmLensProvider(context); + context.subscriptions.push(vscode.languages.registerCodeLensProvider(npmSelector, provider)); + return provider; + } + return undefined; +} + function configureHttpRequest() { const httpSettings = vscode.workspace.getConfiguration('http'); httpRequest.configure(httpSettings.get('proxy', ''), httpSettings.get('proxyStrictSSL', true)); } -async function runScriptFromSource() { - let editor = vscode.window.activeTextEditor; - if (!editor) { - return; - } - let document = editor.document; - let contents = document.getText(); - let selection = editor.selection; - let offset = document.offsetAt(selection.anchor); - let script = findScriptAtPosition(contents, offset); - if (script) { - let uri = document.uri; - let folder = vscode.workspace.getWorkspaceFolder(uri); - if (folder) { - let task = createTask(script, `run ${script}`, folder, uri); - vscode.tasks.executeTask(task); - } - } else { - let message = localize('noScriptFound', 'Could not find a script at the selection.'); - vscode.window.showErrorMessage(message); - } -} - export function deactivate(): void { - if (taskProvider) { - taskProvider.dispose(); - } } diff --git a/extensions/npm/src/npmView.ts b/extensions/npm/src/npmView.ts index 2bb290876a891..f737df23e1b56 100644 --- a/extensions/npm/src/npmView.ts +++ b/extensions/npm/src/npmView.ts @@ -6,14 +6,14 @@ import * as path from 'path'; import { - DebugConfiguration, Event, EventEmitter, ExtensionContext, Task, + Event, EventEmitter, ExtensionContext, Task, TextDocument, ThemeIcon, TreeDataProvider, TreeItem, TreeItemCollapsibleState, Uri, - WorkspaceFolder, commands, debug, window, workspace, tasks, Selection, TaskGroup + WorkspaceFolder, commands, window, workspace, tasks, Selection, TaskGroup } from 'vscode'; import { visit, JSONVisitor } from 'jsonc-parser'; import { NpmTaskDefinition, getPackageJsonUriFromTask, getScripts, - isWorkspaceFolder, getPackageManager, getTaskName, createTask + isWorkspaceFolder, getTaskName, createTask, extractDebugArgFromScript, startDebugging } from './tasks'; import * as nls from 'vscode-nls'; @@ -162,25 +162,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { } private extractDebugArg(scripts: any, task: Task): [string, number] | undefined { - let script: string = scripts[task.name]; - - // matches --debug, --debug=1234, --debug-brk, debug-brk=1234, --inspect, - // --inspect=1234, --inspect-brk, --inspect-brk=1234, - // --inspect=localhost:1245, --inspect=127.0.0.1:1234, --inspect=[aa:1:0:0:0]:1234, --inspect=:1234 - let match = script.match(/--(inspect|debug)(-brk)?(=((\[[0-9a-fA-F:]*\]|[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[a-zA-Z0-9\.]*):)?(\d+))?/); - - if (match) { - if (match[6]) { - return [match[1], parseInt(match[6])]; - } - if (match[1] === 'inspect') { - return [match[1], 9229]; - } - if (match[1] === 'debug') { - return [match[1], 5858]; - } - } - return undefined; + return extractDebugArgFromScript(scripts[task.name]); } private async debugScript(script: NpmScript) { @@ -193,7 +175,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { return; } - let debugArg = await this.extractDebugArg(scripts, task); + let debugArg = this.extractDebugArg(scripts, task); if (!debugArg) { let message = localize('noDebugOptions', 'Could not launch "{0}" for debugging because the scripts lacks a node debug option, e.g. "--inspect-brk".', task.name); let learnMore = localize('learnMore', 'Learn More'); @@ -204,29 +186,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { } return; } - - let protocol = 'inspector'; - if (debugArg[0] === 'debug') { - protocol = 'legacy'; - } - - let packageManager = getPackageManager(script.getFolder()); - const config: DebugConfiguration = { - type: 'node', - request: 'launch', - name: `Debug ${task.name}`, - runtimeExecutable: packageManager, - runtimeArgs: [ - 'run-script', - task.name, - ], - port: debugArg[1], - protocol: protocol - }; - - if (isWorkspaceFolder(task.scope)) { - debug.startDebugging(task.scope, config); - } + startDebugging(task.name, debugArg[0], debugArg[1], script.getFolder()); } private scriptNotValid(task: Task) { diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index 55d81c70500d7..5d19a4c4c0055 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -4,7 +4,10 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { TaskDefinition, Task, TaskGroup, WorkspaceFolder, RelativePattern, ShellExecution, Uri, workspace } from 'vscode'; +import { + TaskDefinition, Task, TaskGroup, WorkspaceFolder, RelativePattern, ShellExecution, Uri, workspace, + DebugConfiguration, debug, TaskProvider, ExtensionContext +} from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; import * as minimatch from 'minimatch'; @@ -22,6 +25,22 @@ type AutoDetect = 'on' | 'off'; let cachedTasks: Task[] | undefined = undefined; +export class NpmTaskProvider implements TaskProvider { + private extensionContext: ExtensionContext; + + constructor(context: ExtensionContext) { + this.extensionContext = context; + } + + public provideTasks() { + return provideNpmScripts(); + } + + public resolveTask(_task: Task): Task | undefined { + return undefined; + } +} + export function invalidateScriptsCache() { cachedTasks = undefined; } @@ -162,7 +181,7 @@ function isExcluded(folder: WorkspaceFolder, packageJsonUri: Uri) { } function isDebugScript(script: string): boolean { - let match = script.match(/--(inspect|debug)(-brk)?(=(\d*))?/); + let match = script.match(/--(inspect|debug)(-brk)?(=((\[[0-9a-fA-F:]*\]|[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[a-zA-Z0-9\.]*):)?(\d+))?/); return match !== null; } @@ -269,6 +288,52 @@ async function readFile(file: string): Promise { }); } +export function extractDebugArgFromScript(scriptValue: string): [string, number] | undefined { + // matches --debug, --debug=1234, --debug-brk, debug-brk=1234, --inspect, + // --inspect=1234, --inspect-brk, --inspect-brk=1234, + // --inspect=localhost:1245, --inspect=127.0.0.1:1234, --inspect=[aa:1:0:0:0]:1234, --inspect=:1234 + let match = scriptValue.match(/--(inspect|debug)(-brk)?(=((\[[0-9a-fA-F:]*\]|[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[a-zA-Z0-9\.]*):)?(\d+))?/); + + if (match) { + if (match[6]) { + return [match[1], parseInt(match[6])]; + } + if (match[1] === 'inspect') { + return [match[1], 9229]; + } + if (match[1] === 'debug') { + return [match[1], 5858]; + } + } + return undefined; +} + +export function startDebugging(scriptName: string, protocol: string, port: number, folder: WorkspaceFolder) { + let p = 'inspector'; + if (protocol === 'debug') { + p = 'legacy'; + } + + let packageManager = getPackageManager(folder); + const config: DebugConfiguration = { + type: 'node', + request: 'launch', + name: `Debug ${scriptName}`, + runtimeExecutable: packageManager, + runtimeArgs: [ + 'run-script', + scriptName, + ], + port: port, + protocol: p + }; + + if (folder) { + debug.startDebugging(folder, config); + } +} + + export type StringMap = { [s: string]: string; }; async function findAllScripts(buffer: string): Promise { @@ -278,7 +343,6 @@ async function findAllScripts(buffer: string): Promise { let visitor: JSONVisitor = { onError(_error: ParseErrorCode, _offset: number, _length: number) { - // TODO: inform user about the parse error }, onObjectEnd() { if (inScripts) { @@ -304,45 +368,39 @@ async function findAllScripts(buffer: string): Promise { return scripts; } -export function findScriptAtPosition(buffer: string, offset: number): string | undefined { +export function findAllScriptRanges(buffer: string): Map { + var scripts: Map = new Map(); let script: string | undefined = undefined; let inScripts = false; - let scriptStart: number | undefined; let visitor: JSONVisitor = { onError(_error: ParseErrorCode, _offset: number, _length: number) { - // TODO: inform user about the parse error }, onObjectEnd() { if (inScripts) { inScripts = false; - scriptStart = undefined; } }, - onLiteralValue(value: any, nodeOffset: number, nodeLength: number) { - if (inScripts && scriptStart) { - if (offset >= scriptStart && offset < nodeOffset + nodeLength) { - // found the script - inScripts = false; - } else { - script = undefined; - } + onLiteralValue(value: any, offset: number, length: number) { + if (script) { + scripts.set(script, [offset, length, value]); + script = undefined; } }, - onObjectProperty(property: string, nodeOffset: number, nodeLength: number) { + onObjectProperty(property: string, offset: number, length: number) { if (property === 'scripts') { inScripts = true; } else if (inScripts) { - scriptStart = nodeOffset; script = property; } } }; visit(buffer, visitor); - return script; + return scripts; } + export async function getScripts(packageJsonUri: Uri): Promise { if (packageJsonUri.scheme !== 'file') { diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index c113f11bebc47..54e6661cac443 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -364,14 +364,14 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider ] }; - let response: Proto.CompletionDetailsResponse; + let details: Proto.CompletionEntryDetails[] | undefined; try { - response = await this.client.execute('completionEntryDetails', args, token); + const response = await this.client.execute('completionEntryDetails', args, token); + details = response.body; } catch { return item; } - const details = response.body; if (!details || !details.length || !details[0]) { return item; } diff --git a/extensions/typescript-language-features/src/features/definitions.ts b/extensions/typescript-language-features/src/features/definitions.ts index 9ff840cdab482..99b9c36624768 100644 --- a/extensions/typescript-language-features/src/features/definitions.ts +++ b/extensions/typescript-language-features/src/features/definitions.ts @@ -17,12 +17,7 @@ export default class TypeScriptDefinitionProvider extends DefinitionProviderBase super(client); } - public async provideDefinition() { - // Implemented by provideDefinition2 - return undefined; - } - - public async provideDefinition2( + public async provideDefinition( document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken | boolean @@ -44,10 +39,11 @@ export default class TypeScriptDefinitionProvider extends DefinitionProviderBase const span = response.body.textSpan ? typeConverters.Range.fromTextSpan(response.body.textSpan) : undefined; return locations .map(location => { - const loc = typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location); - return { - origin: span, - ...loc, + const target = typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location); + return { + originSelectionRange: span, + targetRange: target.range, + targetUri: target.uri, }; }); } catch { diff --git a/extensions/typescript-language-features/src/features/documentHighlight.ts b/extensions/typescript-language-features/src/features/documentHighlight.ts index 0741aa4628b37..dd0fb76f735db 100644 --- a/extensions/typescript-language-features/src/features/documentHighlight.ts +++ b/extensions/typescript-language-features/src/features/documentHighlight.ts @@ -26,18 +26,21 @@ class TypeScriptDocumentHighlightProvider implements vscode.DocumentHighlightPro } const args = typeConverters.Position.toFileLocationRequestArgs(file, position); + let items: Proto.OccurrencesResponseItem[] | undefined; try { const response = await this.client.execute('occurrences', args, token); - if (response && response.body) { - return response.body - .filter(x => !x.isInString) - .map(documentHighlightFromOccurance); - } + items = response.body; } catch { // noop } - return []; + if (!items) { + return []; + } + + return items + .filter(x => !x.isInString) + .map(documentHighlightFromOccurance); } } diff --git a/extensions/typescript-language-features/src/features/documentSymbol.ts b/extensions/typescript-language-features/src/features/documentSymbol.ts index 84d108a4d23e0..84fc9117b9ac0 100644 --- a/extensions/typescript-language-features/src/features/documentSymbol.ts +++ b/extensions/typescript-language-features/src/features/documentSymbol.ts @@ -7,10 +7,8 @@ import * as vscode from 'vscode'; import * as Proto from '../protocol'; import * as PConst from '../protocol.const'; import { ITypeScriptServiceClient } from '../typescriptService'; -import API from '../utils/api'; import * as typeConverters from '../utils/typeConverters'; - const getSymbolKind = (kind: string): vscode.SymbolKind => { switch (kind) { case PConst.Kind.module: return vscode.SymbolKind.Module; @@ -33,60 +31,33 @@ const getSymbolKind = (kind: string): vscode.SymbolKind => { class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolProvider { public constructor( - private readonly client: ITypeScriptServiceClient) { } + private readonly client: ITypeScriptServiceClient + ) { } - public async provideDocumentSymbols(resource: vscode.TextDocument, token: vscode.CancellationToken): Promise { - const filepath = this.client.toPath(resource.uri); - if (!filepath) { - return []; + public async provideDocumentSymbols(resource: vscode.TextDocument, token: vscode.CancellationToken): Promise { + const file = this.client.toPath(resource.uri); + if (!file) { + return undefined; } - const args: Proto.FileRequestArgs = { - file: filepath - }; + + let tree: Proto.NavigationTree | undefined; try { - if (this.client.apiVersion.gte(API.v206)) { - const response = await this.client.execute('navtree', args, token); - if (response.body) { - // The root represents the file. Ignore this when showing in the UI - const tree = response.body; - if (tree.childItems) { - const result = new Array(); - tree.childItems.forEach(item => TypeScriptDocumentSymbolProvider.convertNavTree(resource.uri, result, item)); - return result; - } - } - } else { - const response = await this.client.execute('navbar', args, token); - if (response.body) { - const result = new Array(); - const foldingMap: ObjectMap = Object.create(null); - response.body.forEach(item => TypeScriptDocumentSymbolProvider.convertNavBar(resource.uri, 0, foldingMap, result as vscode.SymbolInformation[], item)); - return result; - } - } - return []; - } catch (e) { - return []; + const args: Proto.FileRequestArgs = { file }; + const response = await this.client.execute('navtree', args, token); + tree = response.body; + } catch { + return undefined; } - } - private static convertNavBar(resource: vscode.Uri, indent: number, foldingMap: ObjectMap, bucket: vscode.SymbolInformation[], item: Proto.NavigationBarItem, containerLabel?: string): void { - const realIndent = indent + item.indent; - const key = `${realIndent}|${item.text}`; - if (realIndent !== 0 && !foldingMap[key] && TypeScriptDocumentSymbolProvider.shouldInclueEntry(item)) { - const result = new vscode.SymbolInformation(item.text, - getSymbolKind(item.kind), - containerLabel ? containerLabel : '', - typeConverters.Location.fromTextSpan(resource, item.spans[0])); - foldingMap[key] = result; - bucket.push(result); - } - if (item.childItems && item.childItems.length > 0) { - for (const child of item.childItems) { - TypeScriptDocumentSymbolProvider.convertNavBar(resource, realIndent + 1, foldingMap, bucket, child, item.text); - } + if (tree && tree.childItems) { + // The root represents the file. Ignore this when showing in the UI + const result: vscode.DocumentSymbol[] = []; + tree.childItems.forEach(item => TypeScriptDocumentSymbolProvider.convertNavTree(resource.uri, result, item)); + return result; } + + return undefined; } private static convertNavTree(resource: vscode.Uri, bucket: vscode.DocumentSymbol[], item: Proto.NavigationTree): boolean { diff --git a/extensions/typescript-language-features/src/features/formatting.ts b/extensions/typescript-language-features/src/features/formatting.ts index 029db28f87055..89f68500c5b2c 100644 --- a/extensions/typescript-language-features/src/features/formatting.ts +++ b/extensions/typescript-language-features/src/features/formatting.ts @@ -10,53 +10,35 @@ import { ConfigurationDependentRegistration } from '../utils/dependentRegistrati import * as typeConverters from '../utils/typeConverters'; import FileConfigurationManager from './fileConfigurationManager'; - class TypeScriptFormattingProvider implements vscode.DocumentRangeFormattingEditProvider, vscode.OnTypeFormattingEditProvider { - private enabled: boolean = true; - public constructor( private readonly client: ITypeScriptServiceClient, private readonly formattingOptionsManager: FileConfigurationManager ) { } - public updateConfiguration(config: vscode.WorkspaceConfiguration): void { - this.enabled = config.get('format.enable', true); - } - - public isEnabled(): boolean { - return this.enabled; - } - - private async doFormat( + public async provideDocumentRangeFormattingEdits( document: vscode.TextDocument, + range: vscode.Range, options: vscode.FormattingOptions, - args: Proto.FormatRequestArgs, token: vscode.CancellationToken - ): Promise { + ): Promise { + const file = this.client.toPath(document.uri); + if (!file) { + return undefined; + } + await this.formattingOptionsManager.ensureConfigurationOptions(document, options, token); + + let edits: Proto.CodeEdit[] | undefined; try { + const args = typeConverters.Range.toFormattingRequestArgs(file, range); const response = await this.client.execute('format', args, token); - if (response.body) { - return response.body.map(typeConverters.TextEdit.fromCodeEdit); - } + edits = response.body; } catch { // noop } - return []; - } - public async provideDocumentRangeFormattingEdits( - document: vscode.TextDocument, - range: vscode.Range, - options: vscode.FormattingOptions, - token: vscode.CancellationToken - ): Promise { - const file = this.client.toPath(document.uri); - if (!file) { - return []; - } - const args = typeConverters.Range.toFormattingRequestArgs(file, range); - return this.doFormat(document, options, args, token); + return (edits || []).map(typeConverters.TextEdit.fromCodeEdit); } public async provideOnTypeFormattingEdits( @@ -66,17 +48,15 @@ class TypeScriptFormattingProvider implements vscode.DocumentRangeFormattingEdit options: vscode.FormattingOptions, token: vscode.CancellationToken ): Promise { - const filepath = this.client.toPath(document.uri); - if (!filepath) { + const file = this.client.toPath(document.uri); + if (!file) { return []; } await this.formattingOptionsManager.ensureConfigurationOptions(document, options, token); const args: Proto.FormatOnKeyRequestArgs = { - file: filepath, - line: position.line + 1, - offset: position.character + 1, + ...typeConverters.Position.toFileLocationRequestArgs(file, position), key: ch }; try { diff --git a/extensions/typescript-language-features/src/features/refactor.ts b/extensions/typescript-language-features/src/features/refactor.ts index c471b10f8ff9f..fbb3a3b88ef13 100644 --- a/extensions/typescript-language-features/src/features/refactor.ts +++ b/extensions/typescript-language-features/src/features/refactor.ts @@ -140,17 +140,18 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider { await this.formattingOptionsManager.ensureConfigurationForDocument(document, undefined); const args: Proto.GetApplicableRefactorsRequestArgs = typeConverters.Range.toFileRangeRequestArgs(file, rangeOrSelection); - let response: Proto.GetApplicableRefactorsResponse; + let refactorings: Proto.ApplicableRefactorInfo[]; try { - response = await this.client.execute('getApplicableRefactors', args, token); - if (!response || !response.body) { + const response = await this.client.execute('getApplicableRefactors', args, token); + if (!response.body) { return undefined; } + refactorings = response.body; } catch { return undefined; } - return this.convertApplicableRefactors(response.body, document, file, rangeOrSelection); + return this.convertApplicableRefactors(refactorings, document, file, rangeOrSelection); } private convertApplicableRefactors( diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 71c6c005d65de..110f41d26589a 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -60,7 +60,6 @@ export interface ITypeScriptServiceClient { execute(command: 'typeDefinition', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise; execute(command: 'references', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise; execute(command: 'navto', args: Proto.NavtoRequestArgs, token?: CancellationToken): Promise; - execute(command: 'navbar', args: Proto.FileRequestArgs, token?: CancellationToken): Promise; execute(command: 'format', args: Proto.FormatRequestArgs, token?: CancellationToken): Promise; execute(command: 'formatonkey', args: Proto.FormatOnKeyRequestArgs, token?: CancellationToken): Promise; execute(command: 'rename', args: Proto.RenameRequestArgs, token?: CancellationToken): Promise; diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index c5666b895dc7b..c5061316b7ace 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -974,7 +974,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient } if (this.apiVersion.gte(API.v222)) { - this.cancellationPipeName = electron.getTempSock('tscancellation'); + this.cancellationPipeName = electron.getTempFile('tscancellation'); args.push('--cancellationPipeName', this.cancellationPipeName + '*'); } diff --git a/extensions/typescript-language-features/src/utils/electron.ts b/extensions/typescript-language-features/src/utils/electron.ts index 34a6534f8ec9d..46129f60f0e53 100644 --- a/extensions/typescript-language-features/src/utils/electron.ts +++ b/extensions/typescript-language-features/src/utils/electron.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import Logger from './logger'; -import { getTempFile, makeRandomHexString } from './temp'; +import * as temp from './temp'; import path = require('path'); -import os = require('os'); +import fs = require('fs'); import net = require('net'); import cp = require('child_process'); @@ -15,13 +15,25 @@ export interface IForkOptions { execArgv?: string[]; } -export function getTempSock(prefix: string): string { - const fullName = `vscode-${prefix}-${makeRandomHexString(20)}`; - return getTempFile(fullName + '.sock'); +const getRootTempDir = (() => { + let dir: string | undefined; + return () => { + if (!dir) { + dir = temp.getTempFile(`vscode-typescript`); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } + } + return dir; + }; +})(); + +export function getTempFile(prefix: string): string { + return path.join(getRootTempDir(), `${prefix}-${temp.makeRandomHexString(20)}.tmp`); } function generatePipeName(): string { - return getPipeName(makeRandomHexString(40)); + return getPipeName(temp.makeRandomHexString(40)); } function getPipeName(name: string): string { @@ -31,7 +43,7 @@ function getPipeName(name: string): string { } // Mac/Unix: use socket file - return path.join(os.tmpdir(), fullName + '.sock'); + return path.join(getRootTempDir(), fullName + '.sock'); } function generatePatchedEnv( diff --git a/gulpfile.js b/gulpfile.js index db6d924ae734a..ebdca25bcb4a0 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -15,12 +15,12 @@ const compilation = require('./build/lib/compilation'); // Fast compile for development time gulp.task('clean-client', util.rimraf('out')); -gulp.task('compile-client', ['clean-client'], compilation.compileTask('out', false)); +gulp.task('compile-client', ['clean-client'], compilation.compileTask('src', 'out', false)); gulp.task('watch-client', ['clean-client'], compilation.watchTask('out', false)); // Full compile, including nls and inline sources in sourcemaps, for build gulp.task('clean-client-build', util.rimraf('out-build')); -gulp.task('compile-client-build', ['clean-client-build'], compilation.compileTask('out-build', true)); +gulp.task('compile-client-build', ['clean-client-build'], compilation.compileTask('src', 'out-build', true)); gulp.task('watch-client-build', ['clean-client-build'], compilation.watchTask('out-build', true)); // Default diff --git a/src/bootstrap-amd.js b/src/bootstrap-amd.js index c8e5800ab5bb6..752d996f6b97c 100644 --- a/src/bootstrap-amd.js +++ b/src/bootstrap-amd.js @@ -69,7 +69,10 @@ loader.config({ nodeCachedDataDir: process.env['VSCODE_NODE_CACHED_DATA_DIR_' + process.pid] }); -loader.define('fs', ['original-fs'], function (originalFS) { return originalFS; }); // replace the patched electron fs with the original node fs for all AMD code +if (process.env['ELECTRON_RUN_AS_NODE'] || process.versions.electron) { + // running in Electron + loader.define('fs', ['original-fs'], function (originalFS) { return originalFS; }); // replace the patched electron fs with the original node fs for all AMD code +} if (nlsConfig.pseudo) { loader(['vs/nls'], function (nlsPlugin) { diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index e78bc4bb97a5f..0966ceca01063 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -114,13 +114,13 @@ export class BreadcrumbsWidget { } layout(dim: dom.Dimension): void { - if (!dim) { - this._scrollable.scanDomNode(); - } else { + if (dim) { this._domNode.style.width = `${dim.width}px`; this._domNode.style.height = `${dim.height}px`; - this._scrollable.scanDomNode(); } + this._scrollable.setRevealOnScroll(false); + this._scrollable.scanDomNode(); + this._scrollable.setRevealOnScroll(true); } style(style: IBreadcrumbsWidgetStyles): void { @@ -206,7 +206,9 @@ export class BreadcrumbsWidget { private _reveal(nth: number): void { const node = this._nodes[nth]; if (node) { + this._scrollable.setRevealOnScroll(false); this._scrollable.setScrollPosition({ scrollLeft: node.offsetLeft }); + this._scrollable.setRevealOnScroll(true); } } diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 8d62877e57d6e..cb972251f771a 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -22,6 +22,7 @@ export interface IIconLabelCreationOptions { export interface IIconLabelValueOptions { title?: string; descriptionTitle?: string; + hideIcon?: boolean; extraClasses?: string[]; italic?: boolean; matches?: IMatch[]; diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index f8ffd1a0feb93..efaf1d52986f5 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./list'; +import { localize } from 'vs/nls'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { isNumber } from 'vs/base/common/types'; import { range, firstIndex } from 'vs/base/common/arrays'; @@ -355,6 +356,11 @@ class DOMFocusController implements IDisposable { return; } + const style = window.getComputedStyle(tabIndexElement); + if (style.visibility === 'hidden' || style.display === 'none') { + return; + } + e.preventDefault(); e.stopPropagation(); tabIndexElement.focus(); @@ -934,7 +940,7 @@ export class List implements ISpliceable, IDisposable { this.onSelectionChange(this._onSelectionChange, this, this.disposables); if (options.ariaLabel) { - this.view.domNode.setAttribute('aria-label', options.ariaLabel); + this.view.domNode.setAttribute('aria-label', localize('aria list', "{0}. Use the navigation keys to navigate.", options.ariaLabel)); } this.style(options); diff --git a/src/vs/base/browser/ui/menu/menu.css b/src/vs/base/browser/ui/menu/menu.css index 80e83e1c4a078..76f2599c526cf 100644 --- a/src/vs/base/browser/ui/menu/menu.css +++ b/src/vs/base/browser/ui/menu/menu.css @@ -40,15 +40,18 @@ flex: 1 1 auto; display: -ms-flexbox; display: flex; + height: 2.6em; + align-items: center; } .monaco-menu .monaco-action-bar.vertical .action-label { -ms-flex: 1 1 auto; flex: 1 1 auto; text-decoration: none; - padding: 0.8em 1em; - line-height: 1.1em; + padding: 0 1em; background: none; + font-size: inherit; + line-height: 1; } .monaco-menu .monaco-action-bar.vertical .keybinding, @@ -56,15 +59,12 @@ display: inline-block; -ms-flex: 2 1 auto; flex: 2 1 auto; - padding: 0.8em 1em; - line-height: 1.1em; - font-size: 12px; + padding: 0 1em; text-align: right; + font-size: inherit; + line-height: 1; } -.monaco-menu .monaco-action-bar.vertical .submenu-indicator { - padding: 0.8em .5em; -} .monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding, .monaco-menu .monaco-action-bar.vertical .action-item.disabled .submenu-indicator { diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 5aafe0bd8a064..3d2f82a5cc7eb 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -163,6 +163,8 @@ export abstract class AbstractScrollableElement extends Widget { private readonly _hideTimeout: TimeoutTimer; private _shouldRender: boolean; + private _revealOnScroll: boolean; + private readonly _onScroll = this._register(new Emitter()); public readonly onScroll: Event = this._onScroll.event; @@ -221,6 +223,8 @@ export abstract class AbstractScrollableElement extends Widget { this._mouseIsOver = false; this._shouldRender = true; + + this._revealOnScroll = true; } public dispose(): void { @@ -286,6 +290,10 @@ export abstract class AbstractScrollableElement extends Widget { } } + public setRevealOnScroll(value: boolean) { + this._revealOnScroll = value; + } + // -------------------- mouse wheel scrolling -------------------- private _setListeningToMouseWheel(shouldListen: boolean): void { @@ -382,7 +390,9 @@ export abstract class AbstractScrollableElement extends Widget { this._shouldRender = true; } - this._reveal(); + if (this._revealOnScroll) { + this._reveal(); + } if (!this._options.lazyRender) { this._render(); diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index a28e7ab81b5f1..e82503e1eb087 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -12,6 +12,8 @@ import { IMessagePassingProtocol, ClientConnectionEvent, IPCServer, IPCClient } import { join } from 'path'; import { tmpdir } from 'os'; import { generateUuid } from 'vs/base/common/uuid'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { TimeoutTimer } from 'vs/base/common/async'; export function generateRandomPipeName(): string { const randomSuffix = generateUuid(); @@ -23,17 +25,28 @@ export function generateRandomPipeName(): string { } } -export class Protocol implements IMessagePassingProtocol { +export class Protocol implements IDisposable, IMessagePassingProtocol { private static readonly _headerLen = 5; - private _onMessage = new Emitter(); + private _isDisposed: boolean; + private _chunks: Buffer[]; + + private _firstChunkTimer: TimeoutTimer; + private _socketDataListener: (data: Buffer) => void; + private _socketEndListener: () => void; + private _socketCloseListener: () => void; + private _onMessage = new Emitter(); readonly onMessage: Event = this._onMessage.event; + private _onClose = new Emitter(); + readonly onClose: Event = this._onClose.event; + constructor(private _socket: Socket, firstDataChunk?: Buffer) { + this._isDisposed = false; + this._chunks = []; - let chunks: Buffer[] = []; let totalLength = 0; const state = { @@ -44,7 +57,7 @@ export class Protocol implements IMessagePassingProtocol { const acceptChunk = (data: Buffer) => { - chunks.push(data); + this._chunks.push(data); totalLength += data.length; while (totalLength > 0) { @@ -53,7 +66,7 @@ export class Protocol implements IMessagePassingProtocol { // expecting header -> read 5bytes for header // information: `bodyIsJson` and `bodyLen` if (totalLength >= Protocol._headerLen) { - const all = Buffer.concat(chunks); + const all = Buffer.concat(this._chunks); state.bodyIsJson = all.readInt8(0) === 1; state.bodyLen = all.readInt32BE(1); @@ -61,7 +74,7 @@ export class Protocol implements IMessagePassingProtocol { const rest = all.slice(Protocol._headerLen); totalLength = rest.length; - chunks = [rest]; + this._chunks = [rest]; } else { break; @@ -73,21 +86,27 @@ export class Protocol implements IMessagePassingProtocol { // the actual message or wait for more data if (totalLength >= state.bodyLen) { - const all = Buffer.concat(chunks); + const all = Buffer.concat(this._chunks); let message = all.toString('utf8', 0, state.bodyLen); if (state.bodyIsJson) { message = JSON.parse(message); } - this._onMessage.fire(message); + // ensure the public getBuffer returns a valid value if invoked from the event listeners const rest = all.slice(state.bodyLen); totalLength = rest.length; - chunks = [rest]; + this._chunks = [rest]; state.bodyIsJson = false; state.bodyLen = -1; state.readHead = true; + this._onMessage.fire(message); + + if (this._isDisposed) { + // check if an event listener lead to our disposal + break; + } } else { break; } @@ -103,14 +122,43 @@ export class Protocol implements IMessagePassingProtocol { } }; - _socket.on('data', (data: Buffer) => { + // Make sure to always handle the firstDataChunk if no more `data` event comes in + this._firstChunkTimer = new TimeoutTimer(); + this._firstChunkTimer.setIfNotSet(() => { + acceptFirstDataChunk(); + }, 0); + + this._socketDataListener = (data: Buffer) => { acceptFirstDataChunk(); acceptChunk(data); - }); + }; + _socket.on('data', this._socketDataListener); - _socket.on('end', () => { + this._socketEndListener = () => { acceptFirstDataChunk(); - }); + }; + _socket.on('end', this._socketEndListener); + + this._socketCloseListener = () => { + this._onClose.fire(); + }; + _socket.once('close', this._socketCloseListener); + } + + public dispose(): void { + this._isDisposed = true; + this._firstChunkTimer.dispose(); + this._socket.removeListener('data', this._socketDataListener); + this._socket.removeListener('end', this._socketEndListener); + this._socket.removeListener('close', this._socketCloseListener); + } + + public end(): void { + this._socket.end(); + } + + public getBuffer(): Buffer { + return Buffer.concat(this._chunks); } public send(message: any): void { @@ -193,18 +241,19 @@ export class Server extends IPCServer { export class Client extends IPCClient { - private _onClose = new Emitter(); - get onClose(): Event { return this._onClose.event; } + public static fromSocket(socket: Socket, id: string): Client { + return new Client(new Protocol(socket), id); + } + + get onClose(): Event { return this.protocol.onClose; } - constructor(private socket: Socket, id: string) { - super(new Protocol(socket), id); - socket.once('close', () => this._onClose.fire()); + constructor(private protocol: Protocol, id: string) { + super(protocol, id); } dispose(): void { super.dispose(); - this.socket.end(); - this.socket = null; + this.protocol.end(); } } @@ -229,7 +278,7 @@ export function connect(hook: any, clientId: string): TPromise { return new TPromise((c, e) => { const socket = createConnection(hook, () => { socket.removeListener('error', e); - c(new Client(socket, clientId)); + c(Client.fromSocket(socket, clientId)); }); socket.once('error', e); diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index 6b48161d02615..eb6ddffba025d 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -87,4 +87,39 @@ suite('IPC, Socket Protocol', () => { }); }); }); + + test('can devolve to a socket and evolve again without losing data', () => { + let resolve: (v: void) => void; + let result = new TPromise((_resolve, _reject) => { + resolve = _resolve; + }); + const sender = new Protocol(stream); + const receiver1 = new Protocol(stream); + + assert.equal(stream.listenerCount('data'), 2); + assert.equal(stream.listenerCount('end'), 2); + + receiver1.onMessage((msg) => { + assert.equal(msg.value, 1); + + let buffer = receiver1.getBuffer(); + receiver1.dispose(); + + assert.equal(stream.listenerCount('data'), 1); + assert.equal(stream.listenerCount('end'), 1); + + const receiver2 = new Protocol(stream, buffer); + receiver2.onMessage((msg) => { + assert.equal(msg.value, 2); + resolve(void 0); + }); + }); + + const msg1 = { value: 1 }; + const msg2 = { value: 2 }; + sender.send(msg1); + sender.send(msg2); + + return result; + }); }); diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts index 7482829660555..d19657a3f925f 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts @@ -501,7 +501,8 @@ export class QuickOpenModel implements IModel, IDataSource, IFilter, - IRunner + IRunner, + IAccessiblityProvider { private _entries: QuickOpenEntry[]; private _dataSource: IDataSource; diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts index cf6dc5ab8ff3f..5d56011feacac 100644 --- a/src/vs/base/parts/tree/browser/treeView.ts +++ b/src/vs/base/parts/tree/browser/treeView.ts @@ -220,7 +220,7 @@ export class ViewItem implements IViewItem { this.element.removeAttribute('id'); } if (this.model.hasChildren()) { - this.element.setAttribute('aria-expanded', String(!!this.model.isExpanded())); + this.element.setAttribute('aria-expanded', String(!!this._styles['expanded'])); } else { this.element.removeAttribute('aria-expanded'); } diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts index ada86e61b63a8..ececd9b461c14 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -54,3 +54,7 @@ export function testRepeat(n: number, description: string, callback: (this: any, test(`${description} (iteration ${i})`, callback); } } + +export function testRepeatOnly(n: number, description: string, callback: (this: any, done: MochaDone) => any): void { + suite.only('repeat', () => testRepeat(n, description, callback)); +} diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index cc305121e2cb4..a642157cdcafc 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -86,7 +86,7 @@ export class IssueReporter extends Disposable { vscodeVersion: `${pkg.name} ${pkg.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`, os: `${os.type()} ${os.arch()} ${os.release()}` }, - extensionsDisabled: this.environmentService.disableExtensions, + extensionsDisabled: !!this.environmentService.disableExtensions, }); this.previewButton = new Button(document.getElementById('issue-reporter')); diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index 998479e62471e..4c5a22871d81c 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -766,6 +766,7 @@ export class CodeMenu { const toggleMinimap = this.createMenuItem(nls.localize({ key: 'miToggleMinimap', comment: ['&& denotes a mnemonic'] }, "Toggle &&Minimap"), 'editor.action.toggleMinimap'); const toggleRenderWhitespace = this.createMenuItem(nls.localize({ key: 'miToggleRenderWhitespace', comment: ['&& denotes a mnemonic'] }, "Toggle &&Render Whitespace"), 'editor.action.toggleRenderWhitespace'); const toggleRenderControlCharacters = this.createMenuItem(nls.localize({ key: 'miToggleRenderControlCharacters', comment: ['&& denotes a mnemonic'] }, "Toggle &&Control Characters"), 'editor.action.toggleRenderControlCharacter'); + const toggleBreadcrumbs = this.createMenuItem(nls.localize({ key: 'miToggleBreadcrumbs', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breadcrumbs"), 'breadcrumbs.toggle'); arrays.coalesce([ commands, @@ -788,7 +789,8 @@ export class CodeMenu { toggleWordWrap, toggleMinimap, toggleRenderWhitespace, - toggleRenderControlCharacters + toggleRenderControlCharacters, + toggleBreadcrumbs ]).forEach(item => viewMenu.append(item)); } diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 1303639321131..a116107708204 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -34,7 +34,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Schemas } from 'vs/base/common/network'; import { normalizeNFC } from 'vs/base/common/normalization'; -import URI from 'vs/base/common/uri'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { Queue } from 'vs/base/common/async'; import { exists } from 'vs/base/node/pfs'; @@ -54,6 +54,11 @@ interface IWindowState { uiState: ISingleWindowState; } +interface IBackwardCompatibleWindowState extends IWindowState { + folderUri?: UriComponents; +} + + interface IWindowsState { lastActiveWindow?: IWindowState; lastPluginDevelopmentHostWindow?: IWindowState; @@ -144,7 +149,7 @@ export class WindowsManager implements IWindowsMainService { @IWorkspacesMainService private workspacesMainService: IWorkspacesMainService, @IInstantiationService private instantiationService: IInstantiationService ) { - this.windowsState = this.stateService.getItem(WindowsManager.windowsStateStorageKey) || { openedWindows: [] }; + this.windowsState = this.getWindowsState(); if (!Array.isArray(this.windowsState.openedWindows)) { this.windowsState.openedWindows = []; } @@ -153,6 +158,32 @@ export class WindowsManager implements IWindowsMainService { this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, environmentService, this); } + private getWindowsState(): IWindowsState { + const windowsState = this.stateService.getItem(WindowsManager.windowsStateStorageKey) || { openedWindows: [] }; + if (windowsState.lastActiveWindow) { + windowsState.lastActiveWindow = this.revive(windowsState.lastActiveWindow); + } + if (windowsState.lastPluginDevelopmentHostWindow) { + windowsState.lastPluginDevelopmentHostWindow = this.revive(windowsState.lastPluginDevelopmentHostWindow); + } + if (windowsState.openedWindows) { + windowsState.openedWindows = arrays.coalesce(windowsState.openedWindows.map(windowState => this.revive(windowState))); + } + return windowsState; + } + + private revive(windowState: IWindowState): IWindowState { + if ((windowState).folderUri) { + const uri = URI.revive((windowState).folderUri); + if (uri.scheme === Schemas.file) { + windowState.folderPath = uri.fsPath; + } else { + return null; + } + } + return windowState; + } + ready(initialUserEnv: IProcessEnvironment): void { this.initialUserEnv = initialUserEnv; diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 6d0718d94db5c..fee65bb4dc381 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1356,22 +1356,22 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE if (this.isSimpleWidget) { commandDelegate = { paste: (source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[]) => { - this.cursor.trigger(source, editorCommon.Handler.Paste, { text, pasteOnNewLine, multicursorText }); + this.trigger(source, editorCommon.Handler.Paste, { text, pasteOnNewLine, multicursorText }); }, type: (source: string, text: string) => { - this.cursor.trigger(source, editorCommon.Handler.Type, { text }); + this.trigger(source, editorCommon.Handler.Type, { text }); }, replacePreviousChar: (source: string, text: string, replaceCharCnt: number) => { - this.cursor.trigger(source, editorCommon.Handler.ReplacePreviousChar, { text, replaceCharCnt }); + this.trigger(source, editorCommon.Handler.ReplacePreviousChar, { text, replaceCharCnt }); }, compositionStart: (source: string) => { - this.cursor.trigger(source, editorCommon.Handler.CompositionStart, undefined); + this.trigger(source, editorCommon.Handler.CompositionStart, undefined); }, compositionEnd: (source: string) => { - this.cursor.trigger(source, editorCommon.Handler.CompositionEnd, undefined); + this.trigger(source, editorCommon.Handler.CompositionEnd, undefined); }, cut: (source: string) => { - this.cursor.trigger(source, editorCommon.Handler.Cut, undefined); + this.trigger(source, editorCommon.Handler.Cut, undefined); } }; } else { diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index a3f19a76245b4..dbd07dd8a74a2 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -1177,7 +1177,7 @@ interface IDataSource { getModifiedEditor(): editorBrowser.ICodeEditor; } -abstract class DiffEditorWidgetStyle extends Disposable { +abstract class DiffEditorWidgetStyle extends Disposable implements IDiffEditorWidgetStyle { _dataSource: IDataSource; _insertColor: Color; @@ -1228,6 +1228,9 @@ abstract class DiffEditorWidgetStyle extends Disposable { protected abstract _getViewZones(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor, renderIndicators: boolean): IEditorsZones; protected abstract _getOriginalEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations; protected abstract _getModifiedEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations; + + public abstract setEnableSplitViewResizing(enableSplitViewResizing: boolean): void; + public abstract layout(): number; } interface IMyViewZone extends editorBrowser.IViewZone { @@ -1529,10 +1532,6 @@ class DiffEdtorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEd this._sash.onDidReset(() => this.onSashReset()); } - public dispose(): void { - super.dispose(); - } - public setEnableSplitViewResizing(enableSplitViewResizing: boolean): void { let newDisableSash = (enableSplitViewResizing === false); if (this._disableSash !== newDisableSash) { @@ -1778,10 +1777,6 @@ class DiffEdtorWidgetInline extends DiffEditorWidgetStyle implements IDiffEditor })); } - public dispose(): void { - super.dispose(); - } - public setEnableSplitViewResizing(enableSplitViewResizing: boolean): void { // Nothing to do.. } diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 556665f4a6e21..1069bff17dd49 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -361,17 +361,17 @@ const editorConfiguration: IConfigurationNode = { 'editor.find.seedSearchStringFromSelection': { 'type': 'boolean', 'default': EDITOR_DEFAULTS.contribInfo.find.seedSearchStringFromSelection, - 'description': nls.localize('find.seedSearchStringFromSelection', "Controls if we seed the search string in Find Widget from editor selection") + 'description': nls.localize('find.seedSearchStringFromSelection', "Controls if we seed the search string in Find Widget from editor selection.") }, 'editor.find.autoFindInSelection': { 'type': 'boolean', 'default': EDITOR_DEFAULTS.contribInfo.find.autoFindInSelection, - 'description': nls.localize('find.autoFindInSelection', "Controls if Find in Selection flag is turned on when multiple characters or lines of text are selected in the editor") + 'description': nls.localize('find.autoFindInSelection', "Controls if the Find in Selection flag is turned on when multiple characters or lines of text are selected in the editor.") }, 'editor.find.globalFindClipboard': { 'type': 'boolean', 'default': EDITOR_DEFAULTS.contribInfo.find.globalFindClipboard, - 'description': nls.localize('find.globalFindClipboard', "Controls if the Find Widget should read or modify the shared find clipboard on macOS"), + 'description': nls.localize('find.globalFindClipboard', "Controls if the Find Widget should read or modify the shared find clipboard on macOS."), 'included': platform.isMacintosh }, 'editor.wordWrap': { @@ -401,7 +401,7 @@ const editorConfiguration: IConfigurationNode = { '- \'off\', \'on\', \'wordWrapColumn\' and \'bounded\' refer to values the setting can take and should not be localized.', '- `editor.wordWrapColumn` refers to a different setting and should not be localized.' ] - }, "Controls how lines should wrap. Can be:\n - 'off' (disable wrapping),\n - 'on' (viewport wrapping),\n - 'wordWrapColumn' (wrap at `editor.wordWrapColumn`) or\n - 'bounded' (wrap at minimum of viewport and `editor.wordWrapColumn`).") + }, "Controls how lines should wrap.") }, 'editor.wordWrapColumn': { 'type': 'integer', @@ -413,7 +413,7 @@ const editorConfiguration: IConfigurationNode = { '- `editor.wordWrap` refers to a different setting and should not be localized.', '- \'wordWrapColumn\' and \'bounded\' refer to values the different setting can take and should not be localized.' ] - }, "Controls the wrapping column of the editor when `editor.wordWrap` is 'wordWrapColumn' or 'bounded'.") + }, "Controls the wrapping column of the editor when [`editor.wordWrap`](#editor.wordWrap) is `wordWrapColumn` or `bounded`.") }, 'editor.wrappingIndent': { 'type': 'string', @@ -440,7 +440,7 @@ const editorConfiguration: IConfigurationNode = { '- `ctrlCmd` refers to a value the setting can take and should not be localized.', '- `Control` and `Command` refer to the modifier keys Ctrl or Cmd on the keyboard and can be localized.' ] - }, "The modifier to be used to add multiple cursors with the mouse. `ctrlCmd` maps to `Control` on Windows and Linux and to `Command` on macOS. The Go To Definition and Open Link mouse gestures will adapt such that they do not conflict with the multicursor modifier. [Read more](https://code.visualstudio.com/docs/editor/codebasics#_multicursor-modifier)") + }, "The modifier to be used to add multiple cursors with the mouse. The Go To Definition and Open Link mouse gestures will adapt such that they do not conflict with the multicursor modifier. [Read more](https://code.visualstudio.com/docs/editor/codebasics#_multicursor-modifier).") }, 'editor.multiCursorMergeOverlapping': { 'type': 'boolean', @@ -495,7 +495,7 @@ const editorConfiguration: IConfigurationNode = { 'editor.formatOnType': { 'type': 'boolean', 'default': EDITOR_DEFAULTS.contribInfo.formatOnType, - 'description': nls.localize('formatOnType', "Controls if the editor should automatically format the line after typing") + 'description': nls.localize('formatOnType', "Controls if the editor should automatically format the line after typing.") }, 'editor.formatOnPaste': { 'type': 'boolean', @@ -613,17 +613,17 @@ const editorConfiguration: IConfigurationNode = { 'type': 'string', 'enum': ['block', 'block-outline', 'line', 'line-thin', 'underline', 'underline-thin'], 'default': editorOptions.cursorStyleToString(EDITOR_DEFAULTS.viewInfo.cursorStyle), - 'description': nls.localize('cursorStyle', "Controls the cursor style, accepted values are 'block', 'block-outline', 'line', 'line-thin', 'underline' and 'underline-thin'") + 'description': nls.localize('cursorStyle', "Controls the cursor style.") }, 'editor.cursorWidth': { 'type': 'integer', 'default': EDITOR_DEFAULTS.viewInfo.cursorWidth, - 'description': nls.localize('cursorWidth', "Controls the width of the cursor when editor.cursorStyle is set to 'line'") + 'description': nls.localize('cursorWidth', "Controls the width of the cursor when [`editor.cursorStyle`](#editor.cursorStyle) is set to `line`.") }, 'editor.fontLigatures': { 'type': 'boolean', 'default': EDITOR_DEFAULTS.viewInfo.fontLigatures, - 'description': nls.localize('fontLigatures', "Enables font ligatures") + 'description': nls.localize('fontLigatures', "Enables font ligatures.") }, 'editor.hideCursorInOverviewRuler': { 'type': 'boolean', @@ -633,8 +633,13 @@ const editorConfiguration: IConfigurationNode = { 'editor.renderWhitespace': { 'type': 'string', 'enum': ['none', 'boundary', 'all'], + 'enumDescriptions': [ + '', + nls.localize('renderWhiteSpace.boundary', "Render whitespace characters except for single spaces between words."), + '' + ], default: EDITOR_DEFAULTS.viewInfo.renderWhitespace, - description: nls.localize('renderWhitespace', "Controls how the editor should render whitespace characters, possibilities are 'none', 'boundary', and 'all'. The 'boundary' option does not render single spaces between words.") + description: nls.localize('renderWhitespace', "Controls how the editor should render whitespace characters.") }, 'editor.renderControlCharacters': { 'type': 'boolean', diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 6b8d3fb2aff1a..02c236a53c29c 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -554,7 +554,7 @@ export interface DefinitionProvider { /** * Provide the definition of the symbol at the given position and document. */ - provideDefinition(model: model.ITextModel, position: Position, token: CancellationToken): DefinitionLink | Thenable; + provideDefinition(model: model.ITextModel, position: Position, token: CancellationToken): Definition | DefinitionLink[] | Thenable; } /** @@ -565,7 +565,7 @@ export interface ImplementationProvider { /** * Provide the implementation of the symbol at the given position and document. */ - provideImplementation(model: model.ITextModel, position: Position, token: CancellationToken): Definition | Thenable; + provideImplementation(model: model.ITextModel, position: Position, token: CancellationToken): Definition | DefinitionLink[] | Thenable; } /** @@ -576,7 +576,7 @@ export interface TypeDefinitionProvider { /** * Provide the type definition of the symbol at the given position and document. */ - provideTypeDefinition(model: model.ITextModel, position: Position, token: CancellationToken): Definition | Thenable; + provideTypeDefinition(model: model.ITextModel, position: Position, token: CancellationToken): Definition | DefinitionLink[] | Thenable; } /** @@ -646,7 +646,7 @@ export const symbolKindToCssClass = (function () { _fromMapping[SymbolKind.TypeParameter] = 'type-parameter'; return function toCssClassName(kind: SymbolKind): string { - return _fromMapping[kind] || 'property'; + return `symbol-icon ${_fromMapping[kind] || 'property'}`; }; })(); diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 5e5f192fb1a06..d478fef73c6b9 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -16,7 +16,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { Position, IPosition } from 'vs/editor/common/core/position'; import { MirrorTextModel as BaseMirrorModel, IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; import { IInplaceReplaceSupportResult, ILink, ISuggestResult, ISuggestion, TextEdit } from 'vs/editor/common/modes'; -import { computeLinks } from 'vs/editor/common/modes/linkComputer'; +import { computeLinks, ILinkComputerTarget } from 'vs/editor/common/modes/linkComputer'; import { BasicInplaceReplace } from 'vs/editor/common/modes/supports/inplaceReplaceSupport'; import { getWordAtText, ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper'; import { createMonacoBaseAPI } from 'vs/editor/common/standalone/standaloneBase'; @@ -50,7 +50,7 @@ export interface IRawModelData { /** * @internal */ -export interface ICommonModel { +export interface ICommonModel extends ILinkComputerTarget, IMirrorModel { uri: URI; version: number; eol: string; diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 1d3e5175ff2f6..538702921260c 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -197,7 +197,7 @@ export class QuickFixAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available")); } } @@ -250,7 +250,7 @@ export class CodeActionCommand extends EditorCommand { }); } - public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, userArg: any) { + public runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any) { const args = CodeActionCommandArgs.fromUser(userArg); return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"), { kind: args.kind, includeSourceActions: true }, args.apply); } @@ -284,7 +284,7 @@ export class RefactorAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.refactor.noneMessage', "No refactorings available"), { kind: CodeActionKind.Refactor }, @@ -313,7 +313,7 @@ export class SourceAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.source.noneMessage', "No source actions available"), { kind: CodeActionKind.Source, includeSourceActions: true }, @@ -340,7 +340,7 @@ export class OrganizeImportsAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.organize.noneMessage', "No organize imports action available"), { kind: CodeActionKind.SourceOrganizeImports, includeSourceActions: true }, diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index e1181ea33cdca..72f66dd91e242 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -162,7 +162,7 @@ export class OutlineRenderer implements IRenderer { renderElement(tree: ITree, element: OutlineGroup | OutlineElement, templateId: string, template: OutlineTemplate): void { if (element instanceof OutlineElement) { - template.icon.className = `outline-element-icon symbol-icon ${symbolKindToCssClass(element.symbol.kind)}`; + template.icon.className = `outline-element-icon ${symbolKindToCssClass(element.symbol.kind)}`; template.label.set(element.symbol.name, element.score ? createMatches(element.score[1]) : undefined, localize('title.template', "{0} ({1})", element.symbol.name, OutlineRenderer._symbolKindNames[element.symbol.kind])); template.detail.innerText = element.symbol.detail || ''; this._renderMarkerInfo(element, template); diff --git a/src/vs/editor/contrib/goToDefinition/clickLinkGesture.ts b/src/vs/editor/contrib/goToDefinition/clickLinkGesture.ts index f8157b3bf0c30..8bc694f358a2d 100644 --- a/src/vs/editor/contrib/goToDefinition/clickLinkGesture.ts +++ b/src/vs/editor/contrib/goToDefinition/clickLinkGesture.ts @@ -52,19 +52,20 @@ export class ClickLinkKeyboardEvent { this.hasTriggerModifier = hasModifier(source, opts.triggerModifier); } } +export type TriggerModifier = 'ctrlKey' | 'shiftKey' | 'altKey' | 'metaKey'; export class ClickLinkOptions { public readonly triggerKey: KeyCode; - public readonly triggerModifier: 'ctrlKey' | 'shiftKey' | 'altKey' | 'metaKey'; + public readonly triggerModifier: TriggerModifier; public readonly triggerSideBySideKey: KeyCode; - public readonly triggerSideBySideModifier: 'ctrlKey' | 'shiftKey' | 'altKey' | 'metaKey'; + public readonly triggerSideBySideModifier: TriggerModifier; constructor( triggerKey: KeyCode, - triggerModifier: 'ctrlKey' | 'shiftKey' | 'altKey' | 'metaKey', + triggerModifier: TriggerModifier, triggerSideBySideKey: KeyCode, - triggerSideBySideModifier: 'ctrlKey' | 'shiftKey' | 'altKey' | 'metaKey' + triggerSideBySideModifier: TriggerModifier ) { this.triggerKey = triggerKey; this.triggerModifier = triggerModifier; diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinition.ts b/src/vs/editor/contrib/goToDefinition/goToDefinition.ts index eee248b2cb031..b0474016f0aa7 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinition.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinition.ts @@ -3,24 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - +import { flatten, coalesce } from 'vs/base/common/arrays'; +import { asWinJsPromise } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { TPromise } from 'vs/base/common/winjs.base'; -import { ITextModel } from 'vs/editor/common/model'; import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; -import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; -import { DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, Location, DefinitionLink } from 'vs/editor/common/modes'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { asWinJsPromise } from 'vs/base/common/async'; import { Position } from 'vs/editor/common/core/position'; -import { flatten } from 'vs/base/common/arrays'; +import { ITextModel } from 'vs/editor/common/model'; +import { DefinitionLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry } from 'vs/editor/common/modes'; +import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; function getDefinitions( model: ITextModel, position: Position, registry: LanguageFeatureRegistry, - provide: (provider: T, model: ITextModel, position: Position, token: CancellationToken) => Location | Location[] | Thenable + provide: (provider: T, model: ITextModel, position: Position, token: CancellationToken) => DefinitionLink | DefinitionLink[] | Thenable ): TPromise { const provider = registry.ordered(model); @@ -35,7 +33,7 @@ function getDefinitions( }); return TPromise.join(promises) .then(flatten) - .then(references => references.filter(x => !!x)); + .then(references => coalesce(references)); } @@ -45,13 +43,13 @@ export function getDefinitionsAtPosition(model: ITextModel, position: Position): }); } -export function getImplementationsAtPosition(model: ITextModel, position: Position): TPromise { +export function getImplementationsAtPosition(model: ITextModel, position: Position): TPromise { return getDefinitions(model, position, ImplementationProviderRegistry, (provider, model, position, token) => { return provider.provideImplementation(model, position, token); }); } -export function getTypeDefinitionsAtPosition(model: ITextModel, position: Position): TPromise { +export function getTypeDefinitionsAtPosition(model: ITextModel, position: Position): TPromise { return getDefinitions(model, position, TypeDefinitionProviderRegistry, (provider, model, position, token) => { return provider.provideTypeDefinition(model, position, token); }); diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts index 8c15d98772cdd..981b9fc99dc9c 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as nls from 'vs/nls'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes'; @@ -13,7 +11,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { Range } from 'vs/editor/common/core/range'; import { registerEditorAction, IActionOptions, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions'; -import { Location } from 'vs/editor/common/modes'; +import { DefinitionLink } from 'vs/editor/common/modes'; import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition } from './goToDefinition'; import { ReferencesController } from 'vs/editor/contrib/referenceSearch/referencesController'; import { ReferencesModel } from 'vs/editor/contrib/referenceSearch/referencesModel'; @@ -67,7 +65,7 @@ export class DefinitionAction extends EditorAction { // * remove falsy references // * find reference at the current pos let idxOfCurrent = -1; - let result: Location[] = []; + const result: DefinitionLink[] = []; for (let i = 0; i < references.length; i++) { let reference = references[i]; if (!reference || !reference.range) { @@ -112,7 +110,7 @@ export class DefinitionAction extends EditorAction { return definitionPromise; } - protected _getDeclarationsAtPosition(model: ITextModel, position: corePosition.Position): TPromise { + protected _getDeclarationsAtPosition(model: ITextModel, position: corePosition.Position): TPromise { return getDefinitionsAtPosition(model, position); } @@ -145,8 +143,8 @@ export class DefinitionAction extends EditorAction { } } - private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location, sideBySide: boolean): TPromise { - let { uri, range } = reference; + private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: DefinitionLink, sideBySide: boolean): TPromise { + const { uri, range } = reference; return editorService.openCodeEditor({ resource: uri, options: { @@ -247,7 +245,7 @@ export class PeekDefinitionAction extends DefinitionAction { } export class ImplementationAction extends DefinitionAction { - protected _getDeclarationsAtPosition(model: ITextModel, position: corePosition.Position): TPromise { + protected _getDeclarationsAtPosition(model: ITextModel, position: corePosition.Position): TPromise { return getImplementationsAtPosition(model, position); } @@ -303,7 +301,7 @@ export class PeekImplementationAction extends ImplementationAction { } export class TypeDefinitionAction extends DefinitionAction { - protected _getDeclarationsAtPosition(model: ITextModel, position: corePosition.Position): TPromise { + protected _getDeclarationsAtPosition(model: ITextModel, position: corePosition.Position): TPromise { return getTypeDefinitionsAtPosition(model, position); } diff --git a/src/vs/editor/contrib/hover/getHover.ts b/src/vs/editor/contrib/hover/getHover.ts index 1c4fabf950511..577c87d814ade 100644 --- a/src/vs/editor/contrib/hover/getHover.ts +++ b/src/vs/editor/contrib/hover/getHover.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import { coalesce } from 'vs/base/common/arrays'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { ITextModel } from 'vs/editor/common/model'; @@ -16,23 +14,23 @@ import { CancellationToken } from 'vs/base/common/cancellation'; export function getHover(model: ITextModel, position: Position, token: CancellationToken): Promise { const supports = HoverProviderRegistry.ordered(model); - const values: Hover[] = []; - const promises = supports.map((support, idx) => { - return Promise.resolve(support.provideHover(model, position, token)).then((result) => { - if (result) { - let hasRange = (typeof result.range !== 'undefined'); - let hasHtmlContent = typeof result.contents !== 'undefined' && result.contents && result.contents.length > 0; - if (hasRange && hasHtmlContent) { - values[idx] = result; - } - } + const promises = supports.map(support => { + return Promise.resolve(support.provideHover(model, position, token)).then(hover => { + return hover && isValid(hover) ? hover : undefined; }, err => { onUnexpectedExternalError(err); + return undefined; }); }); - return Promise.all(promises).then(() => coalesce(values)); + return Promise.all(promises).then(values => coalesce(values)); } registerDefaultLanguageCommand('_executeHoverProvider', (model, position) => getHover(model, position, CancellationToken.None)); + +function isValid(result: Hover) { + const hasRange = (typeof result.range !== 'undefined'); + const hasHtmlContent = typeof result.contents !== 'undefined' && result.contents && result.contents.length > 0; + return hasRange && hasHtmlContent; +} diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 3a1fabd909300..36f324da0a7dc 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -17,7 +17,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { StandaloneKeybindingService, applyConfigurationValues } from 'vs/editor/standalone/browser/simpleServices'; -import { IEditorContextViewService } from 'vs/editor/standalone/browser/standaloneServices'; +import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; @@ -284,7 +284,7 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon export class StandaloneEditor extends StandaloneCodeEditor implements IStandaloneCodeEditor { - private _contextViewService: IEditorContextViewService; + private _contextViewService: ContextViewService; private readonly _configurationService: IConfigurationService; private _ownsModel: boolean; @@ -311,7 +311,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon delete options.model; super(domElement, options, instantiationService, codeEditorService, commandService, contextKeyService, keybindingService, themeService, notificationService); - this._contextViewService = contextViewService; + this._contextViewService = contextViewService; this._configurationService = configurationService; this._register(toDispose); @@ -359,7 +359,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon export class StandaloneDiffEditor extends DiffEditorWidget implements IStandaloneDiffEditor { - private _contextViewService: IEditorContextViewService; + private _contextViewService: ContextViewService; private readonly _configurationService: IConfigurationService; constructor( @@ -384,7 +384,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon super(domElement, options, editorWorkerService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService); - this._contextViewService = contextViewService; + this._contextViewService = contextViewService; this._configurationService = configurationService; this._register(toDispose); diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index e4f58e5df5816..d3275d83f30e9 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -45,11 +45,6 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IListService, ListService } from 'vs/platform/list/browser/listService'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; -export interface IEditorContextViewService extends IContextViewService { - dispose(): void; - setContainer(domNode: HTMLElement): void; -} - export interface IEditorOverrideServices { [index: string]: any; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 0a9e5161b0e88..438105b4d899d 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4922,7 +4922,7 @@ declare namespace monaco.languages { /** * Provide the definition of the symbol at the given position and document. */ - provideDefinition(model: editor.ITextModel, position: Position, token: CancellationToken): DefinitionLink | Thenable; + provideDefinition(model: editor.ITextModel, position: Position, token: CancellationToken): Definition | DefinitionLink[] | Thenable; } /** @@ -4933,7 +4933,7 @@ declare namespace monaco.languages { /** * Provide the implementation of the symbol at the given position and document. */ - provideImplementation(model: editor.ITextModel, position: Position, token: CancellationToken): Definition | Thenable; + provideImplementation(model: editor.ITextModel, position: Position, token: CancellationToken): Definition | DefinitionLink[] | Thenable; } /** @@ -4944,7 +4944,7 @@ declare namespace monaco.languages { /** * Provide the type definition of the symbol at the given position and document. */ - provideTypeDefinition(model: editor.ITextModel, position: Position, token: CancellationToken): Definition | Thenable; + provideTypeDefinition(model: editor.ITextModel, position: Position, token: CancellationToken): Definition | DefinitionLink[] | Thenable; } /** diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 99792a19f0034..f592536793e82 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -29,7 +29,6 @@ export interface ParsedArgs { verbose?: boolean; log?: string; logExtensionHostCommunication?: boolean; - 'disable-extensions'?: boolean; 'extensions-dir'?: string; extensionDevelopmentPath?: string; extensionTestsPath?: string; @@ -38,6 +37,8 @@ export interface ParsedArgs { debugId?: string; debugSearch?: string; debugBrkSearch?: string; + 'disable-extensions'?: boolean; + 'disable-extension'?: string | string[]; 'list-extensions'?: boolean; 'show-versions'?: boolean; 'install-extension'?: string | string[]; @@ -100,7 +101,7 @@ export interface IEnvironmentService { workspacesHome: string; isExtensionDevelopment: boolean; - disableExtensions: boolean; + disableExtensions: boolean | string[]; extensionsPath: string; extensionDevelopmentPath: string; extensionTestsPath: string; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 3a8b2891705f1..efbfbe36d4e2c 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -21,6 +21,7 @@ const options: minimist.Opts = { 'extensionDevelopmentPath', 'extensionTestsPath', 'install-extension', + 'disable-extension', 'uninstall-extension', 'debugId', 'debugPluginHost', @@ -170,6 +171,7 @@ const troubleshootingHelp: { [name: string]: string; } = { '-p, --performance': localize('performance', "Start with the 'Developer: Startup Performance' command enabled."), '--prof-startup': localize('prof-startup', "Run CPU profiler during startup"), '--disable-extensions': localize('disableExtensions', "Disable all installed extensions."), + '--disable-extension ': localize('disableExtension', "Disable an extension."), '--inspect-extensions': localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI."), '--inspect-brk-extensions': localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI."), '--disable-gpu': localize('disableGPU', "Disable GPU hardware acceleration."), diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 06e2171dfb51b..9ef3481c43d6a 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -153,7 +153,21 @@ export class EnvironmentService implements IEnvironmentService { @memoize get extensionTestsPath(): string { return this._args.extensionTestsPath ? path.normalize(this._args.extensionTestsPath) : this._args.extensionTestsPath; } - get disableExtensions(): boolean { return this._args['disable-extensions']; } + get disableExtensions(): boolean | string[] { + if (this._args['disable-extensions']) { + return true; + } + const disableExtensions: string | string[] = this._args['disable-extension']; + if (disableExtensions) { + if (typeof disableExtensions === 'string') { + return [disableExtensions]; + } + if (Array.isArray(disableExtensions) && disableExtensions.length > 0) { + return disableExtensions; + } + } + return false; + } get skipGettingStarted(): boolean { return this._args['skip-getting-started']; } diff --git a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts index 28c6ce6805f06..f56f3aabbbf2f 100644 --- a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts @@ -8,7 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, ILocalExtension, isIExtensionIdentifier, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { getIdFromLocalExtensionId, areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { getIdFromLocalExtensionId, areSameExtensions } 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'; @@ -29,7 +29,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService { @IStorageService private storageService: IStorageService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IEnvironmentService private environmentService: IEnvironmentService, - @IExtensionManagementService extensionManagementService: IExtensionManagementService + @IExtensionManagementService private extensionManagementService: IExtensionManagementService ) { extensionManagementService.onDidUninstallExtension(this._onDidUninstallExtension, this, this.disposables); } @@ -38,7 +38,11 @@ export class ExtensionEnablementService implements IExtensionEnablementService { return this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY; } - getDisabledExtensions(): TPromise { + get allUserExtensionsDisabled(): boolean { + return this.environmentService.disableExtensions === true; + } + + async getDisabledExtensions(): Promise { let result = this._getDisabledExtensions(StorageScope.GLOBAL); @@ -54,14 +58,25 @@ export class ExtensionEnablementService implements IExtensionEnablementService { } } - return TPromise.as(result); + if (this.environmentService.disableExtensions) { + const allInstalledExtensions = await this.extensionManagementService.getInstalled(); + for (const installedExtension of allInstalledExtensions) { + if (this._isExtensionDisabledInEnvironment(installedExtension)) { + if (!result.some(r => areSameExtensions(r, installedExtension.galleryIdentifier))) { + result.push(installedExtension.galleryIdentifier); + } + } + } + } + + return result; } getEnablementState(extension: ILocalExtension): EnablementState { - if (this.environmentService.disableExtensions && extension.type === LocalExtensionType.User) { + if (this._isExtensionDisabledInEnvironment(extension)) { return EnablementState.Disabled; } - const identifier = this._getIdentifier(extension); + const identifier = extension.galleryIdentifier; if (this.hasWorkspace) { if (this._getEnabledExtensions(StorageScope.WORKSPACE).filter(e => areSameExtensions(e, identifier))[0]) { return EnablementState.WorkspaceEnabled; @@ -95,7 +110,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService { if (!this.canChangeEnablement(arg)) { return TPromise.wrap(false); } - identifier = this._getIdentifier(arg); + identifier = arg.galleryIdentifier; } const workspace = newState === EnablementState.WorkspaceDisabled || newState === EnablementState.WorkspaceEnabled; @@ -134,6 +149,17 @@ export class ExtensionEnablementService implements IExtensionEnablementService { return enablementState === EnablementState.WorkspaceEnabled || enablementState === EnablementState.Enabled; } + private _isExtensionDisabledInEnvironment(extension: ILocalExtension): boolean { + if (this.allUserExtensionsDisabled) { + return extension.type === LocalExtensionType.User; + } + const disabledExtensions = this.environmentService.disableExtensions; + if (Array.isArray(disabledExtensions)) { + return disabledExtensions.some(id => areSameExtensions({ id }, extension.galleryIdentifier)); + } + return false; + } + private _getEnablementState(identifier: IExtensionIdentifier): EnablementState { if (this.hasWorkspace) { if (this._getEnabledExtensions(StorageScope.WORKSPACE).filter(e => areSameExtensions(e, identifier))[0]) { @@ -150,10 +176,6 @@ export class ExtensionEnablementService implements IExtensionEnablementService { return EnablementState.Enabled; } - private _getIdentifier(extension: ILocalExtension): IExtensionIdentifier { - return { id: getGalleryExtensionIdFromLocal(extension), uuid: extension.identifier.uuid }; - } - private _enableExtension(identifier: IExtensionIdentifier): void { this._removeFromDisabledExtensions(identifier, StorageScope.WORKSPACE); this._removeFromEnabledExtensions(identifier, StorageScope.WORKSPACE); diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index eaa3ec6ed6f55..e30d7e8787e73 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -343,6 +343,8 @@ export const IExtensionEnablementService = createDecorator; + getDisabledExtensions(): Promise; /** * Returns the enablement state for the given extension diff --git a/src/vs/platform/extensionManagement/test/common/extensionEnablementService.test.ts b/src/vs/platform/extensionManagement/test/common/extensionEnablementService.test.ts index 717a17dd3b0e7..655d4b05b5027 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionEnablementService.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionEnablementService.test.ts @@ -35,10 +35,11 @@ export class TestExtensionEnablementService extends ExtensionEnablementService { constructor(instantiationService: TestInstantiationService) { super(storageService(instantiationService), instantiationService.get(IWorkspaceContextService), instantiationService.get(IEnvironmentService) || instantiationService.stub(IEnvironmentService, {} as IEnvironmentService), - instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: new Emitter() })); + instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService, + { onDidUninstallExtension: new Emitter().event } as IExtensionManagementService)); } - public reset(): TPromise { + public async reset(): Promise { return this.getDisabledExtensions().then(extensions => extensions.forEach(d => this.setEnablement(aLocalExtension(d.id), EnablementState.Enabled))); } } @@ -52,7 +53,7 @@ suite('ExtensionEnablementService Test', () => { setup(() => { instantiationService = new TestInstantiationService(); - instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event }); + instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, getInstalled: () => TPromise.as([]) } as IExtensionManagementService); testObject = new TestExtensionEnablementService(instantiationService); }); @@ -331,6 +332,12 @@ suite('ExtensionEnablementService Test', () => { assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a')), false); }); + test('test canChangeEnablement return false when the extension is disabled in environment', () => { + instantiationService.stub(IEnvironmentService, { disableExtensions: ['pub.a'] } as IEnvironmentService); + testObject = new TestExtensionEnablementService(instantiationService); + assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a')), false); + }); + test('test canChangeEnablement return true for system extensions when extensions are disabled in environment', () => { instantiationService.stub(IEnvironmentService, { disableExtensions: true } as IEnvironmentService); testObject = new TestExtensionEnablementService(instantiationService); @@ -338,12 +345,32 @@ suite('ExtensionEnablementService Test', () => { extension.type = LocalExtensionType.System; assert.equal(testObject.canChangeEnablement(extension), true); }); + + test('test canChangeEnablement return false for system extensions when extension is disabled in environment', () => { + instantiationService.stub(IEnvironmentService, { disableExtensions: ['pub.a'] } as IEnvironmentService); + testObject = new TestExtensionEnablementService(instantiationService); + const extension = aLocalExtension('pub.a'); + extension.type = LocalExtensionType.System; + assert.equal(testObject.canChangeEnablement(extension), true); + }); + + test('test getDisabledExtensions include extensions disabled in enviroment', () => { + instantiationService.stub(IEnvironmentService, { disableExtensions: ['pub.a'] } as IEnvironmentService); + instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, getInstalled: () => TPromise.as([aLocalExtension('pub.a'), aLocalExtension('pub.b')]) } as IExtensionManagementService); + testObject = new TestExtensionEnablementService(instantiationService); + return testObject.getDisabledExtensions() + .then(actual => { + assert.equal(actual.length, 1); + assert.equal(actual[0].id, 'pub.a'); + }); + }); }); function aLocalExtension(id: string, contributes?: IExtensionContributions): ILocalExtension { const [publisher, name] = id.split('.'); return Object.create({ identifier: { id }, + galleryIdentifier: { id, uuid: void 0 }, manifest: { name, publisher, diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index 9b7309f7c55ba..dfb4c357ae84a 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -16,11 +16,13 @@ import { getPathLabel, getBaseLabel } from 'vs/base/common/labels'; import { IPath } from 'vs/platform/windows/common/windows'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { isWindows, isMacintosh, isLinux } from 'vs/base/common/platform'; -import { IWorkspaceIdentifier, IWorkspacesMainService, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceSavedEvent } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, IWorkspacesMainService, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceSavedEvent, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IHistoryMainService, IRecentlyOpened } from 'vs/platform/history/common/history'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { isEqual } from 'vs/base/common/paths'; import { RunOnceScheduler } from 'vs/base/common/async'; +import URI from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; export class HistoryMainService implements IHistoryMainService { @@ -179,7 +181,7 @@ export class HistoryMainService implements IHistoryMainService { let files: string[]; // Get from storage - const storedRecents = this.stateService.getItem(HistoryMainService.recentlyOpenedStorageKey); + const storedRecents = this.getRecentlyOpenedFromStorage(); if (storedRecents) { workspaces = storedRecents.workspaces || []; files = storedRecents.files || []; @@ -216,6 +218,24 @@ export class HistoryMainService implements IHistoryMainService { return workspaceOrFile.id; } + private getRecentlyOpenedFromStorage(): IRecentlyOpened { + const storedRecents: IRecentlyOpened = this.stateService.getItem(HistoryMainService.recentlyOpenedStorageKey) || { workspaces: [], files: [] }; + const result: IRecentlyOpened = { workspaces: [], files: storedRecents.files }; + for (const workspace of storedRecents.workspaces) { + if (typeof workspace === 'string') { + result.workspaces.push(workspace); + } else if (isWorkspaceIdentifier(workspace)) { + result.workspaces.push(workspace); + } else { + const uri = URI.revive(workspace); + if (uri.scheme === Schemas.file) { + result.workspaces.push(uri.fsPath); + } + } + } + return result; + } + private saveRecentlyOpened(recent: IRecentlyOpened): void { this.stateService.setItem(HistoryMainService.recentlyOpenedStorageKey, recent); } diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index f4101fac8a2d4..9bd6cae45877d 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -654,19 +654,21 @@ export class HighlightingWorkbenchTree extends WorkbenchTree { this.input.onDidChange(this.updateHighlights, this, this.disposables); this.disposables.push(attachInputBoxStyler(this.input, themeService)); this.disposables.push(this.input); - this.disposables.push(addStandardDisposableListener(this.input.inputElement, 'keyup', event => { + this.disposables.push(addStandardDisposableListener(this.input.inputElement, 'keydown', event => { //todo@joh make this command/context-key based - if (event.keyCode === KeyCode.DownArrow) { - this.focusNext(); - this.domFocus(); - } else if (event.keyCode === KeyCode.UpArrow) { - this.focusPrevious(); - this.domFocus(); - } else if (event.keyCode === KeyCode.Enter) { - this.setSelection(this.getSelection()); - } else if (event.keyCode === KeyCode.Escape) { - this.input.value = ''; - this.domFocus(); + switch (event.keyCode) { + case KeyCode.DownArrow: + case KeyCode.UpArrow: + this.domFocus(); + break; + case KeyCode.Enter: + case KeyCode.Tab: + this.setSelection(this.getSelection()); + break; + case KeyCode.Escape: + this.input.value = ''; + this.domFocus(); + break; } })); } diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 6b799c0333f42..5be3b6ef6a1e3 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -177,7 +177,7 @@ export const inputBackground = registerColor('input.background', { dark: '#3C3C3 export const inputForeground = registerColor('input.foreground', { dark: foreground, light: foreground, hc: foreground }, nls.localize('inputBoxForeground', "Input box foreground.")); export const inputBorder = registerColor('input.border', { dark: null, light: null, hc: contrastBorder }, nls.localize('inputBoxBorder', "Input box border.")); export const inputActiveOptionBorder = registerColor('inputOption.activeBorder', { dark: '#007ACC', light: '#007ACC', hc: activeContrastBorder }, nls.localize('inputBoxActiveOptionBorder', "Border color of activated options in input fields.")); -export const inputPlaceholderForeground = registerColor('input.placeholderForeground', { dark: null, light: null, hc: null }, nls.localize('inputPlaceholderForeground', "Input box foreground color for placeholder text.")); +export const inputPlaceholderForeground = registerColor('input.placeholderForeground', { light: transparent(foreground, 0.5), dark: transparent(foreground, 0.5), hc: transparent(foreground, 0.7) }, nls.localize('inputPlaceholderForeground', "Input box foreground color for placeholder text.")); export const inputValidationInfoBackground = registerColor('inputValidation.infoBackground', { dark: '#063B49', light: '#D6ECF2', hc: Color.black }, nls.localize('inputValidationInfoBackground', "Input validation background color for information severity.")); export const inputValidationInfoBorder = registerColor('inputValidation.infoBorder', { dark: '#007acc', light: '#007acc', hc: contrastBorder }, nls.localize('inputValidationInfoBorder', "Input validation border color for information severity.")); @@ -197,7 +197,7 @@ export const listActiveSelectionBackground = registerColor('list.activeSelection export const listActiveSelectionForeground = registerColor('list.activeSelectionForeground', { dark: Color.white, light: Color.white, hc: null }, nls.localize('listActiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listInactiveSelectionBackground = registerColor('list.inactiveSelectionBackground', { dark: '#3F3F46', light: '#CCCEDB', hc: null }, nls.localize('listInactiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); export const listInactiveSelectionForeground = registerColor('list.inactiveSelectionForeground', { dark: null, light: null, hc: null }, nls.localize('listInactiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); -export const listInactiveFocusBackground = registerColor('list.inactiveFocusBackground', { dark: '#313135', light: '#d8dae6', hc: null }, nls.localize('listInactiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); +export const listInactiveFocusBackground = registerColor('list.inactiveFocusBackground', { dark: '#313135', light: '#d8dae6', hc: null }, nls.localize('listInactiveFocusBackground', "List/Tree background color for the focused item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); export const listHoverBackground = registerColor('list.hoverBackground', { dark: '#2A2D2E', light: '#F0F0F0', hc: null }, nls.localize('listHoverBackground', "List/Tree background when hovering over items using the mouse.")); export const listHoverForeground = registerColor('list.hoverForeground', { dark: null, light: null, hc: null }, nls.localize('listHoverForeground', "List/Tree foreground when hovering over items using the mouse.")); export const listDropBackground = registerColor('list.dropBackground', { dark: listFocusBackground, light: listFocusBackground, hc: null }, nls.localize('listDropBackground', "List/Tree drag and drop background when moving items around using the mouse.")); @@ -226,7 +226,7 @@ export const progressBarBackground = registerColor('progressBar.background', { d export const breadcrumbsForeground = registerColor('breadcrumb.breadcrumbsForeground', { light: Color.fromHex('#6C6C6C').transparent(.7), dark: Color.fromHex('#CCCCCC').transparent(.7), hc: Color.white.transparent(.7) }, nls.localize('breadcrumbsFocusForeground', "Color of focused breadcrumb items.")); export const breadcrumbsFocusForeground = registerColor('breadcrumb.breadcrumbsFocusForeground', { light: '#6C6C6C', dark: '#CCCCCC', hc: Color.white }, nls.localize('breadcrumbsFocusForeground', "Color of focused breadcrumb items.")); export const breadcrumbsActiveSelectionForeground = registerColor('breadcrumb.breadcrumbsActiveSelectionForeground', { light: '#6C6C6C', dark: '#CCCCCC', hc: Color.white }, nls.localize('breadcrumbsSelectedForegound', "Color of selected breadcrumb items.")); -export const breadcrumbsActiveSelectionBackground = registerColor('breadcrumb.breadcrumbsActiveSelectionBackground', { light: '#F3F3F3', dark: '#252526', hc: Color.black }, nls.localize('breadcrumbsSelectedBackground', "Background color of selected breadcrumb items.")); +export const breadcrumbsPickerBackground = registerColor('breadcrumb.breadcrumbsPickerBackground', { light: '#ECECEC', dark: '#252526', hc: Color.black }, nls.localize('breadcrumbsSelectedBackground', "Background color of breadcrumb item picker.")); /** * Editor background color. diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index c8921a0a5e397..08c63b30296b3 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -6,7 +6,7 @@ 'use strict'; import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, lighten, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionBackground, breadcrumbsActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; +import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, lighten, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; @@ -266,20 +266,16 @@ export function attachStylerCallback(themeService: IThemeService, colors: { [nam export interface IBreadcrumbsWidgetStyleOverrides extends IStyleOverrides { breadcrumbsBackground?: ColorIdentifier; breadcrumbsForeground?: ColorIdentifier; - breadcrumbsHoverBackground?: ColorIdentifier; breadcrumbsHoverForeground?: ColorIdentifier; breadcrumbsFocusForeground?: ColorIdentifier; - breadcrumbsFocusAndSelectionBackground?: ColorIdentifier; breadcrumbsFocusAndSelectionForeground?: ColorIdentifier; } export const defaultBreadcrumbsStyles = { breadcrumbsBackground: editorBackground, breadcrumbsForeground: breadcrumbsForeground, - breadcrumbsHoverBackground: editorBackground, breadcrumbsHoverForeground: breadcrumbsFocusForeground, breadcrumbsFocusForeground: breadcrumbsFocusForeground, - breadcrumbsFocusAndSelectionBackground: breadcrumbsActiveSelectionBackground, breadcrumbsFocusAndSelectionForeground: breadcrumbsActiveSelectionForeground, }; diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 6964c04bf3e47..1700e900b6984 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -156,7 +156,7 @@ export interface IWindowsService { toggleSharedProcess(): TPromise; // Global methods - openWindow(windowId: number, paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean; }): TPromise; + openWindow(windowId: number, paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean, args?: ParsedArgs }): TPromise; openNewWindow(): TPromise; showWindow(windowId: number): TPromise; getWindows(): TPromise<{ id: number; workspace?: IWorkspaceIdentifier; folderPath?: string; title: string; filename?: string; }[]>; @@ -209,7 +209,7 @@ export interface IWindowService { getRecentlyOpened(): TPromise; focusWindow(): TPromise; closeWindow(): TPromise; - openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean; }): TPromise; + openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean, args?: ParsedArgs }): TPromise; isFocused(): TPromise; setDocumentEdited(flag: boolean): TPromise; isMaximized(): TPromise; diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 3fde497f0177e..b60c6ecf2798e 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -59,7 +59,7 @@ export interface IWindowsChannel extends IChannel { call(command: 'onWindowTitleDoubleClick', arg: number): TPromise; call(command: 'setDocumentEdited', arg: [number, boolean]): TPromise; call(command: 'quit'): TPromise; - call(command: 'openWindow', arg: [number, string[], { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }]): TPromise; + call(command: 'openWindow', arg: [number, string[], { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean, args?: ParsedArgs }]): TPromise; call(command: 'openNewWindow'): TPromise; call(command: 'showWindow', arg: number): TPromise; call(command: 'getWindows'): TPromise<{ id: number; workspace?: IWorkspaceIdentifier; folderPath?: string; title: string; filename?: string; }[]>; @@ -344,7 +344,7 @@ export class WindowsChannelClient implements IWindowsService { return this.channel.call('toggleSharedProcess'); } - openWindow(windowId: number, paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): TPromise { + openWindow(windowId: number, paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean, args?: ParsedArgs }): TPromise { return this.channel.call('openWindow', [windowId, paths, options]); } diff --git a/src/vs/platform/windows/electron-browser/windowService.ts b/src/vs/platform/windows/electron-browser/windowService.ts index 837aee8581d15..e9ba9f2d0c308 100644 --- a/src/vs/platform/windows/electron-browser/windowService.ts +++ b/src/vs/platform/windows/electron-browser/windowService.ts @@ -93,7 +93,7 @@ export class WindowService implements IWindowService { return this.windowsService.saveAndEnterWorkspace(this.windowId, path); } - openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean; }): TPromise { + openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean, args?: ParsedArgs }): TPromise { return this.windowsService.openWindow(this.windowId, paths, options); } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index c9e45bedb107e..c47648628ea0b 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -392,7 +392,7 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable return TPromise.as(null); } - openWindow(windowId: number, paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): TPromise { + openWindow(windowId: number, paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean, args?: ParsedArgs }): TPromise { this.logService.trace('windowsService#openWindow'); if (!paths || !paths.length) { return TPromise.as(null); @@ -401,7 +401,7 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable this.windowsMainService.open({ context: OpenContext.API, contextWindowId: windowId, - cli: this.environmentService.args, + cli: options && options.args ? { ...this.environmentService.args, ...options.args } : this.environmentService.args, pathsToOpen: paths, forceNewWindow: options && options.forceNewWindow, forceReuseWindow: options && options.forceReuseWindow, diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 27a09386ff78a..1091741b1c75f 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2156,6 +2156,41 @@ declare module 'vscode' { resolveCodeLens?(codeLens: CodeLens, token: CancellationToken): ProviderResult; } + /** + * Information about where a symbol is defined. + * + * Provides additional metadata over normal [location](#Location) definitions, including the range of + * the defining symbol + */ + export interface DefinitionLink { + /** + * Span of the symbol being defined in the source file. + * + * Used as the underlined span for mouse definition hover. Defaults to the word range at + * the definition position. + */ + originSelectionRange?: Range; + + /** + * The resource identifier of the definition. + */ + targetUri: Uri; + + /** + * The full range of the definition. + * + * For a class definition for example, this would be the entire body of the class definition. + */ + targetRange: Range; + + /** + * The span of the symbol definition. + * + * For a class definition, this would be the class name itself in the class definition. + */ + targetSelectionRange?: Range; + } + /** * The definition of a symbol represented as one or many [locations](#Location). * For most programming languages there is only one location at which a symbol is @@ -2179,7 +2214,7 @@ declare module 'vscode' { * @return A definition or a thenable that resolves to such. The lack of a result can be * signaled by returning `undefined` or `null`. */ - provideDefinition(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + provideDefinition(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; } /** @@ -2197,7 +2232,7 @@ declare module 'vscode' { * @return A definition or a thenable that resolves to such. The lack of a result can be * signaled by returning `undefined` or `null`. */ - provideImplementation(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + provideImplementation(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; } /** @@ -2215,7 +2250,7 @@ declare module 'vscode' { * @return A definition or a thenable that resolves to such. The lack of a result can be * signaled by returning `undefined` or `null`. */ - provideTypeDefinition(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + provideTypeDefinition(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; } /** @@ -4557,22 +4592,22 @@ declare module 'vscode' { /** * The clean task group; */ - public static Clean: TaskGroup; + static Clean: TaskGroup; /** * The build task group; */ - public static Build: TaskGroup; + static Build: TaskGroup; /** * The rebuild all task group; */ - public static Rebuild: TaskGroup; + static Rebuild: TaskGroup; /** * The test all task group; */ - public static Test: TaskGroup; + static Test: TaskGroup; private constructor(id: string, label: string); } @@ -6992,13 +7027,13 @@ declare module 'vscode' { export const onDidChangeConfiguration: Event; /** - * Register a task provider. + * ~~Register a task provider.~~ + * + * @deprecated Use the corresponding function on the `tasks` namespace instead * * @param type The task kind type this provider is registered for. * @param provider A task provider. * @return A [disposable](#Disposable) that unregisters this provider when being disposed. - * - * @deprecated Use the corresponding function on the `tasks` namespace instead */ export function registerTaskProvider(type: string, provider: TaskProvider): Disposable; diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index f899c196ba947..c82b7a08c519a 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1054,47 +1054,4 @@ declare module 'vscode' { export const onDidRenameFile: Event; } //#endregion - - //#region Matt: Deinition range - - /** - * Information about where a symbol is defined. - * - * Provides additional metadata over normal [location](#Location) definitions, including the range of - * the defining symbol - */ - export interface DefinitionLink { - /** - * Span of the symbol being defined in the source file. - * - * Used as the underlined span for mouse definition hover. Defaults to the word range at - * the definition position. - */ - origin?: Range; - - /** - * The resource identifier of the definition. - */ - uri: Uri; - - /** - * The full range of the definition. - * - * For a class definition for example, this would be the entire body of the class definition. - */ - range: Range; - - /** - * The span of the symbol definition. - * - * For a class definition, this would be the class name itself in the class definition. - */ - selectionRange?: Range; - } - - export interface DefinitionProvider { - provideDefinition2?(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; - } - - //#endregion } diff --git a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts index 7ecf37c7311c6..baa55c39fa667 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts @@ -162,7 +162,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerImplementationSupport(handle: number, selector: ISerializedDocumentFilter[]): void { this._registrations[handle] = modes.ImplementationProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { provideImplementation: (model, position, token): Thenable => { - return wireCancellationToken(token, this._proxy.$provideImplementation(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveLocationDto); + return wireCancellationToken(token, this._proxy.$provideImplementation(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveDefinitionLinkDto); } }); } @@ -170,7 +170,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerTypeDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void { this._registrations[handle] = modes.TypeDefinitionProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { provideTypeDefinition: (model, position, token): Thenable => { - return wireCancellationToken(token, this._proxy.$provideTypeDefinition(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveLocationDto); + return wireCancellationToken(token, this._proxy.$provideTypeDefinition(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveDefinitionLinkDto); } }); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts index 59c54e164db9a..6e5d3309ac4aa 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts @@ -20,6 +20,9 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape } from '../node/extHost.protocol'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IWindowService } from 'vs/platform/windows/common/windows'; @extHostNamedCustomer(MainContext.MainThreadWorkspace) export class MainThreadWorkspace implements MainThreadWorkspaceShape { @@ -213,8 +216,18 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { } } -CommandsRegistry.registerCommand('_workbench.enterWorkspace', function (accessor: ServicesAccessor, workspace: URI) { +CommandsRegistry.registerCommand('_workbench.enterWorkspace', async function (accessor: ServicesAccessor, workspace: URI, disableExtensions: string[]) { const workspaceEditingService = accessor.get(IWorkspaceEditingService); + const extensionService = accessor.get(IExtensionService); + const windowService = accessor.get(IWindowService); + + if (disableExtensions && disableExtensions.length) { + const runningExtensions = await extensionService.getExtensions(); + // If requested extension to disable is running, then reload window with given workspace + if (disableExtensions && runningExtensions.some(runningExtension => disableExtensions.some(id => areSameExtensions({ id }, { id: runningExtension.id })))) { + return windowService.openWindow([workspace.fsPath], { args: { _: [], 'disable-extension': disableExtensions } }); + } + } return workspaceEditingService.enterWorkspace(workspace.fsPath); }); \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index c65fd9cf161d1..44947586fd539 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -46,7 +46,6 @@ export interface IEnvironment { isExtensionDevelopmentDebug: boolean; appRoot: string; appSettingsHome: string; - disableExtensions: boolean; extensionDevelopmentPath: string; extensionTestsPath: string; } @@ -812,8 +811,8 @@ export interface ExtHostLanguageFeaturesShape { $provideCodeLenses(handle: number, resource: UriComponents): TPromise; $resolveCodeLens(handle: number, resource: UriComponents, symbol: modes.ICodeLensSymbol): TPromise; $provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise; - $provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise; - $provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise; + $provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise; + $provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise; $provideHover(handle: number, resource: UriComponents, position: IPosition): TPromise; $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition): TPromise; $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext): TPromise; diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index f562cb6d6feea..8dc9a268ce5c5 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -143,6 +143,15 @@ class CodeLensAdapter { } } +function convertToDefinitionLinks(value: vscode.Definition): modes.DefinitionLink[] { + if (Array.isArray(value)) { + return (value as (vscode.DefinitionLink | vscode.Location)[]).map(typeConvert.DefinitionLink.from); + } else if (value) { + return [typeConvert.DefinitionLink.from(value)]; + } + return undefined; +} + class DefinitionAdapter { constructor( @@ -153,29 +162,7 @@ class DefinitionAdapter { provideDefinition(resource: URI, position: IPosition): TPromise { let doc = this._documents.getDocumentData(resource).document; let pos = typeConvert.Position.to(position); - - return asWinJsPromise(token => this._provider.provideDefinition2 ? this._provider.provideDefinition2(doc, pos, token) : this._provider.provideDefinition(doc, pos, token)).then((value): modes.DefinitionLink[] => { - if (Array.isArray(value)) { - return (value as (vscode.DefinitionLink | vscode.Location)[]).map(x => DefinitionAdapter.convertDefinitionLink(x)); - } else if (value) { - return [DefinitionAdapter.convertDefinitionLink(value)]; - } - return undefined; - }); - } - - private static convertDefinitionLink(value: vscode.Location | vscode.DefinitionLink): modes.DefinitionLink { - const definitionLink = value; - return { - origin: definitionLink.origin - ? typeConvert.Range.from(definitionLink.origin) - : undefined, - uri: value.uri, - range: typeConvert.Range.from(value.range), - selectionRange: definitionLink.selectionRange - ? typeConvert.Range.from(definitionLink.selectionRange) - : undefined, - }; + return asWinJsPromise(token => this._provider.provideDefinition(doc, pos, token)).then(convertToDefinitionLinks); } } @@ -186,17 +173,10 @@ class ImplementationAdapter { private readonly _provider: vscode.ImplementationProvider ) { } - provideImplementation(resource: URI, position: IPosition): TPromise { + provideImplementation(resource: URI, position: IPosition): TPromise { let doc = this._documents.getDocumentData(resource).document; let pos = typeConvert.Position.to(position); - return asWinJsPromise(token => this._provider.provideImplementation(doc, pos, token)).then(value => { - if (Array.isArray(value)) { - return value.map(typeConvert.location.from); - } else if (value) { - return typeConvert.location.from(value); - } - return undefined; - }); + return asWinJsPromise(token => this._provider.provideImplementation(doc, pos, token)).then(convertToDefinitionLinks); } } @@ -207,17 +187,10 @@ class TypeDefinitionAdapter { private readonly _provider: vscode.TypeDefinitionProvider ) { } - provideTypeDefinition(resource: URI, position: IPosition): TPromise { + provideTypeDefinition(resource: URI, position: IPosition): TPromise { const doc = this._documents.getDocumentData(resource).document; const pos = typeConvert.Position.to(position); - return asWinJsPromise(token => this._provider.provideTypeDefinition(doc, pos, token)).then(value => { - if (Array.isArray(value)) { - return value.map(typeConvert.location.from); - } else if (value) { - return typeConvert.location.from(value); - } - return undefined; - }); + return asWinJsPromise(token => this._provider.provideTypeDefinition(doc, pos, token)).then(convertToDefinitionLinks); } } @@ -999,7 +972,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise { + $provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise { return this._withAdapter(handle, ImplementationAdapter, adapter => adapter.provideImplementation(URI.revive(resource), position)); } @@ -1009,7 +982,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise { + $provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise { return this._withAdapter(handle, TypeDefinitionAdapter, adapter => adapter.provideTypeDefinition(URI.revive(resource), position)); } diff --git a/src/vs/workbench/api/node/extHostTypeConverters.ts b/src/vs/workbench/api/node/extHostTypeConverters.ts index bf9426044115e..ae449946df0a1 100644 --- a/src/vs/workbench/api/node/extHostTypeConverters.ts +++ b/src/vs/workbench/api/node/extHostTypeConverters.ts @@ -413,6 +413,23 @@ export const location = { } }; +export namespace DefinitionLink { + export function from(value: vscode.Location | vscode.DefinitionLink): modes.DefinitionLink { + const definitionLink = value; + const location = value; + return { + origin: definitionLink.originSelectionRange + ? Range.from(definitionLink.originSelectionRange) + : undefined, + uri: definitionLink.targetUri ? definitionLink.targetUri : location.uri, + range: Range.from(definitionLink.targetRange ? definitionLink.targetRange : location.range), + selectionRange: definitionLink.targetSelectionRange + ? Range.from(definitionLink.targetSelectionRange) + : undefined, + }; + } +} + export namespace Hover { export function from(hover: vscode.Hover): modes.Hover { return { diff --git a/src/vs/workbench/browser/actions/toggleActivityBarVisibility.ts b/src/vs/workbench/browser/actions/toggleActivityBarVisibility.ts index bb784e8947286..9f504717d7349 100644 --- a/src/vs/workbench/browser/actions/toggleActivityBarVisibility.ts +++ b/src/vs/workbench/browser/actions/toggleActivityBarVisibility.ts @@ -8,7 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; @@ -40,4 +40,13 @@ export class ToggleActivityBarVisibilityAction extends Action { } const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, ToggleActivityBarVisibilityAction.LABEL), 'View: Toggle Activity Bar Visibility', nls.localize('view', "View")); \ No newline at end of file +registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, ToggleActivityBarVisibilityAction.LABEL), 'View: Toggle Activity Bar Visibility', nls.localize('view', "View")); + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '2_workbench_layout', + command: { + id: ToggleActivityBarVisibilityAction.ID, + title: nls.localize({ key: 'miToggleActivityBar', comment: ['&& denotes a mnemonic'] }, "Toggle &&Activity Bar") + }, + order: 4 +}); diff --git a/src/vs/workbench/browser/actions/toggleCenteredLayout.ts b/src/vs/workbench/browser/actions/toggleCenteredLayout.ts index 2ab19d4ca4ddc..4be449f24acb3 100644 --- a/src/vs/workbench/browser/actions/toggleCenteredLayout.ts +++ b/src/vs/workbench/browser/actions/toggleCenteredLayout.ts @@ -7,7 +7,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { IPartService } from 'vs/workbench/services/part/common/partService'; @@ -34,3 +34,21 @@ class ToggleCenteredLayout extends Action { const registry = Registry.as(Extensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleCenteredLayout, ToggleCenteredLayout.ID, ToggleCenteredLayout.LABEL), 'View: Toggle Centered Layout', nls.localize('view', "View")); + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '1_toggle_view', + command: { + id: ToggleCenteredLayout.ID, + title: nls.localize('miToggleCenteredLayout', "Toggle Centered Layout") + }, + order: 3 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { + group: '2_layouts', + command: { + id: 'workbench.action.editorLayoutCentered', + title: nls.localize({ key: 'miCenteredEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Centered") + }, + order: 2 +}); diff --git a/src/vs/workbench/browser/actions/toggleEditorLayout.ts b/src/vs/workbench/browser/actions/toggleEditorLayout.ts index 8f553a8fa22d7..32b9cc1c92139 100644 --- a/src/vs/workbench/browser/actions/toggleEditorLayout.ts +++ b/src/vs/workbench/browser/actions/toggleEditorLayout.ts @@ -9,7 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; @@ -73,4 +73,13 @@ CommandsRegistry.registerCommand('_workbench.editor.setGroupOrientation', functi const registry = Registry.as(Extensions.WorkbenchActions); const group = nls.localize('view', "View"); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEditorLayoutAction, ToggleEditorLayoutAction.ID, ToggleEditorLayoutAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_0 } }), 'View: Flip Editor Group Layout', group); \ No newline at end of file +registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEditorLayoutAction, ToggleEditorLayoutAction.ID, ToggleEditorLayoutAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_0 } }), 'View: Flip Editor Group Layout', group); + +MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { + group: 'z_flip', + command: { + id: ToggleEditorLayoutAction.ID, + title: nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Flip &&Layout") + }, + order: 1 +}); diff --git a/src/vs/workbench/browser/actions/toggleSidebarPosition.ts b/src/vs/workbench/browser/actions/toggleSidebarPosition.ts index 3de25a53e7ea7..3c64a5a1c6dc7 100644 --- a/src/vs/workbench/browser/actions/toggleSidebarPosition.ts +++ b/src/vs/workbench/browser/actions/toggleSidebarPosition.ts @@ -8,7 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { IPartService, Position } from 'vs/workbench/services/part/common/partService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; @@ -41,3 +41,12 @@ export class ToggleSidebarPositionAction extends Action { const registry = Registry.as(Extensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.LABEL), 'View: Toggle Side Bar Position', nls.localize('view', "View")); + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '2_workbench_layout', + command: { + id: ToggleSidebarPositionAction.ID, + title: nls.localize({ key: 'miMoveSidebarLeftRight', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Left/Right") + }, + order: 2 +}); diff --git a/src/vs/workbench/browser/actions/toggleSidebarVisibility.ts b/src/vs/workbench/browser/actions/toggleSidebarVisibility.ts index 95f4d8a21e862..e69151cac2b0e 100644 --- a/src/vs/workbench/browser/actions/toggleSidebarVisibility.ts +++ b/src/vs/workbench/browser/actions/toggleSidebarVisibility.ts @@ -8,7 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; @@ -35,4 +35,13 @@ export class ToggleSidebarVisibilityAction extends Action { } const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSidebarVisibilityAction, ToggleSidebarVisibilityAction.ID, ToggleSidebarVisibilityAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_B }), 'View: Toggle Side Bar Visibility', nls.localize('view', "View")); \ No newline at end of file +registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSidebarVisibilityAction, ToggleSidebarVisibilityAction.ID, ToggleSidebarVisibilityAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_B }), 'View: Toggle Side Bar Visibility', nls.localize('view', "View")); + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '2_workbench_layout', + command: { + id: ToggleSidebarVisibilityAction.ID, + title: nls.localize({ key: 'miToggleSidebar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Side Bar") + }, + order: 1 +}); diff --git a/src/vs/workbench/browser/actions/toggleStatusbarVisibility.ts b/src/vs/workbench/browser/actions/toggleStatusbarVisibility.ts index 2fe83369bb881..2ec27e5f76576 100644 --- a/src/vs/workbench/browser/actions/toggleStatusbarVisibility.ts +++ b/src/vs/workbench/browser/actions/toggleStatusbarVisibility.ts @@ -8,7 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; @@ -40,4 +40,13 @@ export class ToggleStatusbarVisibilityAction extends Action { } const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, ToggleStatusbarVisibilityAction.LABEL), 'View: Toggle Status Bar Visibility', nls.localize('view', "View")); \ No newline at end of file +registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, ToggleStatusbarVisibilityAction.LABEL), 'View: Toggle Status Bar Visibility', nls.localize('view', "View")); + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '2_workbench_layout', + command: { + id: ToggleStatusbarVisibilityAction.ID, + title: nls.localize({ key: 'miToggleStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Status Bar") + }, + order: 3 +}); diff --git a/src/vs/workbench/browser/actions/toggleZenMode.ts b/src/vs/workbench/browser/actions/toggleZenMode.ts index 955a69799ceda..a13035026da87 100644 --- a/src/vs/workbench/browser/actions/toggleZenMode.ts +++ b/src/vs/workbench/browser/actions/toggleZenMode.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { IPartService } from 'vs/workbench/services/part/common/partService'; @@ -33,4 +33,13 @@ class ToggleZenMode extends Action { } const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleZenMode, ToggleZenMode.ID, ToggleZenMode.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_Z) }), 'View: Toggle Zen Mode', nls.localize('view', "View")); \ No newline at end of file +registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleZenMode, ToggleZenMode.ID, ToggleZenMode.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_Z) }), 'View: Toggle Zen Mode', nls.localize('view', "View")); + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '1_toggle_view', + command: { + id: ToggleZenMode.ID, + title: nls.localize('miToggleZenMode', "Toggle Zen Mode") + }, + order: 2 +}); diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 3f6da3e94dcd4..35768b3f5fafa 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -199,11 +199,12 @@ export class ResourceLabel extends IconLabel { iconLabelOptions.title = this.computedPathLabel; } - if (!this.computedIconClasses) { - this.computedIconClasses = getIconClasses(this.modelService, this.modeService, resource, this.options && this.options.fileKind); + if (this.options && !this.options.hideIcon) { + if (!this.computedIconClasses) { + this.computedIconClasses = getIconClasses(this.modelService, this.modeService, resource, this.options && this.options.fileKind); + } + iconLabelOptions.extraClasses = this.computedIconClasses.slice(0); } - - iconLabelOptions.extraClasses = this.computedIconClasses.slice(0); if (this.options && this.options.extraClasses) { iconLabelOptions.extraClasses.push(...this.options.extraClasses); } diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index 136fc5bf7943b..ac7b2f27130f9 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -85,7 +85,13 @@ export abstract class BreadcrumbsConfig { return { name, - get value() { return value; }, + get value() { + return value; + }, + set value(newValue: T) { + service.updateValue(name, newValue); + value = newValue; + }, onDidChange: onDidChange.event, dispose(): void { listener.dispose(); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index f038e62afe362..c8a2110bfd2de 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -12,7 +12,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { combinedDisposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; -import { basenameOrAuthority, isEqual } from 'vs/base/common/resources'; +import { isEqual } from 'vs/base/common/resources'; import 'vs/css!./media/breadcrumbscontrol'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; @@ -22,7 +22,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { FileKind, IFileService } from 'vs/platform/files/common/files'; -import { IConstructorSignature1, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler'; @@ -31,10 +31,15 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { FileLabel } from 'vs/workbench/browser/labels'; import { BreadcrumbsConfig, IBreadcrumbsService } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; -import { BreadcrumbsFilePicker, BreadcrumbsOutlinePicker, BreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker'; +import { createBreadcrumbsPicker, BreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker'; import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { localize } from 'vs/nls'; +import { WorkbenchListFocusContextKey, IListService } from 'vs/platform/list/browser/listService'; +import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; class Item extends BreadcrumbsItem { @@ -68,20 +73,21 @@ class Item extends BreadcrumbsItem { render(container: HTMLElement): void { if (this.element instanceof FileElement) { // file/folder - if (this.options.showFileIcons) { - let label = this._instantiationService.createInstance(FileLabel, container, {}); - label.setFile(this.element.uri, { - hidePath: true, - fileKind: this.element.isFile ? FileKind.FILE : FileKind.FOLDER, - fileDecorations: { colors: this.options.showDecorationColors, badges: false } - }); - this._disposables.push(label); + let label = this._instantiationService.createInstance(FileLabel, container, {}); + label.setFile(this.element.uri, { + hidePath: true, + fileKind: this.element.isFile ? FileKind.FILE : FileKind.FOLDER, + hideIcon: !this.element.isFile || !this.options.showFileIcons, + fileDecorations: { colors: this.options.showDecorationColors, badges: false } + }); + this._disposables.push(label); + dom.toggleClass(container, 'file', this.element.isFile); - } else { - let label = new IconLabel(container); - label.setValue(basenameOrAuthority(this.element.uri)); - this._disposables.push(label); - } + } else if (this.element instanceof OutlineModel) { + // has outline element but not in one + let label = document.createElement('div'); + label.innerHTML = '…'; + container.appendChild(label); } else if (this.element instanceof OutlineGroup) { // provider @@ -91,16 +97,15 @@ class Item extends BreadcrumbsItem { } else if (this.element instanceof OutlineElement) { // symbol - if (this.options.showSymbolIcons) { let icon = document.createElement('div'); - icon.className = `symbol-icon ${symbolKindToCssClass(this.element.symbol.kind)}`; + icon.className = symbolKindToCssClass(this.element.symbol.kind); container.appendChild(icon); - container.classList.add('shows-symbol-icon'); + dom.addClass(container, 'shows-symbol-icon'); } - let label = new IconLabel(container); - label.setValue(this.element.symbol.name.replace(/\r|\n|\r\n/g, '\u23CE')); + let title = this.element.symbol.name.replace(/\r|\n|\r\n/g, '\u23CE'); + label.setValue(title, undefined, { title }); this._disposables.push(label); } } @@ -267,16 +272,11 @@ export class BreadcrumbsControl { } // show picker + let picker: BreadcrumbsPicker; this._contextViewService.showContextView({ - getAnchor() { - return event.node; - }, render: (parent: HTMLElement) => { - let ctor: IConstructorSignature1 = element instanceof FileElement ? BreadcrumbsFilePicker : BreadcrumbsOutlinePicker; - let res = this._instantiationService.createInstance(ctor, parent); - res.layout({ width: Math.max(220, dom.getTotalWidth(event.node)), height: 330 }); - res.setInput(element); - let listener = res.onDidPickElement(data => { + picker = createBreadcrumbsPicker(this._instantiationService, parent, element); + let listener = picker.onDidPickElement(data => { this._contextViewService.hideContextView(); this._widget.setFocused(undefined); this._widget.setSelection(undefined); @@ -285,7 +285,29 @@ export class BreadcrumbsControl { this._breadcrumbsPickerShowing = true; this._updateCkBreadcrumbsActive(); - return combinedDisposable([listener, res]); + return combinedDisposable([listener, picker]); + }, + getAnchor() { + + let pickerHeight = 330; + let pickerWidth = Math.max(220, dom.getTotalWidth(event.node)); + let pickerArrowSize = 8; + let pickerArrowOffset: number; + + let data = dom.getDomNodePagePosition(event.node.firstChild as HTMLElement); + let y = data.top + data.height - pickerArrowSize; + let x = data.left; + if (x + pickerWidth >= window.innerWidth) { + x = window.innerWidth - pickerWidth; + } + if (event.payload instanceof StandardMouseEvent) { + pickerArrowOffset = event.payload.posx - x - pickerArrowSize; + } else { + pickerArrowOffset = (data.left + (data.width * .3)) - x; + } + picker.layout(pickerHeight, pickerWidth, pickerArrowSize, Math.max(0, pickerArrowOffset)); + picker.setInput(element); + return { x, y }; }, onHide: () => { this._breadcrumbsPickerShowing = false; @@ -329,6 +351,26 @@ export class BreadcrumbsControl { //#region commands +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: 'breadcrumbs.focus', + title: localize('cmd.focus', "Focus Breadcrumbs") + } +}); +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '5_editor', + order: 99, + command: { + id: 'breadcrumbs.toggle', + title: localize('cmd.toggle', "Toggle Breadcrumbs") + } +}); +CommandsRegistry.registerCommand('breadcrumbs.toggle', accessor => { + let config = accessor.get(IConfigurationService); + let value = BreadcrumbsConfig.IsEnabled.bindTo(config).value; + BreadcrumbsConfig.IsEnabled.bindTo(config).value = !value; +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'breadcrumbs.focus', weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), @@ -405,5 +447,16 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ groups.activeGroup.activeControl.focus(); } }); - +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'breadcrumbs.pickFromTree', + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + primary: KeyCode.Tab, + when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive, WorkbenchListFocusContextKey), + handler(accessor) { + const list = accessor.get(IListService).lastFocusedList; + if (list instanceof Tree) { + list.setSelection([list.getFocus()]); + } + } +}); //#endregion diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index 39e2c016c23f8..f29efc47db18c 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -31,7 +31,7 @@ export class FileElement { ) { } } -export type BreadcrumbElement = FileElement | OutlineGroup | OutlineElement; +export type BreadcrumbElement = FileElement | OutlineModel | OutlineGroup | OutlineElement; type FileInfo = { path: FileElement[], folder: IWorkspaceFolder, showFolder: boolean }; @@ -43,7 +43,7 @@ export class EditorBreadcrumbsModel { private readonly _cfgFilePath: BreadcrumbsConfig<'on' | 'off' | 'last'>; private readonly _cfgSymbolPath: BreadcrumbsConfig<'on' | 'off' | 'last'>; - private _outlineElements: (OutlineGroup | OutlineElement)[] = []; + private _outlineElements: (OutlineModel | OutlineGroup | OutlineElement)[] = []; private _outlineDisposables: IDisposable[] = []; private _onDidUpdate = new Emitter(); @@ -181,11 +181,14 @@ export class EditorBreadcrumbsModel { }); } - private _getOutlineElements(model: OutlineModel, position: IPosition): (OutlineGroup | OutlineElement)[] { + private _getOutlineElements(model: OutlineModel, position: IPosition): (OutlineModel | OutlineGroup | OutlineElement)[] { if (!model) { return []; } let item: OutlineGroup | OutlineElement = model.getItemEnclosingPosition(position); + if (!item) { + return [model]; + } let chain: (OutlineGroup | OutlineElement)[] = []; while (item) { chain.push(item); @@ -201,14 +204,14 @@ export class EditorBreadcrumbsModel { return chain.reverse(); } - private _updateOutlineElements(elements: (OutlineGroup | OutlineElement)[]): void { + private _updateOutlineElements(elements: (OutlineModel | OutlineGroup | OutlineElement)[]): void { if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel._outlineElementEquals)) { this._outlineElements = elements; this._onDidUpdate.fire(this); } } - private static _outlineElementEquals(a: OutlineGroup | OutlineElement, b: OutlineGroup | OutlineElement): boolean { + private static _outlineElementEquals(a: OutlineModel | OutlineGroup | OutlineElement, b: OutlineModel | OutlineGroup | OutlineElement): boolean { if (a === b) { return true; } else if (!a || !b) { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 8f080b9e0e57a..5a49481c2eca6 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -18,51 +18,76 @@ import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/doc import { OutlineDataSource, OutlineItemComparator, OutlineRenderer } from 'vs/editor/contrib/documentSymbols/outlineTree'; import { localize } from 'vs/nls'; import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; import { HighlightingWorkbenchTree, IHighlightingTreeConfiguration, IHighlightingRenderer } from 'vs/platform/list/browser/listService'; import { IThemeService, DARK } from 'vs/platform/theme/common/themeService'; import { FileLabel } from 'vs/workbench/browser/labels'; import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { breadcrumbsActiveSelectionBackground } from 'vs/platform/theme/common/colorRegistry'; +import { breadcrumbsPickerBackground } from 'vs/platform/theme/common/colorRegistry'; import { FuzzyScore, createMatches, fuzzyScore } from 'vs/base/common/filters'; +export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker { + let ctor: IConstructorSignature1 = element instanceof FileElement ? BreadcrumbsFilePicker : BreadcrumbsOutlinePicker; + return instantiationService.createInstance(ctor, parent); +} + export abstract class BreadcrumbsPicker { protected readonly _disposables = new Array(); protected readonly _domNode: HTMLDivElement; - protected readonly _focus: dom.IFocusTracker; + protected readonly _arrow: HTMLDivElement; + protected readonly _treeContainer: HTMLDivElement; protected readonly _tree: HighlightingWorkbenchTree; + protected readonly _focus: dom.IFocusTracker; protected readonly _onDidPickElement = new Emitter(); readonly onDidPickElement: Event = this._onDidPickElement.event; constructor( - container: HTMLElement, + parent: HTMLElement, @IInstantiationService protected readonly _instantiationService: IInstantiationService, @IThemeService protected readonly _themeService: IThemeService, ) { this._domNode = document.createElement('div'); this._domNode.className = 'monaco-breadcrumbs-picker show-file-icons'; - const theme = this._themeService.getTheme(); - const color = theme.getColor(breadcrumbsActiveSelectionBackground); - this._domNode.style.background = color.toString(); - this._domNode.style.boxShadow = `0px 5px 8px ${(theme.type === DARK ? color.darken(.6) : color.darken(.2))}`; - container.appendChild(this._domNode); + parent.appendChild(this._domNode); this._focus = dom.trackFocus(this._domNode); this._focus.onDidBlur(_ => this._onDidPickElement.fire(undefined), undefined, this._disposables); + const theme = this._themeService.getTheme(); + const color = theme.getColor(breadcrumbsPickerBackground); + + this._arrow = document.createElement('div'); + this._arrow.style.width = '0'; + this._arrow.style.borderStyle = 'solid'; + this._arrow.style.borderWidth = '8px'; + this._arrow.style.borderColor = `transparent transparent ${color.toString()}`; + this._domNode.appendChild(this._arrow); + + this._treeContainer = document.createElement('div'); + this._treeContainer.style.background = color.toString(); + this._treeContainer.style.paddingTop = '2px'; + this._treeContainer.style.boxShadow = `0px 5px 8px ${(theme.type === DARK ? color.darken(.6) : color.darken(.2))}`; + this._domNode.appendChild(this._treeContainer); + const treeConifg = this._completeTreeConfiguration({ dataSource: undefined, renderer: undefined }); - this._tree = this._instantiationService.createInstance(HighlightingWorkbenchTree, this._domNode, treeConifg, {}, { placeholder: localize('placeholder', "Find") }); + this._tree = this._instantiationService.createInstance( + HighlightingWorkbenchTree, + this._treeContainer, + treeConifg, + { useShadows: false }, + { placeholder: localize('placeholder', "Find") } + ); this._disposables.push(this._tree.onDidChangeSelection(e => { if (e.payload !== this._tree) { setTimeout(_ => this._onDidChangeSelection(e)); // need to debounce here because this disposes the tree and the tree doesn't like to be disposed on click } })); - this._tree.domFocus(); + this._domNode.focus(); } dispose(): void { @@ -80,15 +105,25 @@ export abstract class BreadcrumbsPicker { this._tree.reveal(selection).then(() => { this._tree.setSelection([selection], this._tree); this._tree.setFocus(selection); + this._tree.domFocus(); }); + } else { + this._tree.focusFirst(); + this._tree.setSelection([this._tree.getFocus()], this._tree); + this._tree.domFocus(); } }, onUnexpectedError); } - layout(dim: dom.Dimension) { - this._domNode.style.width = `${dim.width}px`; - this._domNode.style.height = `${dim.height}px`; - this._tree.layout(dim.height, dim.width); + layout(height: number, width: number, arrowSize: number, arrowOffset: number) { + this._domNode.style.height = `${height}px`; + this._domNode.style.width = `${width}px`; + this._arrow.style.borderWidth = `${arrowSize}px`; + this._arrow.style.marginLeft = `${arrowOffset}px`; + + this._treeContainer.style.height = `${height - 2 * arrowSize}px`; + this._treeContainer.style.width = `${width}px`; + this._tree.layout(); } protected abstract _getInput(input: BreadcrumbElement): any; @@ -250,7 +285,7 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { } protected _getInitialSelection(_tree: ITree, input: BreadcrumbElement): any { - return input; + return input instanceof OutlineModel ? undefined : input; } protected _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration { diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 20167466e78aa..bd435ff853fa8 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -536,3 +536,138 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorComman MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeSavedEditors', "Close Saved Editors in Group"), category } }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOtherEditors', "Close Other Editors in Group"), category } }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRightEditors', "Close Editors to the Right in Group"), category } }); + +// File menu +MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { + group: '1_editor', + command: { + id: ReopenClosedEditorAction.ID, + title: nls.localize({ key: 'miReopenClosedEditor', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed Editor") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { + group: 'z_clear', + command: { + id: ClearRecentFilesAction.ID, + title: nls.localize({ key: 'miClearRecentOpen', comment: ['&& denotes a mnemonic'] }, "&&Clear Recently Opened") + }, + order: 1 +}); + +// Layout menu +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '2_appearance', + title: nls.localize({ key: 'miEditorLayout', comment: ['&& denotes a mnemonic'] }, "Editor &&Layout"), + submenu: MenuId.MenubarLayoutMenu, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { + group: '1_split', + command: { + id: editorCommands.SPLIT_EDITOR_UP, + title: nls.localize({ key: 'miSplitEditorUp', comment: ['&& denotes a mnemonic'] }, "Split &&Up") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { + group: '1_split', + command: { + id: editorCommands.SPLIT_EDITOR_DOWN, + title: nls.localize({ key: 'miSplitEditorDown', comment: ['&& denotes a mnemonic'] }, "Split &&Down") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { + group: '1_split', + command: { + id: editorCommands.SPLIT_EDITOR_LEFT, + title: nls.localize({ key: 'miSplitEditorLeft', comment: ['&& denotes a mnemonic'] }, "Split &&Left") + }, + order: 3 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { + group: '1_split', + command: { + id: editorCommands.SPLIT_EDITOR_RIGHT, + title: nls.localize({ key: 'miSplitEditorRight', comment: ['&& denotes a mnemonic'] }, "Split &&Right") + }, + order: 4 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { + group: '2_layouts', + command: { + id: EditorLayoutSingleAction.ID, + title: nls.localize({ key: 'miSingleColumnEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Single") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { + group: '2_layouts', + command: { + id: EditorLayoutTwoColumnsAction.ID, + title: nls.localize({ key: 'miTwoColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Two Columns") + }, + order: 3 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { + group: '2_layouts', + command: { + id: EditorLayoutThreeColumnsAction.ID, + title: nls.localize({ key: 'miThreeColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&hree Columns") + }, + order: 4 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { + group: '2_layouts', + command: { + id: EditorLayoutTwoRowsAction.ID, + title: nls.localize({ key: 'miTwoRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&wo Rows") + }, + order: 5 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { + group: '2_layouts', + command: { + id: EditorLayoutThreeRowsAction.ID, + title: nls.localize({ key: 'miThreeRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "Three &&Rows") + }, + order: 6 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { + group: '2_layouts', + command: { + id: EditorLayoutTwoByTwoGridAction.ID, + title: nls.localize({ key: 'miTwoByTwoGridEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Grid (2x2)") + }, + order: 7 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { + group: '2_layouts', + command: { + id: EditorLayoutTwoRowsRightAction.ID, + title: nls.localize({ key: 'miTwoRowsRightEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two R&&ows Right") + }, + order: 8 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { + group: '2_layouts', + command: { + id: EditorLayoutTwoColumnsBottomAction.ID, + title: nls.localize({ key: 'miTwoColumnsBottomEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two &&Columns Bottom") + }, + order: 9 +}); diff --git a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css index 649ceeb14c2a8..6020090acfc23 100644 --- a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css @@ -6,6 +6,7 @@ .monaco-workbench > .part.editor > .content .editor-group-container > .title > .label-container { display: flex; justify-content: flex-start; + align-items: center; overflow: hidden; flex: auto; } @@ -33,8 +34,6 @@ .monaco-workbench > .part.editor > .content .editor-group-container > .title .no-tabs-breadcrumbs.breadcrumbs-control { flex: 1 50%; overflow: hidden; - line-height: 35px; - height: 35px; padding: 0 6px; } diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 6217b034240c8..74218a0d7a963 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -268,6 +268,14 @@ } .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item { - padding-right: 4px; + max-width: 260px; } +.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child { + padding-right: 8px; +} + +/* .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:not(:last-child):not(:hover):not(.focused):not(.file) { + min-width: 33px; +} */ + diff --git a/src/vs/workbench/browser/parts/menubar/media/menubarpart.css b/src/vs/workbench/browser/parts/menubar/media/menubarpart.css index e1ee844a3356d..5b52a860a939f 100644 --- a/src/vs/workbench/browser/parts/menubar/media/menubarpart.css +++ b/src/vs/workbench/browser/parts/menubar/media/menubarpart.css @@ -6,7 +6,6 @@ .monaco-workbench > .part.menubar { display: flex; position: absolute; - font-size: 12px; box-sizing: border-box; padding-left: 35px; padding-right: 138px; @@ -39,9 +38,9 @@ } .menubar-menu-items-holder.monaco-menu-container { - box-shadow: 0 2px 8px #A8A8A8; + box-shadow: 0 2px 4px; } .vs-dark .menubar-menu-items-holder.monaco-menu-container { - box-shadow: 0 2px 8px #000; + box-shadow: 0 2px 4px; } \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts b/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts index d74f6c44577aa..6c2e0b4adfc20 100644 --- a/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts +++ b/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts @@ -6,188 +6,18 @@ import * as nls from 'vs/nls'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { isMacintosh } from 'vs/base/common/platform'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -recentMenuRegistration(); -fileMenuRegistration(); editMenuRegistration(); selectionMenuRegistration(); -viewMenuRegistration(); -appearanceMenuRegistration(); -layoutMenuRegistration(); goMenuRegistration(); -debugMenuRegistration(); -tasksMenuRegistration(); if (isMacintosh) { windowMenuRegistration(); } -preferencesMenuRegistration(); helpMenuRegistration(); -// Menu registration - File Menu -function fileMenuRegistration() { - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '1_new', - command: { - id: 'workbench.action.files.newUntitledFile', - title: nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '1_new', - command: { - id: 'workbench.action.newWindow', - title: nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '2_open', - command: { - id: 'workbench.action.files.openFile', - title: nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '2_open', - command: { - id: 'workbench.action.files.openFolder', - title: nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '2_open', - command: { - id: 'workbench.action.openWorkspace', - title: nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - title: nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent"), - submenu: MenuId.MenubarRecentMenu, - group: '2_open', - order: 4 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '3_workspace', - command: { - id: 'workbench.action.addRootFolder', - title: nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '3_workspace', - command: { - id: 'workbench.action.saveWorkspaceAs', - title: nls.localize('miSaveWorkspaceAs', "Save Workspace As...") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '4_save', - command: { - id: 'workbench.action.files.save', - title: nls.localize({ key: 'miSave', comment: ['&& denotes a mnemonic'] }, "&&Save") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '4_save', - command: { - id: 'workbench.action.files.saveAs', - title: nls.localize({ key: 'miSaveAs', comment: ['&& denotes a mnemonic'] }, "Save &&As...") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '4_save', - command: { - id: 'workbench.action.files.saveAll', - title: nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '5_autosave', - command: { - id: 'workbench.action.toggleAutoSave', - title: nls.localize('miAutoSave', "Auto Save") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - title: nls.localize({ key: 'miPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences"), - submenu: MenuId.MenubarPreferencesMenu, - group: '5_autosave', - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '6_close', - command: { - id: '', - title: nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Re&&vert File") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '6_close', - command: { - id: 'workbench.action.closeActiveEditor', - title: nls.localize({ key: 'miCloseEditor', comment: ['&& denotes a mnemonic'] }, "&&Close Editor") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '6_close', - command: { - id: 'workbench.action.closeFolder', - title: nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '6_close', - command: { - id: 'workbench.action.closeWindow', - title: nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Clos&&e Window") - }, - order: 4 - }); - - if (!isMacintosh) { - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: 'z_Exit', - command: { - id: 'workbench.action.quit', - title: nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit") - }, - order: 1 - }); - } -} +// Menu registration function editMenuRegistration() { MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { @@ -273,9 +103,6 @@ function editMenuRegistration() { }); - /// - - MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { group: '5_insert', command: { @@ -441,439 +268,6 @@ function selectionMenuRegistration() { }); } -function recentMenuRegistration() { - // Editor - MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { - group: '1_editor', - command: { - id: 'workbench.action.reopenClosedEditor', - title: nls.localize({ key: 'miReopenClosedEditor', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed Editor") - }, - order: 1 - }); - - // More - MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { - group: 'y_more', - command: { - id: 'workbench.action.openRecent', - title: nls.localize({ key: 'miMore', comment: ['&& denotes a mnemonic'] }, "&&More...") - }, - order: 1 - }); - - // Clear - MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { - group: 'z_clear', - command: { - id: 'workbench.action.clearRecentFiles', - title: nls.localize({ key: 'miClearRecentOpen', comment: ['&& denotes a mnemonic'] }, "&&Clear Recently Opened") - }, - order: 1 - }); - -} - -function viewMenuRegistration() { - - // Command Palette - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '1_open', - command: { - id: 'workbench.action.showCommands', - title: nls.localize({ key: 'miCommandPalette', comment: ['&& denotes a mnemonic'] }, "&&Command Palette...") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '1_open', - command: { - id: 'workbench.action.openView', - title: nls.localize({ key: 'miOpenView', comment: ['&& denotes a mnemonic'] }, "&&Open View...") - }, - order: 2 - }); - - // TODO: Appearance Submenu - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '2_appearance', - title: nls.localize({ key: 'miAppearance', comment: ['&& denotes a mnemonic'] }, "&&Appearance"), - submenu: MenuId.MenubarAppearanceMenu, - order: 1 - }); - - // TODO: Editor Layout Submenu - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '2_appearance', - title: nls.localize({ key: 'miEditorLayout', comment: ['&& denotes a mnemonic'] }, "Editor &&Layout"), - submenu: MenuId.MenubarLayoutMenu, - order: 2 - }); - - // Viewlets - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '3_views', - command: { - id: 'workbench.view.explorer', - title: nls.localize({ key: 'miViewExplorer', comment: ['&& denotes a mnemonic'] }, "&&Explorer") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '3_views', - command: { - id: 'workbench.view.search', - title: nls.localize({ key: 'miViewSearch', comment: ['&& denotes a mnemonic'] }, "&&Search") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '3_views', - command: { - id: 'workbench.view.scm', - title: nls.localize({ key: 'miViewSCM', comment: ['&& denotes a mnemonic'] }, "S&&CM") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '3_views', - command: { - id: 'workbench.view.debug', - title: nls.localize({ key: 'miViewDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug") - }, - order: 4 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '3_views', - command: { - id: 'workbench.view.extensions', - title: nls.localize({ key: 'miViewExtensions', comment: ['&& denotes a mnemonic'] }, "E&&xtensions") - }, - order: 5 - }); - - // Panels - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '4_panels', - command: { - id: 'workbench.action.output.toggleOutput', - title: nls.localize({ key: 'miToggleOutput', comment: ['&& denotes a mnemonic'] }, "&&Output") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '4_panels', - command: { - id: 'workbench.debug.action.toggleRepl', - title: nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "De&&bug Console") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '4_panels', - command: { - id: 'workbench.action.terminal.toggleTerminal', - title: nls.localize({ key: 'miToggleIntegratedTerminal', comment: ['&& denotes a mnemonic'] }, "&&Integrated Terminal") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '4_panels', - command: { - id: 'workbench.actions.view.problems', - title: nls.localize({ key: 'miMarker', comment: ['&& denotes a mnemonic'] }, "&&Problems") - }, - order: 4 - }); - - // Toggle Editor Settings - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '5_editor', - command: { - id: 'workbench.action.toggleWordWrap', - title: nls.localize({ key: 'miToggleWordWrap', comment: ['&& denotes a mnemonic'] }, "Toggle &&Word Wrap") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '5_editor', - command: { - id: 'workbench.action.toggleMinimap', - title: nls.localize({ key: 'miToggleMinimap', comment: ['&& denotes a mnemonic'] }, "Toggle &&Minimap") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '5_editor', - command: { - id: 'workbench.action.toggleRenderWhitespace', - title: nls.localize({ key: 'miToggleRenderWhitespace', comment: ['&& denotes a mnemonic'] }, "Toggle &&Render Whitespace") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '5_editor', - command: { - id: 'workbench.action.toggleRenderControlCharacters', - title: nls.localize({ key: 'miToggleRenderControlCharacters', comment: ['&& denotes a mnemonic'] }, "Toggle &&Control Characters") - }, - order: 4 - }); -} - -function appearanceMenuRegistration() { - MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '1_toggle_view', - command: { - id: 'workbench.action.toggleFullScreen', - title: nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '1_toggle_view', - command: { - id: 'workbench.action.toggleZenMode', - title: nls.localize('miToggleZenMode', "Toggle Zen Mode") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '1_toggle_view', - command: { - id: 'workbench.action.toggleCenteredLayout', - title: nls.localize('miToggleCenteredLayout', "Toggle Centered Layout") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '1_toggle_view', - command: { - id: 'workbench.action.toggleMenuBar', - title: nls.localize({ key: 'miToggleMenuBar', comment: ['&& denotes a mnemonic'] }, "Toggle Menu &&Bar") - }, - order: 4 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '2_workbench_layout', - command: { - id: 'workbench.action.toggleSidebarVisibility', - title: nls.localize({ key: 'miToggleSidebar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Side Bar") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '2_workbench_layout', - command: { - id: 'workbench.action.toggleSidebarPosition', - title: nls.localize({ key: 'miMoveSidebarLeftRight', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Left/Right") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '2_workbench_layout', - command: { - id: 'workbench.action.toggleStatusbarVisibility', - title: nls.localize({ key: 'miToggleStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Status Bar") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '2_workbench_layout', - command: { - id: 'workbench.action.toggleActivityBarVisibility', - title: nls.localize({ key: 'miToggleActivityBar', comment: ['&& denotes a mnemonic'] }, "Toggle &&Activity Bar") - }, - order: 4 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '2_workbench_layout', - command: { - id: 'workbench.action.togglePanel', - title: nls.localize({ key: 'miTogglePanel', comment: ['&& denotes a mnemonic'] }, "Toggle &&Panel") - }, - order: 5 - }); - - // Zoom - MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '3_zoom', - command: { - id: 'workbench.action.zoomIn', - title: nls.localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom In") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '3_zoom', - command: { - id: 'workbench.action.zoomOut', - title: nls.localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "&&Zoom Out") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '3_zoom', - command: { - id: 'workbench.action.zoomReset', - title: nls.localize({ key: 'miZoomReset', comment: ['&& denotes a mnemonic'] }, "&&Reset Zoom") - }, - order: 3 - }); -} - -function layoutMenuRegistration() { - // Split - MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: '1_split', - command: { - id: 'workbench.action.splitEditorUp', - title: nls.localize({ key: 'miSplitEditorUp', comment: ['&& denotes a mnemonic'] }, "Split &&Up") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: '1_split', - command: { - id: 'workbench.action.splitEditorDown', - title: nls.localize({ key: 'miSplitEditorDown', comment: ['&& denotes a mnemonic'] }, "Split &&Down") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: '1_split', - command: { - id: 'workbench.action.splitEditorLeft', - title: nls.localize({ key: 'miSplitEditorLeft', comment: ['&& denotes a mnemonic'] }, "Split &&Left") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: '1_split', - command: { - id: 'workbench.action.splitEditorRight', - title: nls.localize({ key: 'miSplitEditorRight', comment: ['&& denotes a mnemonic'] }, "Split &&Right") - }, - order: 4 - }); - - // Layouts - MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: '2_layouts', - command: { - id: 'workbench.action.editorLayoutSingle', - title: nls.localize({ key: 'miSingleColumnEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Single") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: '2_layouts', - command: { - id: 'workbench.action.editorLayoutCentered', - title: nls.localize({ key: 'miCenteredEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Centered") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: '2_layouts', - command: { - id: 'workbench.action.editorLayoutTwoColumns', - title: nls.localize({ key: 'miTwoColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Two Columns") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: '2_layouts', - command: { - id: 'workbench.action.editorLayoutThreeColumns', - title: nls.localize({ key: 'miThreeColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&hree Columns") - }, - order: 4 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: '2_layouts', - command: { - id: 'workbench.action.editorLayoutTwoRows', - title: nls.localize({ key: 'miTwoRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&wo Rows") - }, - order: 5 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: '2_layouts', - command: { - id: 'workbench.action.editorLayoutThreeRows', - title: nls.localize({ key: 'miThreeRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "Three &&Rows") - }, - order: 6 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: '2_layouts', - command: { - id: 'workbench.action.editorLayoutTwoByTwoGrid', - title: nls.localize({ key: 'miTwoByTwoGridEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Grid (2x2)") - }, - order: 7 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: '2_layouts', - command: { - id: 'workbench.action.editorLayoutTwoRowsRight', - title: nls.localize({ key: 'miTwoRowsRightEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two R&&ows Right") - }, - order: 8 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: '2_layouts', - command: { - id: 'workbench.action.editorLayoutTwoColumnsBottom', - title: nls.localize({ key: 'miTwoColumnsBottomEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two &&Columns Bottom") - }, - order: 9 - }); - - // Flip - MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: 'z_flip', - command: { - id: 'workbench.action.toggleEditorGroupLayout', - title: nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Flip &&Layout") - }, - order: 1 - }); - -} function goMenuRegistration() { // Forward/Back @@ -1052,313 +446,10 @@ function goMenuRegistration() { }); } -function debugMenuRegistration() { - // Start/Stop Debug - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '1_debug', - command: { - id: 'workbench.action.debug.start', - title: nls.localize({ key: 'miStartDebugging', comment: ['&& denotes a mnemonic'] }, "&&Start Debugging"), - precondition: ContextKeyExpr.not('inDebugMode') - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '1_debug', - command: { - id: 'workbench.action.debug.run', - title: nls.localize({ key: 'miStartWithoutDebugging', comment: ['&& denotes a mnemonic'] }, "Start &&Without Debugging"), - precondition: ContextKeyExpr.not('inDebugMode') - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '1_debug', - command: { - id: 'workbench.action.debug.stop', - title: nls.localize({ key: 'miStopDebugging', comment: ['&& denotes a mnemonic'] }, "&&Stop Debugging"), - precondition: ContextKeyExpr.has('inDebugMode') - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '1_debug', - command: { - id: 'workbench.action.debug.restart', - title: nls.localize({ key: 'miRestart Debugging', comment: ['&& denotes a mnemonic'] }, "&&Restart Debugging"), - precondition: ContextKeyExpr.has('inDebugMode') - }, - order: 4 - }); - - // Configuration - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '2_configuration', - command: { - id: 'workbench.action.debug.configure', - title: nls.localize({ key: 'miOpenConfigurations', comment: ['&& denotes a mnemonic'] }, "Open &&Configurations") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '2_configuration', - command: { - id: 'debug.addConfiguration', - title: nls.localize({ key: 'miAddConfiguration', comment: ['&& denotes a mnemonic'] }, "Add Configuration...") - }, - order: 2 - }); - - // Step Commands - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '3_step', - command: { - id: 'workbench.action.debug.stepOver', - title: nls.localize({ key: 'miStepOver', comment: ['&& denotes a mnemonic'] }, "Step &&Over"), - precondition: ContextKeyExpr.has('inDebugMode') - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '3_step', - command: { - id: 'workbench.action.debug.stepInto', - title: nls.localize({ key: 'miStepInto', comment: ['&& denotes a mnemonic'] }, "Step &&Into"), - precondition: ContextKeyExpr.has('inDebugMode') - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '3_step', - command: { - id: 'workbench.action.debug.stepOut', - title: nls.localize({ key: 'miStepOut', comment: ['&& denotes a mnemonic'] }, "Step O&&ut"), - precondition: ContextKeyExpr.has('inDebugMode') - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '3_step', - command: { - id: 'workbench.action.debug.continue', - title: nls.localize({ key: 'miContinue', comment: ['&& denotes a mnemonic'] }, "&&Continue"), - precondition: ContextKeyExpr.has('inDebugMode') - }, - order: 4 - }); - - // New Breakpoints - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '4_new_breakpoint', - command: { - id: 'editor.debug.action.toggleBreakpoint', - title: nls.localize({ key: 'miToggleBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breakpoint") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '4_new_breakpoint', - command: { - id: 'editor.debug.action.conditionalBreakpoint', - title: nls.localize({ key: 'miConditionalBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Conditional Breakpoint...") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '4_new_breakpoint', - command: { - id: 'editor.debug.action.toggleInlineBreakpoint', - title: nls.localize({ key: 'miInlineBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle Inline Breakp&&oint") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '4_new_breakpoint', - command: { - id: 'workbench.debug.viewlet.action.addFunctionBreakpointAction', - title: nls.localize({ key: 'miFunctionBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Function Breakpoint...") - }, - order: 4 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '4_new_breakpoint', - command: { - id: 'editor.debug.action.toggleLogPoint', - title: nls.localize({ key: 'miLogPoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Logpoint...") - }, - order: 5 - }); - - // Modify Breakpoints - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '5_breakpoints', - command: { - id: 'workbench.debug.viewlet.action.enableAllBreakpoints', - title: nls.localize({ key: 'miEnableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Enable All Breakpoints") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '5_breakpoints', - command: { - id: 'workbench.debug.viewlet.action.disableAllBreakpoints', - title: nls.localize({ key: 'miDisableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Disable A&&ll Breakpoints") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { - group: '5_breakpoints', - command: { - id: 'workbench.debug.viewlet.action.removeAllBreakpoints', - title: nls.localize({ key: 'miRemoveAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Remove &&All Breakpoints") - }, - order: 3 - }); - -} - -function tasksMenuRegistration() { - // Run Tasks - MenuRegistry.appendMenuItem(MenuId.MenubarTasksMenu, { - group: '1_run', - command: { - id: 'workbench.action.tasks.runTask', - title: nls.localize({ key: 'miRunTask', comment: ['&& denotes a mnemonic'] }, "&&Run Task...") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarTasksMenu, { - group: '1_run', - command: { - id: 'workbench.action.tasks.build', - title: nls.localize({ key: 'miBuildTask', comment: ['&& denotes a mnemonic'] }, "Run &&Build Task...") - }, - order: 2 - }); - - // Manage Tasks - MenuRegistry.appendMenuItem(MenuId.MenubarTasksMenu, { - group: '2_manage', - command: { - id: 'workbench.action.tasks.showTasks', - title: nls.localize({ key: 'miRunningTask', comment: ['&& denotes a mnemonic'] }, "Show Runnin&&g Tasks...") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarTasksMenu, { - group: '2_manage', - command: { - id: 'workbench.action.tasks.restartTask', - title: nls.localize({ key: 'miRestartTask', comment: ['&& denotes a mnemonic'] }, "R&&estart Running Task...") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarTasksMenu, { - group: '2_manage', - command: { - id: 'workbench.action.tasks.terminate', - title: nls.localize({ key: 'miTerminateTask', comment: ['&& denotes a mnemonic'] }, "&&Terminate Task...") - }, - order: 3 - }); - - // Configure Tasks - MenuRegistry.appendMenuItem(MenuId.MenubarTasksMenu, { - group: '3_configure', - command: { - id: 'workbench.action.tasks.configureTaskRunner', - title: nls.localize({ key: 'miConfigureTask', comment: ['&& denotes a mnemonic'] }, "&&Configure Tasks...") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarTasksMenu, { - group: '3_configure', - command: { - id: 'workbench.action.tasks.configureDefaultBuildTask', - title: nls.localize({ key: 'miConfigureBuildTask', comment: ['&& denotes a mnemonic'] }, "Configure De&&fault Build Task...") - }, - order: 2 - }); - -} - function windowMenuRegistration() { } -function preferencesMenuRegistration() { - MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - group: '1_settings', - command: { - id: 'workbench.action.openSettings2', - title: nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - group: '2_keybindings', - command: { - id: 'workbench.action.openGlobalKeybindings', - title: nls.localize({ key: 'miOpenKeymap', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - group: '2_keybindings', - command: { - id: 'workbench.extensions.action.showRecommendedKeymapExtensions', - title: nls.localize({ key: 'miOpenKeymapExtensions', comment: ['&& denotes a mnemonic'] }, "&&Keymap Extensions") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - group: '3_snippets', - command: { - id: 'workbench.action.openSnippets', - title: nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - group: '4_themes', - command: { - id: 'workbench.action.selectTheme', - title: nls.localize({ key: 'miSelectColorTheme', comment: ['&& denotes a mnemonic'] }, "&&Color Theme") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - group: '4_themes', - command: { - id: 'workbench.action.selectIconTheme', - title: nls.localize({ key: 'miSelectIconTheme', comment: ['&& denotes a mnemonic'] }, "File &&Icon Theme") - }, - order: 2 - }); -} - function helpMenuRegistration() { // Welcome MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { diff --git a/src/vs/workbench/browser/parts/menubar/menubarPart.ts b/src/vs/workbench/browser/parts/menubar/menubarPart.ts index 8bc34ec91f593..f1c8b0bba5010 100644 --- a/src/vs/workbench/browser/parts/menubar/menubarPart.ts +++ b/src/vs/workbench/browser/parts/menubar/menubarPart.ts @@ -1037,6 +1037,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const menuShadow = theme.getColor('widget.shadow'); + if (menuShadow) { + collector.addRule(` + .monaco-shell .monaco-workbench .monaco-menu-container { + box-shadow: 0 2px 4px ${menuShadow}; + } + `); + } + const menuBgColor = theme.getColor(MENU_BACKGROUND); if (menuBgColor) { collector.addRule(` @@ -1141,6 +1150,10 @@ class ModifierKeyEmitter extends Emitter { this._keyStatus.lastKeyReleased = undefined; } + if (this._keyStatus.lastKeyPressed !== this._keyStatus.lastKeyReleased) { + this._keyStatus.lastKeyPressed = undefined; + } + this._keyStatus.altKey = e.altKey; this._keyStatus.ctrlKey = e.ctrlKey; this._keyStatus.shiftKey = e.shiftKey; diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 654186a1c39cf..d3933cb7b9c78 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -10,7 +10,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Action } from 'vs/base/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actions'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IPartService, Parts, Position } from 'vs/workbench/services/part/common/partService'; @@ -177,3 +177,12 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMaximizedP actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL), 'View: Close Panel', nls.localize('view', "View")); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL), 'View: Toggle Panel Position', nls.localize('view', "View")); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, undefined), 'View: Toggle Panel Position', nls.localize('view', "View")); + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '2_workbench_layout', + command: { + id: TogglePanelAction.ID, + title: nls.localize({ key: 'miTogglePanel', comment: ['&& denotes a mnemonic'] }, "Toggle &&Panel") + }, + order: 5 +}); diff --git a/src/vs/workbench/electron-browser/commands.ts b/src/vs/workbench/electron-browser/commands.ts index 26468177c15ae..fd155abd84f88 100644 --- a/src/vs/workbench/electron-browser/commands.ts +++ b/src/vs/workbench/electron-browser/commands.ts @@ -32,6 +32,7 @@ function ensureDOMFocus(widget: ListWidget): void { } } +export const QUIT_ID = 'workbench.action.quit'; export function registerCommands(): void { function focusDown(accessor: ServicesAccessor, arg2?: number): void { @@ -537,7 +538,7 @@ export function registerCommands(): void { }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'workbench.action.quit', + id: QUIT_ID, weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), handler(accessor: ServicesAccessor) { const windowsService = accessor.get(IWindowsService); diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index d72216a4c8d14..847b5e6972fb3 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -14,8 +14,8 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; -import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenIssueReporterAction, ReportPerformanceIssueUsingReporterAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, ToggleMenuBarAction, CloseWorkspaceAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, NavigateUpAction, NavigateDownAction, NavigateLeftAction, NavigateRightAction, IncreaseViewSizeAction, DecreaseViewSizeAction, ShowStartupPerformance, ToggleSharedProcessAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, ShowAboutDialogAction, InspectContextKeysAction, OpenProcessExplorer, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction, ShowAccessibilityOptionsAction } from 'vs/workbench/electron-browser/actions'; -import { registerCommands } from 'vs/workbench/electron-browser/commands'; +import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenIssueReporterAction, ReportPerformanceIssueUsingReporterAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, ToggleMenuBarAction, CloseWorkspaceAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, NavigateUpAction, NavigateDownAction, NavigateLeftAction, NavigateRightAction, IncreaseViewSizeAction, DecreaseViewSizeAction, ShowStartupPerformance, ToggleSharedProcessAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, ShowAboutDialogAction, InspectContextKeysAction, OpenProcessExplorer, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction, ShowAccessibilityOptionsAction, OpenRecentAction } from 'vs/workbench/electron-browser/actions'; +import { registerCommands, QUIT_ID } from 'vs/workbench/electron-browser/commands'; import { AddRootFolderAction, GlobalRemoveRootFolderAction, OpenWorkspaceAction, SaveWorkspaceAsAction, OpenWorkspaceConfigFileAction, DuplicateWorkspaceInNewWindowAction, OpenFileFolderAction, OpenFileAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { inQuickOpenContext, getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; @@ -152,6 +152,172 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_R } }); +// Menu registration - file menu + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '1_new', + command: { + id: NewWindowAction.ID, + title: nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '2_open', + command: { + id: OpenFileAction.ID, + title: nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '2_open', + command: { + id: OpenFolderAction.ID, + title: nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '2_open', + command: { + id: OpenWorkspaceAction.ID, + title: nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...") + }, + order: 3 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + title: nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent"), + submenu: MenuId.MenubarRecentMenu, + group: '2_open', + order: 4 +}); + + +// More +MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { + group: 'y_more', + command: { + id: OpenRecentAction.ID, + title: nls.localize({ key: 'miMore', comment: ['&& denotes a mnemonic'] }, "&&More...") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '3_workspace', + command: { + id: AddRootFolderAction.ID, + title: nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '3_workspace', + command: { + id: SaveWorkspaceAsAction.ID, + title: nls.localize('miSaveWorkspaceAs', "Save Workspace As...") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + title: nls.localize({ key: 'miPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences"), + submenu: MenuId.MenubarPreferencesMenu, + group: '5_autosave', + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '6_close', + command: { + id: CloseWorkspaceAction.ID, + title: nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder") + }, + order: 3 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '6_close', + command: { + id: CloseCurrentWindowAction.ID, + title: nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Clos&&e Window") + }, + order: 4 +}); + +if (!isMacintosh) { + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: 'z_Exit', + command: { + id: QUIT_ID, + title: nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit") + }, + order: 1 + }); +} + +// Appereance menu +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '2_appearance', + title: nls.localize({ key: 'miAppearance', comment: ['&& denotes a mnemonic'] }, "&&Appearance"), + submenu: MenuId.MenubarAppearanceMenu, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '1_toggle_view', + command: { + id: ToggleFullScreenAction.ID, + title: nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '1_toggle_view', + command: { + id: ToggleMenuBarAction.ID, + title: nls.localize({ key: 'miToggleMenuBar', comment: ['&& denotes a mnemonic'] }, "Toggle Menu &&Bar") + }, + order: 4 +}); + +// Zoom + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '3_zoom', + command: { + id: ZoomInAction.ID, + title: nls.localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom In") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '3_zoom', + command: { + id: ZoomOutAction.ID, + title: nls.localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "&&Zoom Out") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '3_zoom', + command: { + id: ZoomResetAction.ID, + title: nls.localize({ key: 'miZoomReset', comment: ['&& denotes a mnemonic'] }, "&&Reset Zoom") + }, + order: 3 +}); + + // Configuration: Workbench const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); diff --git a/src/vs/workbench/electron-browser/media/shell.css b/src/vs/workbench/electron-browser/media/shell.css index 1f6a5fbdb7587..4911010c77c34 100644 --- a/src/vs/workbench/electron-browser/media/shell.css +++ b/src/vs/workbench/electron-browser/media/shell.css @@ -69,9 +69,13 @@ padding: .5em 0; } +.monaco-shell .monaco-menu .monaco-action-bar.vertical .action-menu-item { + height: 1.8em; +} + .monaco-shell .monaco-menu .monaco-action-bar.vertical .action-label:not(.separator), .monaco-shell .monaco-menu .monaco-action-bar.vertical .keybinding { - padding: 0.5em 2em; + padding: 0 1.5em; } .monaco-shell .monaco-menu .monaco-action-bar.vertical .action-label.separator { @@ -80,7 +84,7 @@ } .monaco-shell .monaco-menu .monaco-action-bar.vertical .submenu-indicator { - padding: 0.5em 1em; + padding: 0 1em; } .monaco-shell .monaco-menu .action-item { diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/toggleMinimap.ts b/src/vs/workbench/parts/codeEditor/electron-browser/toggleMinimap.ts index 8361e7b855bdd..36617f7b8c3d6 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/toggleMinimap.ts +++ b/src/vs/workbench/parts/codeEditor/electron-browser/toggleMinimap.ts @@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { Action } from 'vs/base/common/actions'; import { TPromise } from 'vs/base/common/winjs.base'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; export class ToggleMinimapAction extends Action { public static readonly ID = 'editor.action.toggleMinimap'; @@ -32,3 +32,12 @@ export class ToggleMinimapAction extends Action { const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMinimapAction, ToggleMinimapAction.ID, ToggleMinimapAction.LABEL), 'View: Toggle Minimap'); + +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '5_editor', + command: { + id: ToggleMinimapAction.ID, + title: nls.localize({ key: 'miToggleMinimap', comment: ['&& denotes a mnemonic'] }, "Toggle &&Minimap") + }, + order: 2 +}); diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderControlCharacter.ts b/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderControlCharacter.ts index 7c35e71b00c4b..42c4e6bf0686f 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderControlCharacter.ts +++ b/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderControlCharacter.ts @@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { Action } from 'vs/base/common/actions'; import { TPromise } from 'vs/base/common/winjs.base'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; export class ToggleRenderControlCharacterAction extends Action { @@ -33,3 +33,12 @@ export class ToggleRenderControlCharacterAction extends Action { const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleRenderControlCharacterAction, ToggleRenderControlCharacterAction.ID, ToggleRenderControlCharacterAction.LABEL), 'View: Toggle Control Characters'); + +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '5_editor', + command: { + id: ToggleRenderControlCharacterAction.ID, + title: nls.localize({ key: 'miToggleRenderControlCharacters', comment: ['&& denotes a mnemonic'] }, "Toggle &&Control Characters") + }, + order: 4 +}); diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderWhitespace.ts b/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderWhitespace.ts index 856e35dce6007..d9a61a863ebd3 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderWhitespace.ts +++ b/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderWhitespace.ts @@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { Action } from 'vs/base/common/actions'; import { TPromise } from 'vs/base/common/winjs.base'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; export class ToggleRenderWhitespaceAction extends Action { @@ -41,3 +41,12 @@ export class ToggleRenderWhitespaceAction extends Action { const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleRenderWhitespaceAction, ToggleRenderWhitespaceAction.ID, ToggleRenderWhitespaceAction.LABEL), 'View: Toggle Render Whitespace'); + +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '5_editor', + command: { + id: ToggleRenderWhitespaceAction.ID, + title: nls.localize({ key: 'miToggleRenderWhitespace', comment: ['&& denotes a mnemonic'] }, "Toggle &&Render Whitespace") + }, + order: 3 +}); diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/toggleWordWrap.ts b/src/vs/workbench/parts/codeEditor/electron-browser/toggleWordWrap.ts index bd18b1271c44f..882cd01e68bd4 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/toggleWordWrap.ts +++ b/src/vs/workbench/parts/codeEditor/electron-browser/toggleWordWrap.ts @@ -130,11 +130,12 @@ function applyWordWrapState(editor: ICodeEditor, state: IWordWrapState): void { }); } +const TOGGLE_WORD_WRAP_ID = 'editor.action.toggleWordWrap'; class ToggleWordWrapAction extends EditorAction { constructor() { super({ - id: 'editor.action.toggleWordWrap', + id: TOGGLE_WORD_WRAP_ID, label: nls.localize('toggle.wordwrap', "View: Toggle Word Wrap"), alias: 'View: Toggle Word Wrap', precondition: null, @@ -255,7 +256,7 @@ registerEditorAction(ToggleWordWrapAction); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { - id: 'editor.action.toggleWordWrap', + id: TOGGLE_WORD_WRAP_ID, title: nls.localize('unwrapMinified', "Disable wrapping for this file"), iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/parts/codeEditor/electron-browser/media/WordWrap_16x.svg')) } }, @@ -269,7 +270,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { - id: 'editor.action.toggleWordWrap', + id: TOGGLE_WORD_WRAP_ID, title: nls.localize('wrapMinified', "Enable wrapping for this file"), iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/parts/codeEditor/electron-browser/media/WordWrap_16x.svg')) } }, @@ -281,3 +282,14 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { ContextKeyExpr.not(isWordWrapMinifiedKey) ) }); + + +// View menu +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '5_editor', + command: { + id: TOGGLE_WORD_WRAP_ID, + title: nls.localize({ key: 'miToggleWordWrap', comment: ['&& denotes a mnemonic'] }, "Toggle &&Word Wrap") + }, + order: 1 +}); diff --git a/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts b/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts index f2a7ae587b254..41fd94bf268f8 100644 --- a/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts +++ b/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts @@ -25,7 +25,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IModelService } from 'vs/editor/common/services/modelService'; import { SimpleCommentEditor } from './simpleCommentEditor'; import URI from 'vs/base/common/uri'; -import { transparent, editorForeground, inputValidationErrorBorder, textLinkActiveForeground, textLinkForeground, focusBorder } from 'vs/platform/theme/common/colorRegistry'; +import { transparent, editorForeground, inputValidationErrorBorder, textLinkActiveForeground, textLinkForeground, focusBorder, textBlockQuoteBackground, textBlockQuoteBorder, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -477,8 +477,8 @@ export class ReviewZoneWidget extends ZoneWidget { } private setCommentEditorDecorations() { - if (this._commentEditor) { - let model = this._commentEditor.getModel(); + const model = this._commentEditor && this._commentEditor.getModel(); + if (model) { let valueLength = model.getValueLength(); const hasExistingComments = this._commentThread.comments.length > 0; let placeholder = valueLength > 0 ? '' : (hasExistingComments ? 'Reply...' : 'Type a new comment'); @@ -579,6 +579,23 @@ export class ReviewZoneWidget extends ZoneWidget { const focusColor = theme.getColor(focusBorder); if (focusColor) { content.push(`.monaco-editor .review-widget .body .review-comment a:focus { outline: 1px solid ${focusColor}; }`); + content.push(`.monaco-editor .review-widget .body .comment-form .monaco-editor.focused { outline: 1px solid ${focusColor}; }`); + } + + const blockQuoteBackground = theme.getColor(textBlockQuoteBackground); + if (blockQuoteBackground) { + content.push(`.monaco-editor .review-widget .body .review-comment blockquote { background: ${blockQuoteBackground}; }`); + } + + const blockQuoteBOrder = theme.getColor(textBlockQuoteBorder); + if (blockQuoteBOrder) { + content.push(`.monaco-editor .review-widget .body .review-comment blockquote { border-color: ${blockQuoteBOrder}; }`); + } + + const hcBorder = theme.getColor(contrastBorder); + if (hcBorder) { + content.push(`.monaco-editor .review-widget .body .comment-form .review-thread-reply-button { outline-color: ${hcBorder}; }`); + content.push(`.monaco-editor .review-widget .body .comment-form .monaco-editor { outline: 1px solid ${hcBorder}; }`); } this._styleElement.innerHTML = content.join('\n'); diff --git a/src/vs/workbench/parts/comments/electron-browser/commentsEditorContribution.ts b/src/vs/workbench/parts/comments/electron-browser/commentsEditorContribution.ts index c9613b32bad6a..1c35b032439f2 100644 --- a/src/vs/workbench/parts/comments/electron-browser/commentsEditorContribution.ts +++ b/src/vs/workbench/parts/comments/electron-browser/commentsEditorContribution.ts @@ -243,6 +243,7 @@ export class ReviewController implements IEditorContribution { this._commentWidgets = []; this.localToDispose.push(this.editor.onMouseMove(e => this.onEditorMouseMove(e))); + this.localToDispose.push(this.editor.onDidBlurEditorText(() => this.onDidBlurEditorText())); this.localToDispose.push(this.editor.onDidChangeModelContent(() => { if (this._newCommentGlyph) { this.editor.removeContentWidget(this._newCommentGlyph); @@ -317,6 +318,10 @@ export class ReviewController implements IEditorContribution { return; } + if (!this.editor.hasTextFocus()) { + return; + } + const hasCommentingRanges = this._commentInfos.length && this._commentInfos.some(info => !!info.commentingRanges.length); if (hasCommentingRanges && e.target.position && e.target.position.lineNumber !== undefined) { if (this._newCommentGlyph && e.target.element.className !== 'comment-hint') { @@ -338,6 +343,12 @@ export class ReviewController implements IEditorContribution { } } + private onDidBlurEditorText(): void { + if (this._newCommentGlyph) { + this.editor.removeContentWidget(this._newCommentGlyph); + } + } + private getNewCommentAction(line: number): { replyCommand: modes.Command, ownerId: number } { for (let i = 0; i < this._commentInfos.length; i++) { const commentInfo = this._commentInfos[i]; diff --git a/src/vs/workbench/parts/comments/electron-browser/media/review.css b/src/vs/workbench/parts/comments/electron-browser/media/review.css index b39b972ab61eb..dd28fb338ef09 100644 --- a/src/vs/workbench/parts/comments/electron-browser/media/review.css +++ b/src/vs/workbench/parts/comments/electron-browser/media/review.css @@ -11,10 +11,7 @@ } .monaco-editor .comment-hint{ - display: flex; - align-items: center; - justify-content: center; - height: 16px; + height: 20px; width: 20px; padding-left: 2px; background: url('comment.svg') center center no-repeat; @@ -38,6 +35,13 @@ display: flex; } +.monaco-editor .review-widget .body .review-comment blockquote { + margin: 0 7px 0 5px; + padding: 0 16px 0 10px; + border-left-width: 5px; + border-left-style: solid; +} + .monaco-editor .review-widget .body .review-comment .avatar-container { margin-top: 4px !important; } @@ -145,6 +149,7 @@ white-space: nowrap; border: 0px; cursor: text; + outline: 1px solid transparent; } .monaco-editor .review-widget .body .comment-form .review-thread-reply-button:focus { diff --git a/src/vs/workbench/parts/debug/browser/debugCommands.ts b/src/vs/workbench/parts/debug/browser/debugCommands.ts index bf06733430f35..bc62db248c546 100644 --- a/src/vs/workbench/parts/debug/browser/debugCommands.ts +++ b/src/vs/workbench/parts/debug/browser/debugCommands.ts @@ -11,7 +11,7 @@ import * as errors from 'vs/base/common/errors'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IListService } from 'vs/platform/list/browser/listService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_NOT_IN_DEBUG_REPL, CONTEXT_EXPRESSION_SELECTED, CONTEXT_BREAKPOINT_SELECTED } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, CONTEXT_BREAKPOINT_SELECTED } from 'vs/workbench/parts/debug/common/debug'; import { Expression, Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/parts/debug/common/debugModel'; import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -25,6 +25,9 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { InputFocusedContext } from 'vs/platform/workbench/common/contextkeys'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +export const ADD_CONFIGURATION_ID = 'debug.addConfiguration'; +export const TOGGLE_INLINE_BREAKPOINT_ID = 'editor.debug.action.toggleInlineBreakpoint'; + export function registerCommands(): void { KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -171,7 +174,7 @@ export function registerCommands(): void { }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'debug.addConfiguration', + id: ADD_CONFIGURATION_ID, weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), when: undefined, primary: undefined, @@ -196,7 +199,6 @@ export function registerCommands(): void { } }); - const INLINE_BREAKPOINT_COMMAND_ID = 'editor.debug.action.toggleInlineBreakpoint'; const inlineBreakpointHandler = (accessor: ServicesAccessor) => { const debugService = accessor.get(IDebugService); const editorService = accessor.get(IEditorService); @@ -221,23 +223,23 @@ export function registerCommands(): void { weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), primary: KeyMod.Shift | KeyCode.F9, when: EditorContextKeys.editorTextFocus, - id: INLINE_BREAKPOINT_COMMAND_ID, + id: TOGGLE_INLINE_BREAKPOINT_ID, handler: inlineBreakpointHandler }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { - id: INLINE_BREAKPOINT_COMMAND_ID, + id: TOGGLE_INLINE_BREAKPOINT_ID, title: nls.localize('inlineBreakpoint', "Inline Breakpoint"), category: nls.localize('debug', "Debug") } }); MenuRegistry.appendMenuItem(MenuId.EditorContext, { command: { - id: INLINE_BREAKPOINT_COMMAND_ID, + id: TOGGLE_INLINE_BREAKPOINT_ID, title: nls.localize('addInlineBreakpoint', "Add Inline Breakpoint") }, - when: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_NOT_IN_DEBUG_REPL, EditorContextKeys.writable), + when: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, EditorContextKeys.writable, EditorContextKeys.editorTextFocus), group: 'debug', order: 1 }); diff --git a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts b/src/vs/workbench/parts/debug/browser/debugEditorActions.ts index 4c871d8342b84..a8e56c2bc4c73 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugEditorActions.ts @@ -10,17 +10,18 @@ import { Range } from 'vs/editor/common/core/range'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ServicesAccessor, registerEditorAction, EditorAction, IActionOptions } from 'vs/editor/browser/editorExtensions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_NOT_IN_DEBUG_REPL, CONTEXT_DEBUG_STATE, State, REPL_ID, VIEWLET_ID, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, REPL_ID, VIEWLET_ID, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint } from 'vs/workbench/parts/debug/common/debug'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { openBreakpointSource } from 'vs/workbench/parts/debug/browser/breakpointsView'; +export const TOGGLE_BREAKPOINT_ID = 'editor.debug.action.toggleBreakpoint'; class ToggleBreakpointAction extends EditorAction { constructor() { super({ - id: 'editor.debug.action.toggleBreakpoint', + id: TOGGLE_BREAKPOINT_ID, label: nls.localize('toggleBreakpointAction', "Debug: Toggle Breakpoint"), alias: 'Debug: Toggle Breakpoint', precondition: null, @@ -49,11 +50,12 @@ class ToggleBreakpointAction extends EditorAction { } } +export const TOGGLE_CONDITIONAL_BREAKPOINT_ID = 'editor.debug.action.conditionalBreakpoint'; class ConditionalBreakpointAction extends EditorAction { constructor() { super({ - id: 'editor.debug.action.conditionalBreakpoint', + id: TOGGLE_CONDITIONAL_BREAKPOINT_ID, label: nls.localize('conditionalBreakpointEditorAction', "Debug: Add Conditional Breakpoint..."), alias: 'Debug: Add Conditional Breakpoint...', precondition: null @@ -70,11 +72,12 @@ class ConditionalBreakpointAction extends EditorAction { } } +export const TOGGLE_LOG_POINT_ID = 'editor.debug.action.toggleLogPoint'; class LogPointAction extends EditorAction { constructor() { super({ - id: 'editor.debug.action.toggleLogPoint', + id: TOGGLE_LOG_POINT_ID, label: nls.localize('logPointEditorAction', "Debug: Add Logpoint..."), alias: 'Debug: Add Logpoint...', precondition: null @@ -98,7 +101,7 @@ class RunToCursorAction extends EditorAction { id: 'editor.debug.action.runToCursor', label: nls.localize('runToCursor', "Run to Cursor"), alias: 'Debug: Run to Cursor', - precondition: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_NOT_IN_DEBUG_REPL, EditorContextKeys.writable, CONTEXT_DEBUG_STATE.isEqualTo('stopped')), + precondition: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, EditorContextKeys.writable, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), EditorContextKeys.editorTextFocus), menuOpts: { group: 'debug', order: 2 @@ -141,7 +144,7 @@ class SelectionToReplAction extends EditorAction { id: 'editor.debug.action.selectionToRepl', label: nls.localize('debugEvaluate', "Debug: Evaluate"), alias: 'Debug: Evaluate', - precondition: ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE, CONTEXT_NOT_IN_DEBUG_REPL), + precondition: ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus), menuOpts: { group: 'debug', order: 0 @@ -167,7 +170,7 @@ class SelectionToWatchExpressionsAction extends EditorAction { id: 'editor.debug.action.selectionToWatch', label: nls.localize('debugAddToWatch', "Debug: Add to Watch"), alias: 'Debug: Add to Watch', - precondition: ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE, CONTEXT_NOT_IN_DEBUG_REPL), + precondition: ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus), menuOpts: { group: 'debug', order: 1 diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 6d05e411b14d4..8db643264cde8 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -17,7 +17,7 @@ import { Position } from 'vs/editor/common/core/position'; import { ISuggestion } from 'vs/editor/common/modes'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; import { Range, IRange } from 'vs/editor/common/core/range'; -import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -36,11 +36,10 @@ export const BREAKPOINTS_VIEW_ID = 'workbench.debug.breakPointsView'; export const REPL_ID = 'workbench.panel.repl'; export const DEBUG_SERVICE_ID = 'debugService'; export const CONTEXT_DEBUG_TYPE = new RawContextKey('debugType', undefined); -export const CONTEXT_DEBUG_STATE = new RawContextKey('debugState', undefined); -export const CONTEXT_IN_DEBUG_MODE = new RawContextKey('inDebugMode', false); -export const CONTEXT_NOT_IN_DEBUG_MODE: ContextKeyExpr = CONTEXT_IN_DEBUG_MODE.toNegated(); +export const CONTEXT_DEBUG_STATE = new RawContextKey('debugState', 'inactive'); +export const CONTEXT_NOT_IN_DEBUG_MODE = CONTEXT_DEBUG_STATE.isEqualTo('inactive'); +export const CONTEXT_IN_DEBUG_MODE = CONTEXT_DEBUG_STATE.notEqualsTo('inactive'); export const CONTEXT_IN_DEBUG_REPL = new RawContextKey('inDebugRepl', false); -export const CONTEXT_NOT_IN_DEBUG_REPL: ContextKeyExpr = CONTEXT_IN_DEBUG_REPL.toNegated(); export const CONTEXT_BREAKPOINT_WIDGET_VISIBLE = new RawContextKey('breakpointWidgetVisible', false); export const CONTEXT_IN_BREAKPOINT_WIDGET = new RawContextKey('inBreakpointWidget', false); export const CONTEXT_BREAKPOINTS_FOCUSED = new RawContextKey('breakpointsFocused', true); diff --git a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts index a63113023d12e..a9b45955e1f74 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts @@ -30,14 +30,14 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { DebugEditorModelManager } from 'vs/workbench/parts/debug/browser/debugEditorModelManager'; import { StepOverAction, ClearReplAction, FocusReplAction, StepIntoAction, StepOutAction, StartAction, RestartAction, ContinueAction, StopAction, DisconnectAction, PauseAction, AddFunctionBreakpointAction, - ConfigureAction, DisableAllBreakpointsAction, EnableAllBreakpointsAction, RemoveAllBreakpointsAction, RunAction, ReapplyBreakpointsAction, SelectAndStartAction, TerminateThreadAction + ConfigureAction, DisableAllBreakpointsAction, EnableAllBreakpointsAction, RemoveAllBreakpointsAction, RunAction, ReapplyBreakpointsAction, SelectAndStartAction, TerminateThreadAction, ToggleReplAction } from 'vs/workbench/parts/debug/browser/debugActions'; import { DebugActionsWidget } from 'vs/workbench/parts/debug/browser/debugActionsWidget'; import * as service from 'vs/workbench/parts/debug/electron-browser/debugService'; import { DebugContentProvider } from 'vs/workbench/parts/debug/browser/debugContentProvider'; import 'vs/workbench/parts/debug/electron-browser/debugEditorContribution'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { registerCommands } from 'vs/workbench/parts/debug/browser/debugCommands'; +import { registerCommands, ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID } from 'vs/workbench/parts/debug/browser/debugCommands'; import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { StatusBarColorProvider } from 'vs/workbench/parts/debug/browser/statusbarColorProvider'; import { ViewsRegistry } from 'vs/workbench/common/views'; @@ -52,6 +52,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { LoadedScriptsView } from 'vs/workbench/parts/debug/browser/loadedScriptsView'; +import { TOGGLE_LOG_POINT_ID, TOGGLE_CONDITIONAL_BREAKPOINT_ID, TOGGLE_BREAKPOINT_ID } from 'vs/workbench/parts/debug/browser/debugEditorActions'; class OpenDebugViewletAction extends ToggleViewletAction { public static readonly ID = VIEWLET_ID; @@ -227,6 +228,200 @@ registerCommands(); const statusBar = Registry.as(StatusExtensions.Statusbar); statusBar.registerStatusbarItem(new StatusbarItemDescriptor(DebugStatus, StatusbarAlignment.LEFT, 30 /* Low Priority */)); +// View menu + +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '3_views', + command: { + id: VIEWLET_ID, + title: nls.localize({ key: 'miViewDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug") + }, + order: 4 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '4_panels', + command: { + id: ToggleReplAction.ID, + title: nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "De&&bug Console") + }, + order: 2 +}); + +// Debug menu + +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '1_debug', + command: { + id: StartAction.ID, + title: nls.localize({ key: 'miStartDebugging', comment: ['&& denotes a mnemonic'] }, "&&Start Debugging") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '1_debug', + command: { + id: RunAction.ID, + title: nls.localize({ key: 'miStartWithoutDebugging', comment: ['&& denotes a mnemonic'] }, "Start &&Without Debugging") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '1_debug', + command: { + id: StopAction.ID, + title: nls.localize({ key: 'miStopDebugging', comment: ['&& denotes a mnemonic'] }, "&&Stop Debugging"), + precondition: CONTEXT_IN_DEBUG_MODE + }, + order: 3 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '1_debug', + command: { + id: RestartAction.ID, + title: nls.localize({ key: 'miRestart Debugging', comment: ['&& denotes a mnemonic'] }, "&&Restart Debugging"), + precondition: CONTEXT_IN_DEBUG_MODE + }, + order: 4 +}); + +// Configuration +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '2_configuration', + command: { + id: ConfigureAction.ID, + title: nls.localize({ key: 'miOpenConfigurations', comment: ['&& denotes a mnemonic'] }, "Open &&Configurations") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '2_configuration', + command: { + id: ADD_CONFIGURATION_ID, + title: nls.localize({ key: 'miAddConfiguration', comment: ['&& denotes a mnemonic'] }, "Add Configuration...") + }, + order: 2 +}); + +// Step Commands +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '3_step', + command: { + id: StepOverAction.ID, + title: nls.localize({ key: 'miStepOver', comment: ['&& denotes a mnemonic'] }, "Step &&Over"), + precondition: CONTEXT_DEBUG_STATE.isEqualTo('stopped') + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '3_step', + command: { + id: StepIntoAction.ID, + title: nls.localize({ key: 'miStepInto', comment: ['&& denotes a mnemonic'] }, "Step &&Into"), + precondition: CONTEXT_DEBUG_STATE.isEqualTo('stopped') + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '3_step', + command: { + id: StepOutAction.ID, + title: nls.localize({ key: 'miStepOut', comment: ['&& denotes a mnemonic'] }, "Step O&&ut"), + precondition: CONTEXT_DEBUG_STATE.isEqualTo('stopped') + }, + order: 3 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '3_step', + command: { + id: ContinueAction.ID, + title: nls.localize({ key: 'miContinue', comment: ['&& denotes a mnemonic'] }, "&&Continue"), + precondition: CONTEXT_DEBUG_STATE.isEqualTo('stopped') + }, + order: 4 +}); + +// New Breakpoints +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '4_new_breakpoint', + command: { + id: TOGGLE_BREAKPOINT_ID, + title: nls.localize({ key: 'miToggleBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breakpoint") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '4_new_breakpoint', + command: { + id: TOGGLE_CONDITIONAL_BREAKPOINT_ID, + title: nls.localize({ key: 'miConditionalBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Conditional Breakpoint...") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '4_new_breakpoint', + command: { + id: TOGGLE_INLINE_BREAKPOINT_ID, + title: nls.localize({ key: 'miInlineBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle Inline Breakp&&oint") + }, + order: 3 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '4_new_breakpoint', + command: { + id: AddFunctionBreakpointAction.ID, + title: nls.localize({ key: 'miFunctionBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Function Breakpoint...") + }, + order: 4 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '4_new_breakpoint', + command: { + id: TOGGLE_LOG_POINT_ID, + title: nls.localize({ key: 'miLogPoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Logpoint...") + }, + order: 5 +}); + +// Modify Breakpoints +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '5_breakpoints', + command: { + id: EnableAllBreakpointsAction.ID, + title: nls.localize({ key: 'miEnableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Enable All Breakpoints") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '5_breakpoints', + command: { + id: DisableAllBreakpointsAction.ID, + title: nls.localize({ key: 'miDisableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Disable A&&ll Breakpoints") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { + group: '5_breakpoints', + command: { + id: RemoveAllBreakpointsAction.ID, + title: nls.localize({ key: 'miRemoveAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Remove &&All Breakpoints") + }, + order: 3 +}); + // Touch Bar if (isMacintosh) { diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index bc11ac3f751e9..637e1aed9df21 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -76,7 +76,6 @@ export class DebugService implements debug.IDebugService { private configurationManager: ConfigurationManager; private toDispose: lifecycle.IDisposable[]; private toDisposeOnSessionEnd: Map; - private inDebugMode: IContextKey; private debugType: IContextKey; private debugState: IContextKey; private breakpointsToSendOnResourceSaved: Set; @@ -120,7 +119,6 @@ export class DebugService implements debug.IDebugService { this.configurationManager = this.instantiationService.createInstance(ConfigurationManager); this.toDispose.push(this.configurationManager); - this.inDebugMode = debug.CONTEXT_IN_DEBUG_MODE.bindTo(contextKeyService); this.debugType = debug.CONTEXT_DEBUG_TYPE.bindTo(contextKeyService); this.debugState = debug.CONTEXT_DEBUG_STATE.bindTo(contextKeyService); @@ -909,7 +907,6 @@ export class DebugService implements debug.IDebugService { const resolved = configuration.resolved; resolved.__sessionId = sessionId; - this.inDebugMode.set(true); const dbg = this.configurationManager.getDebugger(resolved.type); return this.initializeRawSession(root, configuration, sessionId).then(session => { @@ -985,9 +982,6 @@ export class DebugService implements debug.IDebugService { if (this.model.getReplElements().length > 0) { this.panelService.openPanel(debug.REPL_ID, false).done(undefined, errors.onUnexpectedError); } - if (this.model.getSessions().length === 0) { - this.inDebugMode.reset(); - } this.showError(errorMessage, errors.isErrorWithActions(error) ? error.actions : []); return undefined; @@ -1209,7 +1203,6 @@ export class DebugService implements debug.IDebugService { this.updateStateAndEmit(raw.getId(), debug.State.Inactive); if (this.model.getSessions().length === 0) { - this.inDebugMode.reset(); this.debugType.reset(); this.viewModel.setMultiSessionView(false); diff --git a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts b/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts index 99931a7b9598b..3ff99dc07652c 100644 --- a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts @@ -309,7 +309,7 @@ class WatchExpressionsRenderer implements IRenderer { }); data.name.title = watchExpression.type ? watchExpression.type : watchExpression.value; - if (watchExpression.value) { + if (typeof watchExpression.value === 'string') { data.name.textContent += ':'; } } diff --git a/src/vs/workbench/parts/extensions/common/extensionQuery.ts b/src/vs/workbench/parts/extensions/common/extensionQuery.ts index 8fb85a6d48e87..4a3cc885ec5b1 100644 --- a/src/vs/workbench/parts/extensions/common/extensionQuery.ts +++ b/src/vs/workbench/parts/extensions/common/extensionQuery.ts @@ -3,12 +3,27 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + +import { flatten } from 'vs/base/common/arrays'; + export class Query { constructor(public value: string, public sortBy: string, public groupBy: string) { this.value = value.trim(); } + static autocompletions(): string[] { + const commands = ['installed', 'outdated', 'enabled', 'disabled', 'builtin', 'recommended', 'sort', 'category', 'tag', 'ext']; + const subcommands = { + 'sort': ['installs', 'rating', 'name'], + 'category': ['"programming languages"', 'snippets', 'linters', 'themes', 'debuggers', 'formatters', 'keymaps', '"scm providers"', 'other', '"extension packs"', '"language packs"'], + 'tag': [''], + 'ext': [''] + }; + + return flatten(commands.map(command => subcommands[command] ? subcommands[command].map(subcommand => `${command}:${subcommand}`) : [command])); + } + static parse(value: string): Query { let sortBy = ''; value = value.replace(/@sort:(\w+)(-\w*)?/g, (match, by: string, order: string) => { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts index 997b386f29b27..d7f89ad89158b 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts @@ -49,6 +49,7 @@ import { assign } from 'vs/base/common/objects'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionsTree, IExtensionData } from 'vs/workbench/parts/extensions/browser/extensionsViewer'; +import { ShowCurrentReleaseNotesAction } from 'vs/workbench/parts/update/electron-browser/update'; /** A context key that is set when an extension editor webview has focus. */ export const KEYBINDING_CONTEXT_EXTENSIONEDITOR_WEBVIEW_FOCUS = new RawContextKey('extensionEditorWebviewFocus', undefined); @@ -493,8 +494,11 @@ export class ExtensionEditor extends BaseEditor { this.activeWebview.contents = body; this.activeWebview.onDidClickLink(link => { + if (!link) { + return; + } // Whitelist supported schemes for links - if (link && ['http', 'https', 'mailto'].indexOf(link.scheme) >= 0) { + if (['http', 'https', 'mailto'].indexOf(link.scheme) >= 0 || (link.scheme === 'command' && link.path === ShowCurrentReleaseNotesAction.ID)) { this.openerService.open(link); } }, null, this.contentDisposables); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts index ea4c387478535..1415dd8bcfae1 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import * as errors from 'vs/base/common/errors'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionGalleryService, IExtensionTipsService, ExtensionsLabel, ExtensionsChannelId, PreferencesLabel } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; @@ -236,4 +236,26 @@ CommandsRegistry.registerCommand('_extensions.manage', (accessor: ServicesAccess if (extension.length === 1) { extensionService.open(extension[0]).done(null, errors.onUnexpectedError); } -}); \ No newline at end of file +}); + +// File menu registration + +MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '2_keybindings', + command: { + id: ShowRecommendedKeymapExtensionsAction.ID, + title: localize({ key: 'miOpenKeymapExtensions', comment: ['&& denotes a mnemonic'] }, "&&Keymap Extensions") + }, + order: 2 +}); + +// View menu + +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '3_views', + command: { + id: VIEWLET_ID, + title: localize({ key: 'miViewExtensions', comment: ['&& denotes a mnemonic'] }, "E&&xtensions") + }, + order: 5 +}); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index 507bf50decdb0..0c222ed08c5dc 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -6,21 +6,21 @@ 'use strict'; import 'vs/css!./media/extensionsViewlet'; +import uri from 'vs/base/common/uri'; +import * as modes from 'vs/editor/common/modes'; import { localize } from 'vs/nls'; import { ThrottledDelayer, always } from 'vs/base/common/async'; import { TPromise } from 'vs/base/common/winjs.base'; import { isPromiseCanceledError, onUnexpectedError, create as createError } from 'vs/base/common/errors'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { Event as EventOf, mapEvent, chain } from 'vs/base/common/event'; +import { Event as EventOf, Emitter, chain } from 'vs/base/common/event'; import { IAction } from 'vs/base/common/actions'; -import { domEvent } from 'vs/base/browser/event'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { append, $, addStandardDisposableListener, EventType, addClass, removeClass, toggleClass, Dimension } from 'vs/base/browser/dom'; +import { append, $, addClass, removeClass, toggleClass, Dimension } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -39,7 +39,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorG import Severity from 'vs/base/common/severity'; import { IActivityService, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { inputForeground, inputBackground, inputBorder } from 'vs/platform/theme/common/colorRegistry'; +import { inputForeground, inputBackground, inputBorder, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ViewsRegistry, IViewDescriptor } from 'vs/workbench/common/views'; import { ViewContainerViewlet, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; @@ -58,6 +58,13 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SingleServerExtensionManagementServerService } from 'vs/workbench/services/extensions/node/extensionManagementServerService'; +import { Query } from 'vs/workbench/parts/extensions/common/extensionQuery'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { Range } from 'vs/editor/common/core/range'; +import { Position } from 'vs/editor/common/core/position'; +import { ITextModel } from 'vs/editor/common/model'; interface SearchInputEvent extends Event { target: HTMLInputElement; @@ -252,12 +259,14 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio private searchDelayer: ThrottledDelayer; private root: HTMLElement; - private searchBox: HTMLInputElement; + private searchBox: CodeEditorWidget; private extensionsBox: HTMLElement; private primaryActions: IAction[]; private secondaryActions: IAction[]; private groupByServerAction: IAction; private disposables: IDisposable[] = []; + private monacoStyleContainer: HTMLDivElement; + private placeholderText: HTMLDivElement; constructor( @IPartService partService: IPartService, @@ -275,7 +284,8 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService + @IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService, + @IModelService private modelService: IModelService, ) { super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, partService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); @@ -299,6 +309,28 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)); } }, this, this.disposables); + + modes.SuggestRegistry.register({ scheme: 'extensions', pattern: '**/searchinput', hasAccessToAllModels: true }, { + triggerCharacters: ['@'], + provideCompletionItems: (model: ITextModel, position: Position, _context: modes.SuggestContext) => { + const sortKey = (item: string) => { + if (item.indexOf(':') === -1) { return 'a'; } + else if (/ext:/.test(item) || /tag:/.test(item)) { return 'b'; } + else if (/sort:/.test(item)) { return 'c'; } + else { return 'd'; } + }; + return { + suggestions: this.autoComplete(model.getValue(), position.column).map(item => ( + { + label: item.fullText, + insertText: item.fullText, + overwriteBefore: item.overwrite, + sortText: sortKey(item.fullText), + type: 'keyword' + })) + }; + } + }); } create(parent: HTMLElement): TPromise { @@ -306,31 +338,36 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.root = parent; const header = append(this.root, $('.header')); - - this.searchBox = append(header, $('input.search-box')); - this.searchBox.placeholder = localize('searchExtensions', "Search Extensions in Marketplace"); - this.disposables.push(addStandardDisposableListener(this.searchBox, EventType.FOCUS, () => addClass(this.searchBox, 'synthetic-focus'))); - this.disposables.push(addStandardDisposableListener(this.searchBox, EventType.BLUR, () => removeClass(this.searchBox, 'synthetic-focus'))); + this.monacoStyleContainer = append(header, $('.monaco-container')); + this.searchBox = this.instantiationService.createInstance(CodeEditorWidget, this.monacoStyleContainer, SEARCH_INPUT_OPTIONS, { isSimpleWidget: true }); + this.placeholderText = append(this.monacoStyleContainer, $('.search-placeholder', null, localize('searchExtensions', "Search Extensions in Marketplace"))); this.extensionsBox = append(this.root, $('.extensions')); - const onKeyDown = chain(domEvent(this.searchBox, 'keydown')) - .map(e => new StandardKeyboardEvent(e)); - onKeyDown.filter(e => e.keyCode === KeyCode.Escape).on(this.onEscape, this, this.disposables); + this.searchBox.setModel(this.modelService.createModel('', null, uri.parse('extensions:searchinput'), true)); - const onKeyDownForList = onKeyDown.filter(() => this.count() > 0); - onKeyDownForList.filter(e => e.keyCode === KeyCode.Enter).on(this.onEnter, this, this.disposables); + this.disposables.push(this.searchBox.onDidFocusEditorText(() => addClass(this.monacoStyleContainer, 'synthetic-focus'))); + this.disposables.push(this.searchBox.onDidBlurEditorText(() => removeClass(this.monacoStyleContainer, 'synthetic-focus'))); - const onSearchInput = domEvent(this.searchBox, 'input') as EventOf; - onSearchInput(e => this.triggerSearch(e.immediate), null, this.disposables); + const onKeyDownMonaco = chain(this.searchBox.onKeyDown); + onKeyDownMonaco.filter(e => e.keyCode === KeyCode.Enter).on(e => e.preventDefault(), this, this.disposables); + onKeyDownMonaco.filter(e => e.keyCode === KeyCode.DownArrow).on(() => this.focusListView(), this, this.disposables); - this.onSearchChange = mapEvent(onSearchInput, e => e.target.value); + const searchChangeEvent = new Emitter(); + this.onSearchChange = searchChangeEvent.event; + + this.disposables.push(this.searchBox.getModel().onDidChangeContent(() => { + this.triggerSearch(); + const content = this.searchBox.getValue(); + searchChangeEvent.fire(content); + this.placeholderText.style.visibility = content ? 'hidden' : 'visible'; + })); return super.create(this.extensionsBox) .then(() => this.extensionManagementService.getInstalled(LocalExtensionType.User)) .then(installed => { if (installed.length === 0) { - this.searchBox.value = '@sort:installs'; + this.searchBox.setValue('@sort:installs'); this.searchExtensionsContextKey.set(true); } }); @@ -339,13 +376,19 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio public updateStyles(): void { super.updateStyles(); - this.searchBox.style.backgroundColor = this.getColor(inputBackground); - this.searchBox.style.color = this.getColor(inputForeground); + this.monacoStyleContainer.style.backgroundColor = this.getColor(inputBackground); + this.monacoStyleContainer.style.color = this.getColor(inputForeground); + this.placeholderText.style.color = this.getColor(inputPlaceholderForeground); const inputBorderColor = this.getColor(inputBorder); - this.searchBox.style.borderWidth = inputBorderColor ? '1px' : null; - this.searchBox.style.borderStyle = inputBorderColor ? 'solid' : null; - this.searchBox.style.borderColor = inputBorderColor; + this.monacoStyleContainer.style.borderWidth = inputBorderColor ? '1px' : null; + this.monacoStyleContainer.style.borderStyle = inputBorderColor ? 'solid' : null; + this.monacoStyleContainer.style.borderColor = inputBorderColor; + + let cursor = this.monacoStyleContainer.getElementsByClassName('cursor')[0] as HTMLDivElement; + if (cursor) { + cursor.style.backgroundColor = this.getColor(inputForeground); + } } setVisible(visible: boolean): TPromise { @@ -354,7 +397,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio if (isVisibilityChanged) { if (visible) { this.searchBox.focus(); - this.searchBox.setSelectionRange(0, this.searchBox.value.length); + this.searchBox.setSelection(new Range(1, 1, 1, this.searchBox.getValue().length + 1)); } } }); @@ -366,6 +409,9 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio layout(dimension: Dimension): void { toggleClass(this.root, 'narrow', dimension.width <= 300); + this.searchBox.layout({ height: 20, width: dimension.width - 30 }); + this.placeholderText.style.width = '' + (dimension.width - 30) + 'px'; + super.layout(new Dimension(dimension.width, dimension.height - 38)); } @@ -420,17 +466,19 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio const event = new Event('input', { bubbles: true }) as SearchInputEvent; event.immediate = true; - this.searchBox.value = value; - this.searchBox.dispatchEvent(event); + this.searchBox.setValue(value); } private triggerSearch(immediate = false): void { - this.searchDelayer.trigger(() => this.doSearch(), immediate || !this.searchBox.value ? 0 : 500) - .done(null, err => this.onError(err)); + this.searchDelayer.trigger(() => this.doSearch(), immediate || !this.searchBox.getValue() ? 0 : 500).done(null, err => this.onError(err)); + } + + private normalizedQuery(): string { + return (this.searchBox.getValue() || '').replace(/@category/g, 'category').replace(/@tag:/g, 'tag:').replace(/@ext:/g, 'ext:'); } private doSearch(): TPromise { - const value = this.searchBox.value || ''; + const value = this.normalizedQuery(); this.searchExtensionsContextKey.set(!!value); this.searchInstalledExtensionsContextKey.set(InstalledExtensionsView.isInstalledExtensionsQuery(value)); this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value)); @@ -439,14 +487,14 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.nonEmptyWorkspaceContextKey.set(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY); if (value) { - return this.progress(TPromise.join(this.panels.map(view => (view).show(this.searchBox.value)))); + return this.progress(TPromise.join(this.panels.map(view => (view).show(this.normalizedQuery())))); } return TPromise.as(null); } protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] { const addedViews = super.onDidAddViews(added); - this.progress(TPromise.join(addedViews.map(addedView => (addedView).show(this.searchBox.value)))); + this.progress(TPromise.join(addedViews.map(addedView => (addedView).show(this.normalizedQuery())))); return addedViews; } @@ -464,16 +512,23 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio return this.instantiationService.createInstance(viewDescriptor.ctor, options) as ViewletPanel; } - private count(): number { - return this.panels.reduce((count, view) => (view).count() + count, 0); + private autoComplete(query: string, position: number): { fullText: string, overwrite: number }[] { + if (query.lastIndexOf('@', position - 1) !== query.lastIndexOf(' ', position - 1) + 1) { return []; } + + let wordStart = query.lastIndexOf('@', position - 1) + 1; + let alreadyTypedCount = position - wordStart - 1; + + return Query.autocompletions().map(replacement => ({ fullText: replacement, overwrite: alreadyTypedCount })); } - private onEscape(): void { - this.search(''); + private count(): number { + return this.panels.reduce((count, view) => (view).count() + count, 0); } - private onEnter(): void { - (this.panels[0]).select(); + private focusListView(): void { + if (this.count() > 0) { + this.panels[0].focus(); + } } private onViewletOpen(viewlet: IViewlet): void { @@ -607,4 +662,34 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution { dispose(): void { this.disposables = dispose(this.disposables); } -} \ No newline at end of file +} + +let SEARCH_INPUT_OPTIONS: IEditorOptions = +{ + fontSize: 13, + lineHeight: 22, + wordWrap: 'off', + overviewRulerLanes: 0, + glyphMargin: false, + lineNumbers: 'off', + folding: false, + selectOnLineNumbers: false, + hideCursorInOverviewRuler: true, + selectionHighlight: false, + scrollbar: { + horizontal: 'hidden', + vertical: 'hidden' + }, + ariaLabel: localize('searchExtensions', "Search Extensions in Marketplace"), + cursorWidth: 1, + lineDecorationsWidth: 0, + overviewRulerBorder: false, + scrollBeyondLastLine: false, + renderLineHighlight: 'none', + fixedOverflowWidgets: true, + acceptSuggestionOnEnter: 'smart', + minimap: { + enabled: false + }, + fontFamily: ' -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif' +}; diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts index 6e18d8391ed41..02cd5b25c9fd5 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts @@ -640,6 +640,14 @@ export class ExtensionsListView extends ViewletPanel { static isKeymapsRecommendedExtensionsQuery(query: string): boolean { return /@recommended:keymaps/i.test(query); } + + focus(): void { + super.focus(); + if (!(this.list.getFocus().length || this.list.getSelection().length)) { + this.list.focusNext(); + } + this.list.domFocus(); + } } export class InstalledExtensionsView extends ExtensionsListView { diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css b/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css index a83f6a0c066d4..98391d7599edf 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css @@ -197,6 +197,32 @@ opacity: 0.9; } +.extensions-viewlet .header .monaco-container { + padding: 3px 4px 5px; +} + +.extensions-viewlet .header .monaco-container .suggest-widget { + width: 275px; +} + +.extensions-viewlet .header .monaco-container .monaco-editor-background, +.extensions-viewlet .header .monaco-container .monaco-editor, +.extensions-viewlet .header .monaco-container .mtk1 { + /* allow the embedded monaco to be styled from the outer context */ + background-color: inherit; + color: inherit; +} + +.extensions-viewlet .header .search-placeholder { + position: absolute; + z-index: 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + pointer-events: none; + margin-top: 2px; +} + .vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .bookmark, .vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .bookmark, .vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .icon, diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index e7ccaedb3f64e..a492f570789d3 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -276,7 +276,7 @@ ${this.description} if (!changelogUrl) { if (this.type === LocalExtensionType.System) { - return TPromise.as(nls.localize('checkReleaseNotes', 'Please check the [VS Code Release Notes](https://code.visualstudio.com/updates) for changes to the built-in extensions.')); + return TPromise.as('Please check the [VS Code Release Notes](command:update.showCurrentReleaseNotes) for changes to the built-in extensions.'); } return TPromise.wrapError(new Error('not available')); diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts index 926f041931e7f..1c34aeeffa7d2 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts @@ -16,7 +16,7 @@ import { IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, IExtensionTipsService, ILocalExtension, LocalExtensionType, IGalleryExtension, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, EnablementState, InstallOperation, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { getGalleryExtensionId, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService, getLocalExtensionIdFromGallery, getLocalExtensionIdFromManifest } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService'; import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/common/extensionEnablementService.test'; @@ -79,12 +79,12 @@ suite('ExtensionsActions Test', () => { instantiationService.stub(IURLService, URLService); }); - setup(() => { + setup(async () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); instantiationService.stubPromise(IExtensionManagementService, 'getExtensionsReport', []); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); instantiationService.stub(IExtensionService, { getExtensions: () => TPromise.wrap([]) }); - (instantiationService.get(IExtensionEnablementService)).reset(); + await (instantiationService.get(IExtensionEnablementService)).reset(); instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService)); }); @@ -1207,6 +1207,7 @@ suite('ExtensionsActions Test', () => { assign(localExtension.manifest, { name, publisher: 'pub', version: '1.0.0' }, manifest); localExtension.identifier = { id: getLocalExtensionIdFromManifest(localExtension.manifest) }; localExtension.metadata = { id: localExtension.identifier.id, publisherId: localExtension.manifest.publisher, publisherDisplayName: 'somename' }; + localExtension.galleryIdentifier = { id: getGalleryExtensionIdFromLocal(localExtension), uuid: void 0 }; return localExtension; } diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index f3f0e333ec457..704d31d312aa7 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -17,7 +17,7 @@ import { IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, IExtensionTipsService, ILocalExtension, LocalExtensionType, IGalleryExtension, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, EnablementState, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { getGalleryExtensionId, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService, getLocalExtensionIdFromGallery, getLocalExtensionIdFromManifest } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService'; import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/common/extensionEnablementService.test'; @@ -82,13 +82,13 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stub(IDialogService, { show: () => TPromise.as(0) }); }); - setup(() => { + setup(async () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); instantiationService.stubPromise(IExtensionManagementService, 'getExtensionsReport', []); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); instantiationService.stub(IDialogService, { show: () => TPromise.as(0) }); instantiationService.stubPromise(INotificationService, 'prompt', 0); - (instantiationService.get(IExtensionEnablementService)).reset(); + await (instantiationService.get(IExtensionEnablementService)).reset(); }); teardown(() => { @@ -1238,6 +1238,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { assign(localExtension.manifest, { name, publisher: 'pub', version: '1.0.0' }, manifest); localExtension.identifier = { id: getLocalExtensionIdFromManifest(localExtension.manifest) }; localExtension.metadata = { id: localExtension.identifier.id, publisherId: localExtension.manifest.publisher, publisherDisplayName: 'somename' }; + localExtension.galleryIdentifier = { id: getGalleryExtensionIdFromLocal(localExtension), uuid: void 0 }; return localExtension; } diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts index 9f88aa03a215f..36e761a3e9b4c 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts @@ -476,5 +476,71 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { }); // Empty Editor Group Context Menu -MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: 'workbench.action.files.newUntitledFile', title: nls.localize('newFile', "New File") }, group: '1_file', order: 10 }); +MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: GlobalNewUntitledFileAction.ID, title: nls.localize('newFile', "New File") }, group: '1_file', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: 'workbench.action.quickOpen', title: nls.localize('openFile', "Open File...") }, group: '1_file', order: 20 }); + +// File menu + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '1_new', + command: { + id: GlobalNewUntitledFileAction.ID, + title: nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '4_save', + command: { + id: SAVE_FILE_COMMAND_ID, + title: nls.localize({ key: 'miSave', comment: ['&& denotes a mnemonic'] }, "&&Save") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '4_save', + command: { + id: SAVE_FILE_AS_COMMAND_ID, + title: nls.localize({ key: 'miSaveAs', comment: ['&& denotes a mnemonic'] }, "Save &&As...") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '4_save', + command: { + id: SaveAllAction.ID, + title: nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll") + }, + order: 3 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '5_autosave', + command: { + id: ToggleAutoSaveAction.ID, + title: nls.localize('miAutoSave', "Auto Save") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '6_close', + command: { + id: REVERT_FILE_COMMAND_ID, + title: nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Re&&vert File"), + precondition: DirtyEditorContext + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '6_close', + command: { + id: CLOSE_EDITOR_COMMAND_ID, + title: nls.localize({ key: 'miCloseEditor', comment: ['&& denotes a mnemonic'] }, "&&Close Editor") + }, + order: 2 +}); diff --git a/src/vs/workbench/parts/files/electron-browser/files.contribution.ts b/src/vs/workbench/parts/files/electron-browser/files.contribution.ts index c152f150f4d33..6d90810ce94a4 100644 --- a/src/vs/workbench/parts/files/electron-browser/files.contribution.ts +++ b/src/vs/workbench/parts/files/electron-browser/files.contribution.ts @@ -8,7 +8,7 @@ import URI from 'vs/base/common/uri'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ToggleViewletAction } from 'vs/workbench/browser/viewlet'; import * as nls from 'vs/nls'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; @@ -192,7 +192,7 @@ configurationRegistry.registerConfiguration({ }, 'files.associations': { 'type': 'object', - 'description': nls.localize('associations', "Configure file associations to languages (e.g. \"*.extension\": \"html\"). These have precedence over the default associations of the languages installed."), + 'description': nls.localize('associations', "Configure file associations to languages (e.g. `\"*.extension\": \"html\"`). These have precedence over the default associations of the languages installed."), }, 'files.encoding': { 'type': 'string', @@ -246,17 +246,17 @@ configurationRegistry.registerConfiguration({ 'enum': [AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE], 'enumDescriptions': [ nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.off' }, "A dirty file is never automatically saved."), - nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.afterDelay' }, "A dirty file is automatically saved after the configured 'files.autoSaveDelay'."), + nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.afterDelay' }, "A dirty file is automatically saved after the configured [`files.autoSaveDelay`](#files.autoSaveDelay)."), nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.onFocusChange' }, "A dirty file is automatically saved when the editor loses focus."), nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.onWindowChange' }, "A dirty file is automatically saved when the window loses focus.") ], 'default': AutoSaveConfiguration.OFF, - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSave' }, "Controls auto save of dirty files. Accepted values: '{0}', '{1}', '{2}' (editor loses focus), '{3}' (window loses focus). If set to '{4}', you can configure the delay in [`files.autoSaveDelay`](#files.autoSaveDelay). Read more about autosave [here](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save)", AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE, AutoSaveConfiguration.AFTER_DELAY) + 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSave' }, "Controls auto save of dirty files. Read more about autosave [here](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save).", AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE, AutoSaveConfiguration.AFTER_DELAY) }, 'files.autoSaveDelay': { 'type': 'number', 'default': 1000, - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSaveDelay' }, "Controls the delay in ms after which a dirty file is saved automatically. Only applies when [`files.autoSave`](#files.autoSave) is set to '{0}'", AutoSaveConfiguration.AFTER_DELAY) + 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSaveDelay' }, "Controls the delay in ms after which a dirty file is saved automatically. Only applies when [`files.autoSave`](#files.autoSave) is set to `{0}`.", AutoSaveConfiguration.AFTER_DELAY) }, 'files.watcherExclude': { 'type': 'object', @@ -308,7 +308,7 @@ configurationRegistry.registerConfiguration({ 'editor.formatOnSaveTimeout': { 'type': 'number', 'default': 750, - 'description': nls.localize('formatOnSaveTimeout', "Format on save timeout. Specifies a time limit in milliseconds for formatOnSave-commands. Commands taking longer than the specified timeout will be cancelled."), + 'description': nls.localize('formatOnSaveTimeout', "Format on save timeout. Specifies a time limit in milliseconds for `formatOnSave`-commands. Commands taking longer than the specified timeout will be cancelled."), 'overridable': true, 'scope': ConfigurationScope.RESOURCE } @@ -371,3 +371,13 @@ configurationRegistry.registerConfiguration({ }, } }); + +// View menu +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '3_views', + command: { + id: VIEWLET_ID, + title: nls.localize({ key: 'miViewExplorer', comment: ['&& denotes a mnemonic'] }, "&&Explorer") + }, + order: 1 +}); diff --git a/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css b/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css index eb657380a6774..c3fc2504f7076 100644 --- a/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css +++ b/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css @@ -59,7 +59,7 @@ } .explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row:hover > .monaco-action-bar, -.explorer-viewlet .explorer-open-editors .monaco-list.focused .monaco-list-row.focused > .monaco-action-bar, +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.focused > .monaco-action-bar, .explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty > .monaco-action-bar { visibility: visible; } diff --git a/src/vs/workbench/parts/markers/electron-browser/markers.contribution.ts b/src/vs/workbench/parts/markers/electron-browser/markers.contribution.ts index 789c79ff77b83..b9ba41a1e7aae 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markers.contribution.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markers.contribution.ts @@ -212,3 +212,12 @@ function registerAction(desc: IActionDescriptor) { }); } } + +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '4_panels', + command: { + id: ToggleMarkersPanelAction.ID, + title: localize({ key: 'miMarker', comment: ['&& denotes a mnemonic'] }, "&&Problems") + }, + order: 4 +}); diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index 0436d143f3b3c..4b8f13c73de75 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -98,7 +98,8 @@ export class Renderer implements IRenderer { constructor( @IInstantiationService private instantiationService: IInstantiationService, @IThemeService private themeService: IThemeService, - @IEnvironmentService private environmentService: IEnvironmentService + @IEnvironmentService private environmentService: IEnvironmentService, + @IWorkspaceContextService private contextService: IWorkspaceContextService ) { } @@ -203,7 +204,7 @@ export class Renderer implements IRenderer { if (templateData.resourceLabel instanceof FileLabel) { templateData.resourceLabel.setFile(element.uri, { matches: element.uriMatches }); } else { - templateData.resourceLabel.setLabel({ name: element.name, description: getPathLabel(element.uri, this.environmentService), resource: element.uri }, { matches: element.uriMatches }); + templateData.resourceLabel.setLabel({ name: element.name, description: getPathLabel(element.uri, this.environmentService, this.contextService), resource: element.uri }, { matches: element.uriMatches }); } (templateData).count.setCount(element.filteredCount); } @@ -230,7 +231,7 @@ export class Renderer implements IRenderer { private renderRelatedInfoElement(tree: ITree, element: RelatedInformation, templateData: IRelatedInformationTemplateData) { templateData.resourceLabel.set(paths.basename(element.raw.resource.fsPath), element.uriMatches); - templateData.resourceLabel.element.title = getPathLabel(element.raw.resource, this.environmentService); + templateData.resourceLabel.element.title = getPathLabel(element.raw.resource, this.environmentService, this.contextService); templateData.lnCol.textContent = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(element.raw.startLineNumber, element.raw.startColumn); templateData.description.set(element.raw.message, element.messageMatches); templateData.description.element.title = element.raw.message; diff --git a/src/vs/workbench/parts/output/electron-browser/output.contribution.ts b/src/vs/workbench/parts/output/electron-browser/output.contribution.ts index 63be0b0f1c2db..4442ede409cdc 100644 --- a/src/vs/workbench/parts/output/electron-browser/output.contribution.ts +++ b/src/vs/workbench/parts/output/electron-browser/output.contribution.ts @@ -187,3 +187,12 @@ CommandsRegistry.registerCommand(COMMAND_OPEN_LOG_VIEWER, function (accessor: Se } return null; }); + +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '4_panels', + command: { + id: ToggleOutputAction.ID, + title: nls.localize({ key: 'miToggleOutput', comment: ['&& denotes a mnemonic'] }, "&&Output") + }, + order: 1 +}); diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index 55c7c3a841b7e..bb396a8d92bb7 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -20,18 +20,19 @@ import * as objects from 'vs/base/common/objects'; import { escapeRegExpCharacters, startsWith } from 'vs/base/common/strings'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IAccessibilityProvider, IDataSource, IFilter, IRenderer, ITree } from 'vs/base/parts/tree/browser/tree'; +import { IAccessibilityProvider, IDataSource, IFilter, ITree, IRenderer } from 'vs/base/parts/tree/browser/tree'; import { localize } from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { inputBackground, inputBorder, inputForeground, registerColor, selectBackground, selectBorder, selectForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; -import { attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; +import { attachInputBoxStyler, attachSelectBoxStyler, attachButtonStyler } from 'vs/platform/theme/common/styler'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { SettingsTarget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; import { ITOCEntry } from 'vs/workbench/parts/preferences/browser/settingsLayout'; import { ISearchResult, ISetting, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; +import { Color } from 'vs/base/common/color'; const $ = DOM.$; @@ -482,6 +483,7 @@ export class SettingsRenderer implements IRenderer { private static readonly SETTING_ROW_HEIGHT = 98; private static readonly SETTING_BOOL_ROW_HEIGHT = 65; + public static readonly MAX_ENUM_DESCRIPTIONS = 10; private readonly _onDidChangeSetting: Emitter = new Emitter(); public readonly onDidChangeSetting: Event = this._onDidChangeSetting.event; @@ -626,7 +628,6 @@ export class SettingsRenderer implements IRenderer { const valueElement = DOM.append(container, $('.setting-item-value')); const controlElement = DOM.append(valueElement, $('div.setting-item-control')); - const resetButtonElement = DOM.append(valueElement, $('.reset-button-container')); const toDispose = []; const template: ISettingItemTemplate = { @@ -643,7 +644,6 @@ export class SettingsRenderer implements IRenderer { // Prevent clicks from being handled by list toDispose.push(DOM.addDisposableListener(controlElement, 'mousedown', (e: IMouseEvent) => e.stopPropagation())); - toDispose.push(DOM.addDisposableListener(resetButtonElement, 'mousedown', (e: IMouseEvent) => e.stopPropagation())); toDispose.push(DOM.addStandardDisposableListener(valueElement, 'keydown', (e: StandardKeyboardEvent) => { if (e.keyCode === KeyCode.Escape) { @@ -788,10 +788,17 @@ export class SettingsRenderer implements IRenderer { const common = this.renderCommonTemplate(tree, container, 'complex'); const openSettingsButton = new Button(common.controlElement, { title: true, buttonBackground: null, buttonHoverBackground: null }); - openSettingsButton.onDidClick(() => this._onDidOpenSettings.fire()); + common.toDispose.push(openSettingsButton); + common.toDispose.push(openSettingsButton.onDidClick(() => this._onDidOpenSettings.fire())); openSettingsButton.label = localize('editInSettingsJson', "Edit in settings.json"); openSettingsButton.element.classList.add('edit-in-settings-button'); + common.toDispose.push(attachButtonStyler(openSettingsButton, this.themeService, { + buttonBackground: Color.transparent.toString(), + buttonHoverBackground: Color.transparent.toString(), + buttonForeground: 'foreground' + })); + const template: ISettingComplexItemTemplate = { ...common, button: openSettingsButton @@ -840,9 +847,10 @@ export class SettingsRenderer implements IRenderer { template.labelElement.textContent = element.displayLabel; template.labelElement.title = titleTooltip; - const enumDescriptionText = element.setting.enumDescriptions ? + const enumDescriptionText = element.setting.enumDescriptions && element.setting.enum && element.setting.enum.length < SettingsRenderer.MAX_ENUM_DESCRIPTIONS ? '\n' + element.setting.enumDescriptions - .map((desc, i) => ` - \`${element.setting.enum[i]}\`: ${desc}`) + .map((desc, i) => desc && ` - \`${element.setting.enum[i]}\`: ${desc}`) + .filter(desc => !!desc) .join('\n') : ''; const descriptionText = element.description + enumDescriptionText; @@ -905,9 +913,12 @@ export class SettingsRenderer implements IRenderer { } private renderEnum(dataElement: SettingsTreeSettingElement, isSelected: boolean, template: ISettingEnumItemTemplate, onChange: (value: string) => void): void { - const displayOptions = dataElement.setting.enum.map(escapeInvisibleChars); + const displayOptions = getDisplayEnumOptions(dataElement.setting); template.selectBox.setOptions(displayOptions); + const label = dataElement.displayCategory + ' ' + dataElement.displayLabel; + template.selectBox.setAriaLabel(label); + const idx = dataElement.setting.enum.indexOf(dataElement.value); template.onChange = null; template.selectBox.select(idx); @@ -946,6 +957,20 @@ export class SettingsRenderer implements IRenderer { } } +function getDisplayEnumOptions(setting: ISetting): string[] { + if (setting.enum.length > SettingsRenderer.MAX_ENUM_DESCRIPTIONS && setting.enumDescriptions) { + return setting.enum + .map(escapeInvisibleChars) + .map((value, i) => { + return setting.enumDescriptions[i] ? + `${value}: ${setting.enumDescriptions[i]}` : + value; + }); + } + + return setting.enum.map(escapeInvisibleChars); +} + function escapeInvisibleChars(enumValue: string): string { return enumValue && enumValue .replace(/\n/g, '\\n') diff --git a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts b/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts index b0ea7b33c626a..b443aabf11b9a 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts +++ b/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts @@ -493,3 +493,23 @@ const focusSettingsListCommand = new FocusSettingsListCommand({ kbOpts: { primary: KeyCode.Enter } }); KeybindingsRegistry.registerCommandAndKeybindingRule(focusSettingsListCommand.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.workbenchContrib())); + +// Preferences menu + +MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '1_settings', + command: { + id: OpenSettings2Action.ID, + title: nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '2_keybindings', + command: { + id: OpenGlobalKeybindingsAction.ID, + title: nls.localize({ key: 'miOpenKeymap', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts") + }, + order: 1 +}); diff --git a/src/vs/workbench/parts/quickopen/browser/quickopen.contribution.ts b/src/vs/workbench/parts/quickopen/browser/quickopen.contribution.ts index be4d637908277..5b8cb97d750d7 100644 --- a/src/vs/workbench/parts/quickopen/browser/quickopen.contribution.ts +++ b/src/vs/workbench/parts/quickopen/browser/quickopen.contribution.ts @@ -9,7 +9,7 @@ import * as env from 'vs/base/common/platform'; import * as nls from 'vs/nls'; import { QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions as QuickOpenExtensions } from 'vs/workbench/browser/quickopen'; import { Registry } from 'vs/platform/registry/common/platform'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { GotoSymbolAction, GOTO_SYMBOL_PREFIX, SCOPE_PREFIX, GotoSymbolHandler } from 'vs/workbench/parts/quickopen/browser/gotoSymbolHandler'; @@ -144,4 +144,24 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen } ] ) -); \ No newline at end of file +); + +// View menu + +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '1_open', + command: { + id: ShowAllCommandsAction.ID, + title: nls.localize({ key: 'miCommandPalette', comment: ['&& denotes a mnemonic'] }, "&&Command Palette...") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '1_open', + command: { + id: OpenViewPickerAction.ID, + title: nls.localize({ key: 'miOpenView', comment: ['&& denotes a mnemonic'] }, "&&Open View...") + }, + order: 2 +}); diff --git a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts index 56943a892a055..6a37c940969b4 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts @@ -13,7 +13,7 @@ import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, To import { VIEWLET_ID } from 'vs/workbench/parts/scm/common/scm'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { StatusUpdater, StatusBarController } from './scmActivity'; import { SCMViewlet } from 'vs/workbench/parts/scm/electron-browser/scmViewlet'; @@ -88,3 +88,14 @@ Registry.as(ConfigurationExtensions.Configuration).regis } } }); + +// View menu + +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '3_views', + command: { + id: VIEWLET_ID, + title: localize({ key: 'miViewSCM', comment: ['&& denotes a mnemonic'] }, "S&&CM") + }, + order: 3 +}); diff --git a/src/vs/workbench/parts/search/common/queryBuilder.ts b/src/vs/workbench/parts/search/common/queryBuilder.ts index 52392ff39036b..525753bcae8f9 100644 --- a/src/vs/workbench/parts/search/common/queryBuilder.ts +++ b/src/vs/workbench/parts/search/common/queryBuilder.ts @@ -75,12 +75,14 @@ export class QueryBuilder { this.resolveSmartCaseToCaseSensitive(contentPattern); } - const query = { + const query: ISearchQuery = { type, folderQueries, usingSearchPaths: !!(searchPaths && searchPaths.length), extraFileResources: options.extraFileResources, - filePattern: options.filePattern, + filePattern: options.filePattern + ? options.filePattern.trim() + : options.filePattern, excludePattern, includePattern, maxResults: options.maxResults, diff --git a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts index 6aabebef7d994..23a3349681d44 100644 --- a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts @@ -615,3 +615,14 @@ registerLanguageCommand('_executeWorkspaceSymbolProvider', function (accessor, a } return getWorkspaceSymbols(query); }); + +// View menu + +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '3_views', + command: { + id: VIEW_ID, + title: nls.localize({ key: 'miViewSearch', comment: ['&& denotes a mnemonic'] }, "&&Search") + }, + order: 2 +}); diff --git a/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts b/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts index c11c6994b9694..c3e961993fc8d 100644 --- a/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts +++ b/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts @@ -233,6 +233,21 @@ suite('QueryBuilder', () => { }); }); + test('file pattern trimming', () => { + const content = 'content'; + assertEqualQueries( + queryBuilder.text( + PATTERN_INFO, + undefined, + { filePattern: ` ${content} ` } + ), + { + contentPattern: PATTERN_INFO, + filePattern: content, + type: QueryType.Text + }); + }); + test('exclude ./ syntax', () => { assertEqualQueries( queryBuilder.text( diff --git a/src/vs/workbench/parts/search/test/common/searchModel.test.ts b/src/vs/workbench/parts/search/test/common/searchModel.test.ts index e0be7f5547196..589973b27a612 100644 --- a/src/vs/workbench/parts/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/parts/search/test/common/searchModel.test.ts @@ -6,20 +6,20 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { SearchModel } from 'vs/workbench/parts/search/common/searchModel'; +import { timeout } from 'vs/base/common/async'; import URI from 'vs/base/common/uri'; -import { IFileMatch, IFolderQuery, ILineMatch, ISearchService, ISearchComplete, ISearchProgressItem, IUncachedSearchStats, ISearchQuery } from 'vs/platform/search/common/search'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { DeferredTPromise } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { IModelService } from 'vs/editor/common/services/modelService'; +import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { timeout } from 'vs/base/common/async'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { DeferredTPromise } from 'vs/base/test/common/utils'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { IFileMatch, IFolderQuery, ILineMatch, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchService, IUncachedSearchStats } from 'vs/platform/search/common/search'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { SearchModel } from 'vs/workbench/parts/search/common/searchModel'; const nullEvent = new class { @@ -158,7 +158,7 @@ suite('SearchModel', () => { }); }); - test.skip('Search Model: Search reports timed telemetry on search when progress is called', () => { + test('Search Model: Search reports timed telemetry on search when progress is called', () => { let target2 = sinon.spy(); stub(nullEvent, 'stop', target2); let target1 = sinon.stub().returns(nullEvent); @@ -171,8 +171,10 @@ suite('SearchModel', () => { let testObject = instantiationService.createInstance(SearchModel); let result = testObject.search({ contentPattern: { pattern: 'somestring' }, type: 1, folderQueries }); - return timeout(1).then(() => { - return result.then(() => { + return result.then(() => { + return timeout(1).then(() => { + // timeout because promise handlers may run in a different order. We only care that these + // are fired at some point. assert.ok(target1.calledWith('searchResultsFirstRender')); assert.ok(target1.calledWith('searchResultsFinished')); // assert.equal(1, target2.callCount); diff --git a/src/vs/workbench/parts/snippets/electron-browser/configureSnippets.ts b/src/vs/workbench/parts/snippets/electron-browser/configureSnippets.ts index 71045971e745d..c739895403565 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/configureSnippets.ts +++ b/src/vs/workbench/parts/snippets/electron-browser/configureSnippets.ts @@ -217,3 +217,12 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { category: nls.localize('preferences', "Preferences") } }); + +MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '3_snippets', + command: { + id, + title: nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets") + }, + order: 1 +}); diff --git a/src/vs/workbench/parts/tasks/common/tasks.ts b/src/vs/workbench/parts/tasks/common/tasks.ts index 7d92c1af7da1e..9a771032c99ce 100644 --- a/src/vs/workbench/parts/tasks/common/tasks.ts +++ b/src/vs/workbench/parts/tasks/common/tasks.ts @@ -12,6 +12,9 @@ import { UriComponents } from 'vs/base/common/uri'; import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ProblemMatcher } from 'vs/workbench/parts/tasks/common/problemMatcher'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export const TASK_RUNNING_STATE = new RawContextKey('taskRunning', false); export enum ShellQuoting { /** diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 1f2742364268b..c8f2b40f41636 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -31,7 +31,7 @@ import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { Registry } from 'vs/platform/registry/common/platform'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { MenuRegistry } from 'vs/platform/actions/common/actions'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -75,7 +75,7 @@ import { ITaskSystem, ITaskResolver, ITaskSummary, TaskExecuteKind, TaskError, T import { Task, CustomTask, ConfiguringTask, ContributedTask, InMemoryTask, TaskEvent, TaskEventKind, TaskSet, TaskGroup, GroupType, ExecutionEngine, JsonSchemaVersion, TaskSourceKind, - TaskSorter, TaskIdentifier, KeyedTaskIdentifier + TaskSorter, TaskIdentifier, KeyedTaskIdentifier, TASK_RUNNING_STATE } from 'vs/workbench/parts/tasks/common/tasks'; import { ITaskService, ITaskProvider, RunOptions, CustomizationProperties, TaskFilter } from 'vs/workbench/parts/tasks/common/taskService'; import { getTemplates as getTaskTemplates } from 'vs/workbench/parts/tasks/common/taskTemplates'; @@ -459,6 +459,8 @@ class TaskService implements ITaskService { private _taskSystemListener: IDisposable; private _recentlyUsedTasks: LinkedMap; + private _taskRunningState: IContextKey; + private _outputChannel: IOutputChannel; private readonly _onDidStateChange: Emitter; @@ -482,7 +484,9 @@ class TaskService implements ITaskService { @IOpenerService private openerService: IOpenerService, @IWindowService private readonly _windowService: IWindowService, @IDialogService private dialogService: IDialogService, - @INotificationService private notificationService: INotificationService + @INotificationService private notificationService: INotificationService, + @IContextKeyService contextKeyService: IContextKeyService, + ) { this._configHasErrors = false; this._workspaceTasksPromise = undefined; @@ -521,6 +525,7 @@ class TaskService implements ITaskService { this.updateSetup(folderSetup); this.updateWorkspaceTasks(); }); + this._taskRunningState = TASK_RUNNING_STATE.bindTo(contextKeyService); lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown())); this._onDidStateChange = new Emitter(); this.registerCommands(); @@ -1292,6 +1297,9 @@ class TaskService implements ITaskService { this._taskSystem = system; } this._taskSystemListener = this._taskSystem.onDidStateChange((event) => { + if (this._taskSystem) { + this._taskRunningState.set(this._taskSystem.isActiveSync()); + } this._onDidStateChange.fire(event); }); return this._taskSystem; @@ -2420,6 +2428,75 @@ class TaskService implements ITaskService { } } +MenuRegistry.appendMenuItem(MenuId.MenubarTasksMenu, { + group: '1_run', + command: { + id: 'workbench.action.tasks.runTask', + title: nls.localize({ key: 'miRunTask', comment: ['&& denotes a mnemonic'] }, "&&Run Task...") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarTasksMenu, { + group: '1_run', + command: { + id: 'workbench.action.tasks.build', + title: nls.localize({ key: 'miBuildTask', comment: ['&& denotes a mnemonic'] }, "Run &&Build Task...") + }, + order: 2 +}); + +// Manage Tasks +MenuRegistry.appendMenuItem(MenuId.MenubarTasksMenu, { + group: '2_manage', + command: { + precondition: TASK_RUNNING_STATE, + id: 'workbench.action.tasks.showTasks', + title: nls.localize({ key: 'miRunningTask', comment: ['&& denotes a mnemonic'] }, "Show Runnin&&g Tasks...") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarTasksMenu, { + group: '2_manage', + command: { + precondition: TASK_RUNNING_STATE, + id: 'workbench.action.tasks.restartTask', + title: nls.localize({ key: 'miRestartTask', comment: ['&& denotes a mnemonic'] }, "R&&estart Running Task...") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarTasksMenu, { + group: '2_manage', + command: { + precondition: TASK_RUNNING_STATE, + id: 'workbench.action.tasks.terminate', + title: nls.localize({ key: 'miTerminateTask', comment: ['&& denotes a mnemonic'] }, "&&Terminate Task...") + }, + order: 3 +}); + +// Configure Tasks +MenuRegistry.appendMenuItem(MenuId.MenubarTasksMenu, { + group: '3_configure', + command: { + id: 'workbench.action.tasks.configureTaskRunner', + title: nls.localize({ key: 'miConfigureTask', comment: ['&& denotes a mnemonic'] }, "&&Configure Tasks...") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarTasksMenu, { + group: '3_configure', + command: { + id: 'workbench.action.tasks.configureDefaultBuildTask', + title: nls.localize({ key: 'miConfigureBuildTask', comment: ['&& denotes a mnemonic'] }, "Configure De&&fault Build Task...") + }, + order: 2 +}); + + MenuRegistry.addCommand({ id: ConfigureTaskAction.ID, title: { value: ConfigureTaskAction.TEXT, original: 'Configure Task' }, category: { value: tasksCategory, original: 'Tasks' } }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.showLog', title: { value: nls.localize('ShowLogAction.label', "Show Task Log"), original: 'Show Task Log' }, category: { value: tasksCategory, original: 'Tasks' } }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.runTask', title: { value: nls.localize('RunTaskAction.label', "Run Task"), original: 'Run Task' }, category: { value: tasksCategory, original: 'Tasks' } }); @@ -2488,6 +2565,7 @@ let schema: IJSONSchema = { import schemaVersion1 from './jsonSchema_v1'; import schemaVersion2 from './jsonSchema_v2'; import { TaskDefinitionRegistry } from 'vs/workbench/parts/tasks/common/taskDefinitionRegistry'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; schema.definitions = { ...schemaVersion1.definitions, ...schemaVersion2.definitions, diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index ffcd27e8f4672..3e637c51d8fd9 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -14,9 +14,11 @@ export const TERMINAL_PANEL_ID = 'workbench.panel.terminal'; export const TERMINAL_SERVICE_ID = 'terminalService'; -/** A context key that is set when the integrated terminal has focus. */ +/** A context key that is set when there is at least one opened integrated terminal. */ +export const KEYBINDING_CONTEXT_TERMINAL_IS_OPEN = new RawContextKey('terminalIsOpen', false); +/** A context key that is set when the integrated terminal has focus. */ export const KEYBINDING_CONTEXT_TERMINAL_FOCUS = new RawContextKey('terminalFocus', undefined); -/** A context key that is set when the integrated terminal does not have focus. */ +/** A context key that is set when the integrated terminal does not have focus. */ export const KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED: ContextKeyExpr = KEYBINDING_CONTEXT_TERMINAL_FOCUS.toNegated(); /** A keybinding context key that is set when the integrated terminal has text selected. */ diff --git a/src/vs/workbench/parts/terminal/common/terminalMenu.ts b/src/vs/workbench/parts/terminal/common/terminalMenu.ts index df51b81f073cb..52498a02f5195 100644 --- a/src/vs/workbench/parts/terminal/common/terminalMenu.ts +++ b/src/vs/workbench/parts/terminal/common/terminalMenu.ts @@ -6,8 +6,21 @@ import * as nls from 'vs/nls'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { TERMINAL_COMMAND_ID } from 'vs/workbench/parts/terminal/common/terminalCommands'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; export function setupTerminalMenu() { + + // View menu + + MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '4_panels', + command: { + id: TERMINAL_COMMAND_ID.TOGGLE, + title: nls.localize({ key: 'miToggleIntegratedTerminal', comment: ['&& denotes a mnemonic'] }, "&&Integrated Terminal") + }, + order: 3 + }); + // Manage const manageGroup = '1_manage'; MenuRegistry.appendMenuItem(MenuId.MenubarTerminalMenu, { @@ -22,7 +35,8 @@ export function setupTerminalMenu() { group: manageGroup, command: { id: TERMINAL_COMMAND_ID.SPLIT, - title: nls.localize({ key: 'miSplitTerminal', comment: ['&& denotes a mnemonic'] }, "&&Split Terminal") + title: nls.localize({ key: 'miSplitTerminal', comment: ['&& denotes a mnemonic'] }, "&&Split Terminal"), + precondition: ContextKeyExpr.has('terminalIsOpen') }, order: 2 }); @@ -31,7 +45,8 @@ export function setupTerminalMenu() { group: manageGroup, command: { id: TERMINAL_COMMAND_ID.KILL, - title: nls.localize({ key: 'miKillTerminal', comment: ['&& denotes a mnemonic'] }, "&&Kill Terminal") + title: nls.localize({ key: 'miKillTerminal', comment: ['&& denotes a mnemonic'] }, "&&Kill Terminal"), + precondition: ContextKeyExpr.has('terminalIsOpen') }, order: 3 }); @@ -42,7 +57,8 @@ export function setupTerminalMenu() { group: runGroup, command: { id: TERMINAL_COMMAND_ID.CLEAR, - title: nls.localize({ key: 'miClear', comment: ['&& denotes a mnemonic'] }, "&&Clear") + title: nls.localize({ key: 'miClear', comment: ['&& denotes a mnemonic'] }, "&&Clear"), + precondition: ContextKeyExpr.has('terminalIsOpen') }, order: 1 }); @@ -69,7 +85,8 @@ export function setupTerminalMenu() { group: navigationGroup, command: { id: TERMINAL_COMMAND_ID.SCROLL_TO_PREVIOUS_COMMAND, - title: nls.localize({ key: 'miScrollToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Previous Command") + title: nls.localize({ key: 'miScrollToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Previous Command"), + precondition: ContextKeyExpr.has('terminalIsOpen') }, order: 1 }); @@ -77,7 +94,8 @@ export function setupTerminalMenu() { group: navigationGroup, command: { id: TERMINAL_COMMAND_ID.SCROLL_TO_NEXT_COMMAND, - title: nls.localize({ key: 'miScrollToNextCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Next Command") + title: nls.localize({ key: 'miScrollToNextCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Next Command"), + precondition: ContextKeyExpr.has('terminalIsOpen') }, order: 2 }); @@ -85,7 +103,8 @@ export function setupTerminalMenu() { group: navigationGroup, command: { id: TERMINAL_COMMAND_ID.SELECT_TO_PREVIOUS_COMMAND, - title: nls.localize({ key: 'miSelectToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Select To Previous Command") + title: nls.localize({ key: 'miSelectToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Select To Previous Command"), + precondition: ContextKeyExpr.has('terminalIsOpen') }, order: 3 }); @@ -93,8 +112,9 @@ export function setupTerminalMenu() { group: navigationGroup, command: { id: TERMINAL_COMMAND_ID.SELECT_TO_NEXT_COMMAND, - title: nls.localize({ key: 'miSelectToNextCommand', comment: ['&& denotes a mnemonic'] }, "Select To Next Command") + title: nls.localize({ key: 'miSelectToNextCommand', comment: ['&& denotes a mnemonic'] }, "Select To Next Command"), + precondition: ContextKeyExpr.has('terminalIsOpen') }, order: 4 }); -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index 20383cf4d66e0..2c28f173a594f 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -9,7 +9,7 @@ import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/c import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IPartService } from 'vs/workbench/services/part/common/partService'; -import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID, ITerminalTab, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID, ITerminalTab, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN } from 'vs/workbench/parts/terminal/common/terminal'; import { TPromise } from 'vs/base/common/winjs.base'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -71,6 +71,18 @@ export abstract class TerminalService implements ITerminalService { this.onTabDisposed(tab => this._removeTab(tab)); lifecycleService.when(LifecyclePhase.Restoring).then(() => this._restoreTabs()); + + this._handleContextKeys(); + } + + private _handleContextKeys(): void { + const terminalIsOpenContext = KEYBINDING_CONTEXT_TERMINAL_IS_OPEN.bindTo(this._contextKeyService); + + const updateTerminalContextKeys = () => { + terminalIsOpenContext.set(this.terminalInstances.length > 0); + }; + + this.onInstancesChanged(() => updateTerminalContextKeys()); } protected abstract _showTerminalCloseConfirmation(): TPromise; diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css b/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css index 42d4a6496dbeb..c5da665535ef6 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css @@ -17,6 +17,7 @@ height: 100%; width: 100%; box-sizing: border-box; + overflow: hidden; } .monaco-workbench .panel.integrated-terminal .terminal-tab { diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 7d1a41951b3dc..6b98f36479739 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -665,6 +665,10 @@ export class TerminalInstance implements ITerminalInstance { const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10); const height = parseInt(computedStyle.getPropertyValue('height').replace('px', ''), 10); this.layout(new dom.Dimension(width, height)); + // HACK: Trigger another async layout to ensure xterm's CharMeasure is ready to use, + // this hack can be removed when https://github.com/xtermjs/xterm.js/issues/702 is + // supported. + setTimeout(() => this.layout(new dom.Dimension(width, height)), 0); } } } diff --git a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts index 4d2291134ef95..dfa15ae450fde 100644 --- a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts +++ b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts @@ -10,7 +10,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { Action } from 'vs/base/common/actions'; import { firstIndex } from 'vs/base/common/arrays'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen'; @@ -228,3 +228,21 @@ const developerCategory = localize('developer', "Developer"); const generateColorThemeDescriptor = new SyncActionDescriptor(GenerateColorThemeAction, GenerateColorThemeAction.ID, GenerateColorThemeAction.LABEL); Registry.as(Extensions.WorkbenchActions).registerWorkbenchAction(generateColorThemeDescriptor, 'Developer: Generate Color Theme From Current Settings', developerCategory); + +MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '4_themes', + command: { + id: SelectColorThemeAction.ID, + title: localize({ key: 'miSelectColorTheme', comment: ['&& denotes a mnemonic'] }, "&&Color Theme") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '4_themes', + command: { + id: SelectIconThemeAction.ID, + title: localize({ key: 'miSelectIconTheme', comment: ['&& denotes a mnemonic'] }, "File &&Icon Theme") + }, + order: 2 +}); diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index c5943fb7778c8..71733b374d508 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -369,7 +369,6 @@ export class ExtensionHostProcessWorker { isExtensionDevelopmentDebug: this._isExtensionDevDebug, appRoot: this._environmentService.appRoot, appSettingsHome: this._environmentService.appSettingsHome, - disableExtensions: this._environmentService.disableExtensions, extensionDevelopmentPath: this._environmentService.extensionDevelopmentPath, extensionTestsPath: this._environmentService.extensionTestsPath }, diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index c2f0c78bfe2c2..45c442c325bb9 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -281,8 +281,8 @@ export class ExtensionService extends Disposable implements IExtensionService { this.startDelayed(lifecycleService); - if (this._environmentService.disableExtensions) { - this._notificationService.prompt(Severity.Info, nls.localize('extensionsDisabled', "All extensions are temporarily disabled. Reload the window to return to the previous state."), [{ + if (this._extensionEnablementService.allUserExtensionsDisabled) { + this._notificationService.prompt(Severity.Info, nls.localize('extensionsDisabled', "All installed extensions are temporarily disabled. Reload the window to return to the previous state."), [{ label: nls.localize('Reload', "Reload"), run: () => { this._windowService.reloadWindow(); @@ -513,7 +513,7 @@ export class ExtensionService extends Disposable implements IExtensionService { this._logOrShowMessage(severity, this._isDev ? messageWithSource(source, message) : message); }); - return ExtensionService._scanInstalledExtensions(this._windowService, this._notificationService, this._environmentService, log) + return ExtensionService._scanInstalledExtensions(this._windowService, this._notificationService, this._environmentService, this._extensionEnablementService, log) .then(({ system, user, development }) => { let result: { [extensionId: string]: IExtensionDescription; } = {}; system.forEach((systemExtension) => { @@ -536,7 +536,7 @@ export class ExtensionService extends Disposable implements IExtensionService { }); } - private _getRuntimeExtensions(allExtensions: IExtensionDescription[]): TPromise { + private _getRuntimeExtensions(allExtensions: IExtensionDescription[]): Promise { return this._extensionEnablementService.getDisabledExtensions() .then(disabledExtensions => { @@ -759,7 +759,7 @@ export class ExtensionService extends Disposable implements IExtensionService { return result; } - private static _scanInstalledExtensions(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, log: ILog): TPromise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> { + private static _scanInstalledExtensions(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, extensionEnablementService: IExtensionEnablementService, log: ILog): TPromise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> { const translationConfig: TPromise = platform.translationsConfigFile ? pfs.readFile(platform.translationsConfigFile, 'utf8').then((content) => { @@ -831,7 +831,7 @@ export class ExtensionService extends Disposable implements IExtensionService { } const userExtensions = ( - environmentService.disableExtensions || !environmentService.extensionsPath + extensionEnablementService.allUserExtensionsDisabled || !environmentService.extensionsPath ? TPromise.as([]) : this._scanExtensionsWithCache( windowService, diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index fa632fcf4f34b..2df703e1c1f5d 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -64,7 +64,6 @@ import 'vs/workbench/parts/scm/electron-browser/scmViewlet'; // can be packaged import 'vs/workbench/parts/debug/electron-browser/debug.contribution'; import 'vs/workbench/parts/debug/browser/debugQuickOpen'; import 'vs/workbench/parts/debug/electron-browser/repl'; -import 'vs/workbench/parts/debug/browser/debugEditorActions'; import 'vs/workbench/parts/debug/browser/debugViewlet'; // can be packaged separately import 'vs/workbench/parts/markers/electron-browser/markers.contribution'; @@ -142,4 +141,4 @@ import 'vs/workbench/parts/navigation/common/navigation.contribution'; // services import 'vs/workbench/services/bulkEdit/electron-browser/bulkEditService'; -import 'vs/workbench/parts/experiments/electron-browser/experiments.contribution'; \ No newline at end of file +import 'vs/workbench/parts/experiments/electron-browser/experiments.contribution';