From 5e41d2aafbaef691b2b0d41483d08b74d255b73e Mon Sep 17 00:00:00 2001 From: Erich Gamma Date: Thu, 1 Feb 2018 14:39:12 +0100 Subject: [PATCH 001/362] enable checkJS --- build/builtin/browser-main.js | 1 + build/gulpfile.editor.js | 5 ++++- build/gulpfile.extensions.js | 4 ++++ build/gulpfile.hygiene.js | 13 +++++++++---- build/gulpfile.mixin.js | 3 +++ build/gulpfile.vscode.js | 13 +++++++++++-- build/gulpfile.vscode.linux.js | 5 ++++- build/gulpfile.vscode.win32.js | 2 ++ build/lib/builtInExtensions.js | 4 +++- build/lib/bundle.ts | 8 ++++---- build/lib/optimize.ts | 4 ++-- build/lib/util.ts | 4 ++-- build/lib/watch/watch-nsfw.js | 2 +- build/lib/watch/watch-win32.js | 3 ++- build/npm/postinstall.js | 1 + build/npm/update-localization-extension.js | 1 - build/package.json | 7 ++++--- build/tsconfig.build.json | 7 +++++++ build/tsconfig.json | 7 ++++++- src/buildfile.js | 1 + 20 files changed, 71 insertions(+), 24 deletions(-) create mode 100644 build/tsconfig.build.json diff --git a/build/builtin/browser-main.js b/build/builtin/browser-main.js index 160bac61968..482398ef0da 100644 --- a/build/builtin/browser-main.js +++ b/build/builtin/browser-main.js @@ -13,6 +13,7 @@ const builtInExtensionsPath = path.join(__dirname, '..', 'builtInExtensions.json const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json'); function readJson(filePath) { + //@ts-ignore review return JSON.parse(fs.readFileSync(filePath)); } diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 1a09abd4f55..835ccf10d04 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -12,6 +12,7 @@ var File = require('vinyl'); var root = path.dirname(__dirname); var sha1 = util.getVersion(root); +// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file var semver = require('./monaco/package.json').version; var headerVersion = semver + '(' + sha1 + ')'; @@ -79,7 +80,8 @@ gulp.task('optimize-editor', ['clean-optimized-editor', 'compile-client-build'], bundleLoader: false, header: BUNDLED_FILE_HEADER, bundleInfo: true, - out: 'out-editor' + out: 'out-editor', + languages: undefined })); gulp.task('clean-minified-editor', util.rimraf('out-editor-min')); @@ -155,6 +157,7 @@ gulp.task('editor-distro', ['clean-editor-distro', 'minify-editor', 'optimize-ed }); gulp.task('analyze-editor-distro', function() { + // @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file var bundleInfo = require('../out-editor/bundleInfo.json'); var graph = bundleInfo.graph; var bundles = bundleInfo.bundles; diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index 55094f4f449..c9c244c38d8 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -94,8 +94,11 @@ const tasks = compilations.map(function (tsconfigFile) { sourceRoot: '../src' })) .pipe(tsFilter.restore) + // @ts-ignore review .pipe(build ? nlsDev.createAdditionalLanguageFiles(languages, i18nPath, out) : es.through()) + // @ts-ignore review .pipe(build ? nlsDev.bundleMetaDataFiles(headerId, headerOut) : es.through()) + // @ts-ignore review .pipe(build ? nlsDev.bundleLanguageFiles() : es.through()) .pipe(reporter.end(emitError)); @@ -143,6 +146,7 @@ const tasks = compilations.map(function (tsconfigFile) { const watchInput = watcher(src, srcOpts); return watchInput + // @ts-ignore review .pipe(util.incremental(() => pipeline(true), input)) .pipe(gulp.dest(out)); }); diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index d6402600d27..514de09af32 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -151,8 +151,8 @@ gulp.task('tslint', () => { return vfs.src(all, { base: '.', follow: true, allowEmpty: true }) .pipe(filter(tslintFilter)) - .pipe(gulptslint({ rulesDirectory: 'build/lib/tslint' })) - .pipe(gulptslint.report(options)); + .pipe(gulptslint.default({ rulesDirectory: 'build/lib/tslint' })) + .pipe(gulptslint.default.report(options)); }); const hygiene = exports.hygiene = (some, options) => { @@ -202,6 +202,11 @@ const hygiene = exports.hygiene = (some, options) => { verify: true, tsfmt: true, // verbose: true + // keep checkJS happy + editorconfig: undefined, + replace: undefined, + tsconfig: undefined, + tslint: undefined }).then(result => { if (result.error) { console.error(result.message); @@ -227,9 +232,9 @@ const hygiene = exports.hygiene = (some, options) => { const tsl = es.through(function (file) { const configuration = tslint.Configuration.findConfiguration(null, '.'); - const options = { formatter: 'json', rulesDirectory: 'build/lib/tslint' }; + const linterOptions = { fix: false, formatter: 'json', rulesDirectory: 'build/lib/tslint' }; const contents = file.contents.toString('utf8'); - const linter = new tslint.Linter(options); + const linter = new tslint.Linter(linterOptions); linter.lint(file.relative, contents, configuration.results); const result = linter.getResult(); diff --git a/build/gulpfile.mixin.js b/build/gulpfile.mixin.js index abf3ce4a90c..a370981ab3c 100644 --- a/build/gulpfile.mixin.js +++ b/build/gulpfile.mixin.js @@ -14,6 +14,8 @@ const util = require('./lib/util'); const remote = require('gulp-remote-src'); const zip = require('gulp-vinyl-zip'); const assign = require('object-assign'); + +// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const pkg = require('../package.json'); gulp.task('mixin', function () { @@ -54,6 +56,7 @@ gulp.task('mixin', function () { .pipe(util.rebase(2)) .pipe(productJsonFilter) .pipe(buffer()) + // @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file .pipe(json(o => assign({}, require('../product.json'), o))) .pipe(productJsonFilter.restore); diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 240153c168d..44ea17427ed 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -27,7 +27,9 @@ const common = require('./lib/optimize'); const nlsDev = require('vscode-nls-dev'); const root = path.dirname(__dirname); const commit = util.getVersion(root); +// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const packageJson = require('../package.json'); +// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const product = require('../product.json'); const crypto = require('crypto'); const i18n = require('./lib/i18n'); @@ -37,6 +39,7 @@ const getElectronVersion = require('./lib/electron').getElectronVersion; // const createAsar = require('./lib/asar').createAsar; const productionDependencies = deps.getProductionDependencies(path.dirname(__dirname)); +//@ts-ignore review const baseModules = Object.keys(process.binding('natives')).filter(n => !/^_|\//.test(n)); const nodeModules = ['electron', 'original-fs'] .concat(Object.keys(product.dependencies || {})) @@ -44,8 +47,8 @@ const nodeModules = ['electron', 'original-fs'] .concat(baseModules); // Build - -const builtInExtensions = require('./builtInExtensions'); +// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file +const builtInExtensions = require('./builtInExtensions.json'); const excludedExtensions = [ 'vscode-api-tests', @@ -104,6 +107,8 @@ gulp.task('optimize-vscode', ['clean-optimized-vscode', 'compile-build', 'compil header: BUNDLED_FILE_HEADER, out: 'out-vscode', languages: languages, + // @ts-ignore review + bundleInfo: undefined })); @@ -245,6 +250,7 @@ function packageTask(platform, arch, opts) { // // TODO@Dirk: this filter / buffer is here to make sure the nls.json files are buffered .pipe(nlsFilter) .pipe(buffer()) + //@ts-ignore review .pipe(nlsDev.createAdditionalLanguageFiles(languages, path.join(__dirname, '..', 'i18n'))) .pipe(nlsFilter.restore); })); @@ -297,6 +303,7 @@ function packageTask(platform, arch, opts) { .pipe(util.cleanNodeModule('native-is-elevated', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['**/*.node'])) .pipe(util.cleanNodeModule('native-watchdog', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node'])) .pipe(util.cleanNodeModule('spdlog', ['binding.gyp', 'build/**', 'deps/**', 'src/**', 'test/**'], ['**/*.node'])) + //@ts-ignore review .pipe(util.cleanNodeModule('jschardet', ['dist/**'])) .pipe(util.cleanNodeModule('windows-foreground-love', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node'])) .pipe(util.cleanNodeModule('windows-process-tree', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node'])) @@ -439,6 +446,7 @@ gulp.task('vscode-translations-pull', function () { gulp.task('vscode-translations-import', function () { [...i18n.defaultLanguages, ...i18n.extraLanguages].forEach(language => { gulp.src(`../vscode-localization/${language.id}/build/*/*.xlf`) + //@ts-ignore review .pipe(i18n.prepareI18nFiles(language)) .pipe(vfs.dest(`./i18n/${language.folderName}`)); gulp.src(`../vscode-localization/${language.id}/setup/*/*.xlf`) @@ -470,6 +478,7 @@ gulp.task('upload-vscode-sourcemaps', ['minify-vscode'], () => { const allConfigDetailsPath = path.join(os.tmpdir(), 'configuration.json'); gulp.task('upload-vscode-configuration', ['generate-vscode-configuration'], () => { const branch = process.env.BUILD_SOURCEBRANCH; + //@ts-ignore review if (!branch.endsWith('/master') && branch.indexOf('/release/') < 0) { console.log(`Only runs on master and release branches, not ${branch}`); return; diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index 28f062bf749..a658758de98 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -12,9 +12,12 @@ const shell = require('gulp-shell'); const es = require('event-stream'); const vfs = require('vinyl-fs'); const util = require('./lib/util'); +// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const packageJson = require('../package.json'); +// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const product = require('../product.json'); -const rpmDependencies = require('../resources/linux/rpm/dependencies'); +// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file +const rpmDependencies = require('../resources/linux/rpm/dependencies.json'); const linuxPackageRevision = Math.floor(new Date().getTime() / 1000); diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index 15459f7dae8..7ed143cf21c 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -11,7 +11,9 @@ const assert = require('assert'); const cp = require('child_process'); const _7z = require('7zip')['7z']; const util = require('./lib/util'); +// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const pkg = require('../package.json'); +// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file const product = require('../product.json'); const vfs = require('vinyl-fs'); diff --git a/build/lib/builtInExtensions.js b/build/lib/builtInExtensions.js index 245509379c1..c538416ce4d 100644 --- a/build/lib/builtInExtensions.js +++ b/build/lib/builtInExtensions.js @@ -17,7 +17,8 @@ const ext = require('./extensions'); const util = require('gulp-util'); const root = path.dirname(path.dirname(__dirname)); -const builtInExtensions = require('../builtInExtensions'); +// @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file +const builtInExtensions = require('../builtInExtensions.json'); const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json'); function getExtensionPath(extension) { @@ -34,6 +35,7 @@ function isUpToDate(extension) { const packageContents = fs.readFileSync(packagePath); try { + //@ts-ignore review const diskVersion = JSON.parse(packageContents).version; return (diskVersion === extension.version); } catch (err) { diff --git a/build/lib/bundle.ts b/build/lib/bundle.ts index e188ce44de1..fd17d817fd5 100644 --- a/build/lib/bundle.ts +++ b/build/lib/bundle.ts @@ -44,11 +44,11 @@ interface ILoaderPluginReqFunc { export interface IEntryPoint { name: string; - include: string[]; - exclude: string[]; + include?: string[]; + exclude?: string[]; prepend: string[]; - append: string[]; - dest: string; + append?: string[]; + dest?: string; } interface IEntryPointMap { diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index c47f513e436..bf818454343 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -30,7 +30,7 @@ function log(prefix: string, message: string): void { gulpUtil.log(gulpUtil.colors.cyan('[' + prefix + ']'), message); } -export function loaderConfig(emptyPaths: string[]) { +export function loaderConfig(emptyPaths?: string[]) { const result = { paths: { 'vs': 'out-build/vs', @@ -293,7 +293,7 @@ function uglifyWithCopyrights(): NodeJS.ReadWriteStream { return es.duplex(input, output); } -export function minifyTask(src: string, sourceMapBaseUrl: string): (cb: any) => void { +export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => void { const sourceMappingURL = sourceMapBaseUrl && (f => `${sourceMapBaseUrl}/${f.relative}.map`); return cb => { diff --git a/build/lib/util.ts b/build/lib/util.ts index 8f4f1ea75e9..9a4a2b44346 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -28,7 +28,7 @@ export interface IStreamProvider { (cancellationToken?: ICancellationToken): NodeJS.ReadWriteStream; } -export function incremental(streamProvider: IStreamProvider, initial: NodeJS.ReadWriteStream, supportsCancellation: boolean): NodeJS.ReadWriteStream { +export function incremental(streamProvider: IStreamProvider, initial: NodeJS.ReadWriteStream, supportsCancellation?: boolean): NodeJS.ReadWriteStream { const input = es.through(); const output = es.through(); let state = 'idle'; @@ -223,7 +223,7 @@ export function rimraf(dir: string): (cb: any) => void { _rimraf(dir, { maxBusyTries: 1 }, (err: any) => { if (!err) { return cb(); - }; + } if (err.code === 'ENOTEMPTY' && ++retries < 5) { return setTimeout(() => retry(cb), 10); diff --git a/build/lib/watch/watch-nsfw.js b/build/lib/watch/watch-nsfw.js index c9fe9192f31..7c70c95b13f 100644 --- a/build/lib/watch/watch-nsfw.js +++ b/build/lib/watch/watch-nsfw.js @@ -30,7 +30,7 @@ function watch(root) { path: path, base: root }); - + //@ts-ignore review file.event = type; result.emit('data', file); } diff --git a/build/lib/watch/watch-win32.js b/build/lib/watch/watch-win32.js index a6c4ea66470..aae55adc6ec 100644 --- a/build/lib/watch/watch-win32.js +++ b/build/lib/watch/watch-win32.js @@ -25,6 +25,7 @@ function watch(root) { var child = cp.spawn(watcherPath, [root]); child.stdout.on('data', function(data) { + //@ts-ignore review var lines = data.toString('utf8').split('\n'); for (var i = 0; i < lines.length; i++) { var line = lines[i].trim(); @@ -46,7 +47,7 @@ function watch(root) { path: changePathFull, base: root }); - + //@ts-ignore review file.event = toChangeType(changeType); result.emit('data', file); } diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index 4d7b1524998..5a2eb75d6b4 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -49,6 +49,7 @@ extensions.forEach(extension => yarnInstall(`extensions/${extension}`)); function yarnInstallBuildDependencies() { // make sure we install the deps of build/lib/watch for the system installed // node, since that is the driver of gulp + //@ts-ignore review const env = Object.assign({}, process.env); const watchPath = path.join(path.dirname(__dirname), 'lib', 'watch'); const yarnrcPath = path.join(watchPath, '.yarnrc'); diff --git a/build/npm/update-localization-extension.js b/build/npm/update-localization-extension.js index ca41f25783e..985fbd28cb7 100644 --- a/build/npm/update-localization-extension.js +++ b/build/npm/update-localization-extension.js @@ -15,7 +15,6 @@ let rimraf = require('rimraf'); function update(idOrPath) { if (!idOrPath) { throw new Error('Argument must be the location of the localization extension.'); - return; } let locExtFolder = idOrPath; if (/^\w{2}(-\w+)?$/.test(idOrPath)) { diff --git a/build/package.json b/build/package.json index 25acf1c2251..bcc32e8dec0 100644 --- a/build/package.json +++ b/build/package.json @@ -17,8 +17,9 @@ "xml2js": "^0.4.17" }, "scripts": { - "compile": "tsc", - "watch": "tsc --watch", - "postinstall": "npm run compile" + "compile": "tsc -p tsconfig.build.json", + "watch": "tsc -p tsconfig.build.json --watch", + "postinstall": "npm run compile", + "npmCheckJs": "tsc --noEmit" } } \ No newline at end of file diff --git a/build/tsconfig.build.json b/build/tsconfig.build.json new file mode 100644 index 00000000000..2402a092886 --- /dev/null +++ b/build/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "allowJs": false, + "checkJs": false + } +} \ No newline at end of file diff --git a/build/tsconfig.json b/build/tsconfig.json index 04f3963055d..b68c9b6dafa 100644 --- a/build/tsconfig.json +++ b/build/tsconfig.json @@ -7,7 +7,12 @@ "preserveConstEnums": true, "sourceMap": false, "experimentalDecorators": true, - "newLine": "LF" + "newLine": "LF", + // enable JavaScript type checking for the language service + // use the tsconfig.build.json for compiling wich disable JavaScript + // type checking so that JavaScript file are not transpiled + "allowJs": true, + "checkJs": true }, "exclude": [ "node_modules/**" diff --git a/src/buildfile.js b/src/buildfile.js index 58a2532f458..bd05c387ae0 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -10,6 +10,7 @@ exports.base = [{ append: [ 'vs/base/worker/workerMain' ], dest: 'vs/base/worker/workerMain.js' }]; +//@ts-ignore review exports.workbench = require('./vs/workbench/buildfile').collectModules(['vs/workbench/workbench.main']); exports.code = require('./vs/code/buildfile').collectModules(); From 95982533bc4c46d257cb7c18e68ef2c8f30ce030 Mon Sep 17 00:00:00 2001 From: Erich Gamma Date: Thu, 1 Feb 2018 16:16:41 +0100 Subject: [PATCH 002/362] more ts-ignore --- build/builtin/browser-main.js | 1 + build/lib/util.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtin/browser-main.js b/build/builtin/browser-main.js index 482398ef0da..83a5ba4e592 100644 --- a/build/builtin/browser-main.js +++ b/build/builtin/browser-main.js @@ -6,6 +6,7 @@ const fs = require('fs'); const path = require('path'); const os = require('os'); +// @ts-ignore review const { remote } = require('electron'); const dialog = remote.dialog; diff --git a/build/lib/util.js b/build/lib/util.js index f9f3f7a792a..679e049f5a5 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -173,7 +173,6 @@ function rimraf(dir) { if (!err) { return cb(); } - ; if (err.code === 'ENOTEMPTY' && ++retries < 5) { return setTimeout(function () { return retry(cb); }, 10); } From 415dd25e10862cb7a839ddfe38c5fa4afcda49a4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 9 Feb 2018 06:45:58 -0800 Subject: [PATCH 003/362] Introduce terminal group/tab concept --- .../parts/terminal/common/terminal.ts | 11 ++++ .../parts/terminal/common/terminalService.ts | 51 ++++++++++++------ .../electron-browser/terminalInstance.ts | 4 +- .../electron-browser/terminalService.ts | 15 ++++-- .../terminal/electron-browser/terminalTab.ts | 52 +++++++++++++++++++ 5 files changed, 111 insertions(+), 22 deletions(-) create mode 100644 src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index 78bb4ad8a6f..65d928b3067 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -147,6 +147,7 @@ export interface ITerminalService { activeTerminalInstanceIndex: number; configHelper: ITerminalConfigHelper; onActiveInstanceChanged: Event; + onTabDisposed: Event; onInstanceDisposed: Event; onInstanceProcessIdReady: Event; onInstancesChanged: Event; @@ -176,6 +177,12 @@ export interface ITerminalService { setWorkspaceShellAllowed(isAllowed: boolean): void; } +export interface ITerminalTab { + terminalInstances: ITerminalInstance[]; + + addDisposable(disposable: IDisposable): void; +} + export interface ITerminalInstance { /** * The ID of the terminal instance, this is an arbitrary number only used to identify the @@ -198,6 +205,8 @@ export interface ITerminalInstance { */ onDisposed: Event; + onProcessIdReady: Event; + /** * The title of the terminal. This is either title or the process currently running or an * explicit name given to the terminal instance through the extension API. @@ -389,4 +398,6 @@ export interface ITerminalInstance { * Sets the title of the terminal instance. */ setTitle(title: string, eventFromProcess: boolean): void; + + addDisposable(disposable: IDisposable): void; } diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index c8da8c1253d..9698ebcd89a 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 } 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 } 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 } from 'vs/workbench/parts/terminal/common/terminal'; import { TPromise } from 'vs/base/common/winjs.base'; export abstract class TerminalService implements ITerminalService { @@ -20,16 +20,19 @@ export abstract class TerminalService implements ITerminalService { protected _findWidgetVisible: IContextKey; protected _terminalContainer: HTMLElement; protected _onInstancesChanged: Emitter; + protected _onTabDisposed: Emitter; protected _onInstanceDisposed: Emitter; protected _onInstanceProcessIdReady: Emitter; protected _onInstanceTitleChanged: Emitter; - protected _terminalInstances: ITerminalInstance[]; + protected _terminalTabs: ITerminalTab[]; + protected abstract _terminalInstances: ITerminalInstance[]; private _activeTerminalInstanceIndex: number; private _onActiveInstanceChanged: Emitter; public get activeTerminalInstanceIndex(): number { return this._activeTerminalInstanceIndex; } public get onActiveInstanceChanged(): Event { return this._onActiveInstanceChanged.event; } + public get onTabDisposed(): Event { return this._onTabDisposed.event; } public get onInstanceDisposed(): Event { return this._onInstanceDisposed.event; } public get onInstanceProcessIdReady(): Event { return this._onInstanceProcessIdReady.event; } public get onInstanceTitleChanged(): Event { return this._onInstanceTitleChanged.event; } @@ -44,11 +47,11 @@ export abstract class TerminalService implements ITerminalService { @IPartService private _partService: IPartService, @ILifecycleService lifecycleService: ILifecycleService ) { - this._terminalInstances = []; this._activeTerminalInstanceIndex = 0; this._isShuttingDown = false; this._onActiveInstanceChanged = new Emitter(); + this._onTabDisposed = new Emitter(); this._onInstanceDisposed = new Emitter(); this._onInstanceProcessIdReady = new Emitter(); this._onInstanceTitleChanged = new Emitter(); @@ -58,7 +61,7 @@ export abstract class TerminalService implements ITerminalService { lifecycleService.onShutdown(() => this._onShutdown()); this._terminalFocusContextKey = KEYBINDING_CONTEXT_TERMINAL_FOCUS.bindTo(this._contextKeyService); this._findWidgetVisible = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE.bindTo(this._contextKeyService); - this.onInstanceDisposed((terminalInstance) => { this._removeInstance(terminalInstance); }); + this.onTabDisposed(tab => this._removeTab(tab)); } protected abstract _showTerminalCloseConfirmation(): TPromise; @@ -89,44 +92,58 @@ export abstract class TerminalService implements ITerminalService { } private _onShutdown(): void { - this.terminalInstances.forEach(instance => { - instance.dispose(); - }); + this.terminalInstances.forEach(instance => instance.dispose()); } public getInstanceLabels(): string[] { - return this._terminalInstances.map((instance, index) => `${index + 1}: ${instance.title}`); + return this._terminalTabs.map((tab, index) => { + let title = tab.terminalInstances[0].title; + for (let i = 1; i < tab.terminalInstances.length; i++) { + title += `, ${tab.terminalInstances[i].title}`; + } + return `${index + 1}: ${title}`; + }); } - private _removeInstance(terminalInstance: ITerminalInstance): void { - let index = this.terminalInstances.indexOf(terminalInstance); - let wasActiveInstance = terminalInstance === this.getActiveInstance(); + private _removeTab(tab: ITerminalTab): void { + let index = this._terminalTabs.indexOf(tab); + let wasActiveInstance = tab === this._getActiveTab(); if (index !== -1) { - this.terminalInstances.splice(index, 1); + this._terminalTabs.splice(index, 1); } - if (wasActiveInstance && this.terminalInstances.length > 0) { - let newIndex = index < this.terminalInstances.length ? index : this.terminalInstances.length - 1; + if (wasActiveInstance && this._terminalTabs.length > 0) { + let newIndex = index < this._terminalTabs.length ? index : this._terminalTabs.length - 1; this.setActiveInstanceByIndex(newIndex); - if (terminalInstance.hadFocusOnExit) { + // TODO: Needs to be made to work with multiple instances in a tab + if (tab.terminalInstances[0].hadFocusOnExit) { this.getActiveInstance().focus(true); } } // Hide the panel if there are no more instances, provided that VS Code is not shutting // down. When shutting down the panel is locked in place so that it is restored upon next // launch. - if (this.terminalInstances.length === 0 && !this._isShuttingDown) { + if (this._terminalTabs.length === 0 && !this._isShuttingDown) { this.hidePanel(); } + // TODO: This should be onTabsChanged? this._onInstancesChanged.fire(); if (wasActiveInstance) { this._onActiveInstanceChanged.fire(); } } + private _getActiveTab(): ITerminalTab { + // TODO: TerminalService needs to track the active tab + // TODO: The tab should track its active instance + if (this.activeTerminalInstanceIndex < 0 || this.activeTerminalInstanceIndex >= this._terminalTabs.length) { + return null; + } + return this._terminalTabs[this.activeTerminalInstanceIndex]; + } + public getActiveInstance(): ITerminalInstance { if (this.activeTerminalInstanceIndex < 0 || this.activeTerminalInstanceIndex >= this.terminalInstances.length) { return null; - } return this.terminalInstances[this.activeTerminalInstanceIndex]; } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 63c8088d148..76c5ab2d2ed 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -78,7 +78,7 @@ export class TerminalInstance implements ITerminalInstance { private _processReady: TPromise; private _isDisposed: boolean; private _onDisposed: Emitter; - private _onProcessIdReady: Emitter; + private _onProcessIdReady: Emitter; private _onTitleChanged: Emitter; private _process: cp.ChildProcess; private _processId: number; @@ -105,7 +105,7 @@ export class TerminalInstance implements ITerminalInstance { public get id(): number { return this._id; } public get processId(): number { return this._processId; } public get onDisposed(): Event { return this._onDisposed.event; } - public get onProcessIdReady(): Event { return this._onProcessIdReady.event; } + public get onProcessIdReady(): Event { return this._onProcessIdReady.event; } public get onTitleChanged(): Event { return this._onTitleChanged.event; } public get title(): string { return this._title; } public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 53d7571edef..10526198a08 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -16,18 +16,23 @@ import { IQuickOpenService, IPickOpenEntry, IPickOptions } from 'vs/platform/qui import { ITerminalInstance, ITerminalService, IShellLaunchConfig, ITerminalConfigHelper, NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, TERMINAL_PANEL_ID } from 'vs/workbench/parts/terminal/common/terminal'; import { TerminalService as AbstractTerminalService } from 'vs/workbench/parts/terminal/common/terminalService'; import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; -import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance'; import { TPromise } from 'vs/base/common/winjs.base'; import { IChoiceService, IMessageService } from 'vs/platform/message/common/message'; import Severity from 'vs/base/common/severity'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { TERMINAL_DEFAULT_SHELL_WINDOWS } from 'vs/workbench/parts/terminal/electron-browser/terminal'; import { TerminalPanel } from 'vs/workbench/parts/terminal/electron-browser/terminalPanel'; +import { TerminalTab } from 'vs/workbench/parts/terminal/electron-browser/terminalTab'; export class TerminalService extends AbstractTerminalService implements ITerminalService { private _configHelper: TerminalConfigHelper; public get configHelper(): ITerminalConfigHelper { return this._configHelper; } + protected _terminalTabs: TerminalTab[]; + protected get _terminalInstances(): ITerminalInstance[] { + return this._terminalTabs.reduce((p, c) => p.concat(c.terminalInstances), []); + } + constructor( @IContextKeyService _contextKeyService: IContextKeyService, @IPanelService _panelService: IPanelService, @@ -42,17 +47,21 @@ export class TerminalService extends AbstractTerminalService implements ITermina ) { super(_contextKeyService, _panelService, _partService, _lifecycleService); + this._terminalTabs = []; this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper); } public createInstance(shell: IShellLaunchConfig = {}, wasNewTerminalAction?: boolean): ITerminalInstance { - let terminalInstance = this._instantiationService.createInstance(TerminalInstance, + const terminalTab = this._instantiationService.createInstance(TerminalTab, this._terminalFocusContextKey, this._configHelper, this._terminalContainer, shell); - terminalInstance.addDisposable(terminalInstance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged)); + this._terminalTabs.push(terminalTab); + terminalTab.addDisposable(terminalTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed)); + const terminalInstance = terminalTab.terminalInstances[0]; terminalInstance.addDisposable(terminalInstance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed)); + terminalInstance.addDisposable(terminalInstance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged)); terminalInstance.addDisposable(terminalInstance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady)); this.terminalInstances.push(terminalInstance); if (this.terminalInstances.length === 1) { diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts new file mode 100644 index 00000000000..a9b180ad29b --- /dev/null +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITerminalInstance, IShellLaunchConfig, ITerminalTab } from 'vs/workbench/parts/terminal/common/terminal'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; +import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance'; +import Event, { Emitter } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; + +export class TerminalTab implements ITerminalTab { + private _terminalInstances: ITerminalInstance[] = []; + private _disposables: IDisposable[] = []; + + public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } + + private _onDisposed: Emitter; + public get onDisposed(): Event { return this._onDisposed.event; } + + constructor( + terminalFocusContextKey: IContextKey, + configHelper: TerminalConfigHelper, + container: HTMLElement, + shellLaunchConfig: IShellLaunchConfig, + @IInstantiationService instantiationService: IInstantiationService + ) { + this._onDisposed = new Emitter(); + + const instance = instantiationService.createInstance(TerminalInstance, + terminalFocusContextKey, + configHelper, + container, + shellLaunchConfig); + this._terminalInstances.push(instance); + instance.addDisposable(instance.onDisposed(instance => this._onInstanceDisposed(instance))); + } + + private _onInstanceDisposed(instance: ITerminalInstance): void { + + // TODO: Listen for disposed on TerminalService and handle appropriately (remove the tab and its instance from the service) + + this._onDisposed.fire(this); + this._terminalInstances = []; + } + + public addDisposable(disposable: IDisposable): void { + this._disposables.push(disposable); + } +} From 100b58a1ede6704856f6367572199204057703de Mon Sep 17 00:00:00 2001 From: peidaqi Date: Sat, 10 Feb 2018 06:42:52 -0800 Subject: [PATCH 004/362] Making the script work with both Python2 and 3 Added enclosing parenthesis to the print() statement so it works if the sys default python is changed to Python3. --- resources/darwin/bin/code.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/darwin/bin/code.sh b/resources/darwin/bin/code.sh index 9dba3f8e378..42b8bc287c5 100755 --- a/resources/darwin/bin/code.sh +++ b/resources/darwin/bin/code.sh @@ -3,9 +3,9 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. -function realpath() { /usr/bin/python -c "import os,sys; print os.path.realpath(sys.argv[1])" "$0"; } +function realpath() { /usr/bin/python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" "$0"; } CONTENTS="$(dirname "$(dirname "$(dirname "$(dirname "$(realpath "$0")")")")")" ELECTRON="$CONTENTS/MacOS/Electron" CLI="$CONTENTS/Resources/app/out/cli.js" ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" "$@" -exit $? \ No newline at end of file +exit $? From ec8e845f03b2fe8e05df67c65517d53517a3a952 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 10 Feb 2018 08:29:47 -0800 Subject: [PATCH 005/362] Get 2 terminals showing --- .../parts/terminal/common/terminal.ts | 6 +- .../parts/terminal/common/terminalService.ts | 12 +- .../electron-browser/media/terminal.css | 7 +- .../electron-browser/terminalInstance.ts | 22 ++- .../electron-browser/terminalPanel.ts | 12 +- .../electron-browser/terminalService.ts | 23 ++- .../terminal/electron-browser/terminalTab.ts | 183 +++++++++++++++++- 7 files changed, 242 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index 65d928b3067..1b4dd35fd03 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -6,7 +6,7 @@ import Event from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { TPromise } from 'vs/base/common/winjs.base'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -153,6 +153,7 @@ export interface ITerminalService { onInstancesChanged: Event; onInstanceTitleChanged: Event; terminalInstances: ITerminalInstance[]; + terminalTabs: ITerminalTab[]; createInstance(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance; getInstanceFromId(terminalId: number): ITerminalInstance; @@ -180,7 +181,10 @@ export interface ITerminalService { export interface ITerminalTab { terminalInstances: ITerminalInstance[]; + setVisible(visible: boolean): void; + layout(width: number, height: number): void; addDisposable(disposable: IDisposable): void; + split(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, shellLaunchConfig: IShellLaunchConfig): void; } export interface ITerminalInstance { diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index 9698ebcd89a..8d8b07ee318 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -38,6 +38,7 @@ export abstract class TerminalService implements ITerminalService { public get onInstanceTitleChanged(): Event { return this._onInstanceTitleChanged.event; } public get onInstancesChanged(): Event { return this._onInstancesChanged.event; } public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } + public get terminalTabs(): ITerminalTab[] { return this._terminalTabs; } public abstract get configHelper(): ITerminalConfigHelper; @@ -166,9 +167,16 @@ export abstract class TerminalService implements ITerminalService { } const didInstanceChange = this._activeTerminalInstanceIndex !== terminalIndex; this._activeTerminalInstanceIndex = terminalIndex; - this._terminalInstances.forEach((terminalInstance, i) => { - terminalInstance.setVisible(i === terminalIndex); + + // TODO: Optimize + const activeInstance = this.terminalInstances[this.activeTerminalInstanceIndex]; + this._terminalTabs.forEach(t => { + t.setVisible(t.terminalInstances.indexOf(activeInstance) !== -1); }); + + // this._terminalInstances.forEach((terminalInstance, i) => { + // terminalInstance.setVisible(i === terminalIndex); + // }); // Only fire the event if there was a change if (didInstanceChange) { this._onActiveInstanceChanged.fire(); 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 5ce4d42203a..f6762d02fb4 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css @@ -15,7 +15,7 @@ .monaco-workbench .panel.integrated-terminal .terminal-outer-container { height: 100%; - padding-left: 20px; /*Don't use right padding in case xterm.js misbehaves*/ + margin: 0 20px; padding-bottom: 2px; width: 100%; box-sizing: border-box; @@ -58,6 +58,11 @@ font-variant-ligatures: none; } +.monaco-workbench .panel.integrated-terminal .split-view-view { + /* Make relative as terminal absolute positioning needs it deeper in the tree */ + position: relative; +} + .monaco-workbench .panel.integrated-terminal.enable-ligatures { font-variant-ligatures: normal; } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 76c5ab2d2ed..71a1c4d4539 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -246,12 +246,12 @@ export class TerminalInstance implements ITerminalInstance { const outerContainer = document.querySelector('.terminal-outer-container'); const outerContainerStyle = getComputedStyle(outerContainer); - const padding = parseInt(outerContainerStyle.paddingLeft.split('px')[0], 10); + const marginLeft = parseInt(outerContainerStyle.marginLeft.split('px')[0], 10); + const marginRight = parseInt(outerContainerStyle.marginRight.split('px')[0], 10); const paddingBottom = parseInt(outerContainerStyle.paddingBottom.split('px')[0], 10); - // Use left padding as right padding, right padding is not defined in CSS just in case - // xterm.js causes an unexpected overflow. - const innerWidth = width - padding * 2; + // TODO: Ensure horizontal padding/margin work fine for split panes + const innerWidth = width - (marginLeft + marginRight); const innerHeight = height - paddingBottom; TerminalInstance._lastKnownDimensions = new Dimension(innerWidth, innerHeight); @@ -313,6 +313,18 @@ export class TerminalInstance implements ITerminalInstance { this._instanceDisposables.push(this._themeService.onThemeChange(theme => this._updateTheme(theme))); } + public reattachToElement(container: HTMLElement): void { + if (!this._wrapperElement) { + throw new Error('The terminal instance has not been attached to a container yet'); + } + + if (this._wrapperElement.parentNode) { + this._wrapperElement.parentNode.removeChild(this._wrapperElement); + } + this._container = container; + this._container.appendChild(this._wrapperElement); + } + public attachToElement(container: HTMLElement): void { this._xtermReadyPromise.then(() => { if (this._wrapperElement) { @@ -1052,7 +1064,9 @@ export class TerminalInstance implements ITerminalInstance { } public layout(dimension: Dimension): void { + console.log('TerminalInstance.layout, dimension', dimension); const terminalWidth = this._evaluateColsAndRows(dimension.width, dimension.height); + console.log('TerminalInstance.layout, terminalWidth', terminalWidth); if (!terminalWidth) { return; } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts index e6fa28ca700..2215bd21703 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts @@ -92,9 +92,10 @@ export class TerminalPanel extends Panel { if (!dimension) { return; } - this._terminalService.terminalInstances.forEach((t) => { - t.layout(dimension); - }); + // this._terminalService.terminalInstances.forEach((t) => { + // t.layout(dimension); + // }); + this._terminalService.terminalTabs.forEach(t => t.layout(dimension.width, dimension.height)); } public setVisible(visible: boolean): TPromise { @@ -169,6 +170,7 @@ export class TerminalPanel extends Panel { } public focus(): void { + console.log('TerminalPanel.focus'); const activeInstance = this._terminalService.getActiveInstance(); if (activeInstance) { activeInstance.focus(true); @@ -261,6 +263,10 @@ export class TerminalPanel extends Panel { const instance = this._terminalService.getActiveInstance(); if (instance) { + + // TODO: This and other usages for getActiveInstance().focus() are now invalid, + // TerminalService should defer the active instance to the TerminalTab + console.log('clicked, focus'); this._terminalService.getActiveInstance().focus(); } })); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 10526198a08..745f07306e7 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -52,14 +52,23 @@ export class TerminalService extends AbstractTerminalService implements ITermina } public createInstance(shell: IShellLaunchConfig = {}, wasNewTerminalAction?: boolean): ITerminalInstance { - const terminalTab = this._instantiationService.createInstance(TerminalTab, - this._terminalFocusContextKey, - this._configHelper, - this._terminalContainer, - shell); - this._terminalTabs.push(terminalTab); + let terminalTab; + let terminalInstance; + if (this.terminalTabs.length === 0) { + terminalTab = this._instantiationService.createInstance(TerminalTab, + this._terminalFocusContextKey, + this._configHelper, + this._terminalContainer, + shell); + this._terminalTabs.push(terminalTab); + terminalInstance = terminalTab.terminalInstances[0]; + } else { + // Always Split temporarily + terminalTab = this.terminalTabs[0]; + terminalTab.split(this._terminalFocusContextKey, this._configHelper, shell); + terminalInstance = terminalTab.terminalInstances[terminalTab.terminalInstances.length - 1]; + } terminalTab.addDisposable(terminalTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed)); - const terminalInstance = terminalTab.terminalInstances[0]; terminalInstance.addDisposable(terminalInstance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed)); terminalInstance.addDisposable(terminalInstance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged)); terminalInstance.addDisposable(terminalInstance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady)); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts index a9b180ad29b..9e22518b3ee 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts @@ -8,12 +8,149 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance'; -import Event, { Emitter } from 'vs/base/common/event'; +import Event, { Emitter, anyEvent } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { SplitView, Orientation, IView } from 'vs/base/browser/ui/splitview/splitview'; + +class SplitPane implements IView { + // TODO: What's a good number for the min? + public minimumSize: number = 10; + public maximumSize: number = Number.MAX_VALUE; + protected _size: number; + + public orientation: Orientation | undefined; + private _splitView: SplitView | undefined; + private _children: SplitPane[] = []; + private _container: HTMLElement; + + public instance: ITerminalInstance; + private _isContainerSet: boolean = false; + + private _onDidChange: Event = Event.None; + public get onDidChange(): Event { + return this._onDidChange; + } + + constructor( + private _parent?: SplitPane, + public orthogonalSize?: number, + private _needsReattach?: boolean + ) { + } + + protected branch(container: HTMLElement, orientation: Orientation, instance: ITerminalInstance): void { + this.orientation = orientation; + while (container.children.length > 0) { + container.removeChild(container.firstChild); + } + + this._splitView = new SplitView(container, { orientation }); + this.layout(this._size); + this.orthogonalLayout(this.orthogonalSize); + + this.addChild(this.orthogonalSize / 2, this._size, this.instance, 0, this._isContainerSet); + this.addChild(this.orthogonalSize / 2, this._size, instance); + } + + public split(instance: ITerminalInstance): void { + if (this._parent && this._parent.orientation === orientation) { + const index = this._parent._children.indexOf(this); + this._parent.addChild(this._size / 2, this.orthogonalSize, instance, index + 1); + } else { + // TODO: Ensure terminal reattach is handled properly + this.branch(this._container, this.orientation, instance); + } + } + + private addChild(size: number, orthogonalSize: number, instance: ITerminalInstance, index?: number, needsReattach?: boolean): void { + const child = new SplitPane(this, orthogonalSize, needsReattach); + child.instance = instance; + this._splitView.addView(child, size, index); + + if (typeof index === 'number') { + this._children.splice(index, 0, child); + } else { + this._children.push(child); + } + + this._onDidChange = anyEvent(...this._children.map(c => c.onDidChange)); + } + + public render(container: HTMLElement): void { + this._container = container; + console.log('render'); + // throw new Error("Method not implemented."); + if (!this._isContainerSet && this.instance) { + if (this._needsReattach) { + console.log('reattachToElement'); + (this.instance).reattachToElement(container); + } else { + console.log('attachToElement'); + this.instance.attachToElement(container); + } + this._isContainerSet = true; + } + } + + public layout(size: number): void { + this._size = size; + if (!this._size || !this.orthogonalSize) { + return; + } + + console.log('layout', size, this.orthogonalSize); + + if (this.orientation === Orientation.VERTICAL) { + this.instance.layout({ width: this._size, height: this.orthogonalSize }); + } else { + this.instance.layout({ width: this.orthogonalSize, height: this._size }); + } + } + + public orthogonalLayout(size: number): void { + this.orthogonalSize = size; + + if (this._splitView) { + this._splitView.layout(size); + } + } +} + +class RootSplitPane extends SplitPane { + private _width: number; + private _height: number; + + protected branch(container: HTMLElement, orientation: Orientation, instance: ITerminalInstance): void { + if (orientation === Orientation.VERTICAL) { + this._size = this._width; + this.orthogonalSize = this._height; + } else { + this._size = this._height; + this.orthogonalSize = this._width; + } + + super.branch(container, orientation, instance); + } + + public layoutBox(width: number, height: number): void { + if (this.orientation === Orientation.VERTICAL) { + this.layout(width); + this.orthogonalLayout(height); + } else if (this.orientation === Orientation.HORIZONTAL) { + this.layout(height); + this.orthogonalLayout(width); + } else { + this._width = width; + this._height = height; + } + } +} export class TerminalTab implements ITerminalTab { private _terminalInstances: ITerminalInstance[] = []; private _disposables: IDisposable[] = []; + private _rootSplitPane: RootSplitPane; + private _splitPanes: SplitPane[] = []; public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } @@ -23,19 +160,51 @@ export class TerminalTab implements ITerminalTab { constructor( terminalFocusContextKey: IContextKey, configHelper: TerminalConfigHelper, - container: HTMLElement, + private _container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, - @IInstantiationService instantiationService: IInstantiationService + @IInstantiationService private _instantiationService: IInstantiationService ) { this._onDisposed = new Emitter(); - const instance = instantiationService.createInstance(TerminalInstance, + const instance = this._instantiationService.createInstance(TerminalInstance, terminalFocusContextKey, configHelper, - container, + undefined, shellLaunchConfig); this._terminalInstances.push(instance); instance.addDisposable(instance.onDisposed(instance => this._onInstanceDisposed(instance))); + + this._rootSplitPane = new RootSplitPane(); + this._rootSplitPane.instance = instance; + // TODO: Only render if it's visible? + this._rootSplitPane.render(this._container); + // TODO: Is _splitPanes useful? + this._splitPanes.push(this._rootSplitPane); + } + + public setVisible(visible: boolean): void { + this._container.style.display = visible ? 'block' : 'none'; + // TODO: probably don't need to tell terminal instances about visiblility anymore? + this.terminalInstances.forEach(i => i.setVisible(visible)); + } + + public split( + terminalFocusContextKey: IContextKey, + configHelper: TerminalConfigHelper, + shellLaunchConfig: IShellLaunchConfig + ): void { + const instance = this._instantiationService.createInstance(TerminalInstance, + terminalFocusContextKey, + configHelper, + undefined, + shellLaunchConfig); + this._terminalInstances.push(instance); + instance.addDisposable(instance.onDisposed(instance => this._onInstanceDisposed(instance))); + + this._rootSplitPane.orientation = Orientation.HORIZONTAL; + this._rootSplitPane.split(instance); + const pane2 = new SplitPane(); + this._splitPanes.push(pane2); } private _onInstanceDisposed(instance: ITerminalInstance): void { @@ -49,4 +218,8 @@ export class TerminalTab implements ITerminalTab { public addDisposable(disposable: IDisposable): void { this._disposables.push(disposable); } + + public layout(width: number, height: number): void { + this._rootSplitPane.layoutBox(width, height); + } } From cbbbe6a517fe4c65d811cf46209e46cfcd57e157 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 10 Feb 2018 08:37:02 -0800 Subject: [PATCH 006/362] Move title code to tab --- .../parts/terminal/browser/terminalQuickOpen.ts | 2 +- src/vs/workbench/parts/terminal/common/terminal.ts | 3 ++- .../workbench/parts/terminal/common/terminalService.ts | 10 ++-------- .../parts/terminal/electron-browser/terminalActions.ts | 4 ++-- .../parts/terminal/electron-browser/terminalTab.ts | 8 ++++++++ 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/parts/terminal/browser/terminalQuickOpen.ts b/src/vs/workbench/parts/terminal/browser/terminalQuickOpen.ts index 14553747832..1ba6c79b538 100644 --- a/src/vs/workbench/parts/terminal/browser/terminalQuickOpen.ts +++ b/src/vs/workbench/parts/terminal/browser/terminalQuickOpen.ts @@ -109,7 +109,7 @@ export class TerminalPickerHandler extends QuickOpenHandler { } private getTerminals(): TerminalEntry[] { - const terminals = this.terminalService.getInstanceLabels(); + const terminals = this.terminalService.getTabLabels(); const terminalEntries = terminals.map(terminal => { return new TerminalEntry(terminal, this.terminalService); }); diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index 1b4dd35fd03..c10d254de21 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -158,7 +158,7 @@ export interface ITerminalService { createInstance(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance; getInstanceFromId(terminalId: number): ITerminalInstance; getInstanceFromIndex(terminalIndex: number): ITerminalInstance; - getInstanceLabels(): string[]; + getTabLabels(): string[]; getActiveInstance(): ITerminalInstance; setActiveInstance(terminalInstance: ITerminalInstance): void; setActiveInstanceByIndex(terminalIndex: number): void; @@ -180,6 +180,7 @@ export interface ITerminalService { export interface ITerminalTab { terminalInstances: ITerminalInstance[]; + title: string; setVisible(visible: boolean): void; layout(width: number, height: number): void; diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index 8d8b07ee318..fafd61507a6 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -96,14 +96,8 @@ export abstract class TerminalService implements ITerminalService { this.terminalInstances.forEach(instance => instance.dispose()); } - public getInstanceLabels(): string[] { - return this._terminalTabs.map((tab, index) => { - let title = tab.terminalInstances[0].title; - for (let i = 1; i < tab.terminalInstances.length; i++) { - title += `, ${tab.terminalInstances[i].title}`; - } - return `${index + 1}: ${title}`; - }); + public getTabLabels(): string[] { + return this._terminalTabs.map((tab, index) => `${index + 1}: ${tab.title}`); } private _removeTab(tab: ITerminalTab): void { diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index 001c4303a1f..dbd08f40c9c 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -490,7 +490,7 @@ export class SwitchTerminalInstanceActionItem extends SelectActionItem { @IThemeService themeService: IThemeService, @IContextViewService contextViewService: IContextViewService ) { - super(null, action, terminalService.getInstanceLabels(), terminalService.activeTerminalInstanceIndex, contextViewService); + super(null, action, terminalService.getTabLabels(), terminalService.activeTerminalInstanceIndex, contextViewService); this.toDispose.push(terminalService.onInstancesChanged(this._updateItems, this)); this.toDispose.push(terminalService.onActiveInstanceChanged(this._updateItems, this)); @@ -499,7 +499,7 @@ export class SwitchTerminalInstanceActionItem extends SelectActionItem { } private _updateItems(): void { - this.setOptions(this.terminalService.getInstanceLabels(), this.terminalService.activeTerminalInstanceIndex); + this.setOptions(this.terminalService.getTabLabels(), this.terminalService.activeTerminalInstanceIndex); } } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts index 9e22518b3ee..365274138b2 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts @@ -182,6 +182,14 @@ export class TerminalTab implements ITerminalTab { this._splitPanes.push(this._rootSplitPane); } + public get title(): string { + let title = this.terminalInstances[0].title; + for (let i = 1; i < this.terminalInstances.length; i++) { + title += `, ${this.terminalInstances[i].title}`; + } + return title; + } + public setVisible(visible: boolean): void { this._container.style.display = visible ? 'block' : 'none'; // TODO: probably don't need to tell terminal instances about visiblility anymore? From 4dbb144302773e22138973227cc8bc10e45dbe00 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 10 Feb 2018 18:05:33 -0800 Subject: [PATCH 007/362] Layout orientation correctly --- .../parts/terminal/common/terminal.ts | 3 +- .../parts/terminal/common/terminalService.ts | 35 +++++++++--- .../electron-browser/terminalActions.ts | 2 +- .../electron-browser/terminalInstance.ts | 6 ++ .../electron-browser/terminalService.ts | 1 + .../terminal/electron-browser/terminalTab.ts | 55 +++++++++++++------ 6 files changed, 74 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index c10d254de21..c6d306f2b33 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -146,7 +146,7 @@ export interface ITerminalService { activeTerminalInstanceIndex: number; configHelper: ITerminalConfigHelper; - onActiveInstanceChanged: Event; + onActiveTabChanged: Event; onTabDisposed: Event; onInstanceDisposed: Event; onInstanceProcessIdReady: Event; @@ -182,6 +182,7 @@ export interface ITerminalTab { terminalInstances: ITerminalInstance[]; title: string; + attachToElement(element: HTMLElement): void; setVisible(visible: boolean): void; layout(width: number, height: number): void; addDisposable(disposable: IDisposable): void; diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index fafd61507a6..9078aba4bba 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -27,11 +27,13 @@ export abstract class TerminalService implements ITerminalService { protected _terminalTabs: ITerminalTab[]; protected abstract _terminalInstances: ITerminalInstance[]; + private _activeTabIndex: number; + // TODO: Remove _activeTerminalInstanceIndex private _activeTerminalInstanceIndex: number; - private _onActiveInstanceChanged: Emitter; + private _onActiveTabChanged: Emitter; public get activeTerminalInstanceIndex(): number { return this._activeTerminalInstanceIndex; } - public get onActiveInstanceChanged(): Event { return this._onActiveInstanceChanged.event; } + public get onActiveTabChanged(): Event { return this._onActiveTabChanged.event; } public get onTabDisposed(): Event { return this._onTabDisposed.event; } public get onInstanceDisposed(): Event { return this._onInstanceDisposed.event; } public get onInstanceProcessIdReady(): Event { return this._onInstanceProcessIdReady.event; } @@ -48,10 +50,11 @@ export abstract class TerminalService implements ITerminalService { @IPartService private _partService: IPartService, @ILifecycleService lifecycleService: ILifecycleService ) { + this._activeTabIndex = 0; this._activeTerminalInstanceIndex = 0; this._isShuttingDown = false; - this._onActiveInstanceChanged = new Emitter(); + this._onActiveTabChanged = new Emitter(); this._onTabDisposed = new Emitter(); this._onInstanceDisposed = new Emitter(); this._onInstanceProcessIdReady = new Emitter(); @@ -102,11 +105,11 @@ export abstract class TerminalService implements ITerminalService { private _removeTab(tab: ITerminalTab): void { let index = this._terminalTabs.indexOf(tab); - let wasActiveInstance = tab === this._getActiveTab(); + let wasActiveTab = tab === this._getActiveTab(); if (index !== -1) { this._terminalTabs.splice(index, 1); } - if (wasActiveInstance && this._terminalTabs.length > 0) { + if (wasActiveTab && this._terminalTabs.length > 0) { let newIndex = index < this._terminalTabs.length ? index : this._terminalTabs.length - 1; this.setActiveInstanceByIndex(newIndex); // TODO: Needs to be made to work with multiple instances in a tab @@ -122,8 +125,8 @@ export abstract class TerminalService implements ITerminalService { } // TODO: This should be onTabsChanged? this._onInstancesChanged.fire(); - if (wasActiveInstance) { - this._onActiveInstanceChanged.fire(); + if (wasActiveTab) { + this._onActiveTabChanged.fire(); } } @@ -155,6 +158,21 @@ export abstract class TerminalService implements ITerminalService { this.setActiveInstanceByIndex(this._getIndexFromId(terminalInstance.id)); } + public setActiveTabByIndex(tabIndex: number): void { + if (tabIndex >= this._terminalTabs.length) { + return; + } + + const didTabChange = this._activeTabIndex !== tabIndex; + this._activeTabIndex = tabIndex; + + this._terminalTabs.forEach((t, i) => t.setVisible(i === this._activeTabIndex)); + if (didTabChange) { + this._onActiveTabChanged.fire(); + } + } + + // TODO: Remove setActiveInstanceByIndex? public setActiveInstanceByIndex(terminalIndex: number): void { if (terminalIndex >= this._terminalInstances.length) { return; @@ -173,7 +191,8 @@ export abstract class TerminalService implements ITerminalService { // }); // Only fire the event if there was a change if (didInstanceChange) { - this._onActiveInstanceChanged.fire(); + // TODO: If this method is being kept this should only fire when the tab is actually changed + this._onActiveTabChanged.fire(); } } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index dbd08f40c9c..5d6fccd4dcb 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -493,7 +493,7 @@ export class SwitchTerminalInstanceActionItem extends SelectActionItem { super(null, action, terminalService.getTabLabels(), terminalService.activeTerminalInstanceIndex, contextViewService); this.toDispose.push(terminalService.onInstancesChanged(this._updateItems, this)); - this.toDispose.push(terminalService.onActiveInstanceChanged(this._updateItems, this)); + this.toDispose.push(terminalService.onActiveTabChanged(this._updateItems, this)); this.toDispose.push(terminalService.onInstanceTitleChanged(this._updateItems, this)); this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService)); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 71a1c4d4539..df5df5d4e9e 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -202,10 +202,16 @@ export class TerminalInstance implements ITerminalInstance { * @return The terminal's width if it requires a layout. */ private _evaluateColsAndRows(width: number, height: number): number { + // Ignore if dimensions are undefined or 0 + if (!width || !height) { + return null; + } + const dimension = this._getDimension(width, height); if (!dimension) { return null; } + const font = this._configHelper.getFont(); // Because xterm.js converts from CSS pixels to actual pixels through diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 745f07306e7..6d3a5b531ea 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -247,6 +247,7 @@ export class TerminalService extends AbstractTerminalService implements ITermina public setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void { this._configHelper.panelContainer = panelContainer; this._terminalContainer = terminalContainer; + this._terminalTabs.forEach(tab => tab.attachToElement(this._terminalContainer)); this._terminalInstances.forEach(terminalInstance => { terminalInstance.attachToElement(this._terminalContainer); }); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts index 365274138b2..d24748489c1 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts @@ -9,7 +9,7 @@ import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-brows import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance'; import Event, { Emitter, anyEvent } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { SplitView, Orientation, IView } from 'vs/base/browser/ui/splitview/splitview'; class SplitPane implements IView { @@ -64,6 +64,7 @@ class SplitPane implements IView { private addChild(size: number, orthogonalSize: number, instance: ITerminalInstance, index?: number, needsReattach?: boolean): void { const child = new SplitPane(this, orthogonalSize, needsReattach); + child.orientation = this.orientation; child.instance = instance; this._splitView.addView(child, size, index); @@ -77,6 +78,9 @@ class SplitPane implements IView { } public render(container: HTMLElement): void { + if (!container) { + return; + } this._container = container; console.log('render'); // throw new Error("Method not implemented."); @@ -101,17 +105,18 @@ class SplitPane implements IView { console.log('layout', size, this.orthogonalSize); if (this.orientation === Orientation.VERTICAL) { - this.instance.layout({ width: this._size, height: this.orthogonalSize }); - } else { this.instance.layout({ width: this.orthogonalSize, height: this._size }); + } else { + this.instance.layout({ width: this._size, height: this.orthogonalSize }); } } public orthogonalLayout(size: number): void { this.orthogonalSize = size; + console.log('orthogonalLayout', this._size, this.orthogonalSize); if (this._splitView) { - this._splitView.layout(size); + this._splitView.layout(this.orthogonalSize); } } } @@ -146,12 +151,13 @@ class RootSplitPane extends SplitPane { } } -export class TerminalTab implements ITerminalTab { +export class TerminalTab extends Disposable implements ITerminalTab { private _terminalInstances: ITerminalInstance[] = []; - private _disposables: IDisposable[] = []; private _rootSplitPane: RootSplitPane; private _splitPanes: SplitPane[] = []; + // private _activeInstanceIndex: number; + public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } private _onDisposed: Emitter; @@ -164,6 +170,7 @@ export class TerminalTab implements ITerminalTab { shellLaunchConfig: IShellLaunchConfig, @IInstantiationService private _instantiationService: IInstantiationService ) { + super(); this._onDisposed = new Emitter(); const instance = this._instantiationService.createInstance(TerminalInstance, @@ -172,6 +179,7 @@ export class TerminalTab implements ITerminalTab { undefined, shellLaunchConfig); this._terminalInstances.push(instance); + // this._activeInstanceIndex = 0; instance.addDisposable(instance.onDisposed(instance => this._onInstanceDisposed(instance))); this._rootSplitPane = new RootSplitPane(); @@ -182,6 +190,23 @@ export class TerminalTab implements ITerminalTab { this._splitPanes.push(this._rootSplitPane); } + public dispose(): void { + super.dispose(); + this._terminalInstances = []; + } + + private _onInstanceDisposed(instance: ITerminalInstance): void { + + // TODO: Listen for disposed on TerminalService and handle appropriately (remove the tab and its instance from the service) + + this._onDisposed.fire(this); + this.dispose(); + } + + public attachToElement(element: HTMLElement): void { + this._container = element; + } + public get title(): string { let title = this.terminalInstances[0].title; for (let i = 1; i < this.terminalInstances.length; i++) { @@ -191,7 +216,9 @@ export class TerminalTab implements ITerminalTab { } public setVisible(visible: boolean): void { - this._container.style.display = visible ? 'block' : 'none'; + if (this._container) { + this._container.style.display = visible ? 'block' : 'none'; + } // TODO: probably don't need to tell terminal instances about visiblility anymore? this.terminalInstances.forEach(i => i.setVisible(visible)); } @@ -211,20 +238,12 @@ export class TerminalTab implements ITerminalTab { this._rootSplitPane.orientation = Orientation.HORIZONTAL; this._rootSplitPane.split(instance); - const pane2 = new SplitPane(); - this._splitPanes.push(pane2); - } - - private _onInstanceDisposed(instance: ITerminalInstance): void { - - // TODO: Listen for disposed on TerminalService and handle appropriately (remove the tab and its instance from the service) - - this._onDisposed.fire(this); - this._terminalInstances = []; + // const pane2 = new SplitPane(); + // this._splitPanes.push(pane2); } public addDisposable(disposable: IDisposable): void { - this._disposables.push(disposable); + this._register(disposable); } public layout(width: number, height: number): void { From b00aa99d395b43eb260ceeb75e607af3307bfdc7 Mon Sep 17 00:00:00 2001 From: ergun1017 Date: Sun, 11 Feb 2018 15:58:03 +0300 Subject: [PATCH 008/362] Added copy property path action to debug viewlet --- .../electron-browser/electronDebugActions.ts | 17 +++++++++++++++++ .../debug/electron-browser/variablesView.ts | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/debug/electron-browser/electronDebugActions.ts b/src/vs/workbench/parts/debug/electron-browser/electronDebugActions.ts index 96e069a9a59..e07ad11f52b 100644 --- a/src/vs/workbench/parts/debug/electron-browser/electronDebugActions.ts +++ b/src/vs/workbench/parts/debug/electron-browser/electronDebugActions.ts @@ -34,6 +34,23 @@ export class CopyValueAction extends Action { } } +export class CopyPropertyPathAction extends Action { + static readonly ID = 'workbench.debug.viewlet.action.copyPropertyPath'; + static LABEL = nls.localize('copyPropertyPath', "Copy Property Path"); + + constructor(id: string, label: string, private value: any) { + super(id, label, 'debug-action copy-property-path'); + } + + public run(): TPromise { + if (this.value instanceof Variable) { + clipboard.writeText(this.value.evaluateName); + } + + return TPromise.as(null); + } +} + export class CopyAction extends Action { static readonly ID = 'workbench.debug.action.copy'; static LABEL = nls.localize('copy', "Copy"); diff --git a/src/vs/workbench/parts/debug/electron-browser/variablesView.ts b/src/vs/workbench/parts/debug/electron-browser/variablesView.ts index 6d6b197dc1e..99c47eb17ce 100644 --- a/src/vs/workbench/parts/debug/electron-browser/variablesView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/variablesView.ts @@ -21,7 +21,7 @@ import { twistiePixels, renderViewTree, IVariableTemplateData, BaseDebugControll import { TPromise } from 'vs/base/common/winjs.base'; import { IAction, IActionItem } from 'vs/base/common/actions'; import { SetValueAction, AddToWatchExpressionsAction } from 'vs/workbench/parts/debug/browser/debugActions'; -import { CopyValueAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions'; +import { CopyValueAction, CopyPropertyPathAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel'; import { equalsIgnoreCase } from 'vs/base/common/strings'; @@ -179,6 +179,7 @@ class VariablesActionProvider implements IActionProvider { const actions: IAction[] = []; const variable = element; actions.push(new SetValueAction(SetValueAction.ID, SetValueAction.LABEL, variable, this.debugService, this.keybindingService)); + actions.push(new CopyPropertyPathAction(CopyPropertyPathAction.ID, CopyPropertyPathAction.LABEL, variable)); actions.push(new CopyValueAction(CopyValueAction.ID, CopyValueAction.LABEL, variable, this.debugService)); actions.push(new Separator()); actions.push(new AddToWatchExpressionsAction(AddToWatchExpressionsAction.ID, AddToWatchExpressionsAction.LABEL, variable, this.debugService, this.keybindingService)); From 5972d9b8fa7079d9de19f2124b3aebb19fa1179e Mon Sep 17 00:00:00 2001 From: ergun1017 Date: Sun, 11 Feb 2018 23:20:18 +0300 Subject: [PATCH 009/362] Added margin-left: -1px to remove effect of the left border --- .../parts/files/electron-browser/media/explorerviewlet.css | 1 + 1 file changed, 1 insertion(+) 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 eb657380a67..9cd2408fa2c 100644 --- a/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css +++ b/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css @@ -109,6 +109,7 @@ .monaco-workbench.linux .explorer-viewlet .explorer-item .monaco-inputbox, .monaco-workbench.mac .explorer-viewlet .explorer-item .monaco-inputbox { height: 22px; + margin-left: -1px; } .explorer-viewlet .explorer-item .monaco-inputbox > .wrapper > .input { From c9fc911330c508be64b3bb04b661c2955fcd41d1 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 12 Feb 2018 12:40:53 +0100 Subject: [PATCH 010/362] fix formatting --- build/gulpfile.editor.js | 38 +++++++++++++++++----------------- build/gulpfile.extensions.js | 2 +- build/gulpfile.vscode.js | 10 ++++----- build/lib/watch/watch-nsfw.js | 18 ++++++++-------- build/lib/watch/watch-win32.js | 8 +++---- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 835ccf10d04..f282b006cd8 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -22,14 +22,14 @@ var editorEntryPoints = [ { name: 'vs/editor/editor.main', include: [], - exclude: [ 'vs/css', 'vs/nls' ], - prepend: [ 'out-build/vs/css.js', 'out-build/vs/nls.js' ], + exclude: ['vs/css', 'vs/nls'], + prepend: ['out-build/vs/css.js', 'out-build/vs/nls.js'], }, { name: 'vs/base/common/worker/simpleWorker', - include: [ 'vs/editor/common/services/editorSimpleWorker' ], - prepend: [ 'vs/loader.js' ], - append: [ 'vs/base/worker/workerMain' ], + include: ['vs/editor/common/services/editorSimpleWorker'], + prepend: ['vs/loader.js'], + append: ['vs/base/worker/workerMain'], dest: 'vs/base/worker/workerMain.js' } ]; @@ -88,7 +88,7 @@ gulp.task('clean-minified-editor', util.rimraf('out-editor-min')); gulp.task('minify-editor', ['clean-minified-editor', 'optimize-editor'], common.minifyTask('out-editor')); gulp.task('clean-editor-distro', util.rimraf('out-monaco-editor-core')); -gulp.task('editor-distro', ['clean-editor-distro', 'minify-editor', 'optimize-editor'], function() { +gulp.task('editor-distro', ['clean-editor-distro', 'minify-editor', 'optimize-editor'], function () { return es.merge( // other assets es.merge( @@ -99,7 +99,7 @@ gulp.task('editor-distro', ['clean-editor-distro', 'minify-editor', 'optimize-ed // package.json gulp.src('build/monaco/package.json') - .pipe(es.through(function(data) { + .pipe(es.through(function (data) { var json = JSON.parse(data.contents.toString()); json.private = false; data.contents = new Buffer(JSON.stringify(json, null, ' ')); @@ -109,7 +109,7 @@ gulp.task('editor-distro', ['clean-editor-distro', 'minify-editor', 'optimize-ed // README.md gulp.src('build/monaco/README-npm.md') - .pipe(es.through(function(data) { + .pipe(es.through(function (data) { this.emit('data', new File({ path: data.path.replace(/README-npm\.md/, 'README.md'), base: data.base, @@ -126,10 +126,10 @@ gulp.task('editor-distro', ['clean-editor-distro', 'minify-editor', 'optimize-ed // min folder es.merge( gulp.src('out-editor-min/**/*') - ).pipe(filterStream(function(path) { + ).pipe(filterStream(function (path) { // no map files return !/(\.js\.map$)|(nls\.metadata\.json$)|(bundleInfo\.json$)/.test(path); - })).pipe(es.through(function(data) { + })).pipe(es.through(function (data) { // tweak the sourceMappingURL if (!/\.js$/.test(data.path)) { this.emit('data', data); @@ -149,43 +149,43 @@ gulp.task('editor-distro', ['clean-editor-distro', 'minify-editor', 'optimize-ed // min-maps folder es.merge( gulp.src('out-editor-min/**/*') - ).pipe(filterStream(function(path) { + ).pipe(filterStream(function (path) { // no map files return /\.js\.map$/.test(path); })).pipe(gulp.dest('out-monaco-editor-core/min-maps')) ); }); -gulp.task('analyze-editor-distro', function() { +gulp.task('analyze-editor-distro', function () { // @ts-ignore Microsoft/TypeScript#21262 complains about a require of a JSON file var bundleInfo = require('../out-editor/bundleInfo.json'); var graph = bundleInfo.graph; var bundles = bundleInfo.bundles; var inverseGraph = {}; - Object.keys(graph).forEach(function(module) { + Object.keys(graph).forEach(function (module) { var dependencies = graph[module]; - dependencies.forEach(function(dep) { + dependencies.forEach(function (dep) { inverseGraph[dep] = inverseGraph[dep] || []; inverseGraph[dep].push(module); }); }); var detailed = {}; - Object.keys(bundles).forEach(function(entryPoint) { + Object.keys(bundles).forEach(function (entryPoint) { var included = bundles[entryPoint]; var includedMap = {}; - included.forEach(function(included) { + included.forEach(function (included) { includedMap[included] = true; }); var explanation = []; - included.map(function(included) { + included.map(function (included) { if (included.indexOf('!') >= 0) { return; } - var reason = (inverseGraph[included]||[]).filter(function(mod) { + var reason = (inverseGraph[included] || []).filter(function (mod) { return !!includedMap[mod]; }); explanation.push({ @@ -201,7 +201,7 @@ gulp.task('analyze-editor-distro', function() { }); function filterStream(testFunc) { - return es.through(function(data) { + return es.through(function (data) { if (!testFunc(data.relative)) { return; } diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index c9c244c38d8..ac1b08c6fd2 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -31,7 +31,7 @@ const compilations = glob.sync('**/tsconfig.json', { const getBaseUrl = out => `https://ticino.blob.core.windows.net/sourcemaps/${commit}/${out}`; -const languages = i18n.defaultLanguages.concat(process.env.VSCODE_QUALITY !== 'stable' ? i18n.extraLanguages: []); +const languages = i18n.defaultLanguages.concat(process.env.VSCODE_QUALITY !== 'stable' ? i18n.extraLanguages : []); const tasks = compilations.map(function (tsconfigFile) { const absolutePath = path.join(extensionsPath, tsconfigFile); diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index c324b78063a..849a8a1018d 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -312,7 +312,7 @@ function packageTask(platform, arch, opts) { .pipe(util.cleanNodeModule('node-pty', ['binding.gyp', 'build/**', 'src/**', 'tools/**'], ['build/Release/*.exe', 'build/Release/*.dll', 'build/Release/*.node'])) .pipe(util.cleanNodeModule('nsfw', ['binding.gyp', 'build/**', 'src/**', 'openpa/**', 'includes/**'], ['**/*.node', '**/*.a'])) .pipe(util.cleanNodeModule('vsda', ['binding.gyp', 'README.md', 'build/**', '*.bat', '*.sh', '*.cpp', '*.h'], ['build/Release/vsda.node'])); - // .pipe(createAsar(path.join(process.cwd(), 'node_modules'), ['**/*.node', '**/vscode-ripgrep/bin/*', '**/node-pty/build/Release/*'], 'app/node_modules.asar')); + // .pipe(createAsar(path.join(process.cwd(), 'node_modules'), ['**/*.node', '**/vscode-ripgrep/bin/*', '**/node-pty/build/Release/*'], 'app/node_modules.asar')); let all = es.merge( packageJsonStream, @@ -408,7 +408,7 @@ const apiHostname = process.env.TRANSIFEX_API_URL; const apiName = process.env.TRANSIFEX_API_NAME; const apiToken = process.env.TRANSIFEX_API_TOKEN; -gulp.task('vscode-translations-push', [ 'optimize-vscode' ], function () { +gulp.task('vscode-translations-push', ['optimize-vscode'], function () { const pathToMetadata = './out-vscode/nls.metadata.json'; const pathToExtensions = './extensions/*'; const pathToSetup = 'build/win32/**/{Default.isl,messages.en.isl}'; @@ -418,10 +418,10 @@ gulp.task('vscode-translations-push', [ 'optimize-vscode' ], function () { gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()), gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions()) ).pipe(i18n.findObsoleteResources(apiHostname, apiName, apiToken) - ).pipe(i18n.pushXlfFiles(apiHostname, apiName, apiToken)); + ).pipe(i18n.pushXlfFiles(apiHostname, apiName, apiToken)); }); -gulp.task('vscode-translations-push-test', [ 'optimize-vscode' ], function () { +gulp.task('vscode-translations-push-test', ['optimize-vscode'], function () { const pathToMetadata = './out-vscode/nls.metadata.json'; const pathToExtensions = './extensions/*'; const pathToSetup = 'build/win32/**/{Default.isl,messages.en.isl}'; @@ -431,7 +431,7 @@ gulp.task('vscode-translations-push-test', [ 'optimize-vscode' ], function () { gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()), gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions()) ).pipe(i18n.findObsoleteResources(apiHostname, apiName, apiToken) - ).pipe(vfs.dest('../vscode-transifex-input')); + ).pipe(vfs.dest('../vscode-transifex-input')); }); gulp.task('vscode-translations-pull', function () { diff --git a/build/lib/watch/watch-nsfw.js b/build/lib/watch/watch-nsfw.js index 7c70c95b13f..306ab481039 100644 --- a/build/lib/watch/watch-nsfw.js +++ b/build/lib/watch/watch-nsfw.js @@ -35,7 +35,7 @@ function watch(root) { result.emit('data', file); } - nsfw(root, function(events) { + nsfw(root, function (events) { for (var i = 0; i < events.length; i++) { var e = events[i]; var changeType = e.action; @@ -47,16 +47,16 @@ function watch(root) { handleEvent(path.join(e.directory, e.file), toChangeType(changeType)); } } - }).then(function(watcher) { + }).then(function (watcher) { watcher.start(); - }); + }); - return result; + return result; } var cache = Object.create(null); -module.exports = function(pattern, options) { +module.exports = function (pattern, options) { options = options || {}; var cwd = path.normalize(options.cwd || process.cwd()); @@ -66,7 +66,7 @@ module.exports = function(pattern, options) { watcher = cache[cwd] = watch(cwd); } - var rebase = !options.base ? es.through() : es.mapSync(function(f) { + var rebase = !options.base ? es.through() : es.mapSync(function (f) { f.base = options.base; return f; }); @@ -74,13 +74,13 @@ module.exports = function(pattern, options) { return watcher .pipe(filter(['**', '!.git{,/**}'])) // ignore all things git .pipe(filter(pattern)) - .pipe(es.map(function(file, cb) { - fs.stat(file.path, function(err, stat) { + .pipe(es.map(function (file, cb) { + fs.stat(file.path, function (err, stat) { if (err && err.code === 'ENOENT') { return cb(null, file); } if (err) { return cb(); } if (!stat.isFile()) { return cb(); } - fs.readFile(file.path, function(err, contents) { + fs.readFile(file.path, function (err, contents) { if (err && err.code === 'ENOENT') { return cb(null, file); } if (err) { return cb(); } diff --git a/build/lib/watch/watch-win32.js b/build/lib/watch/watch-win32.js index aae55adc6ec..de9c76c90d6 100644 --- a/build/lib/watch/watch-win32.js +++ b/build/lib/watch/watch-win32.js @@ -24,7 +24,7 @@ function watch(root) { var result = es.through(); var child = cp.spawn(watcherPath, [root]); - child.stdout.on('data', function(data) { + child.stdout.on('data', function (data) { //@ts-ignore review var lines = data.toString('utf8').split('\n'); for (var i = 0; i < lines.length; i++) { @@ -53,11 +53,11 @@ function watch(root) { } }); - child.stderr.on('data', function(data) { + child.stderr.on('data', function (data) { result.emit('error', data); }); - child.on('exit', function(code) { + child.on('exit', function (code) { result.emit('error', 'Watcher died with code ' + code); child = null; }); @@ -71,7 +71,7 @@ function watch(root) { var cache = Object.create(null); -module.exports = function(pattern, options) { +module.exports = function (pattern, options) { options = options || {}; var cwd = path.normalize(options.cwd || process.cwd()); From a10fc27932f1aa0452cb341b542194ec45d74352 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2018 08:29:36 +0100 Subject: [PATCH 011/362] notifications - scaffold a notification service --- .../standalone/browser/simpleServices.ts | 25 ++++++++++++++++ .../standalone/browser/standaloneServices.ts | 5 +++- .../notification/common/notification.ts | 28 ++++++++++++++++++ src/vs/workbench/electron-browser/actions.ts | 7 +++-- src/vs/workbench/electron-browser/shell.ts | 4 +++ .../notification/browser/notificationList.ts | 20 +++++++++++++ .../browser/notificationService.ts | 29 +++++++++++++++++++ 7 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 src/vs/platform/notification/common/notification.ts create mode 100644 src/vs/workbench/services/notification/browser/notificationList.ts create mode 100644 src/vs/workbench/services/notification/browser/notificationService.ts diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 87566d71df1..bcd770656cb 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -39,6 +39,7 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe import { OS } from 'vs/base/common/platform'; import { IRange } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; +import { INotificationService, INotification } from 'vs/platform/notification/common/notification'; export class SimpleEditor implements IEditor { @@ -280,6 +281,30 @@ export class SimpleMessageService implements IMessageService { } } +export class SimpleNotificationService implements INotificationService { + + public _serviceBrand: any; + + private static readonly Empty: INotification = { dispose: () => undefined }; + + public notify(sev: Severity, message: string): INotification { + + switch (sev) { + case Severity.Error: + console.error(message); + break; + case Severity.Warning: + console.warn(message); + break; + default: + console.log(message); + break; + } + + return SimpleNotificationService.Empty; + } +} + export class StandaloneCommandService implements ICommandService { _serviceBrand: any; diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 552156aca88..e6b0aa999df 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -33,7 +33,7 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { CodeEditorServiceImpl } from 'vs/editor/browser/services/codeEditorServiceImpl'; import { SimpleConfigurationService, SimpleResourceConfigurationService, SimpleMenuService, SimpleMessageService, - SimpleProgressService, StandaloneCommandService, StandaloneKeybindingService, + SimpleProgressService, StandaloneCommandService, StandaloneKeybindingService, SimpleNotificationService, StandaloneTelemetryService, SimpleWorkspaceContextService } from 'vs/editor/standalone/browser/simpleServices'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; @@ -41,6 +41,7 @@ import { IMenuService } from 'vs/platform/actions/common/actions'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export interface IEditorContextViewService extends IContextViewService { dispose(): void; @@ -127,6 +128,8 @@ export module StaticServices { export const messageService = define(IMessageService, () => new SimpleMessageService()); + export const notificationService = define(INotificationService, () => new SimpleNotificationService()); + export const markerService = define(IMarkerService, () => new MarkerService()); export const modeService = define(IModeService, (o) => new ModeServiceImpl()); diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts new file mode 100644 index 00000000000..71ea3b33df4 --- /dev/null +++ b/src/vs/platform/notification/common/notification.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { Severity } from 'vs/platform/message/common/message'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IDisposable } from 'vs/base/common/lifecycle'; + +export const INotificationService = createDecorator('notificationService'); + +export interface INotification extends IDisposable { + +} + +export interface INotificationService { + + _serviceBrand: any; + + notify(sev: Severity, message: string): INotification; + + // notify(sev: Severity, message: Error): () => void; + // notify(sev: Severity, message: string[]): () => void; + // notify(sev: Severity, message: Error[]): () => void; + // notify(sev: Severity, message: IMessageWithAction): () => void; +} \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index f04c6bdbf0e..44be3bf33e5 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -51,6 +51,7 @@ import { getDomNodePagePosition, createStyleSheet, createCSSRule } from 'vs/base import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Context } from 'vs/platform/contextkey/browser/contextKeyService'; import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; +import { INotificationService } from 'vs/platform/notification/common/notification'; // --- actions @@ -1571,13 +1572,15 @@ export class ShowAboutDialogAction extends Action { constructor( id: string, label: string, - @IWindowsService private windowsService: IWindowsService + @INotificationService private notificationService: INotificationService ) { super(id, label); } run(): TPromise { - return this.windowsService.openAboutDialog(); + this.notificationService.notify(Severity.Info, 'This is a message with a [link](https://code.visualstudio.com).'); + + return TPromise.as(undefined); } } diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index 7dee2b7d03c..e382649668d 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -94,6 +94,8 @@ import { ILocalizationsChannel, LocalizationsChannelClient } from 'vs/platform/l import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; import { WorkbenchIssueService } from 'vs/workbench/services/issue/electron-browser/workbenchIssueService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { NotificationService } from 'vs/workbench/services/notification/browser/notificationService'; /** * Services that we require for the Shell @@ -401,6 +403,8 @@ export class WorkbenchShell { serviceCollection.set(IMessageService, this.messageService); serviceCollection.set(IChoiceService, this.messageService); + serviceCollection.set(INotificationService, new SyncDescriptor(NotificationService, container)); + const lifecycleService = instantiationService.createInstance(LifecycleService); this.toUnbind.push(lifecycleService.onShutdown(reason => this.dispose(reason))); serviceCollection.set(ILifecycleService, lifecycleService); diff --git a/src/vs/workbench/services/notification/browser/notificationList.ts b/src/vs/workbench/services/notification/browser/notificationList.ts new file mode 100644 index 00000000000..3d7741bf4af --- /dev/null +++ b/src/vs/workbench/services/notification/browser/notificationList.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Severity } from 'vs/platform/message/common/message'; + +export class NotificationList { + + constructor( + private container: HTMLElement + ) { + } + + public show(severity: Severity, notification: string): void { + console.log(this.container); + } +} \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/notificationService.ts b/src/vs/workbench/services/notification/browser/notificationService.ts new file mode 100644 index 00000000000..a4674cc868c --- /dev/null +++ b/src/vs/workbench/services/notification/browser/notificationService.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { INotificationService, INotification } from 'vs/platform/notification/common/notification'; +import { Severity } from 'vs/platform/message/common/message'; +import { NotificationList } from 'vs/workbench/services/notification/browser/notificationList'; + +export class NotificationService implements INotificationService { + + public _serviceBrand: any; + + private handler: NotificationList; + + constructor( + container: HTMLElement + ) { + this.handler = new NotificationList(container); + } + + public notify(sev: Severity, message: string): INotification { + this.handler.show(sev, message); + + return { dispose: () => void 0 }; + } +} \ No newline at end of file From 549d1c08485c5a62a3d6f39d915860c87f39fa13 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2018 11:34:42 +0100 Subject: [PATCH 012/362] notifications - show something top center --- .../browser/media/notificationList.css | 18 ++++++ .../notification/browser/notificationList.ts | 38 +++++++++++- .../browser/notificationService.ts | 14 ++++- .../browser/notificationViewer.ts | 59 +++++++++++++++++++ .../notification/common/notificationsModel.ts | 36 +++++++++++ 5 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 src/vs/workbench/services/notification/browser/media/notificationList.css create mode 100644 src/vs/workbench/services/notification/browser/notificationViewer.ts create mode 100644 src/vs/workbench/services/notification/common/notificationsModel.ts diff --git a/src/vs/workbench/services/notification/browser/media/notificationList.css b/src/vs/workbench/services/notification/browser/media/notificationList.css new file mode 100644 index 00000000000..c547c9479a7 --- /dev/null +++ b/src/vs/workbench/services/notification/browser/media/notificationList.css @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.notifications-list-container { + position: absolute; + width: 600px; + z-index: 1000; + padding-bottom: 6px; + left: 50%; + margin-left: -300px; + display: none; +} + +.notifications-list-container.visible { + display: block; +} \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/notificationList.ts b/src/vs/workbench/services/notification/browser/notificationList.ts index 3d7741bf4af..fb3b7ec6b21 100644 --- a/src/vs/workbench/services/notification/browser/notificationList.ts +++ b/src/vs/workbench/services/notification/browser/notificationList.ts @@ -5,16 +5,50 @@ 'use strict'; +import 'vs/css!./media/notificationList'; import { Severity } from 'vs/platform/message/common/message'; +import { addClass } from 'vs/base/browser/dom'; +import { WorkbenchList } from 'vs/platform/list/browser/listService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotificationViewItem, NotificationViewItem } from 'vs/workbench/services/notification/common/notificationsModel'; +import { NotificationRenderer, NotificationsDelegate } from 'vs/workbench/services/notification/browser/notificationViewer'; +import { IListOptions } from 'vs/base/browser/ui/list/listWidget'; +// tslint:disable-next-line:translation-remind (TODO@Ben) +import { localize } from 'vs/nls'; export class NotificationList { + private listContainer: HTMLElement; + private list: WorkbenchList; constructor( - private container: HTMLElement + private container: HTMLElement, + @IInstantiationService private instantiationService: IInstantiationService ) { + this.create(); + } + + private create(): void { + + // List Container + this.listContainer = document.createElement('div'); + addClass(this.listContainer, 'notifications-list-container'); + + // List + this.list = this.instantiationService.createInstance( + WorkbenchList, + this.listContainer, + new NotificationsDelegate(), + [new NotificationRenderer()], + { ariaLabel: localize('notificationsList', "Notifications List") } as IListOptions + ); + + this.container.appendChild(this.listContainer); } public show(severity: Severity, notification: string): void { - console.log(this.container); + addClass(this.listContainer, 'visible'); + + this.list.splice(0, 0, [new NotificationViewItem(severity, notification)]); + this.list.layout(); } } \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/notificationService.ts b/src/vs/workbench/services/notification/browser/notificationService.ts index a4674cc868c..c5462542494 100644 --- a/src/vs/workbench/services/notification/browser/notificationService.ts +++ b/src/vs/workbench/services/notification/browser/notificationService.ts @@ -8,6 +8,7 @@ import { INotificationService, INotification } from 'vs/platform/notification/common/notification'; import { Severity } from 'vs/platform/message/common/message'; import { NotificationList } from 'vs/workbench/services/notification/browser/notificationList'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class NotificationService implements INotificationService { @@ -16,12 +17,21 @@ export class NotificationService implements INotificationService { private handler: NotificationList; constructor( - container: HTMLElement + container: HTMLElement, + @IInstantiationService private instantiationService: IInstantiationService ) { - this.handler = new NotificationList(container); + } + + private createHandler(): void { + // TODO should this be a setter to pass in from outside? + this.handler = this.instantiationService.createInstance(NotificationList, document.getElementById('workbench.main.container')); } public notify(sev: Severity, message: string): INotification { + if (!this.handler) { + this.createHandler(); + } + this.handler.show(sev, message); return { dispose: () => void 0 }; diff --git a/src/vs/workbench/services/notification/browser/notificationViewer.ts b/src/vs/workbench/services/notification/browser/notificationViewer.ts new file mode 100644 index 00000000000..0a7ecb16207 --- /dev/null +++ b/src/vs/workbench/services/notification/browser/notificationViewer.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * 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 'vs/css!./media/notificationList'; +import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list'; +import { INotificationViewItem, NotificationViewItem } from 'vs/workbench/services/notification/common/notificationsModel'; + +export class NotificationsDelegate implements IDelegate { + + public getHeight(element: INotificationViewItem): number { + return 22; + } + + public getTemplateId(element: INotificationViewItem): string { + if (element instanceof NotificationViewItem) { + return NotificationRenderer.ID; + } + + return void 0; + } +} + +export interface INotificationTemplateData { + container: HTMLElement; + label: HTMLElement; +} + +export class NotificationRenderer implements IRenderer { + + public static readonly ID = 'notification'; + + public get templateId() { + return NotificationRenderer.ID; + } + + public renderTemplate(container: HTMLElement): INotificationTemplateData { + const data: INotificationTemplateData = Object.create(null); + + data.container = document.createElement('div'); + container.appendChild(data.container); + + data.label = document.createElement('span'); + container.appendChild(data.label); + + return data; + } + + public renderElement(element: INotificationViewItem, index: number, data: INotificationTemplateData): void { + data.label.innerText = element.message; + } + + public disposeTemplate(templateData: INotificationTemplateData): void { + // Method not implemented + } +} \ No newline at end of file diff --git a/src/vs/workbench/services/notification/common/notificationsModel.ts b/src/vs/workbench/services/notification/common/notificationsModel.ts new file mode 100644 index 00000000000..fe960c0f324 --- /dev/null +++ b/src/vs/workbench/services/notification/common/notificationsModel.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Severity } from 'vs/platform/message/common/message'; + +export class INotificationsModel { + +} + +export class NotificationsModel implements INotificationsModel { + +} + +export class INotificationViewItem { + readonly severity: Severity; + readonly message: string; +} + +export class NotificationViewItem implements INotificationViewItem { + + constructor(private _severity: Severity, private _message: string) { + + } + + public get severity(): Severity { + return this._severity; + } + + public get message(): string { + return this._message; + } +} \ No newline at end of file From c5d4092d68bb5e4f53e31898e78f23691cde0790 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2018 12:17:15 +0100 Subject: [PATCH 013/362] simple markdown with links support --- build/lib/i18n.resources.json | 4 +++ src/vs/base/browser/htmlContentRenderer.ts | 7 +++- .../browser/media/notificationList.css | 1 - .../notification/browser/notificationList.ts | 32 ++++++++++++++++--- .../browser/notificationViewer.ts | 25 ++++++++++++--- .../notification/common/notificationsModel.ts | 7 ++-- 6 files changed, 63 insertions(+), 13 deletions(-) diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index a94b0a43327..15d309a69db 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -170,6 +170,10 @@ "name": "vs/workbench/services/mode", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/notification", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/progress", "project": "vscode-workbench" diff --git a/src/vs/base/browser/htmlContentRenderer.ts b/src/vs/base/browser/htmlContentRenderer.ts index 49eb0112767..bd2ce2799ed 100644 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ b/src/vs/base/browser/htmlContentRenderer.ts @@ -9,7 +9,7 @@ import * as DOM from 'vs/base/browser/dom'; import { defaultGenerator } from 'vs/base/common/idGenerator'; import { escape } from 'vs/base/common/strings'; import { removeMarkdownEscapes, IMarkdownString } from 'vs/base/common/htmlContent'; -import { marked } from 'vs/base/common/marked/marked'; +import { marked, MarkedRenderer } from 'vs/base/common/marked/marked'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; export interface RenderOptions { @@ -18,6 +18,7 @@ export interface RenderOptions { actionCallback?: (content: string, event?: IMouseEvent) => void; codeBlockRenderer?: (modeId: string, value: string) => Thenable; codeBlockRenderCallback?: () => void; + joinRendererConfiguration?: (renderer: MarkedRenderer) => void; } function createElement(options: RenderOptions): HTMLElement { @@ -156,6 +157,10 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions }); } + if (options.joinRendererConfiguration) { + options.joinRendererConfiguration(renderer); + } + element.innerHTML = marked(markdown.value, { sanitize: true, renderer diff --git a/src/vs/workbench/services/notification/browser/media/notificationList.css b/src/vs/workbench/services/notification/browser/media/notificationList.css index c547c9479a7..40edb046d6e 100644 --- a/src/vs/workbench/services/notification/browser/media/notificationList.css +++ b/src/vs/workbench/services/notification/browser/media/notificationList.css @@ -7,7 +7,6 @@ position: absolute; width: 600px; z-index: 1000; - padding-bottom: 6px; left: 50%; margin-left: -300px; display: none; diff --git a/src/vs/workbench/services/notification/browser/notificationList.ts b/src/vs/workbench/services/notification/browser/notificationList.ts index fb3b7ec6b21..d8a3fe0123d 100644 --- a/src/vs/workbench/services/notification/browser/notificationList.ts +++ b/src/vs/workbench/services/notification/browser/notificationList.ts @@ -13,20 +13,42 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { INotificationViewItem, NotificationViewItem } from 'vs/workbench/services/notification/common/notificationsModel'; import { NotificationRenderer, NotificationsDelegate } from 'vs/workbench/services/notification/browser/notificationViewer'; import { IListOptions } from 'vs/base/browser/ui/list/listWidget'; -// tslint:disable-next-line:translation-remind (TODO@Ben) import { localize } from 'vs/nls'; +import { Themable, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND } from 'vs/workbench/common/theme'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; -export class NotificationList { +export class NotificationList extends Themable { private listContainer: HTMLElement; private list: WorkbenchList; constructor( private container: HTMLElement, - @IInstantiationService private instantiationService: IInstantiationService + @IInstantiationService private instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService ) { + super(themeService); + this.create(); } + protected updateStyles(): void { + if (this.listContainer) { + const background = this.getColor(NOTIFICATIONS_BACKGROUND); + this.listContainer.style.background = background ? background.toString() : null; + + const foreground = this.getColor(NOTIFICATIONS_FOREGROUND); + this.listContainer.style.color = foreground ? foreground.toString() : null; + + const outlineColor = this.getColor(contrastBorder); + this.listContainer.style.outlineColor = outlineColor ? outlineColor.toString() : null; + + const widgetShadowColor = this.getColor(widgetShadow); + this.listContainer.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : null; + } + } + private create(): void { // List Container @@ -43,12 +65,14 @@ export class NotificationList { ); this.container.appendChild(this.listContainer); + + this.updateStyles(); } public show(severity: Severity, notification: string): void { addClass(this.listContainer, 'visible'); - this.list.splice(0, 0, [new NotificationViewItem(severity, notification)]); + this.list.splice(0, 0, [new NotificationViewItem(severity, { value: notification, isTrusted: true } as IMarkdownString)]); this.list.layout(); } } \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/notificationViewer.ts b/src/vs/workbench/services/notification/browser/notificationViewer.ts index 0a7ecb16207..571c6947f61 100644 --- a/src/vs/workbench/services/notification/browser/notificationViewer.ts +++ b/src/vs/workbench/services/notification/browser/notificationViewer.ts @@ -8,6 +8,8 @@ import 'vs/css!./media/notificationList'; import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list'; import { INotificationViewItem, NotificationViewItem } from 'vs/workbench/services/notification/common/notificationsModel'; +import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer'; +import { clearNode } from 'vs/base/browser/dom'; export class NotificationsDelegate implements IDelegate { @@ -26,13 +28,20 @@ export class NotificationsDelegate implements IDelegate { export interface INotificationTemplateData { container: HTMLElement; - label: HTMLElement; + message: HTMLElement; } export class NotificationRenderer implements IRenderer { public static readonly ID = 'notification'; + private static readonly MARKED_NOOP = (text?: string) => text || ''; + private static readonly MARKED_NOOP_TARGETS = [ + 'blockquote', 'br', 'code', 'codespan', 'del', 'em', 'heading', 'hr', 'html', + 'image', 'list', 'listitem', 'paragraph', 'strong', 'table', 'tablecell', + 'tablerow' + ]; + public get templateId() { return NotificationRenderer.ID; } @@ -40,17 +49,25 @@ export class NotificationRenderer implements IRenderer NotificationRenderer.MARKED_NOOP_TARGETS.forEach(fn => renderer[fn] = NotificationRenderer.MARKED_NOOP) + })); } public disposeTemplate(templateData: INotificationTemplateData): void { diff --git a/src/vs/workbench/services/notification/common/notificationsModel.ts b/src/vs/workbench/services/notification/common/notificationsModel.ts index fe960c0f324..9f9fca1eb39 100644 --- a/src/vs/workbench/services/notification/common/notificationsModel.ts +++ b/src/vs/workbench/services/notification/common/notificationsModel.ts @@ -6,6 +6,7 @@ 'use strict'; import { Severity } from 'vs/platform/message/common/message'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; export class INotificationsModel { @@ -17,12 +18,12 @@ export class NotificationsModel implements INotificationsModel { export class INotificationViewItem { readonly severity: Severity; - readonly message: string; + readonly message: IMarkdownString; } export class NotificationViewItem implements INotificationViewItem { - constructor(private _severity: Severity, private _message: string) { + constructor(private _severity: Severity, private _message: IMarkdownString) { } @@ -30,7 +31,7 @@ export class NotificationViewItem implements INotificationViewItem { return this._severity; } - public get message(): string { + public get message(): IMarkdownString { return this._message; } } \ No newline at end of file From 41626d125dbedb9ca617912e7f79bb27ebfb62cd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2018 12:20:45 +0100 Subject: [PATCH 014/362] hook in opener service --- .../services/notification/browser/notificationList.ts | 2 +- .../notification/browser/notificationViewer.ts | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/notification/browser/notificationList.ts b/src/vs/workbench/services/notification/browser/notificationList.ts index d8a3fe0123d..73dc994d261 100644 --- a/src/vs/workbench/services/notification/browser/notificationList.ts +++ b/src/vs/workbench/services/notification/browser/notificationList.ts @@ -60,7 +60,7 @@ export class NotificationList extends Themable { WorkbenchList, this.listContainer, new NotificationsDelegate(), - [new NotificationRenderer()], + [this.instantiationService.createInstance(NotificationRenderer)], { ariaLabel: localize('notificationsList', "Notifications List") } as IListOptions ); diff --git a/src/vs/workbench/services/notification/browser/notificationViewer.ts b/src/vs/workbench/services/notification/browser/notificationViewer.ts index 571c6947f61..662155ebd1d 100644 --- a/src/vs/workbench/services/notification/browser/notificationViewer.ts +++ b/src/vs/workbench/services/notification/browser/notificationViewer.ts @@ -10,6 +10,9 @@ import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list'; import { INotificationViewItem, NotificationViewItem } from 'vs/workbench/services/notification/common/notificationsModel'; import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer'; import { clearNode } from 'vs/base/browser/dom'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import URI from 'vs/base/common/uri'; +import { onUnexpectedError } from 'vs/base/common/errors'; export class NotificationsDelegate implements IDelegate { @@ -42,6 +45,11 @@ export class NotificationRenderer implements IRenderer NotificationRenderer.MARKED_NOOP_TARGETS.forEach(fn => renderer[fn] = NotificationRenderer.MARKED_NOOP) + joinRendererConfiguration: renderer => NotificationRenderer.MARKED_NOOP_TARGETS.forEach(fn => renderer[fn] = NotificationRenderer.MARKED_NOOP), + actionCallback: (content: string) => this.openerService.open(URI.parse(content)).then(void 0, onUnexpectedError) })); } From e62cdf40195e0376c685cbc594bad940865fb673 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2018 13:11:43 +0100 Subject: [PATCH 015/362] notifications - simple icons support --- src/vs/workbench/electron-browser/actions.ts | 4 +- .../media/notification-error-inverse.svg | 1 + .../browser/media/notification-error.svg | 1 + .../media/notification-info-inverse.svg | 1 + .../browser/media/notification-info.svg | 1 + .../media/notification-warning-inverse.svg | 1 + .../browser/media/notification-warning.svg | 1 + .../browser/media/notificationList.css | 61 ++++++++++++++++++- .../notification/browser/notificationList.ts | 13 +++- .../browser/notificationViewer.ts | 38 ++++++++++-- 10 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 src/vs/workbench/services/notification/browser/media/notification-error-inverse.svg create mode 100644 src/vs/workbench/services/notification/browser/media/notification-error.svg create mode 100644 src/vs/workbench/services/notification/browser/media/notification-info-inverse.svg create mode 100644 src/vs/workbench/services/notification/browser/media/notification-info.svg create mode 100644 src/vs/workbench/services/notification/browser/media/notification-warning-inverse.svg create mode 100644 src/vs/workbench/services/notification/browser/media/notification-warning.svg diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index 44be3bf33e5..51e98c957cc 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -1578,7 +1578,9 @@ export class ShowAboutDialogAction extends Action { } run(): TPromise { - this.notificationService.notify(Severity.Info, 'This is a message with a [link](https://code.visualstudio.com).'); + this.notificationService.notify(Severity.Info, 'This is a info message with a [link](https://code.visualstudio.com).'); + this.notificationService.notify(Severity.Warning, 'This is a warning message with a [link](https://code.visualstudio.com).'); + this.notificationService.notify(Severity.Error, 'This is a error message with a [link](https://code.visualstudio.com).'); return TPromise.as(undefined); } diff --git a/src/vs/workbench/services/notification/browser/media/notification-error-inverse.svg b/src/vs/workbench/services/notification/browser/media/notification-error-inverse.svg new file mode 100644 index 00000000000..3c852a7ffde --- /dev/null +++ b/src/vs/workbench/services/notification/browser/media/notification-error-inverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/media/notification-error.svg b/src/vs/workbench/services/notification/browser/media/notification-error.svg new file mode 100644 index 00000000000..a1ddb39fed6 --- /dev/null +++ b/src/vs/workbench/services/notification/browser/media/notification-error.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/media/notification-info-inverse.svg b/src/vs/workbench/services/notification/browser/media/notification-info-inverse.svg new file mode 100644 index 00000000000..d38c363e0e4 --- /dev/null +++ b/src/vs/workbench/services/notification/browser/media/notification-info-inverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/media/notification-info.svg b/src/vs/workbench/services/notification/browser/media/notification-info.svg new file mode 100644 index 00000000000..6e2e22f67bc --- /dev/null +++ b/src/vs/workbench/services/notification/browser/media/notification-info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/media/notification-warning-inverse.svg b/src/vs/workbench/services/notification/browser/media/notification-warning-inverse.svg new file mode 100644 index 00000000000..df44e61b326 --- /dev/null +++ b/src/vs/workbench/services/notification/browser/media/notification-warning-inverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/media/notification-warning.svg b/src/vs/workbench/services/notification/browser/media/notification-warning.svg new file mode 100644 index 00000000000..f4e2a84b0af --- /dev/null +++ b/src/vs/workbench/services/notification/browser/media/notification-warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/media/notificationList.css b/src/vs/workbench/services/notification/browser/media/notificationList.css index 40edb046d6e..fef55b14863 100644 --- a/src/vs/workbench/services/notification/browser/media/notificationList.css +++ b/src/vs/workbench/services/notification/browser/media/notificationList.css @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.notifications-list-container { +.monaco-workbench > .notifications-list-container { position: absolute; width: 600px; z-index: 1000; @@ -12,6 +12,63 @@ display: none; } -.notifications-list-container.visible { +.monaco-workbench > .notifications-list-container.visible { display: block; +} + +.monaco-workbench > .notifications-list-container .monaco-list-row { + border-left: 3px solid grey; /* TODO make themable */ + border-right: 3px solid grey; + border-bottom: 3px solid grey; +} + +.monaco-workbench > .notifications-list-container .monaco-list-row:first { + border-top: 3px solid grey; +} + +/** Notification: Container */ + +.monaco-workbench > .notifications-list-container .notification-list-item { + display: flex; +} + +/** Notification: Icon */ + +.monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-icon { + height: 22px; + margin-right: 4px; + margin-left: 4px; + background-position: center; + background-repeat: no-repeat; + flex: 0 0 16px; +} + +.monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-icon.icon-info { + background-image: url('notification-info.svg'); +} + +.monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-icon.icon-warning { + background-image: url('notification-warning.svg'); +} + +.monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-icon.icon-error { + background-image: url('notification-error.svg'); +} + +.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-icon.icon-info { + background-image: url('notification-info-inverse.svg'); +} + +.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-icon.icon-warning { + background-image: url('notification-warning-inverse.svg'); +} + +.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-icon.icon-error { + background-image: url('notification-error-inverse.svg'); +} + +/** Notification: Message */ + +.monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-message { + } \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/notificationList.ts b/src/vs/workbench/services/notification/browser/notificationList.ts index 73dc994d261..d073db5c9f4 100644 --- a/src/vs/workbench/services/notification/browser/notificationList.ts +++ b/src/vs/workbench/services/notification/browser/notificationList.ts @@ -15,8 +15,8 @@ import { NotificationRenderer, NotificationsDelegate } from 'vs/workbench/servic import { IListOptions } from 'vs/base/browser/ui/list/listWidget'; import { localize } from 'vs/nls'; import { Themable, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND } from 'vs/workbench/common/theme'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { contrastBorder, widgetShadow, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IMarkdownString } from 'vs/base/common/htmlContent'; export class NotificationList extends Themable { @@ -75,4 +75,11 @@ export class NotificationList extends Themable { this.list.splice(0, 0, [new NotificationViewItem(severity, { value: notification, isTrusted: true } as IMarkdownString)]); this.list.layout(); } -} \ No newline at end of file +} + +registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + const linkColor = theme.getColor(textLinkForeground); + if (linkColor) { + collector.addRule(`.monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-message a { color: ${linkColor}; }`); + } +}); diff --git a/src/vs/workbench/services/notification/browser/notificationViewer.ts b/src/vs/workbench/services/notification/browser/notificationViewer.ts index 662155ebd1d..36b954161f2 100644 --- a/src/vs/workbench/services/notification/browser/notificationViewer.ts +++ b/src/vs/workbench/services/notification/browser/notificationViewer.ts @@ -9,15 +9,16 @@ import 'vs/css!./media/notificationList'; import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list'; import { INotificationViewItem, NotificationViewItem } from 'vs/workbench/services/notification/common/notificationsModel'; import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer'; -import { clearNode } from 'vs/base/browser/dom'; +import { clearNode, addClass, removeClass } from 'vs/base/browser/dom'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import URI from 'vs/base/common/uri'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { Severity } from 'vs/platform/message/common/message'; export class NotificationsDelegate implements IDelegate { public getHeight(element: INotificationViewItem): number { - return 22; + return 44; } public getTemplateId(element: INotificationViewItem): string { @@ -31,6 +32,7 @@ export class NotificationsDelegate implements IDelegate { export interface INotificationTemplateData { container: HTMLElement; + icon: HTMLElement; message: HTMLElement; } @@ -38,6 +40,7 @@ export class NotificationRenderer implements IRenderer text || ''; private static readonly MARKED_NOOP_TARGETS = [ 'blockquote', 'br', 'code', 'codespan', 'del', 'em', 'heading', 'hr', 'html', @@ -59,17 +62,42 @@ export class NotificationRenderer implements IRenderer { + const domAction = element.severity === this.toSeverity(severity) ? addClass : removeClass; + domAction(data.icon, `icon-${severity}`); + }); + // Message (simple markdown with links support) clearNode(data.message); data.message.appendChild(renderMarkdown(element.message, { From c3d792b836989d352ebd1cd76ca786995f1d9112 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 12 Feb 2018 06:17:23 -0800 Subject: [PATCH 016/362] Draw the split boundary --- .../terminal/electron-browser/media/terminal.css | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) 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 f6762d02fb4..b30f7df8efe 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css @@ -61,6 +61,17 @@ .monaco-workbench .panel.integrated-terminal .split-view-view { /* Make relative as terminal absolute positioning needs it deeper in the tree */ position: relative; + box-sizing: border-box; +} + +.monaco-workbench .panel.integrated-terminal .split-view-view:not(:first-child) { + border-left: 1px solid rgba(0, 0, 0, 0.3); +} +.vs-dark .monaco-workbench .panel.integrated-terminal .split-view-view:not(:first-child) { + border-left: 1px solid rgba(255, 255, 255, 0.3); +} +.hc-black .monaco-workbench .panel.integrated-terminal .split-view-view:not(:first-child) { + border-left: 1px solid rgba(255, 255, 255, 0.5); } .monaco-workbench .panel.integrated-terminal.enable-ligatures { @@ -111,8 +122,6 @@ opacity: 0 !important; } -/* Terminal actions */ - /* Light theme */ .monaco-workbench .terminal-action.kill { background: url('kill.svg') center center no-repeat; } .monaco-workbench .terminal-action.new { background: url('new.svg') center center no-repeat; } From d56d8ac5b0d78de77fe47e0a2c5a38e72cf8da13 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 12 Feb 2018 06:48:49 -0800 Subject: [PATCH 017/362] Remove unneeded code --- src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts index d24748489c1..358876144d7 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts @@ -238,8 +238,6 @@ export class TerminalTab extends Disposable implements ITerminalTab { this._rootSplitPane.orientation = Orientation.HORIZONTAL; this._rootSplitPane.split(instance); - // const pane2 = new SplitPane(); - // this._splitPanes.push(pane2); } public addDisposable(disposable: IDisposable): void { From 09ee4e5fa04ada6b5f76b51b1cc9b615ca825fca Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2018 15:58:46 +0100 Subject: [PATCH 018/362] notifications - simple expand/collapse --- src/vs/base/browser/htmlContentRenderer.ts | 2 +- src/vs/workbench/electron-browser/actions.ts | 2 +- .../browser/media/notificationList.css | 13 ++++++++----- .../notification/browser/notificationList.ts | 14 ++++++++++++++ .../notification/browser/notificationViewer.ts | 2 +- .../notification/common/notificationsModel.ts | 18 +++++++++++++++++- 6 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/vs/base/browser/htmlContentRenderer.ts b/src/vs/base/browser/htmlContentRenderer.ts index bd2ce2799ed..583108e9b6b 100644 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ b/src/vs/base/browser/htmlContentRenderer.ts @@ -109,7 +109,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions return text; } else { - return `${text}`; + return `${text}`; } }; renderer.paragraph = (text): string => { diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index 51e98c957cc..78e4a254324 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -1578,7 +1578,7 @@ export class ShowAboutDialogAction extends Action { } run(): TPromise { - this.notificationService.notify(Severity.Info, 'This is a info message with a [link](https://code.visualstudio.com).'); + this.notificationService.notify(Severity.Info, 'This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com).'); this.notificationService.notify(Severity.Warning, 'This is a warning message with a [link](https://code.visualstudio.com).'); this.notificationService.notify(Severity.Error, 'This is a error message with a [link](https://code.visualstudio.com).'); diff --git a/src/vs/workbench/services/notification/browser/media/notificationList.css b/src/vs/workbench/services/notification/browser/media/notificationList.css index fef55b14863..b6db56bc0cf 100644 --- a/src/vs/workbench/services/notification/browser/media/notificationList.css +++ b/src/vs/workbench/services/notification/browser/media/notificationList.css @@ -17,19 +17,20 @@ } .monaco-workbench > .notifications-list-container .monaco-list-row { - border-left: 3px solid grey; /* TODO make themable */ - border-right: 3px solid grey; - border-bottom: 3px solid grey; + border-left: 2px solid grey; /* TODO make themable */ + border-right: 2px solid grey; + border-bottom: 2px solid grey; } .monaco-workbench > .notifications-list-container .monaco-list-row:first { - border-top: 3px solid grey; + border-top: 2px solid grey; } /** Notification: Container */ .monaco-workbench > .notifications-list-container .notification-list-item { display: flex; + padding: 5px; } /** Notification: Icon */ @@ -40,6 +41,7 @@ margin-left: 4px; background-position: center; background-repeat: no-repeat; + background-size: cover; flex: 0 0 16px; } @@ -70,5 +72,6 @@ /** Notification: Message */ .monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-message { - + white-space: normal; + line-height: 22px; } \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/notificationList.ts b/src/vs/workbench/services/notification/browser/notificationList.ts index d073db5c9f4..32718f40d08 100644 --- a/src/vs/workbench/services/notification/browser/notificationList.ts +++ b/src/vs/workbench/services/notification/browser/notificationList.ts @@ -64,6 +64,20 @@ export class NotificationList extends Themable { { ariaLabel: localize('notificationsList', "Notifications List") } as IListOptions ); + this.list.onOpen(notifications => { + const notification = notifications.elements[0]; + const index = notifications.indexes[0]; + + if (notification.expanded) { + notification.collapse(); + } else { + notification.expand(); + } + + this.list.splice(index, 1, [notification]); + this.list.layout(); + }); + this.container.appendChild(this.listContainer); this.updateStyles(); diff --git a/src/vs/workbench/services/notification/browser/notificationViewer.ts b/src/vs/workbench/services/notification/browser/notificationViewer.ts index 36b954161f2..dddb7ed45c3 100644 --- a/src/vs/workbench/services/notification/browser/notificationViewer.ts +++ b/src/vs/workbench/services/notification/browser/notificationViewer.ts @@ -18,7 +18,7 @@ import { Severity } from 'vs/platform/message/common/message'; export class NotificationsDelegate implements IDelegate { public getHeight(element: INotificationViewItem): number { - return 44; + return element.expanded ? 70 : 35; } public getTemplateId(element: INotificationViewItem): string { diff --git a/src/vs/workbench/services/notification/common/notificationsModel.ts b/src/vs/workbench/services/notification/common/notificationsModel.ts index 9f9fca1eb39..71e45fc394c 100644 --- a/src/vs/workbench/services/notification/common/notificationsModel.ts +++ b/src/vs/workbench/services/notification/common/notificationsModel.ts @@ -16,15 +16,23 @@ export class NotificationsModel implements INotificationsModel { } -export class INotificationViewItem { +export interface INotificationViewItem { readonly severity: Severity; readonly message: IMarkdownString; + readonly expanded: boolean; + + expand(): void; + collapse(): void; } export class NotificationViewItem implements INotificationViewItem { + private _expanded: boolean; constructor(private _severity: Severity, private _message: IMarkdownString) { + } + public get expanded(): boolean { + return this._expanded; } public get severity(): Severity { @@ -34,4 +42,12 @@ export class NotificationViewItem implements INotificationViewItem { public get message(): IMarkdownString { return this._message; } + + public expand(): void { + this._expanded = true; + } + + public collapse(): void { + this._expanded = false; + } } \ No newline at end of file From 6b3164252bd7eae02a1b7fe16860a3d19ae2c102 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 9 Feb 2018 13:06:37 +0100 Subject: [PATCH 019/362] update grammar: improve reporting of exceeded github rate limit --- build/npm/update-grammar.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/build/npm/update-grammar.js b/build/npm/update-grammar.js index d65112b3456..21c37de6762 100644 --- a/build/npm/update-grammar.js +++ b/build/npm/update-grammar.js @@ -37,6 +37,10 @@ function download(url, redirectCount) { response.on('data', function (data) { content += data.toString(); }).on('end', function () { + if (response.statusCode === 403 && response.headers['x-ratelimit-remaining'] === '0') { + e('GitHub API rate exceeded. Set GITHUB_TOKEN environment variable to increase rate limit.'); + return; + } let count = redirectCount || 0; if (count < 5 && response.statusCode >= 300 && response.statusCode <= 303 || response.statusCode === 307) { let location = response.headers['location']; @@ -64,12 +68,8 @@ function getCommitSha(repoId, repoPath) { commitDate: lastCommit.commit.author.date }); } catch (e) { - console.error("Failed extracting the SHA: " + content); - return Promise.resolve(null); + return Promise.reject(new Error("Failed extracting the SHA: " + content)); } - }, function () { - console.error('Failed loading ' + commitInfo); - return Promise.resolve(null); }); } @@ -86,8 +86,7 @@ exports.update = function (repoId, repoPath, dest, modifyGrammar, version = 'mas } else if (ext === '.json') { grammar = JSON.parse(content); } else { - console.error('Unknown file extension: ' + ext); - return; + return Promise.reject(new Error(('Unknown file extension: ' + ext))); } if (modifyGrammar) { modifyGrammar(grammar); @@ -118,11 +117,14 @@ exports.update = function (repoId, repoPath, dest, modifyGrammar, version = 'mas console.log('Updated ' + path.basename(dest)); } } catch (e) { - console.error(e); + return Promise.reject(e); } }); - }, console.error); + }, console.error).catch(e => { + console.error(e); + process.exit(1); + }); }; if (path.basename(process.argv[1]) === 'update-grammar.js') { From 8fdf170a0850c1cc027382f31650aaf300d3ae2a Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 12 Feb 2018 16:54:35 +0100 Subject: [PATCH 020/362] update grammar script: remove unused properties --- build/npm/update-grammar.js | 10 +- .../bat/syntaxes/batchfile.tmLanguage.json | 6 +- .../clojure/syntaxes/clojure.tmLanguage.json | 19 +- .../syntaxes/coffeescript.tmLanguage.json | 10 +- extensions/cpp/syntaxes/c.tmLanguage.json | 8 +- extensions/cpp/syntaxes/cpp.tmLanguage.json | 22 +- .../cpp/syntaxes/platform.tmLanguage.json | 9 +- .../csharp/syntaxes/csharp.tmLanguage.json | 4 - extensions/css/syntaxes/css.tmLanguage.json | 7 +- extensions/diff/syntaxes/diff.tmLanguage.json | 12 +- .../docker/syntaxes/docker.tmLanguage.json | 8 +- .../fsharp/syntaxes/fsharp.tmLanguage.json | 5 - .../gitsyntax/syntaxes/diff.tmLanguage.json | 12 +- .../syntaxes/git-commit.tmLanguage.json | 11 +- .../syntaxes/git-rebase.tmLanguage.json | 8 +- extensions/go/syntaxes/go.tmLanguage.json | 7 +- .../groovy/syntaxes/groovy.tmLanguage.json | 12 +- .../syntaxes/Handlebars.tmLanguage.json | 90 ++-- extensions/hlsl/syntaxes/hlsl.tmLanguage.json | 12 +- extensions/html/syntaxes/html.tmLanguage.json | 18 +- extensions/ini/syntaxes/ini.tmLanguage.json | 10 +- extensions/java/syntaxes/java.tmLanguage.json | 6 +- .../syntaxes/JavaScript.tmLanguage.json | 13 +- .../syntaxes/JavaScriptReact.tmLanguage.json | 13 +- extensions/json/syntaxes/JSON.tmLanguage.json | 19 +- extensions/less/syntaxes/less.tmLanguage.json | 8 - extensions/log/syntaxes/log.tmLanguage.json | 8 +- extensions/lua/syntaxes/lua.tmLanguage.json | 14 +- extensions/make/syntaxes/make.tmLanguage.json | 11 +- .../syntaxes/markdown.tmLanguage.json | 12 +- .../syntaxes/objective-c++.tmLanguage.json | 7 +- .../syntaxes/objective-c.tmLanguage.json | 10 +- extensions/perl/syntaxes/perl.tmLanguage.json | 18 +- .../perl/syntaxes/perl6.tmLanguage.json | 13 +- extensions/php/syntaxes/html.tmLanguage.json | 20 +- .../syntaxes/powershell.tmLanguage.json | 10 +- extensions/pug/syntaxes/pug.tmLanguage.json | 8 +- .../syntaxes/MagicPython.tmLanguage.json | 21 - .../syntaxes/MagicRegExp.tmLanguage.json | 4 - extensions/r/syntaxes/r.tmLanguage.json | 15 +- .../razor/syntaxes/cshtml.tmLanguage.json | 4 - extensions/ruby/syntaxes/ruby.tmLanguage.json | 45 +- extensions/rust/syntaxes/rust.tmLanguage.json | 407 +++++++++--------- extensions/scss/syntaxes/scss.tmLanguage.json | 9 +- .../syntaxes/shaderlab.tmLanguage.json | 5 +- .../syntaxes/shell-unix-bash.tmLanguage.json | 64 +-- extensions/sql/syntaxes/sql.tmLanguage.json | 11 +- .../swift/syntaxes/swift.tmLanguage.json | 6 +- .../syntaxes/TypeScript.tmLanguage.json | 10 +- .../syntaxes/TypeScriptReact.tmLanguage.json | 10 +- .../vb/syntaxes/asp-vb-net.tmlanguage.json | 11 +- extensions/xml/syntaxes/xml.tmLanguage.json | 82 +--- extensions/xml/syntaxes/xsl.tmLanguage.json | 6 +- extensions/yaml/syntaxes/yaml.tmLanguage.json | 17 +- 54 files changed, 353 insertions(+), 864 deletions(-) diff --git a/build/npm/update-grammar.js b/build/npm/update-grammar.js index 21c37de6762..822de7c59c2 100644 --- a/build/npm/update-grammar.js +++ b/build/npm/update-grammar.js @@ -46,7 +46,7 @@ function download(url, redirectCount) { let location = response.headers['location']; if (location) { console.log("Redirected " + url + " to " + location); - download(location, count+1).then(c, e); + download(location, count + 1).then(c, e); return; } } @@ -86,7 +86,7 @@ exports.update = function (repoId, repoPath, dest, modifyGrammar, version = 'mas } else if (ext === '.json') { grammar = JSON.parse(content); } else { - return Promise.reject(new Error(('Unknown file extension: ' + ext))); + return Promise.reject(new Error('Unknown file extension: ' + ext)); } if (modifyGrammar) { modifyGrammar(grammar); @@ -103,8 +103,10 @@ exports.update = function (repoId, repoPath, dest, modifyGrammar, version = 'mas if (info) { result.version = 'https://github.com/' + repoId + '/commit/' + info.commitSha; } - for (let key in grammar) { - if (!result.hasOwnProperty(key)) { + + let keys = ['name', 'scopeName', 'comment', 'injections', 'patterns', 'repository']; + for (let key of keys) { + if (grammar.hasOwnProperty(key)) { result[key] = grammar[key]; } } diff --git a/extensions/bat/syntaxes/batchfile.tmLanguage.json b/extensions/bat/syntaxes/batchfile.tmLanguage.json index 42adcf64854..2aee0692ad8 100644 --- a/extensions/bat/syntaxes/batchfile.tmLanguage.json +++ b/extensions/bat/syntaxes/batchfile.tmLanguage.json @@ -5,12 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/mmims/language-batchfile/commit/3dd105c31484e5975144478dac1aa91d8f51e528", - "scopeName": "source.batchfile", "name": "Batch File", - "fileTypes": [ - "bat", - "cmd" - ], + "scopeName": "source.batchfile", "patterns": [ { "include": "#commands" diff --git a/extensions/clojure/syntaxes/clojure.tmLanguage.json b/extensions/clojure/syntaxes/clojure.tmLanguage.json index 033e3e2a178..3d059513af0 100644 --- a/extensions/clojure/syntaxes/clojure.tmLanguage.json +++ b/extensions/clojure/syntaxes/clojure.tmLanguage.json @@ -5,25 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/atom/language-clojure/commit/ecc790326bc8e14220e4d2d72a392a30876c3219", - "scopeName": "source.clojure", - "fileTypes": [ - "boot", - "clj", - "clj.hl", - "cljc", - "cljs", - "cljs.hl", - "cljx", - "clojure", - "edn", - "org", - "joke", - "joker" - ], - "foldingStartMarker": "\\(\\s*$", - "foldingStopMarker": "^\\s*\\)", - "firstLineMatch": "(?x)\n# Hashbang\n^\\#!.*(?:\\s|\\/)\n boot\n(?:$|\\s)\n|\n# Modeline\n(?i:\n # Emacs\n -\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n clojure(script)?\n (?=[\\s;]|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n clojure\n (?=\\s|:|$)\n)", "name": "Clojure", + "scopeName": "source.clojure", "patterns": [ { "include": "#comment" diff --git a/extensions/coffeescript/syntaxes/coffeescript.tmLanguage.json b/extensions/coffeescript/syntaxes/coffeescript.tmLanguage.json index 0e4c904a029..5fcff295c61 100644 --- a/extensions/coffeescript/syntaxes/coffeescript.tmLanguage.json +++ b/extensions/coffeescript/syntaxes/coffeescript.tmLanguage.json @@ -5,16 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/atom/language-coffee-script/commit/a0da2a73ad817e2fc13c2ef8fcd2624017c39610", - "scopeName": "source.coffee", "name": "CoffeeScript", - "fileTypes": [ - "coffee", - "Cakefile", - "coffee.erb", - "cson", - "_coffee" - ], - "firstLineMatch": "(?x)\n# Hashbang\n^\\#!.*(?:\\s|\\/)\n coffee\n(?:$|\\s)\n|\n# Modeline\n(?i:\n # Emacs\n -\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n coffee\n (?=[\\s;]|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n coffee\n (?=\\s|:|$)\n)", + "scopeName": "source.coffee", "patterns": [ { "match": "(new)\\s+(?:(?:(class)\\s+(\\w+(?:\\.\\w*)*)?)|(\\w+(?:\\.\\w*)*))", diff --git a/extensions/cpp/syntaxes/c.tmLanguage.json b/extensions/cpp/syntaxes/c.tmLanguage.json index d5143bd4901..bcd5470a64c 100644 --- a/extensions/cpp/syntaxes/c.tmLanguage.json +++ b/extensions/cpp/syntaxes/c.tmLanguage.json @@ -5,14 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/atom/language-c/commit/9c0c5f202741a5647025db8d5df5fefba47b036c", - "scopeName": "source.c", - "fileTypes": [ - "c", - "h.in", - "xpm" - ], - "firstLineMatch": "(?i)-\\*-[^*]*(Mode:\\s*)?C(\\s*;.*?)?\\s*-\\*-", "name": "C", + "scopeName": "source.c", "patterns": [ { "include": "#preprocessor-rule-enabled" diff --git a/extensions/cpp/syntaxes/cpp.tmLanguage.json b/extensions/cpp/syntaxes/cpp.tmLanguage.json index 4cd5c4e8d91..f29bc255ba8 100644 --- a/extensions/cpp/syntaxes/cpp.tmLanguage.json +++ b/extensions/cpp/syntaxes/cpp.tmLanguage.json @@ -5,28 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/atom/language-c/commit/3a269f88b12e512fb9495dc006a1dabf325d3d7f", - "scopeName": "source.cpp", - "fileTypes": [ - "cc", - "cpp", - "cp", - "cxx", - "c++", - "cu", - "cuh", - "h", - "hh", - "hpp", - "hxx", - "h++", - "inl", - "ino", - "ipp", - "tcc", - "tpp" - ], - "firstLineMatch": "(?i)-\\*-[^*]*(Mode:\\s*)?C\\+\\+(\\s*;.*?)?\\s*-\\*-", "name": "C++", + "scopeName": "source.cpp", "patterns": [ { "include": "#special_block" diff --git a/extensions/cpp/syntaxes/platform.tmLanguage.json b/extensions/cpp/syntaxes/platform.tmLanguage.json index bc4821b4f63..14fb45016c0 100644 --- a/extensions/cpp/syntaxes/platform.tmLanguage.json +++ b/extensions/cpp/syntaxes/platform.tmLanguage.json @@ -5,10 +5,9 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/textmate/c.tmbundle/commit/9aa365882274ca52f01722f3dbb169b9539a20ee", - "comment": "This file was generated with clang-C using MacOSX10.12.sdk", - "fileTypes": [], - "hideFromUser": true, "name": "Platform", + "scopeName": "source.c.platform", + "comment": "This file was generated with clang-C using MacOSX10.12.sdk", "patterns": [ { "match": "\\bkCF(?:CalendarUnitWeek|Gregorian(?:AllUnits|Units(?:Days|Hours|M(?:inutes|onths)|Seconds|Years)))\\b", @@ -568,7 +567,5 @@ } ] } - }, - "scopeName": "source.c.platform", - "uuid": "3E3CB242-CEBA-4B61-9806-9A97B5783D2A" + } } \ No newline at end of file diff --git a/extensions/csharp/syntaxes/csharp.tmLanguage.json b/extensions/csharp/syntaxes/csharp.tmLanguage.json index 074c761a4d5..fb3f667f5b1 100644 --- a/extensions/csharp/syntaxes/csharp.tmLanguage.json +++ b/extensions/csharp/syntaxes/csharp.tmLanguage.json @@ -7,10 +7,6 @@ "version": "https://github.com/dotnet/csharp-tmLanguage/commit/ca22c5211b87a6a1a8fd5356895237bb821df728", "name": "C#", "scopeName": "source.cs", - "fileTypes": [ - "cs" - ], - "uuid": "f7de61e2-bdde-4e2a-a139-8221b179584e", "patterns": [ { "include": "#preprocessor" diff --git a/extensions/css/syntaxes/css.tmLanguage.json b/extensions/css/syntaxes/css.tmLanguage.json index 4c734ddba93..a8fc2fe565e 100644 --- a/extensions/css/syntaxes/css.tmLanguage.json +++ b/extensions/css/syntaxes/css.tmLanguage.json @@ -5,13 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/atom/language-css/commit/2bc1e294e2440ad91197263cd9f95dc4b00bab2f", - "scopeName": "source.css", "name": "CSS", - "fileTypes": [ - "css", - "css.erb" - ], - "firstLineMatch": "(?xi)\n# Emacs modeline\n-\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n css\n(?=[\\s;]|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n css\n(?=\\s|:|$)", + "scopeName": "source.css", "patterns": [ { "include": "#comment-block" diff --git a/extensions/diff/syntaxes/diff.tmLanguage.json b/extensions/diff/syntaxes/diff.tmLanguage.json index e0d60de1081..d60bbb145f7 100644 --- a/extensions/diff/syntaxes/diff.tmLanguage.json +++ b/extensions/diff/syntaxes/diff.tmLanguage.json @@ -5,14 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/textmate/diff.tmbundle/commit/0593bb775eab1824af97ef2172fd38822abd97d7", - "fileTypes": [ - "patch", - "diff", - "rej" - ], - "firstLineMatch": "(?x)^\n\t\t(===\\ modified\\ file\n\t\t|==== \\s* // .+ \\s - \\s .+ \\s+ ====\n\t\t|Index:\\ \n\t\t|---\\ [^%\\n]\n\t\t|\\*\\*\\*.*\\d{4}\\s*$\n\t\t|\\d+(,\\d+)* (a|d|c) \\d+(,\\d+)* $\n\t\t|diff\\ --git\\ \n\t\t|commit\\ [0-9a-f]{40}$\n\t\t)", - "keyEquivalent": "^~D", "name": "Diff", + "scopeName": "source.diff", "patterns": [ { "captures": { @@ -162,7 +156,5 @@ "match": "^Only in .*: .*$\\n?", "name": "meta.diff.only-in" } - ], - "scopeName": "source.diff", - "uuid": "7E848FF4-708E-11D9-97B4-0011242E4184" + ] } \ No newline at end of file diff --git a/extensions/docker/syntaxes/docker.tmLanguage.json b/extensions/docker/syntaxes/docker.tmLanguage.json index 44a028e7858..f7f414636c4 100644 --- a/extensions/docker/syntaxes/docker.tmLanguage.json +++ b/extensions/docker/syntaxes/docker.tmLanguage.json @@ -5,10 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/moby/moby/commit/abd39744c6f3ed854500e423f5fabf952165161f", - "fileTypes": [ - "Dockerfile" - ], "name": "Dockerfile", + "scopeName": "source.dockerfile", "patterns": [ { "captures": { @@ -100,7 +98,5 @@ "comment": "comment.line", "match": "^(\\s*)((#).*$\\n?)" } - ], - "scopeName": "source.dockerfile", - "uuid": "a39d8795-59d2-49af-aa00-fe74ee29576e" + ] } \ No newline at end of file diff --git a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json index a47bc570fbe..eb16f2829ad 100644 --- a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json +++ b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json @@ -7,11 +7,6 @@ "version": "https://github.com/ionide/ionide-fsgrammar/commit/9aa4b1055e3173225bc0d4b4e48542c2450beb99", "name": "fsharp", "scopeName": "source.fsharp", - "fileTypes": [ - "fs" - ], - "foldingStartMarker": "", - "foldingStopMarker": "", "patterns": [ { "include": "#comments" diff --git a/extensions/gitsyntax/syntaxes/diff.tmLanguage.json b/extensions/gitsyntax/syntaxes/diff.tmLanguage.json index e0d60de1081..d60bbb145f7 100644 --- a/extensions/gitsyntax/syntaxes/diff.tmLanguage.json +++ b/extensions/gitsyntax/syntaxes/diff.tmLanguage.json @@ -5,14 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/textmate/diff.tmbundle/commit/0593bb775eab1824af97ef2172fd38822abd97d7", - "fileTypes": [ - "patch", - "diff", - "rej" - ], - "firstLineMatch": "(?x)^\n\t\t(===\\ modified\\ file\n\t\t|==== \\s* // .+ \\s - \\s .+ \\s+ ====\n\t\t|Index:\\ \n\t\t|---\\ [^%\\n]\n\t\t|\\*\\*\\*.*\\d{4}\\s*$\n\t\t|\\d+(,\\d+)* (a|d|c) \\d+(,\\d+)* $\n\t\t|diff\\ --git\\ \n\t\t|commit\\ [0-9a-f]{40}$\n\t\t)", - "keyEquivalent": "^~D", "name": "Diff", + "scopeName": "source.diff", "patterns": [ { "captures": { @@ -162,7 +156,5 @@ "match": "^Only in .*: .*$\\n?", "name": "meta.diff.only-in" } - ], - "scopeName": "source.diff", - "uuid": "7E848FF4-708E-11D9-97B4-0011242E4184" + ] } \ No newline at end of file diff --git a/extensions/gitsyntax/syntaxes/git-commit.tmLanguage.json b/extensions/gitsyntax/syntaxes/git-commit.tmLanguage.json index 5b2ccacd56d..c2b8d628ab0 100644 --- a/extensions/gitsyntax/syntaxes/git-commit.tmLanguage.json +++ b/extensions/gitsyntax/syntaxes/git-commit.tmLanguage.json @@ -5,13 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/textmate/git.tmbundle/commit/93897a78c6e52bef13dadc0d4091d203c5facb40", - "fileTypes": [ - "COMMIT_EDITMSG", - "MERGE_MSG" - ], - "foldingStartMarker": "^\\+\\+\\+", - "foldingStopMarker": "^---", "name": "Git Commit Message", + "scopeName": "text.git-commit", "patterns": [ { "begin": "\\A(?!# Please enter the commit message)", @@ -142,7 +137,5 @@ } ] } - }, - "scopeName": "text.git-commit", - "uuid": "BFE83C06-8508-44BE-A975-95A57BF619A7" + } } \ No newline at end of file diff --git a/extensions/gitsyntax/syntaxes/git-rebase.tmLanguage.json b/extensions/gitsyntax/syntaxes/git-rebase.tmLanguage.json index 89ef957a1b9..3ee481bede1 100644 --- a/extensions/gitsyntax/syntaxes/git-rebase.tmLanguage.json +++ b/extensions/gitsyntax/syntaxes/git-rebase.tmLanguage.json @@ -5,10 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/textmate/git.tmbundle/commit/d1db42c2d71948662098183a6df519fb53a7a15b", - "fileTypes": [ - "git-rebase-todo" - ], "name": "Git Rebase Message", + "scopeName": "text.git-rebase", "patterns": [ { "captures": { @@ -34,7 +32,5 @@ "match": "^\\s*(pick|p|reword|r|edit|e|squash|s|fixup|f|exec|x|drop|d)\\s+([0-9a-f]+)\\s+(.*)$", "name": "meta.commit-command.git-rebase" } - ], - "scopeName": "text.git-rebase", - "uuid": "7F1CC209-5F6D-486A-8180-09FA282381A1" + ] } \ No newline at end of file diff --git a/extensions/go/syntaxes/go.tmLanguage.json b/extensions/go/syntaxes/go.tmLanguage.json index f4e17874a84..bc73a1d91bd 100644 --- a/extensions/go/syntaxes/go.tmLanguage.json +++ b/extensions/go/syntaxes/go.tmLanguage.json @@ -5,14 +5,9 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/atom/language-go/commit/b6fd68f74efa109679e31fe6f4a41ac105262d0e", - "scopeName": "source.go", "name": "Go", + "scopeName": "source.go", "comment": "Go language", - "fileTypes": [ - "go" - ], - "foldingStartMarker": "({|\\()\\s*$", - "foldingStopMarker": "(}|\\))\\s*$", "patterns": [ { "include": "#comments" diff --git a/extensions/groovy/syntaxes/groovy.tmLanguage.json b/extensions/groovy/syntaxes/groovy.tmLanguage.json index 9e8be43b53b..57ebab39405 100644 --- a/extensions/groovy/syntaxes/groovy.tmLanguage.json +++ b/extensions/groovy/syntaxes/groovy.tmLanguage.json @@ -5,14 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/textmate/groovy.tmbundle/commit/85d8f7c97ae473ccb9473f6c8d27e4ec957f4be1", - "fileTypes": [ - "groovy", - "gvy" - ], - "foldingStartMarker": "(\\{\\s*$|^\\s*// \\{\\{\\{)", - "foldingStopMarker": "^\\s*(\\}|// \\}\\}\\}$)", - "keyEquivalent": "^~G", "name": "Groovy", + "scopeName": "source.groovy", "patterns": [ { "captures": { @@ -1385,7 +1379,5 @@ } ] } - }, - "scopeName": "source.groovy", - "uuid": "B3A64888-EBBB-4436-8D9E-F1169C5D7613" + } } \ No newline at end of file diff --git a/extensions/handlebars/syntaxes/Handlebars.tmLanguage.json b/extensions/handlebars/syntaxes/Handlebars.tmLanguage.json index ddd7547935f..957f16ae035 100644 --- a/extensions/handlebars/syntaxes/Handlebars.tmLanguage.json +++ b/extensions/handlebars/syntaxes/Handlebars.tmLanguage.json @@ -6,6 +6,42 @@ ], "version": "https://github.com/daaain/Handlebars/commit/790f2b0222098a3a236bd9e91bb9a039eeca4d8e", "name": "Handlebars", + "scopeName": "text.html.handlebars", + "patterns": [ + { + "include": "#yfm" + }, + { + "include": "#extends" + }, + { + "include": "#block_comments" + }, + { + "include": "#comments" + }, + { + "include": "#block_helper" + }, + { + "include": "#end_block" + }, + { + "include": "#else_token" + }, + { + "include": "#partial_and_var" + }, + { + "include": "#inline_script" + }, + { + "include": "#html_tags" + }, + { + "include": "text.html.basic" + } + ], "repository": { "html_tags": { "patterns": [ @@ -812,57 +848,5 @@ } ] } - }, - "scopeName": "text.html.handlebars", - "patterns": [ - { - "include": "#yfm" - }, - { - "include": "#extends" - }, - { - "include": "#block_comments" - }, - { - "include": "#comments" - }, - { - "include": "#block_helper" - }, - { - "include": "#end_block" - }, - { - "include": "#else_token" - }, - { - "include": "#partial_and_var" - }, - { - "include": "#inline_script" - }, - { - "include": "#html_tags" - }, - { - "include": "text.html.basic" - } - ], - "fileTypes": [ - "handlebars", - "handlebars.html", - "hbr", - "hbrs", - "hbs", - "hdbs", - "hjs", - "mu", - "mustache", - "rac", - "stache", - "template", - "tmpl" - ], - "uuid": "70E91676-DE0A-4266-A2B9-3AD2E535E484" + } } \ No newline at end of file diff --git a/extensions/hlsl/syntaxes/hlsl.tmLanguage.json b/extensions/hlsl/syntaxes/hlsl.tmLanguage.json index 475923aaca5..dd8f5356fee 100644 --- a/extensions/hlsl/syntaxes/hlsl.tmLanguage.json +++ b/extensions/hlsl/syntaxes/hlsl.tmLanguage.json @@ -5,18 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/tgjones/shaders-tmLanguage/commit/cd1ef40f549f9ce2b9e6b73498688de114a85382", - "scopeName": "source.hlsl", "name": "HLSL", - "fileTypes": [ - "hlsl", - "hlsli", - "fx", - "fxh", - "vsh", - "psh", - "cginc", - "compute" - ], + "scopeName": "source.hlsl", "patterns": [ { "name": "comment.line.block.hlsl", diff --git a/extensions/html/syntaxes/html.tmLanguage.json b/extensions/html/syntaxes/html.tmLanguage.json index 0efe7ca55f0..6b9b1f58e7d 100644 --- a/extensions/html/syntaxes/html.tmLanguage.json +++ b/extensions/html/syntaxes/html.tmLanguage.json @@ -5,16 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/textmate/html.tmbundle/commit/a723f08ebd49c67c22aca08dd8f17d0bf836ec93", - "fileTypes": [ - "html", - "htm", - "shtml", - "xhtml", - "inc", - "tmpl", - "tpl" - ], - "firstLineMatch": "<(?i:(!DOCTYPE\\s*)?html)", + "name": "HTML", + "scopeName": "text.html.basic", "injections": { "R:text.html - (comment.block, text.html source)": { "comment": "Use R: to ensure this matches after any other injections.", @@ -26,8 +18,6 @@ ] } }, - "keyEquivalent": "^~H", - "name": "HTML", "patterns": [ { "begin": "(<)([a-zA-Z][a-zA-Z0-9:-]*)(?=[^>]*>)", @@ -746,7 +736,5 @@ "match": "(?<==)(?:[^\\s<>/'\"]|/(?!>))+", "name": "string.unquoted.html" } - }, - "scopeName": "text.html.basic", - "uuid": "17994EC8-6B1D-11D9-AC3A-000D93589AF6" + } } \ No newline at end of file diff --git a/extensions/ini/syntaxes/ini.tmLanguage.json b/extensions/ini/syntaxes/ini.tmLanguage.json index ff87d4c6dc8..bd1496149f7 100644 --- a/extensions/ini/syntaxes/ini.tmLanguage.json +++ b/extensions/ini/syntaxes/ini.tmLanguage.json @@ -5,12 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/textmate/ini.tmbundle/commit/2af0cbb0704940f967152616f2f1ff0aae6287a6", - "fileTypes": [ - "ini", - "conf" - ], - "keyEquivalent": "^~I", "name": "Ini", + "scopeName": "source.ini", "patterns": [ { "begin": "(^[ \\t]+)?(?=#)", @@ -113,7 +109,5 @@ }, "name": "string.quoted.double.ini" } - ], - "scopeName": "source.ini", - "uuid": "77DC23B6-8A90-11D9-BAA4-000A9584EC8C" + ] } \ No newline at end of file diff --git a/extensions/java/syntaxes/java.tmLanguage.json b/extensions/java/syntaxes/java.tmLanguage.json index 5be7bc9f4ee..0d29f449e02 100644 --- a/extensions/java/syntaxes/java.tmLanguage.json +++ b/extensions/java/syntaxes/java.tmLanguage.json @@ -5,12 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/atom/language-java/commit/5c2863da1425d61914d2e04ef31b86f8c5883c5f", - "scopeName": "source.java", "name": "Java", - "fileTypes": [ - "java", - "bsh" - ], + "scopeName": "source.java", "patterns": [ { "begin": "\\b(package)\\b\\s*", diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index 9411c748bee..a8527fe9618 100644 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json @@ -4,16 +4,9 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/24c7fb5254f50dad85eae03836b1b9ff92ecf273", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/77d21fd8d3f750fcf67bdf7436f4e46565cba350", "name": "JavaScript (with React support)", "scopeName": "source.js", - "fileTypes": [ - ".js", - ".jsx", - ".es6", - ".mjs" - ], - "uuid": "805375ec-d614-41f5-8993-5843fe63ea82", "patterns": [ { "include": "#directives" @@ -3157,6 +3150,10 @@ { "name": "keyword.operator.ternary.js", "match": "(\\?|\\:)" + }, + { + "name": "keyword.operator.expression.infer.js", + "match": "(?]?\\d+|m)?|(?!^)\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n\tperl\n (?=\\s|:|$)\n)", - "keyEquivalent": "^~P", "name": "Perl", + "scopeName": "source.perl", + "comment": "\n\tTODO:\tInclude RegExp syntax\n", "patterns": [ { "include": "#line_comment" @@ -2545,7 +2535,5 @@ } ] } - }, - "scopeName": "source.perl", - "uuid": "EDBFE125-6B1C-11D9-9189-000D93589AF6" + } } \ No newline at end of file diff --git a/extensions/perl/syntaxes/perl6.tmLanguage.json b/extensions/perl/syntaxes/perl6.tmLanguage.json index 109dc422363..89c69ebe79b 100644 --- a/extensions/perl/syntaxes/perl6.tmLanguage.json +++ b/extensions/perl/syntaxes/perl6.tmLanguage.json @@ -5,15 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/textmate/perl.tmbundle/commit/d9841a0878239fa43f88c640f8d458590f97e8f5", - "fileTypes": [ - "p6", - "pl6", - "pm6", - "nqp" - ], - "firstLineMatch": "(?x)\n# Hashbang\n^\\#!.*(?:\\s|\\/|(?<=!)\\b)(?:perl6|nqp)(?:$|\\s)\n|\n# Perl 6 pragma\n\\buse\\s+v6\\b\n|\n# Modeline\n(?i:\n # Emacs\n -\\*-(?:(?:(?!mode\\s*:)[^:;]+:[^:;]+;)*\\s*mode\\s*:)?\\s*\n\tperl6\n \\s*(?:;[^:;]+:[^:;]+?)*;?\\s*-\\*-\n |\n # Vim\n (?:(?:\\s|^)vi(?:m[<=>]?\\d+|m)?|(?!^)\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n\tperl6\n (?=\\s|:|$)\n)", - "keyEquivalent": "^~P", "name": "Perl 6", + "scopeName": "source.perl.6", "patterns": [ { "begin": "^=begin", @@ -318,7 +311,5 @@ } ] } - }, - "scopeName": "source.perl.6", - "uuid": "E685440C-0E20-4424-9693-864D5240A269" + } } \ No newline at end of file diff --git a/extensions/php/syntaxes/html.tmLanguage.json b/extensions/php/syntaxes/html.tmLanguage.json index 747d39b1d0d..1ed6de8d2c9 100644 --- a/extensions/php/syntaxes/html.tmLanguage.json +++ b/extensions/php/syntaxes/html.tmLanguage.json @@ -5,26 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/atom/language-php/commit/29c140e1531e0b5e842e5bfd4377f879d8b79cd4", - "scopeName": "text.html.php", "name": "PHP", - "fileTypes": [ - "aw", - "ctp", - "inc", - "install", - "module", - "php", - "php_cs", - "php3", - "php4", - "php5", - "phpt", - "phtml", - "profile" - ], - "firstLineMatch": "(?x)\n# Hashbang\n^\\#!.*(?:\\s|\\/)\n php\\d?\n(?:$|\\s)\n|\n# Modeline\n(?i:\n # Emacs\n -\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n php\n (?=[\\s;]|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n (?:php|phtml)\n (?=\\s|:|$)\n)\n|\n# Regular opening PHP tags\n^\\s*<\\?(?i:php|=|\\s|$)", - "foldingStartMarker": "(/\\*|\\{\\s*$|<<]?\\d+|m)?|\\sex)\n (?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))\n (?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*\n [\\s:]\n (?:filetype|ft|syntax)\\s*=\n ruby\n (?=\\s|:|$)\n)", - "keyEquivalent": "^~R", "name": "Ruby", + "scopeName": "source.ruby", + "comment": "\n\tTODO: unresolved issues\n\n\ttext:\n\t\"p <", - "patterns": [ - { - "include": "#block_comment" - }, - { - "include": "#line_comment" - }, - { - "include": "#sigils" - }, - { - "include": "#mut" - }, - { - "include": "#lifetime" - }, - { - "include": "#core_types" - }, - { - "include": "#core_marker" - }, - { - "include": "#core_traits" - }, - { - "include": "#std_types" - }, - { - "include": "#std_traits" - }, - { - "include": "#type_params" - } - ] - } - }, "patterns": [ { "comment": "Implementation", @@ -654,5 +450,206 @@ } ] } - ] + ], + "repository": { + "block_doc_comment": { + "comment": "Block documentation comment", + "name": "comment.block.documentation.rust", + "begin": "/\\*[\\*!](?![\\*/])", + "end": "\\*/", + "patterns": [ + { + "include": "#block_doc_comment" + }, + { + "include": "#block_comment" + } + ] + }, + "block_comment": { + "comment": "Block comment", + "name": "comment.block.rust", + "begin": "/\\*", + "end": "\\*/", + "patterns": [ + { + "include": "#block_doc_comment" + }, + { + "include": "#block_comment" + } + ] + }, + "line_doc_comment": { + "comment": "Single-line documentation comment", + "name": "comment.line.documentation.rust", + "begin": "//[!/](?=[^/])", + "end": "$" + }, + "line_comment": { + "comment": "Single-line comment", + "name": "comment.line.double-slash.rust", + "begin": "//", + "end": "$" + }, + "escaped_character": { + "name": "constant.character.escape.rust", + "match": "\\\\(x[0-9A-Fa-f]{2}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)" + }, + "string_literal": { + "comment": "Double-quote string literal", + "name": "string.quoted.double.rust", + "begin": "b?\"", + "end": "\"", + "patterns": [ + { + "include": "#escaped_character" + } + ] + }, + "raw_string_literal": { + "comment": "Raw double-quote string literal", + "name": "string.quoted.double.raw.rust", + "begin": "b?r(#*)\"", + "end": "\"\\1" + }, + "sigils": { + "comment": "Sigil", + "name": "keyword.operator.sigil.rust", + "match": "[&*](?=[a-zA-Z0-9_\\(\\[\\|\\\"]+)" + }, + "self": { + "comment": "Self variable", + "name": "variable.language.rust", + "match": "\\bself\\b" + }, + "mut": { + "comment": "Mutable storage modifier", + "name": "storage.modifier.mut.rust", + "match": "\\bmut\\b" + }, + "box": { + "comment": "Box storage modifier", + "name": "storage.modifier.box.rust", + "match": "\\bbox\\b" + }, + "const": { + "comment": "Const storage modifier", + "name": "storage.modifier.const.rust", + "match": "\\bconst\\b" + }, + "pub": { + "comment": "Visibility modifier", + "name": "storage.modifier.visibility.rust", + "match": "\\bpub\\b" + }, + "unsafe": { + "comment": "Unsafe code keyword", + "name": "keyword.other.unsafe.rust", + "match": "\\bunsafe\\b" + }, + "where": { + "comment": "Generic where clause", + "name": "keyword.other.where.rust", + "match": "\\bwhere\\b" + }, + "lifetime": { + "comment": "Named lifetime", + "name": "storage.modifier.lifetime.rust", + "match": "'([a-zA-Z_][a-zA-Z0-9_]*)\\b", + "captures": { + "1": { + "name": "entity.name.lifetime.rust" + } + } + }, + "ref_lifetime": { + "comment": "Reference with named lifetime", + "match": "&('([a-zA-Z_][a-zA-Z0-9_]*))\\b", + "captures": { + "1": { + "name": "storage.modifier.lifetime.rust" + }, + "2": { + "name": "entity.name.lifetime.rust" + } + } + }, + "core_types": { + "comment": "Built-in/core type", + "name": "storage.type.core.rust", + "match": "\\b(bool|char|usize|isize|u8|u16|u32|u64|u128|i8|i16|i32|i64|i128|f32|f64|str|Self|Option|Result)\\b" + }, + "core_vars": { + "comment": "Core type variant", + "name": "support.constant.core.rust", + "match": "\\b(Some|None|Ok|Err)\\b" + }, + "core_marker": { + "comment": "Core trait (marker)", + "name": "support.type.marker.rust", + "match": "\\b(Copy|Send|Sized|Sync)\\b" + }, + "core_traits": { + "comment": "Core trait", + "name": "support.type.core.rust", + "match": "\\b(Drop|Fn|FnMut|FnOnce|Clone|PartialEq|PartialOrd|Eq|Ord|AsRef|AsMut|Into|From|Default|Iterator|Extend|IntoIterator|DoubleEndedIterator|ExactSizeIterator)\\b" + }, + "std_types": { + "comment": "Standard library type", + "name": "storage.class.std.rust", + "match": "\\b(Box|String|Vec|Path|PathBuf)\\b" + }, + "std_traits": { + "comment": "Standard library trait", + "name": "support.type.std.rust", + "match": "\\b(ToOwned|ToString)\\b" + }, + "type": { + "comment": "A type", + "name": "entity.name.type.rust", + "match": "\\b([A-Za-z][_A-Za-z0-9]*|_[_A-Za-z0-9]+)\\b" + }, + "type_params": { + "comment": "Type parameters", + "name": "meta.type_params.rust", + "begin": "<(?![=<])", + "end": "(?", + "patterns": [ + { + "include": "#block_comment" + }, + { + "include": "#line_comment" + }, + { + "include": "#sigils" + }, + { + "include": "#mut" + }, + { + "include": "#lifetime" + }, + { + "include": "#core_types" + }, + { + "include": "#core_marker" + }, + { + "include": "#core_traits" + }, + { + "include": "#std_types" + }, + { + "include": "#std_traits" + }, + { + "include": "#type_params" + } + ] + } + } } \ No newline at end of file diff --git a/extensions/scss/syntaxes/scss.tmLanguage.json b/extensions/scss/syntaxes/scss.tmLanguage.json index 6e97f761713..80c0c3ad84f 100644 --- a/extensions/scss/syntaxes/scss.tmLanguage.json +++ b/extensions/scss/syntaxes/scss.tmLanguage.json @@ -5,15 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/atom/language-sass/commit/a07688bd078f420f56df6221e9263d80e738869b", - "scopeName": "source.css.scss", "name": "SCSS", - "fileTypes": [ - "scss", - "css.scss", - "css.scss.erb", - "scss.erb", - "scss.liquid" - ], + "scopeName": "source.css.scss", "patterns": [ { "include": "#variable_setting" diff --git a/extensions/shaderlab/syntaxes/shaderlab.tmLanguage.json b/extensions/shaderlab/syntaxes/shaderlab.tmLanguage.json index 2922a8a5ac3..6b7ad9d7fe2 100644 --- a/extensions/shaderlab/syntaxes/shaderlab.tmLanguage.json +++ b/extensions/shaderlab/syntaxes/shaderlab.tmLanguage.json @@ -5,11 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/tgjones/shaders-tmLanguage/commit/c72c8b39380ba5a86c58ceed053b5d965ebf38b3", - "scopeName": "source.shaderlab", "name": "ShaderLab", - "fileTypes": [ - "shader" - ], + "scopeName": "source.shaderlab", "patterns": [ { "name": "comment.line.double-slash.shaderlab", diff --git a/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json b/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json index 874d7d16a00..0089e5aa5c4 100644 --- a/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json +++ b/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json @@ -4,35 +4,9 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/atom/language-shellscript/commit/5206d247e33bb4e7ebeb320f10814ecd4132f9eb", - "scopeName": "source.shell", + "version": "https://github.com/atom/language-shellscript/commit/4c3711edbe8eac6f501976893976b1ac6a043d50", "name": "Shell Script", - "fileTypes": [ - "sh", - "bash", - "ksh", - "zsh", - "zsh-theme", - "zshenv", - "zlogin", - "zlogout", - "zprofile", - "zshrc", - "bashrc", - "bash_aliases", - "bash_profile", - "bash_login", - "profile", - "bash_logout", - ".textmate_init", - "npmrc", - "PKGBUILD", - "install", - "cygport", - "bats", - "ebuild" - ], - "firstLineMatch": "(?x)\n# Hashbang\n^\\#!.*(?:\\s|\\/)\n (?:bash|zsh|sh|tcsh|ksh|dash|ash|csh|rc)\n(?:$|\\s)\n|\n# Modeline\n(?i:\n # Emacs\n -\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n (?:shell-script|sh)\n (?=[\\s;]|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s* set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n sh\n (?=\\s|:|$)\n)", + "scopeName": "source.shell", "patterns": [ { "include": "#comment" @@ -141,7 +115,7 @@ ] }, "comment": { - "begin": "(^\\s+)?(?<=^|\\W)(?=#)(?!#{)", + "begin": "(^\\s+)?(?<=^|\\W)(?\n)\n|\n# Modeline\n(?i:\n # Emacs\n -\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n xml\n (?=[\\s;]|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n xml\n (?=\\s|:|$)\n)", + "scopeName": "text.xml", "patterns": [ { "begin": "(<\\?)\\s*([-_a-zA-Z0-9]+)", diff --git a/extensions/xml/syntaxes/xsl.tmLanguage.json b/extensions/xml/syntaxes/xsl.tmLanguage.json index 116fe42ea46..c89f50050fe 100644 --- a/extensions/xml/syntaxes/xsl.tmLanguage.json +++ b/extensions/xml/syntaxes/xsl.tmLanguage.json @@ -5,12 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/atom/language-xml/commit/507de2ee7daca60cf02e9e21fbeb92bbae73e280", - "scopeName": "text.xml.xsl", "name": "XSL", - "fileTypes": [ - "xsl", - "xslt" - ], + "scopeName": "text.xml.xsl", "patterns": [ { "begin": "(<)(xsl)((:))(template)", diff --git a/extensions/yaml/syntaxes/yaml.tmLanguage.json b/extensions/yaml/syntaxes/yaml.tmLanguage.json index 9d755531ed0..6f08b6e72c9 100644 --- a/extensions/yaml/syntaxes/yaml.tmLanguage.json +++ b/extensions/yaml/syntaxes/yaml.tmLanguage.json @@ -5,19 +5,8 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/textmate/yaml.tmbundle/commit/efc96efafe5e48480cf55a2ed124b388cbea4440", - "fileTypes": [ - "yaml", - "yml", - "rviz", - "reek", - "clang-format", - "yaml-tmlanguage", - "syntax", - "sublime-syntax" - ], - "firstLineMatch": "^%YAML( ?1.\\d+)?", - "keyEquivalent": "^~Y", "name": "YAML", + "scopeName": "source.yaml", "patterns": [ { "include": "#comment" @@ -628,7 +617,5 @@ } ] } - }, - "scopeName": "source.yaml", - "uuid": "686AD6AE-33F3-4493-9512-9E9FC1D5417F" + } } \ No newline at end of file From b10ccf746fa0fb8692459085b0b6fba94747c69e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 12 Feb 2018 07:57:27 -0800 Subject: [PATCH 021/362] Work on removal of splits --- .../parts/terminal/common/terminalService.ts | 7 +- .../electron-browser/terminalActions.ts | 1 + .../electron-browser/terminalPanel.ts | 25 ++--- .../terminal/electron-browser/terminalTab.ts | 98 ++++++++++++++++--- 4 files changed, 101 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index 9078aba4bba..c310b2f6395 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -104,11 +104,14 @@ export abstract class TerminalService implements ITerminalService { } private _removeTab(tab: ITerminalTab): void { - let index = this._terminalTabs.indexOf(tab); - let wasActiveTab = tab === this._getActiveTab(); + // Get the index of the tab and remove it from the list + const index = this._terminalTabs.indexOf(tab); + const wasActiveTab = tab === this._getActiveTab(); if (index !== -1) { this._terminalTabs.splice(index, 1); } + + // Adjust focus if the tab was active if (wasActiveTab && this._terminalTabs.length > 0) { let newIndex = index < this._terminalTabs.length ? index : this._terminalTabs.length - 1; this.setActiveInstanceByIndex(newIndex); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index 5d6fccd4dcb..d38ff111fc1 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -353,6 +353,7 @@ export class FocusPreviousTerminalAction extends Action { return this.terminalService.showPanel(true); } } + export class TerminalPasteAction extends Action { public static readonly ID = 'workbench.action.terminal.paste'; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts index 2215bd21703..407beda708a 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts @@ -256,20 +256,21 @@ export class TerminalPanel extends Panel { } this._cancelContextMenu = false; })); - this._register(dom.addDisposableListener(this._parentDomElement, 'click', (event) => { - if (event.which === 3) { - return; - } + // TODO: Not sure if this is needed anymore? + // this._register(dom.addDisposableListener(this._parentDomElement, 'click', (event) => { + // if (event.which === 3) { + // return; + // } - const instance = this._terminalService.getActiveInstance(); - if (instance) { + // const instance = this._terminalService.getActiveInstance(); + // if (instance) { - // TODO: This and other usages for getActiveInstance().focus() are now invalid, - // TerminalService should defer the active instance to the TerminalTab - console.log('clicked, focus'); - this._terminalService.getActiveInstance().focus(); - } - })); + // // TODO: This and other usages for getActiveInstance().focus() are now invalid, + // // TerminalService should defer the active instance to the TerminalTab + // console.log('clicked, focus'); + // this._terminalService.getActiveInstance().focus(); + // } + // })); this._register(dom.addDisposableListener(this._parentDomElement, 'keyup', (event: KeyboardEvent) => { if (event.keyCode === 27) { // Keep terminal open on escape diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts index 358876144d7..1aaea069860 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts @@ -16,20 +16,20 @@ class SplitPane implements IView { // TODO: What's a good number for the min? public minimumSize: number = 10; public maximumSize: number = Number.MAX_VALUE; + + public instance: ITerminalInstance; + public orientation: Orientation | undefined; + protected _size: number; - public orientation: Orientation | undefined; private _splitView: SplitView | undefined; private _children: SplitPane[] = []; private _container: HTMLElement; - - public instance: ITerminalInstance; private _isContainerSet: boolean = false; - private _onDidChange: Event = Event.None; - public get onDidChange(): Event { - return this._onDidChange; - } + + public get children(): SplitPane[] { return this._children; } + public get onDidChange(): Event { return this._onDidChange; } constructor( private _parent?: SplitPane, @@ -50,6 +50,9 @@ class SplitPane implements IView { this.addChild(this.orthogonalSize / 2, this._size, this.instance, 0, this._isContainerSet); this.addChild(this.orthogonalSize / 2, this._size, instance); + + // Instance is now owned by the first child + this.instance = null; } public split(instance: ITerminalInstance): void { @@ -77,6 +80,20 @@ class SplitPane implements IView { this._onDidChange = anyEvent(...this._children.map(c => c.onDidChange)); } + public remove(): void { + if (!this._parent) { + return; + } + + this._parent.removeChild(this); + } + + public removeChild(child: SplitPane): void { + const index = this._children.indexOf(child); + this._children.splice(index, 1); + this._splitView.removeView(index); + } + public render(container: HTMLElement): void { if (!container) { return; @@ -97,8 +114,9 @@ class SplitPane implements IView { } public layout(size: number): void { + // Only layout when both sizes are known and the SplitPane owns an instance this._size = size; - if (!this._size || !this.orthogonalSize) { + if (!this._size || !this.orthogonalSize || !this.instance) { return; } @@ -154,9 +172,8 @@ class RootSplitPane extends SplitPane { export class TerminalTab extends Disposable implements ITerminalTab { private _terminalInstances: ITerminalInstance[] = []; private _rootSplitPane: RootSplitPane; - private _splitPanes: SplitPane[] = []; - // private _activeInstanceIndex: number; + private _activeInstanceIndex: number; public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } @@ -179,15 +196,15 @@ export class TerminalTab extends Disposable implements ITerminalTab { undefined, shellLaunchConfig); this._terminalInstances.push(instance); - // this._activeInstanceIndex = 0; + this._activeInstanceIndex = 0; instance.addDisposable(instance.onDisposed(instance => this._onInstanceDisposed(instance))); this._rootSplitPane = new RootSplitPane(); this._rootSplitPane.instance = instance; // TODO: Only render if it's visible? this._rootSplitPane.render(this._container); - // TODO: Is _splitPanes useful? - this._splitPanes.push(this._rootSplitPane); + + // TODO: Listen to instance focus and update activeInstanceIndex accordingly } public dispose(): void { @@ -195,12 +212,59 @@ export class TerminalTab extends Disposable implements ITerminalTab { this._terminalInstances = []; } + public get activeInstance(): ITerminalInstance { + if (this._terminalInstances.length === 0) { + return null; + } + return this._terminalInstances[this._activeInstanceIndex]; + } + private _onInstanceDisposed(instance: ITerminalInstance): void { + // Get the index of the instance and remove it from the list + const index = this._terminalInstances.indexOf(instance); + const wasActiveInstance = instance === this.activeInstance; + if (index !== -1) { + this._terminalInstances.splice(index, 1); + } - // TODO: Listen for disposed on TerminalService and handle appropriately (remove the tab and its instance from the service) + // Adjust focus if the instance was active + if (wasActiveInstance && this._terminalInstances.length > 0) { + let newIndex = index < this._terminalInstances.length ? index : this._terminalInstances.length - 1; + this._setActiveInstanceByIndex(newIndex); + if (instance.hadFocusOnExit) { + this.activeInstance.focus(true); + } + } - this._onDisposed.fire(this); - this.dispose(); + // TODO: Find instance's SplitPane and unsplit it + this._findSplitPane(instance).remove(); + + // console.log('splitPane: ', splitPane); + + + // Dispose the tab if it was the last instance + if (this._terminalInstances.length === 0) { + console.log('Disposed terminal tab!'); + this._onDisposed.fire(this); + this.dispose(); + } + } + + private _findSplitPane(instance: ITerminalInstance): SplitPane { + const openList: SplitPane[] = [this._rootSplitPane]; + while (openList.length > 0) { + const current = openList.shift(); + if (current.instance === instance) { + return current; + } + openList.push.apply(openList, current.children); + } + return null; + } + + private _setActiveInstanceByIndex(index: number): void { + this._activeInstanceIndex = index; + // TODO: Fire events like in TerminalService.setActiveInstanceByIndex? } public attachToElement(element: HTMLElement): void { @@ -238,6 +302,8 @@ export class TerminalTab extends Disposable implements ITerminalTab { this._rootSplitPane.orientation = Orientation.HORIZONTAL; this._rootSplitPane.split(instance); + // TOOD: Set this correctly + this._activeInstanceIndex = 1; } public addDisposable(disposable: IDisposable): void { From 4f306c91c8842f0ced5e34d10df80cbab459b5e5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 12 Feb 2018 17:02:42 +0100 Subject: [PATCH 022/362] Setter and getter for data provider in tree viewer --- .../electron-browser/mainThreadTreeViews.ts | 2 +- .../browser/parts/views/customView.ts | 21 ++++++------------- src/vs/workbench/common/views.ts | 4 +--- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts index 50f205d1be5..6b327816fcb 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts @@ -31,7 +31,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie $registerTreeViewDataProvider(treeViewId: string): void { const dataProvider = this._register(new TreeViewDataProvider(treeViewId, this._proxy, this.messageService)); this._dataProviders.set(treeViewId, dataProvider); - this.viewsService.registerTreeViewDataProvider(treeViewId, dataProvider); + this.viewsService.getTreeViewer(treeViewId).dataProvider = dataProvider; } $refresh(treeViewId: string, itemsToRefresh: { [treeItemHandle: string]: ITreeItem }): void { diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index f276597e0e2..fd26b0a3372 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/views'; import Event, { Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; -import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, dispose } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TPromise } from 'vs/base/common/winjs.base'; import * as DOM from 'vs/base/browser/dom'; @@ -52,14 +52,6 @@ export class CustomViewsService extends Disposable implements ICustomViewsServic return this.viewers.get(id); } - registerTreeViewDataProvider(id: string, dataProvider: ITreeViewDataProvider): void { - const treeViewer = this.getTreeViewer(id); - if (treeViewer) { - treeViewer.setDataProvider(dataProvider); - dataProvider.onDispose(() => treeViewer.setDataProvider(null)); - } - } - private createViewers(viewDescriptors: IViewDescriptor[]): void { for (const viewDescriptor of viewDescriptors) { if ((viewDescriptor).treeView) { @@ -104,7 +96,7 @@ class CustomTreeViewer extends Disposable implements ITreeViewer { private refreshing = 0; private _dataProvider: ITreeViewDataProvider; - private dataProviderElementChangeListener: IDisposable; + private dataProviderDisposables: IDisposable[] = []; constructor( private id: string, @@ -123,10 +115,8 @@ class CustomTreeViewer extends Disposable implements ITreeViewer { return this._dataProvider; } - setDataProvider(dataProvider: ITreeViewDataProvider) { - if (this.dataProviderElementChangeListener) { - this.dataProviderElementChangeListener.dispose(); - } + set dataProvider(dataProvider: ITreeViewDataProvider) { + dispose(this.dataProviderDisposables); if (dataProvider) { const customTreeView: CustomTreeViewer = this; this._dataProvider = new class implements ITreeViewDataProvider { @@ -146,7 +136,8 @@ class CustomTreeViewer extends Disposable implements ITreeViewer { }); } }; - this.dataProviderElementChangeListener = this._register(dataProvider.onDidChange(elements => this.refresh(elements))); + this._register(dataProvider.onDidChange(elements => this.refresh(elements), this, this.dataProviderDisposables)); + this._register(dataProvider.onDispose(() => this.dataProvider = null, this, this.dataProviderDisposables)); } else { this._dataProvider = null; } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index cab9d6035ca..402d35a7536 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -158,7 +158,7 @@ export interface IViewsViewlet extends IViewlet { export interface ITreeViewer extends IDisposable { - readonly dataProvider: ITreeViewDataProvider; + dataProvider: ITreeViewDataProvider; refresh(treeItems?: ITreeItem[]): TPromise; @@ -185,8 +185,6 @@ export interface ICustomViewsService { _serviceBrand: any; getTreeViewer(id: string): ITreeViewer; - - registerTreeViewDataProvider(id: string, ITreeViewDataProvider): void; } export type TreeViewItemHandleArg = { From 0377a09114dfdccd737e8c6b30e1a005429d651b Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 12 Feb 2018 08:52:54 -0800 Subject: [PATCH 023/362] Fix #43424. Turn off auto indent for yaml. --- extensions/yaml/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/yaml/package.json b/extensions/yaml/package.json index 07ea6da422c..16f3a4261c0 100644 --- a/extensions/yaml/package.json +++ b/extensions/yaml/package.json @@ -23,7 +23,8 @@ "configurationDefaults": { "[yaml]": { "editor.insertSpaces": true, - "editor.tabSize": 2 + "editor.tabSize": 2, + "editor.autoIndent": false } } } From 9ecf8e8884f0d19af43bcd6b9ae4ab92b207cb22 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 12 Feb 2018 11:12:29 +0100 Subject: [PATCH 024/362] file actions cleanup --- .../files/electron-browser/fileActions.ts | 58 ++----------------- 1 file changed, 4 insertions(+), 54 deletions(-) diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.ts index a7ffb1d52ab..e881b123159 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.ts @@ -36,7 +36,7 @@ import { IEditorGroupService } from 'vs/workbench/services/group/common/groupSer import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IUntitledResourceInput } from 'vs/platform/editor/common/editor'; -import { IInstantiationService, IConstructorSignature2, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor, IConstructorSignature2 } from 'vs/platform/instantiation/common/instantiation'; import { IMessageService, IMessageWithAction, IConfirmation, Severity, CancelAction, IConfirmationResult, getConfirmMessage } from 'vs/platform/message/common/message'; import { ITextModel } from 'vs/editor/common/model'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -472,55 +472,6 @@ export class NewFolderAction extends BaseNewAction { } } -export abstract class BaseGlobalNewAction extends Action { - private toDispose: Action; - - constructor( - id: string, - label: string, - @IViewletService private viewletService: IViewletService, - @IInstantiationService private instantiationService: IInstantiationService, - @IMessageService private messageService: IMessageService - ) { - super(id, label); - } - - public run(): TPromise { - return this.viewletService.openViewlet(VIEWLET_ID, true).then((viewlet) => { - return TPromise.timeout(100).then(() => { // use a timeout to prevent the explorer from revealing the active file - viewlet.focus(); - - const explorer = viewlet; - const explorerView = explorer.getExplorerView(); - - // Not having a folder opened - if (!explorerView) { - return this.messageService.show(Severity.Info, nls.localize('openFolderFirst', "Open a folder first to create files or folders within.")); - } - - if (!explorerView.isExpanded()) { - explorerView.setExpanded(true); - } - - const action = this.toDispose = this.instantiationService.createInstance(this.getAction(), explorerView.getViewer(), null); - - return explorer.getActionRunner().run(action); - }); - }); - } - - protected abstract getAction(): IConstructorSignature2; - - public dispose(): void { - super.dispose(); - - if (this.toDispose) { - this.toDispose.dispose(); - this.toDispose = null; - } - } -} - /* Create new file from anywhere: Open untitled */ export class GlobalNewUntitledFileAction extends Action { public static readonly ID = 'workbench.action.files.newUntitledFile'; @@ -1525,7 +1476,7 @@ function getContext(listWidget: ListWidget, viewletService: IViewletService): IE // TODO@isidor these commands are calling into actions due to the complex inheritance action structure. // It should be the other way around, that actions call into commands. -function openExplorerAndRunAction(accessor: ServicesAccessor, isFolderAction: boolean): TPromise { +function openExplorerAndRunAction(accessor: ServicesAccessor, constructor: IConstructorSignature2): TPromise { const instantationService = accessor.get(IInstantiationService); const listService = accessor.get(IListService); const viewletService = accessor.get(IViewletService); @@ -1540,7 +1491,6 @@ function openExplorerAndRunAction(accessor: ServicesAccessor, isFolderAction: bo if (explorerView && explorerView.isVisible() && explorerView.isExpanded()) { explorerView.focus(); const explorerContext = getContext(listService.lastFocusedList, viewletService); - const constructor = isFolderAction ? NewFolderAction : NewFileAction; const action = instantationService.createInstance(constructor, listService.lastFocusedList, explorerContext.stat); return action.run(explorerContext); @@ -1553,14 +1503,14 @@ function openExplorerAndRunAction(accessor: ServicesAccessor, isFolderAction: bo CommandsRegistry.registerCommand({ id: NEW_FILE_COMMAND_ID, handler: (accessor) => { - return openExplorerAndRunAction(accessor, false); + return openExplorerAndRunAction(accessor, NewFileAction); } }); CommandsRegistry.registerCommand({ id: NEW_FOLDER_COMMAND_ID, handler: (accessor) => { - return openExplorerAndRunAction(accessor, true); + return openExplorerAndRunAction(accessor, NewFolderAction); } }); From 84091fdb05f56c7a6170cf40411b25dd53539d0c Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 12 Feb 2018 17:19:11 +0100 Subject: [PATCH 025/362] debug: startDebugging take launch as an argument fixes #43461 --- .../mainThreadDebugService.ts | 8 ++-- .../parts/debug/browser/debugActions.ts | 2 +- .../parts/debug/browser/debugQuickOpen.ts | 2 +- src/vs/workbench/parts/debug/common/debug.ts | 2 +- .../debug/electron-browser/debugService.ts | 39 ++++++++++--------- .../parts/debug/test/common/mockDebug.ts | 36 ++++++++--------- 6 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts index f971f5722be..aed656accd0 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts @@ -8,7 +8,6 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import uri from 'vs/base/common/uri'; import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData } from 'vs/workbench/parts/debug/common/debug'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext, IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto @@ -25,8 +24,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape { constructor( extHostContext: IExtHostContext, - @IDebugService private debugService: IDebugService, - @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IDebugService private debugService: IDebugService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDebugService); this._toDispose = []; @@ -180,8 +178,8 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape { public $startDebugging(_folderUri: uri | undefined, nameOrConfiguration: string | IConfig): TPromise { const folderUriString = _folderUri ? uri.revive(_folderUri).toString() : undefined; - const folder = folderUriString ? this.contextService.getWorkspace().folders.filter(wf => wf.uri.toString() === folderUriString).pop() : undefined; - return this.debugService.startDebugging(folder, nameOrConfiguration).then(x => { + const launch = folderUriString ? this.debugService.getConfigurationManager().getLaunches().filter(l => l.workspace && l.workspace.uri.toString() === folderUriString).pop() : undefined; + return this.debugService.startDebugging(launch, nameOrConfiguration).then(x => { return true; }, err => { return TPromise.wrapError(err && err.message ? err.message : 'cannot start debugging'); diff --git a/src/vs/workbench/parts/debug/browser/debugActions.ts b/src/vs/workbench/parts/debug/browser/debugActions.ts index 8cbe0a6d287..fb303070b97 100644 --- a/src/vs/workbench/parts/debug/browser/debugActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugActions.ts @@ -126,7 +126,7 @@ export class StartAction extends AbstractDebugAction { public run(): TPromise { const launch = this.debugService.getConfigurationManager().selectedConfiguration.launch; - return this.debugService.startDebugging(launch ? launch.workspace : undefined, undefined, this.isNoDebug()); + return this.debugService.startDebugging(launch, undefined, this.isNoDebug()); } protected isNoDebug(): boolean { diff --git a/src/vs/workbench/parts/debug/browser/debugQuickOpen.ts b/src/vs/workbench/parts/debug/browser/debugQuickOpen.ts index c75ddf12c13..16f3a4b8de8 100644 --- a/src/vs/workbench/parts/debug/browser/debugQuickOpen.ts +++ b/src/vs/workbench/parts/debug/browser/debugQuickOpen.ts @@ -70,7 +70,7 @@ class StartDebugEntry extends Model.QuickOpenEntry { } // Run selected debug configuration this.debugService.getConfigurationManager().selectConfiguration(this.launch, this.configurationName); - this.debugService.startDebugging(this.launch.workspace).done(undefined, e => this.messageService.show(Severity.Error, e)); + this.debugService.startDebugging(this.launch).done(undefined, e => this.messageService.show(Severity.Error, e)); return true; } diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 69096337e0d..164cad54b53 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -624,7 +624,7 @@ export interface IDebugService { * Also saves all files, manages if compounds are present in the configuration * and resolveds configurations via DebugConfigurationProviders. */ - startDebugging(root: IWorkspaceFolder, configOrName?: IConfig | string, noDebug?: boolean): TPromise; + startDebugging(launch: ILaunch, configOrName?: IConfig | string, noDebug?: boolean): TPromise; /** * Restarts a process or creates a new one if there is no active session. diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index f2400633557..8aee38d766b 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -658,10 +658,10 @@ export class DebugService implements debug.IDebugService { this.model.removeWatchExpressions(id); } - public startDebugging(root: IWorkspaceFolder, configOrName?: debug.IConfig | string, noDebug = false): TPromise { + public startDebugging(launch: debug.ILaunch, configOrName?: debug.IConfig | string, noDebug = false): TPromise { // make sure to save all files and that the configuration is up to date - return this.extensionService.activateByEvent('onDebug').then(() => this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration(root).then(() => + return this.extensionService.activateByEvent('onDebug').then(() => this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration(launch.workspace).then(() => this.extensionService.whenInstalledExtensionsRegistered().then(() => { if (this.model.getProcesses().length === 0) { this.removeReplExpressions(); @@ -669,8 +669,9 @@ export class DebugService implements debug.IDebugService { this.model.getBreakpoints().forEach(bp => bp.verified = false); } this.launchJsonChanged = false; - const launch = root ? this.configurationManager.getLaunches().filter(l => l.workspace && l.workspace.uri.toString() === root.uri.toString()).pop() - : this.configurationManager.getWorkspaceLaunch(); + if (!launch) { + launch = this.configurationManager.selectedConfiguration.launch; + } let config: debug.IConfig, compound: debug.ICompound; if (!configOrName) { @@ -695,28 +696,28 @@ export class DebugService implements debug.IDebugService { return TPromise.as(null); } - let rootForName: IWorkspaceFolder; + let launchForName: debug.ILaunch; if (typeof configData === 'string') { const launchesContainingName = this.configurationManager.getLaunches().filter(l => !!l.getConfiguration(name)); if (launchesContainingName.length === 1) { - rootForName = launchesContainingName[0].workspace; + launchForName = launchesContainingName[0]; } else if (launchesContainingName.length > 1 && launchesContainingName.indexOf(launch) >= 0) { // If there are multiple launches containing the configuration give priority to the configuration in the current launch - rootForName = launch.workspace; + launchForName = launch; } else { return TPromise.wrapError(new Error(launchesContainingName.length === 0 ? nls.localize('noConfigurationNameInWorkspace', "Could not find launch configuration '{0}' in the workspace.", name) : nls.localize('multipleConfigurationNamesInWorkspace', "There are multiple launch configurations `{0}` in the workspace. Use folder name to qualify the configuration.", name))); } } else if (configData.folder) { - const root = this.contextService.getWorkspace().folders.filter(f => f.name === configData.folder).pop(); - if (root) { - rootForName = root; + const launchesMatchingConfigData = this.configurationManager.getLaunches().filter(l => l.workspace && l.workspace.name === configData.folder && !!l.getConfiguration(configData.name)); + if (launchesMatchingConfigData.length === 1) { + launchForName = launchesMatchingConfigData[0]; } else { return TPromise.wrapError(new Error(nls.localize('noFolderWithName', "Can not find folder with name '{0}' for configuration '{1}' in compound '{2}'.", configData.folder, configData.name, compound.name))); } } - return this.startDebugging(rootForName, name, noDebug); + return this.startDebugging(launchForName, name, noDebug); })); } if (configOrName && !config) { @@ -751,7 +752,7 @@ export class DebugService implements debug.IDebugService { this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config).then(config => { // a falsy config indicates an aborted launch if (config && config.type) { - return this.createProcess(root, config, sessionId); + return this.createProcess(launch, config, sessionId); } if (launch) { @@ -766,8 +767,7 @@ export class DebugService implements debug.IDebugService { ))); } - private createProcess(root: IWorkspaceFolder, config: debug.IConfig, sessionId: string): TPromise { - const launch = root ? this.configurationManager.getLaunches().filter(l => l.workspace && l.workspace.uri.toString() === root.uri.toString()).pop() : undefined; + private createProcess(launch: debug.ILaunch, config: debug.IConfig, sessionId: string): TPromise { return this.textFileService.saveAll().then(() => (launch ? launch.resolveConfiguration(config) : TPromise.as(config)).then(resolvedConfig => { if (!resolvedConfig) { @@ -791,12 +791,13 @@ export class DebugService implements debug.IDebugService { this.toDisposeOnSessionEnd.set(sessionId, []); - return this.runPreLaunchTask(sessionId, root, resolvedConfig.preLaunchTask).then((taskSummary: ITaskSummary) => { + const workspace = launch ? launch.workspace : undefined; + return this.runPreLaunchTask(sessionId, workspace, resolvedConfig.preLaunchTask).then((taskSummary: ITaskSummary) => { const errorCount = resolvedConfig.preLaunchTask ? this.markerService.getStatistics().errors : 0; const successExitCode = taskSummary && taskSummary.exitCode === 0; const failureExitCode = taskSummary && taskSummary.exitCode !== undefined && taskSummary.exitCode !== 0; if (successExitCode || (errorCount === 0 && !failureExitCode)) { - return this.doCreateProcess(root, resolvedConfig, sessionId); + return this.doCreateProcess(workspace, resolvedConfig, sessionId); } const message = errorCount > 1 ? nls.localize('preLaunchTaskErrors', "Build errors have been detected during preLaunchTask '{0}'.", resolvedConfig.preLaunchTask) : @@ -806,7 +807,7 @@ export class DebugService implements debug.IDebugService { return this.choiceService.choose(severity.Error, message, [nls.localize('debugAnyway', "Debug Anyway"), nls.localize('showErrors', "Show Errors"), nls.localize('cancel', "Cancel")], 2, true).then(choice => { switch (choice) { case 0: - return this.doCreateProcess(root, resolvedConfig, sessionId); + return this.doCreateProcess(workspace, resolvedConfig, sessionId); case 1: return this.panelService.openPanel(Constants.MARKERS_PANEL_ID).then(() => undefined); default: @@ -817,7 +818,7 @@ export class DebugService implements debug.IDebugService { return this.choiceService.choose(severity.Error, err.message, [nls.localize('debugAnyway', "Debug Anyway"), debugactions.ConfigureAction.LABEL, this.taskService.configureAction().label, nls.localize('cancel', "Cancel")], 3, true).then(choice => { switch (choice) { case 0: - return this.doCreateProcess(root, resolvedConfig, sessionId); + return this.doCreateProcess(workspace, resolvedConfig, sessionId); case 1: return launch && launch.openConfigFile(false); case 2: @@ -1072,7 +1073,7 @@ export class DebugService implements debug.IDebugService { config.noDebug = process.configuration.noDebug; } config.__restart = restartData; - this.startDebugging(process.session.root, config).then(() => c(null), err => e(err)); + this.startDebugging(launch, config).then(() => c(null), err => e(err)); }, 300); }); }).then(() => { diff --git a/src/vs/workbench/parts/debug/test/common/mockDebug.ts b/src/vs/workbench/parts/debug/test/common/mockDebug.ts index 81acf9c471d..f1484bdf3e0 100644 --- a/src/vs/workbench/parts/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/parts/debug/test/common/mockDebug.ts @@ -6,44 +6,44 @@ import uri from 'vs/base/common/uri'; import Event, { Emitter } from 'vs/base/common/event'; import { TPromise } from 'vs/base/common/winjs.base'; -import * as debug from 'vs/workbench/parts/debug/common/debug'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { ILaunch, IDebugService, State, DebugEvent, IProcess, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IModel, IViewModel, ISession } from 'vs/workbench/parts/debug/common/debug'; -export class MockDebugService implements debug.IDebugService { +export class MockDebugService implements IDebugService { public _serviceBrand: any; - public get state(): debug.State { + public get state(): State { return null; } - public get onDidCustomEvent(): Event { + public get onDidCustomEvent(): Event { return null; } - public get onDidNewProcess(): Event { + public get onDidNewProcess(): Event { return null; } - public get onDidEndProcess(): Event { + public get onDidEndProcess(): Event { return null; } - public get onDidChangeState(): Event { + public get onDidChangeState(): Event { return null; } - public getConfigurationManager(): debug.IConfigurationManager { + public getConfigurationManager(): IConfigurationManager { return null; } - public focusStackFrame(focusedStackFrame: debug.IStackFrame): void { + public focusStackFrame(focusedStackFrame: IStackFrame): void { } - public addBreakpoints(uri: uri, rawBreakpoints: debug.IBreakpointData[]): TPromise { + public addBreakpoints(uri: uri, rawBreakpoints: IBreakpointData[]): TPromise { return TPromise.as(null); } - public updateBreakpoints(uri: uri, data: { [id: string]: debug.IBreakpointUpdateData }): void { } + public updateBreakpoints(uri: uri, data: { [id: string]: IBreakpointUpdateData }): void { } public enableOrDisableBreakpoints(enabled: boolean): TPromise { return TPromise.as(null); @@ -85,7 +85,7 @@ export class MockDebugService implements debug.IDebugService { public removeWatchExpressions(id?: string): void { } - public startDebugging(root: IWorkspaceFolder, configOrName?: debug.IConfig | string, noDebug?: boolean): TPromise { + public startDebugging(launch: ILaunch, configOrName?: IConfig | string, noDebug?: boolean): TPromise { return TPromise.as(null); } @@ -97,11 +97,11 @@ export class MockDebugService implements debug.IDebugService { return TPromise.as(null); } - public getModel(): debug.IModel { + public getModel(): IModel { return null; } - public getViewModel(): debug.IViewModel { + public getViewModel(): IViewModel { return null; } @@ -110,7 +110,7 @@ export class MockDebugService implements debug.IDebugService { public sourceIsNotAvailable(uri: uri): void { } } -export class MockSession implements debug.ISession { +export class MockSession implements ISession { public readyForBreakpoints = true; public emittedStopped = true; @@ -166,7 +166,7 @@ export class MockSession implements debug.ISession { return {}; } - public get onDidEvent(): Event { + public get onDidEvent(): Event { return null; } @@ -175,8 +175,8 @@ export class MockSession implements debug.ISession { return emitter.event; } - public get onDidExitAdapter(): Event { - const emitter = new Emitter(); + public get onDidExitAdapter(): Event { + const emitter = new Emitter(); return emitter.event; } From 7b4a881dba66b6e5edbd070e7e30f4f0798c2229 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2018 18:09:18 +0100 Subject: [PATCH 026/362] notifications - first cut source and actions support --- src/vs/workbench/electron-browser/actions.ts | 2 +- .../browser/media/notificationList.css | 56 +++++++++++++--- .../notification/browser/notificationList.ts | 15 ++++- .../browser/notificationViewer.ts | 65 +++++++++++++++++-- .../notification/common/notificationsModel.ts | 15 ++++- 5 files changed, 133 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index 78e4a254324..27adc685ece 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -1580,7 +1580,7 @@ export class ShowAboutDialogAction extends Action { run(): TPromise { this.notificationService.notify(Severity.Info, 'This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com).'); this.notificationService.notify(Severity.Warning, 'This is a warning message with a [link](https://code.visualstudio.com).'); - this.notificationService.notify(Severity.Error, 'This is a error message with a [link](https://code.visualstudio.com).'); + this.notificationService.notify(Severity.Error, 'This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com).This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com).'); return TPromise.as(undefined); } diff --git a/src/vs/workbench/services/notification/browser/media/notificationList.css b/src/vs/workbench/services/notification/browser/media/notificationList.css index b6db56bc0cf..e25b80abb88 100644 --- a/src/vs/workbench/services/notification/browser/media/notificationList.css +++ b/src/vs/workbench/services/notification/browser/media/notificationList.css @@ -29,13 +29,18 @@ /** Notification: Container */ .monaco-workbench > .notifications-list-container .notification-list-item { - display: flex; padding: 5px; } +/** Notification: Main Row */ + +.monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-main-row { + display: flex; +} + /** Notification: Icon */ -.monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-icon { +.monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon { height: 22px; margin-right: 4px; margin-left: 4px; @@ -45,33 +50,64 @@ flex: 0 0 16px; } -.monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-icon.icon-info { +.monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-info { background-image: url('notification-info.svg'); } -.monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-icon.icon-warning { +.monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-warning { background-image: url('notification-warning.svg'); } -.monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-icon.icon-error { +.monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-error { background-image: url('notification-error.svg'); } -.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-icon.icon-info { +.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-info { background-image: url('notification-info-inverse.svg'); } -.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-icon.icon-warning { +.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-warning { background-image: url('notification-warning-inverse.svg'); } -.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-icon.icon-error { +.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-error { background-image: url('notification-error-inverse.svg'); } /** Notification: Message */ -.monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-message { - white-space: normal; +.monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-message { line-height: 22px; + overflow: hidden; + text-overflow: ellipsis; +} + +.monaco-workbench > .notifications-list-container .notification-list-item.expanded > .notification-list-item-message { + white-space: normal; +} + +/** Notification: Details Row */ + +.monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-details-row { + display: flex; + align-items: center; +} + +/** Notification: Source */ + +.monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-source { + opacity: 0.7; + flex: 1; + font-size: 12px; +} + +/** Notification: Actions */ + +.monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-actions-container .monaco-button { + max-width: fit-content; + padding: 5px; +} + +.monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-actions-container .monaco-button:not(:first-of-type) { + margin-left: 5px; } \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/notificationList.ts b/src/vs/workbench/services/notification/browser/notificationList.ts index 32718f40d08..6c0e7fcb8f8 100644 --- a/src/vs/workbench/services/notification/browser/notificationList.ts +++ b/src/vs/workbench/services/notification/browser/notificationList.ts @@ -18,6 +18,7 @@ import { Themable, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND } from 'vs import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { contrastBorder, widgetShadow, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { Action } from 'vs/base/common/actions'; export class NotificationList extends Themable { private listContainer: HTMLElement; @@ -86,7 +87,17 @@ export class NotificationList extends Themable { public show(severity: Severity, notification: string): void { addClass(this.listContainer, 'visible'); - this.list.splice(0, 0, [new NotificationViewItem(severity, { value: notification, isTrusted: true } as IMarkdownString)]); + this.list.splice(0, 0, [ + new NotificationViewItem( + severity, + { value: notification, isTrusted: true } as IMarkdownString, + 'VS Code Core', + [ + new Action('id.reload', 'Reload Window', null, true, () => { console.log('Reload Window'); return void 0; }), + new Action('id.cancel', 'Cancel', null, true, () => { console.log('Cancel'); return void 0; }) + ] + ) + ]); this.list.layout(); } } @@ -94,6 +105,6 @@ export class NotificationList extends Themable { registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const linkColor = theme.getColor(textLinkForeground); if (linkColor) { - collector.addRule(`.monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-message a { color: ${linkColor}; }`); + collector.addRule(`.monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-message a { color: ${linkColor}; }`); } }); diff --git a/src/vs/workbench/services/notification/browser/notificationViewer.ts b/src/vs/workbench/services/notification/browser/notificationViewer.ts index dddb7ed45c3..a4ae742e776 100644 --- a/src/vs/workbench/services/notification/browser/notificationViewer.ts +++ b/src/vs/workbench/services/notification/browser/notificationViewer.ts @@ -9,16 +9,20 @@ import 'vs/css!./media/notificationList'; import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list'; import { INotificationViewItem, NotificationViewItem } from 'vs/workbench/services/notification/common/notificationsModel'; import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer'; -import { clearNode, addClass, removeClass } from 'vs/base/browser/dom'; +import { clearNode, addClass, removeClass, toggleClass } from 'vs/base/browser/dom'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import URI from 'vs/base/common/uri'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Severity } from 'vs/platform/message/common/message'; +import { localize } from 'vs/nls'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { attachButtonStyler } from 'vs/platform/theme/common/styler'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; export class NotificationsDelegate implements IDelegate { public getHeight(element: INotificationViewItem): number { - return element.expanded ? 70 : 35; + return element.expanded ? 70 : 32; } public getTemplateId(element: INotificationViewItem): string { @@ -32,8 +36,14 @@ export class NotificationsDelegate implements IDelegate { export interface INotificationTemplateData { container: HTMLElement; + + mainRow: HTMLElement; icon: HTMLElement; message: HTMLElement; + + detailsRow: HTMLElement; + source: HTMLElement; + actionsContainer: HTMLElement; } export class NotificationRenderer implements IRenderer { @@ -49,7 +59,8 @@ export class NotificationRenderer implements IRenderer { const domAction = element.severity === this.toSeverity(severity) ? addClass : removeClass; @@ -105,6 +141,25 @@ export class NotificationRenderer implements IRenderer NotificationRenderer.MARKED_NOOP_TARGETS.forEach(fn => renderer[fn] = NotificationRenderer.MARKED_NOOP), actionCallback: (content: string) => this.openerService.open(URI.parse(content)).then(void 0, onUnexpectedError) })); + + // Source + if (element.expanded) { + data.source.innerText = localize('notificationSource', "Source: {0}", element.source); + } else { + data.source.innerText = ''; + } + + // Actions + clearNode(data.actionsContainer); + if (element.expanded) { + element.actions.forEach(action => { + const button = new Button(data.actionsContainer); + attachButtonStyler(button, this.themeService); // TODO dispose + + button.label = action.label; + button.onDidClick(() => action.run()); + }); + } } public disposeTemplate(templateData: INotificationTemplateData): void { diff --git a/src/vs/workbench/services/notification/common/notificationsModel.ts b/src/vs/workbench/services/notification/common/notificationsModel.ts index 71e45fc394c..a4f25c63ff3 100644 --- a/src/vs/workbench/services/notification/common/notificationsModel.ts +++ b/src/vs/workbench/services/notification/common/notificationsModel.ts @@ -7,6 +7,7 @@ import { Severity } from 'vs/platform/message/common/message'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { IAction } from 'vs/base/common/actions'; export class INotificationsModel { @@ -20,15 +21,17 @@ export interface INotificationViewItem { readonly severity: Severity; readonly message: IMarkdownString; readonly expanded: boolean; + readonly source: string; + readonly actions: IAction[]; expand(): void; collapse(): void; } export class NotificationViewItem implements INotificationViewItem { - private _expanded: boolean; + private _expanded: boolean = false; - constructor(private _severity: Severity, private _message: IMarkdownString) { + constructor(private _severity: Severity, private _message: IMarkdownString, private _source: string, private _actions: IAction[]) { } public get expanded(): boolean { @@ -43,6 +46,14 @@ export class NotificationViewItem implements INotificationViewItem { return this._message; } + public get source(): string { + return this._source; + } + + public get actions(): IAction[] { + return this._actions; + } + public expand(): void { this._expanded = true; } From 1a4c5b439bf4ef6020efb4653076d650d3871135 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 12 Feb 2018 18:13:53 +0100 Subject: [PATCH 027/362] fixes #43480 --- .../debug/browser/debugContentProvider.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/debugContentProvider.ts b/src/vs/workbench/parts/debug/browser/debugContentProvider.ts index c1ff118d0e0..90cd905c6ca 100644 --- a/src/vs/workbench/parts/debug/browser/debugContentProvider.ts +++ b/src/vs/workbench/parts/debug/browser/debugContentProvider.ts @@ -73,20 +73,24 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC }; } + const createErrModel = (message: string) => { + this.debugService.sourceIsNotAvailable(resource); + const modePromise = this.modeService.getOrCreateMode(MIME_TEXT); + const model = this.modelService.createModel(message, modePromise, resource); + + return model; + }; + return process.session.source({ sourceReference: sourceRef, source: rawSource }).then(response => { + if (!response) { + return createErrModel(localize('canNotResolveSource', "Could not resolve resource {0}, no response from debug extension.", resource.toString())); + } const mime = response.body.mimeType || guessMimeTypes(resource.path)[0]; const modePromise = this.modeService.getOrCreateMode(mime); const model = this.modelService.createModel(response.body.content, modePromise, resource); return model; - }, (err: DebugProtocol.ErrorResponse) => { - - this.debugService.sourceIsNotAvailable(resource); - const modePromise = this.modeService.getOrCreateMode(MIME_TEXT); - const model = this.modelService.createModel(err.message, modePromise, resource); - - return model; - }); + }, (err: DebugProtocol.ErrorResponse) => createErrModel(err.message)); } } From 0b78875693750c98cb09ead032fb391474b9d1d8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 12 Feb 2018 09:57:47 -0800 Subject: [PATCH 028/362] Create split vertical command --- .../parts/terminal/common/terminal.ts | 4 ++- .../parts/terminal/common/terminalService.ts | 33 +++++++++++++++++++ .../electron-browser/terminal.contribution.ts | 3 +- .../electron-browser/terminalActions.ts | 21 ++++++++++++ .../terminal/electron-browser/terminalTab.ts | 4 ++- 5 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index c6d306f2b33..5d91923df5f 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -165,6 +165,7 @@ export interface ITerminalService { setActiveInstanceToNext(): void; setActiveInstanceToPrevious(): void; getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance; + splitInstanceVertically(instance: ITerminalInstance): void; showPanel(focus?: boolean): TPromise; hidePanel(): void; @@ -181,12 +182,13 @@ export interface ITerminalService { export interface ITerminalTab { terminalInstances: ITerminalInstance[]; title: string; + onDisposed: Event; attachToElement(element: HTMLElement): void; setVisible(visible: boolean): void; layout(width: number, height: number): void; addDisposable(disposable: IDisposable): void; - split(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, shellLaunchConfig: IShellLaunchConfig): void; + split(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance; } export interface ITerminalInstance { diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index c310b2f6395..f71a6f79546 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -221,6 +221,39 @@ export abstract class TerminalService implements ITerminalService { this.setActiveInstanceByIndex(newIndex); } + public splitInstanceVertically(instanceToSplit: ITerminalInstance): void { + const tab = this._getTabForInstance(instanceToSplit); + if (!tab) { + return; + } + const instance = tab.split(this._terminalFocusContextKey, this.configHelper, {}); + // TOOD: The below should be shared with ITerminalService.createInstance + tab.addDisposable(tab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed)); + instance.addDisposable(instance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed)); + instance.addDisposable(instance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged)); + instance.addDisposable(instance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady)); + this._onInstancesChanged.fire(); + + // TODO: This shouldn't be needed + tab.setVisible(true); + } + + private _getTabForInstance(instance: ITerminalInstance): ITerminalTab { + let instanceIndex = this._activeTabIndex; + let currentTabIndex = 0; + while (instanceIndex >= 0 && currentTabIndex < this._terminalTabs.length) { + const tab = this._terminalTabs[currentTabIndex]; + const count = tab.terminalInstances.length; + if (instanceIndex < count) { + return tab; + } + if (instanceIndex > count) { + instanceIndex -= count; + } + } + return null; + } + public showPanel(focus?: boolean): TPromise { return new TPromise((complete) => { let panel = this._panelService.getActivePanel(); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts index 51db98bf363..05204c67959 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts @@ -18,7 +18,7 @@ import { TERMINAL_DEFAULT_SHELL_UNIX_LIKE, TERMINAL_DEFAULT_SHELL_WINDOWS } from import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, CreateNewInActiveWorkspaceTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction, AllowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand, RenameTerminalAction, SelectAllTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, ShowNextFindTermTerminalFindWidgetAction, ShowPreviousFindTermTerminalFindWidgetAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, TERMINAL_PICKER_PREFIX, MoveToLineStartTerminalAction, MoveToLineEndTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; +import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, CreateNewInActiveWorkspaceTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction, AllowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand, RenameTerminalAction, SelectAllTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, ShowNextFindTermTerminalFindWidgetAction, ShowPreviousFindTermTerminalFindWidgetAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, TERMINAL_PICKER_PREFIX, MoveToLineStartTerminalAction, MoveToLineEndTerminalAction, SplitVerticalTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ShowAllCommandsAction } from 'vs/workbench/parts/quickopen/browser/commandsHandler'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; @@ -409,6 +409,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(MoveToLineEndTer primary: null, mac: { primary: KeyMod.CtrlCmd | KeyCode.RightArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Move To Line End', category); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SplitVerticalTerminalAction, SplitVerticalTerminalAction.ID, SplitVerticalTerminalAction.LABEL), 'Terminal: Split Vertically', category); terminalCommands.setup(); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index d38ff111fc1..bda95a6d678 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -296,6 +296,27 @@ export class CreateNewInActiveWorkspaceTerminalAction extends Action { } } +export class SplitVerticalTerminalAction extends Action { + public static readonly ID = 'workbench.action.terminal.splitVertical'; + public static readonly LABEL = nls.localize('workbench.action.terminal.splitVertical', "Split the terminal vertically"); + + constructor( + id: string, label: string, + @ITerminalService private _terminalService: ITerminalService + ) { + super(id, label); + } + + public run(event?: any): TPromise { + const instance = this._terminalService.getActiveInstance(); + if (!instance) { + return TPromise.as(void 0); + } + this._terminalService.splitInstanceVertically(instance); + return this._terminalService.showPanel(true); + } +} + export class FocusActiveTerminalAction extends Action { public static readonly ID = 'workbench.action.terminal.focus'; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts index 1aaea069860..458dd2c0d21 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts @@ -291,7 +291,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { terminalFocusContextKey: IContextKey, configHelper: TerminalConfigHelper, shellLaunchConfig: IShellLaunchConfig - ): void { + ): ITerminalInstance { const instance = this._instantiationService.createInstance(TerminalInstance, terminalFocusContextKey, configHelper, @@ -304,6 +304,8 @@ export class TerminalTab extends Disposable implements ITerminalTab { this._rootSplitPane.split(instance); // TOOD: Set this correctly this._activeInstanceIndex = 1; + + return instance; } public addDisposable(disposable: IDisposable): void { From 9b21cff59fd37ec53ffcd937c8595dd3a722d4c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 12 Feb 2018 10:18:38 -0800 Subject: [PATCH 029/362] Working on correct state --- .../parts/terminal/common/terminal.ts | 2 ++ .../parts/terminal/common/terminalService.ts | 22 +++++++++++------- .../electron-browser/terminalActions.ts | 4 ++-- .../electron-browser/terminalService.ts | 23 ++++++------------- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index 5d91923df5f..cbffe83d000 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -144,6 +144,7 @@ export interface IShellLaunchConfig { export interface ITerminalService { _serviceBrand: any; + activeTabIndex: number; activeTerminalInstanceIndex: number; configHelper: ITerminalConfigHelper; onActiveTabChanged: Event; @@ -180,6 +181,7 @@ export interface ITerminalService { } export interface ITerminalTab { + activeInstance: ITerminalInstance; terminalInstances: ITerminalInstance[]; title: string; onDisposed: Event; diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index f71a6f79546..458bfd99526 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -28,10 +28,11 @@ export abstract class TerminalService implements ITerminalService { protected abstract _terminalInstances: ITerminalInstance[]; private _activeTabIndex: number; - // TODO: Remove _activeTerminalInstanceIndex + // TODO: Remove _activeTerminalInstanceIndex, this is owned by tab now private _activeTerminalInstanceIndex: number; private _onActiveTabChanged: Emitter; + public get activeTabIndex(): number { return this._activeTabIndex; } public get activeTerminalInstanceIndex(): number { return this._activeTerminalInstanceIndex; } public get onActiveTabChanged(): Event { return this._onActiveTabChanged.event; } public get onTabDisposed(): Event { return this._onTabDisposed.event; } @@ -100,10 +101,12 @@ export abstract class TerminalService implements ITerminalService { } public getTabLabels(): string[] { + console.log('tabs', this._terminalTabs); return this._terminalTabs.map((tab, index) => `${index + 1}: ${tab.title}`); } private _removeTab(tab: ITerminalTab): void { + console.log('_removeTab'); // Get the index of the tab and remove it from the list const index = this._terminalTabs.indexOf(tab); const wasActiveTab = tab === this._getActiveTab(); @@ -143,10 +146,11 @@ export abstract class TerminalService implements ITerminalService { } public getActiveInstance(): ITerminalInstance { - if (this.activeTerminalInstanceIndex < 0 || this.activeTerminalInstanceIndex >= this.terminalInstances.length) { + const tab = this._getActiveTab(); + if (!tab) { return null; } - return this.terminalInstances[this.activeTerminalInstanceIndex]; + return tab.activeInstance; } public getInstanceFromId(terminalId: number): ITerminalInstance { @@ -185,8 +189,12 @@ export abstract class TerminalService implements ITerminalService { // TODO: Optimize const activeInstance = this.terminalInstances[this.activeTerminalInstanceIndex]; - this._terminalTabs.forEach(t => { - t.setVisible(t.terminalInstances.indexOf(activeInstance) !== -1); + this._terminalTabs.forEach((t, i) => { + const isTabActive = t.terminalInstances.indexOf(activeInstance) !== -1; + t.setVisible(isTabActive); + if (isTabActive) { + this._activeTabIndex = i; + } }); // this._terminalInstances.forEach((terminalInstance, i) => { @@ -247,9 +255,7 @@ export abstract class TerminalService implements ITerminalService { if (instanceIndex < count) { return tab; } - if (instanceIndex > count) { - instanceIndex -= count; - } + instanceIndex -= count; } return null; } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index bda95a6d678..dc6b7e3054b 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -512,7 +512,7 @@ export class SwitchTerminalInstanceActionItem extends SelectActionItem { @IThemeService themeService: IThemeService, @IContextViewService contextViewService: IContextViewService ) { - super(null, action, terminalService.getTabLabels(), terminalService.activeTerminalInstanceIndex, contextViewService); + super(null, action, terminalService.getTabLabels(), terminalService.activeTabIndex, contextViewService); this.toDispose.push(terminalService.onInstancesChanged(this._updateItems, this)); this.toDispose.push(terminalService.onActiveTabChanged(this._updateItems, this)); @@ -521,7 +521,7 @@ export class SwitchTerminalInstanceActionItem extends SelectActionItem { } private _updateItems(): void { - this.setOptions(this.terminalService.getTabLabels(), this.terminalService.activeTerminalInstanceIndex); + this.setOptions(this.terminalService.getTabLabels(), this.terminalService.activeTabIndex); } } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 6d3a5b531ea..53ebe938812 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -52,22 +52,13 @@ export class TerminalService extends AbstractTerminalService implements ITermina } public createInstance(shell: IShellLaunchConfig = {}, wasNewTerminalAction?: boolean): ITerminalInstance { - let terminalTab; - let terminalInstance; - if (this.terminalTabs.length === 0) { - terminalTab = this._instantiationService.createInstance(TerminalTab, - this._terminalFocusContextKey, - this._configHelper, - this._terminalContainer, - shell); - this._terminalTabs.push(terminalTab); - terminalInstance = terminalTab.terminalInstances[0]; - } else { - // Always Split temporarily - terminalTab = this.terminalTabs[0]; - terminalTab.split(this._terminalFocusContextKey, this._configHelper, shell); - terminalInstance = terminalTab.terminalInstances[terminalTab.terminalInstances.length - 1]; - } + const terminalTab = this._instantiationService.createInstance(TerminalTab, + this._terminalFocusContextKey, + this._configHelper, + this._terminalContainer, + shell); + this._terminalTabs.push(terminalTab); + const terminalInstance = terminalTab.terminalInstances[0]; terminalTab.addDisposable(terminalTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed)); terminalInstance.addDisposable(terminalInstance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed)); terminalInstance.addDisposable(terminalInstance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged)); From 79d8060a903b201c6b9fd16d6d4b1f22f2f3892a Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Mon, 12 Feb 2018 11:21:52 -0800 Subject: [PATCH 030/362] Add toggle for duplicate search API in issue reporter, #43519 --- .../electron-browser/issue/issueReporter.html | 3 +- .../issue/issueReporterMain.ts | 192 ++++++++++++------ .../issue/media/issueReporter.css | 15 +- src/vs/platform/issue/common/issue.ts | 4 + .../issue/electron-main/issueService.ts | 17 +- 5 files changed, 164 insertions(+), 67 deletions(-) diff --git a/src/vs/code/electron-browser/issue/issueReporter.html b/src/vs/code/electron-browser/issue/issueReporter.html index 695de78a4cb..b3c0a427fc7 100644 --- a/src/vs/code/electron-browser/issue/issueReporter.html +++ b/src/vs/code/electron-browser/issue/issueReporter.html @@ -4,7 +4,8 @@ - + + diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 8c6a04f03a5..d3a82fe2377 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -31,7 +31,7 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProper import { WindowsChannelClient } from 'vs/platform/windows/common/windowsIpc'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel'; -import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData } from 'vs/platform/issue/common/issue'; +import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures } from 'vs/platform/issue/common/issue'; import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage'; import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { debounce } from 'vs/base/common/decorators'; @@ -39,8 +39,14 @@ import * as platform from 'vs/base/common/platform'; const MAX_URL_LENGTH = 5400; +interface SearchResult { + html_url: string; + title: string; +} + export interface IssueReporterConfiguration extends IWindowConfiguration { data: IssueReporterData; + features: IssueReporterFeatures; } export function startup(configuration: IssueReporterConfiguration) { @@ -55,6 +61,7 @@ export class IssueReporter extends Disposable { private telemetryService: ITelemetryService; private issueReporterModel: IssueReporterModel; private shouldQueueSearch = true; + private features: IssueReporterFeatures; private receivedSystemInfo = false; private receivedPerformanceInfo = false; @@ -78,6 +85,8 @@ export class IssueReporter extends Disposable { reprosWithoutExtensions: false }); + this.features = configuration.features; + ipcRenderer.on('issuePerformanceInfoResponse', (event, info) => { this.issueReporterModel.update(info); this.receivedPerformanceInfo = true; @@ -175,15 +184,15 @@ export class IssueReporter extends Disposable { } if (styles.sliderBackgroundColor) { - content.push(`body::-webkit-scrollbar-thumb { background-color: ${styles.sliderBackgroundColor}; }`); + content.push(`.issues-container::-webkit-scrollbar-thumb, body::-webkit-scrollbar-thumb { background-color: ${styles.sliderBackgroundColor}; }`); } if (styles.sliderActiveColor) { - content.push(`body::-webkit-scrollbar-thumb:active { background-color: ${styles.sliderActiveColor}; }`); + content.push(`.issues-container::-webkit-scrollbar-thumb:active, body::-webkit-scrollbar-thumb:active { background-color: ${styles.sliderActiveColor}; }`); } if (styles.sliderHoverColor) { - content.push(`body::-webkit-scrollbar-thumb:hover { background-color: ${styles.sliderHoverColor}; }`); + content.push(`.issues-container::-webkit-scrollbar-thumb:hover, body::-webkit-scrollbar-thumb:hover { background-color: ${styles.sliderHoverColor}; }`); } styleTag.innerHTML = content.join('\n'); @@ -321,7 +330,7 @@ export class IssueReporter extends Disposable { this.issueReporterModel.update({ issueDescription: (event.target).value }); }); - document.getElementById('issue-title').addEventListener('input', (e) => { this.searchGitHub(e); }); + document.getElementById('issue-title').addEventListener('input', (e) => { this.searchIssues(e); }); document.getElementById('github-submit-btn').addEventListener('click', () => this.createIssue()); @@ -405,62 +414,120 @@ export class IssueReporter extends Disposable { } @debounce(300) - private searchGitHub(event: Event): void { + private searchIssues(event: Event): void { const title = (event.target).value; - const similarIssues = document.getElementById('similar-issues'); if (title) { - const query = `is:issue+repo:microsoft/vscode+${title}`; - window.fetch(`https://api.github.com/search/issues?q=${query}`).then((response) => { - response.json().then(result => { - similarIssues.innerHTML = ''; - if (result && result.items && result.items.length) { - const issues = $('ul'); - const issuesText = $('div.list-title'); - issuesText.textContent = localize('similarIssues', "Similar issues"); + if (this.features.useDuplicateSearch) { + this.searchDuplicates(title); + } else { + this.searchGitHub(title); + } + } else { + this.clearSearchResults(); + } + } - const { items } = result; - const numResultsToDisplay = items.length < 5 ? items.length : 5; - for (let i = 0; i < numResultsToDisplay; i++) { - const link = $('a', { href: items[i].html_url }); - link.textContent = items[i].title; - link.addEventListener('click', openLink); - link.addEventListener('auxclick', openLink); + private clearSearchResults(): void { + const similarIssues = document.getElementById('similar-issues'); + similarIssues.innerHTML = ''; + } - const item = $('li', {}, link); - issues.appendChild(item); - } + private searchDuplicates(title: string): void { + // TODO: Change to HTTPS + const url = 'http://vscode-probot.westus.cloudapp.azure.com:5010/duplicate_candidates'; + const init = { + method: 'POST', + body: JSON.stringify({ + title + }), + headers: new Headers({ + 'Content-Type': 'application/json' + }) + }; - similarIssues.appendChild(issuesText); - similarIssues.appendChild(issues); - } else if (result && result.items) { - const message = $('div.list-title'); - message.textContent = localize('noResults', "No results found"); - similarIssues.appendChild(message); - } else { - const message = $('div.list-title'); - message.textContent = localize('rateLimited', "GitHub query limit exceeded. Please wait."); - similarIssues.appendChild(message); + window.fetch(url, init).then((response) => { + response.json().then(result => { + this.clearSearchResults(); - const resetTime = response.headers.get('X-RateLimit-Reset'); - const timeToWait = parseInt(resetTime) - Math.floor(Date.now() / 1000); - if (this.shouldQueueSearch) { - this.shouldQueueSearch = false; - setTimeout(() => { - this.searchGitHub(event); - this.shouldQueueSearch = true; - }, timeToWait * 1000); - } + if (result && result.candidates) { + const normalizedResults = result.candidates.map(result => { + return { + html_url: `https://github.com/Microsoft/vscode/issues/${result.number}`, + title: result.title + }; + }); - throw new Error(result.message); - } - }).catch((error) => { - this.logSearchError(error); - }); + this.displaySearchResults(normalizedResults); + } else { + throw new Error(); + } }).catch((error) => { this.logSearchError(error); }); + }).catch((error) => { + this.logSearchError(error); + }); + } + + private searchGitHub(title: string): void { + const query = `is:issue+repo:microsoft/vscode+${title}`; + const similarIssues = document.getElementById('similar-issues'); + + window.fetch(`https://api.github.com/search/issues?q=${query}`).then((response) => { + response.json().then(result => { + similarIssues.innerHTML = ''; + if (result && result.items) { + this.displaySearchResults(result.items); + } else { + // If the items property isn't present, the rate limit has been hit + const message = $('div.list-title'); + message.textContent = localize('rateLimited', "GitHub query limit exceeded. Please wait."); + similarIssues.appendChild(message); + + const resetTime = response.headers.get('X-RateLimit-Reset'); + const timeToWait = parseInt(resetTime) - Math.floor(Date.now() / 1000); + if (this.shouldQueueSearch) { + this.shouldQueueSearch = false; + setTimeout(() => { + this.searchGitHub(title); + this.shouldQueueSearch = true; + }, timeToWait * 1000); + } + + throw new Error(result.message); + } + }).catch((error) => { + this.logSearchError(error); + }); + }).catch((error) => { + this.logSearchError(error); + }); + } + + private displaySearchResults(results: SearchResult[]) { + const similarIssues = document.getElementById('similar-issues'); + if (results.length) { + const issues = $('ul.issues-container'); + const issuesText = $('div.list-title'); + issuesText.textContent = localize('similarIssues', "Similar issues"); + + const numResultsToDisplay = results.length < 5 ? results.length : 5; + for (let i = 0; i < numResultsToDisplay; i++) { + const link = $('a', { href: results[i].html_url }); + link.textContent = results[i].title; + link.addEventListener('click', (e) => this.openLink(e)); + link.addEventListener('auxclick', (e) => this.openLink(e)); + + const item = $('li.issue', {}, link); + issues.appendChild(item); + } + + similarIssues.appendChild(issuesText); + similarIssues.appendChild(issues); } else { - similarIssues.innerHTML = ''; + const message = $('div.list-title'); + message.textContent = localize('noResults', "No results found"); + similarIssues.appendChild(message); } } @@ -703,6 +770,22 @@ export class IssueReporter extends Disposable { return table; } + + private openLink(event: MouseEvent): void { + event.preventDefault(); + event.stopPropagation(); + // Exclude right click + if (event.which < 3) { + shell.openExternal((event.target).href); + + /* __GDPR__ + "issueReporterViewSimilarIssue" : { + "usingDuplicatesAPI" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('issueReporterViewSimilarIssue', { usingDuplicatesAPI: this.features.useDuplicateSearch }); + } + } } // helper functions @@ -713,12 +796,3 @@ function hide(el) { function show(el) { el.classList.remove('hidden'); } - -function openLink(event: MouseEvent) { - event.preventDefault(); - event.stopPropagation(); - // Exclude right click - if (event.which < 3) { - shell.openExternal((event.target).href); - } -} diff --git a/src/vs/code/electron-browser/issue/media/issueReporter.css b/src/vs/code/electron-browser/issue/media/issueReporter.css index 74c25832695..6e89ecb0dce 100644 --- a/src/vs/code/electron-browser/issue/media/issueReporter.css +++ b/src/vs/code/electron-browser/issue/media/issueReporter.css @@ -281,6 +281,7 @@ input, select, textarea { a { color: #CCCCCC; + text-decoration: none; } .invalid-input { @@ -347,10 +348,20 @@ button { } } -body::-webkit-scrollbar { +.issues-container::-webkit-scrollbar, body::-webkit-scrollbar { width: 14px; } -body::-webkit-scrollbar-thumb { +.issues-container::-webkit-scrollbar, body::-webkit-scrollbar-thumb { min-height: 20px; } + +.issues-container { + margin-top: .5em; + height: 108px; + overflow-y: auto; +} + +.issues-container > .issue { + padding: 1px 0; +} \ No newline at end of file diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index e095b0e2a4b..31ab3916514 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -55,6 +55,10 @@ export interface ISettingsSearchIssueReporterData extends IssueReporterData { filterResultCount: number; } +export interface IssueReporterFeatures { + useDuplicateSearch: boolean; +} + export interface IIssueService { _serviceBrand: any; openReporter(data: IssueReporterData): TPromise; diff --git a/src/vs/platform/issue/electron-main/issueService.ts b/src/vs/platform/issue/electron-main/issueService.ts index 13179eb7184..ff083572f83 100644 --- a/src/vs/platform/issue/electron-main/issueService.ts +++ b/src/vs/platform/issue/electron-main/issueService.ts @@ -9,12 +9,13 @@ import { TPromise, Promise } from 'vs/base/common/winjs.base'; import { localize } from 'vs/nls'; import * as objects from 'vs/base/common/objects'; import { parseArgs } from 'vs/platform/environment/node/argv'; -import { IIssueService, IssueReporterData } from 'vs/platform/issue/common/issue'; +import { IIssueService, IssueReporterData, IssueReporterFeatures } from 'vs/platform/issue/common/issue'; import { BrowserWindow, ipcMain, screen } from 'electron'; import { ILaunchService } from 'vs/code/electron-main/launch'; import { getPerformanceInfo, PerformanceInfo, getSystemInfo, SystemInfo } from 'vs/code/electron-main/diagnostics'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { isMacintosh } from 'vs/base/common/platform'; +import { IConfigurationService } from '../../configuration/common/configuration'; const DEFAULT_BACKGROUND_COLOR = '#1E1E1E'; @@ -26,7 +27,8 @@ export class IssueService implements IIssueService { constructor( private machineId: string, @IEnvironmentService private environmentService: IEnvironmentService, - @ILaunchService private launchService: ILaunchService + @ILaunchService private launchService: ILaunchService, + @IConfigurationService private configurationService: IConfigurationService ) { } openReporter(data: IssueReporterData): TPromise { @@ -61,7 +63,11 @@ export class IssueService implements IIssueService { this._issueWindow.setMenuBarVisibility(false); // workaround for now, until a menu is implemented - this._issueWindow.loadURL(this.getIssueReporterPath(data)); + const features: IssueReporterFeatures = { + useDuplicateSearch: this.configurationService.getValue('issueReporter.searchDuplicates') + }; + + this._issueWindow.loadURL(this.getIssueReporterPath(data, features)); return TPromise.as(null); } @@ -158,13 +164,14 @@ export class IssueService implements IIssueService { }); } - private getIssueReporterPath(data: IssueReporterData) { + private getIssueReporterPath(data: IssueReporterData, features: IssueReporterFeatures) { const windowConfiguration = { appRoot: this.environmentService.appRoot, nodeCachedDataDir: this.environmentService.nodeCachedDataDir, windowId: this._issueWindow.id, machineId: this.machineId, - data + data, + features }; const environment = parseArgs(process.argv); From d35ceef5c6e92cc23620ea4e9ea40d13c741e4c2 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 12 Feb 2018 11:43:13 -0800 Subject: [PATCH 031/362] Remove extra padding on zoomed in image view Fixes #43379 --- .../workbench/browser/parts/editor/media/resourceviewer.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/media/resourceviewer.css b/src/vs/workbench/browser/parts/editor/media/resourceviewer.css index 54dac885b18..6f4e21c6ef5 100644 --- a/src/vs/workbench/browser/parts/editor/media/resourceviewer.css +++ b/src/vs/workbench/browser/parts/editor/media/resourceviewer.css @@ -13,7 +13,7 @@ } .monaco-resource-viewer.image { - padding: 10px 10px 0 10px; + padding: 0; background-position: 0 0, 8px 8px; background-size: 16px 16px; display: flex; @@ -37,8 +37,8 @@ } .monaco-resource-viewer img.scale-to-fit { - max-width: 100%; - max-height: 100%; + max-width: calc(100% - 20px); + max-height: calc(100% - 20px); object-fit: contain; } From 0f6f38f9ccea44123b890b43d20859faefa4fcb0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 12 Feb 2018 11:49:27 -0800 Subject: [PATCH 032/362] Get creating new tabs and switching working --- .../parts/terminal/common/terminal.ts | 8 +- .../parts/terminal/common/terminalService.ts | 97 ++++++++++--------- .../electron-browser/media/terminal.css | 4 + .../electron-browser/terminalActions.ts | 15 +-- .../terminal/electron-browser/terminalTab.ts | 35 +++++-- 5 files changed, 96 insertions(+), 63 deletions(-) diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index cbffe83d000..33ba7ab8797 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -145,7 +145,6 @@ export interface ITerminalService { _serviceBrand: any; activeTabIndex: number; - activeTerminalInstanceIndex: number; configHelper: ITerminalConfigHelper; onActiveTabChanged: Event; onTabDisposed: Event; @@ -163,11 +162,13 @@ export interface ITerminalService { getActiveInstance(): ITerminalInstance; setActiveInstance(terminalInstance: ITerminalInstance): void; setActiveInstanceByIndex(terminalIndex: number): void; - setActiveInstanceToNext(): void; - setActiveInstanceToPrevious(): void; getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance; splitInstanceVertically(instance: ITerminalInstance): void; + setActiveTabToNext(): void; + setActiveTabToPrevious(): void; + setActiveTabByIndex(tabIndex: number): void; + showPanel(focus?: boolean): TPromise; hidePanel(): void; focusFindWidget(): TPromise; @@ -186,6 +187,7 @@ export interface ITerminalTab { title: string; onDisposed: Event; + setActiveInstanceByIndex(index: number): void; attachToElement(element: HTMLElement): void; setVisible(visible: boolean): void; layout(width: number, height: number): void; diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index 458bfd99526..7dcdd2acc2d 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -28,12 +28,9 @@ export abstract class TerminalService implements ITerminalService { protected abstract _terminalInstances: ITerminalInstance[]; private _activeTabIndex: number; - // TODO: Remove _activeTerminalInstanceIndex, this is owned by tab now - private _activeTerminalInstanceIndex: number; private _onActiveTabChanged: Emitter; public get activeTabIndex(): number { return this._activeTabIndex; } - public get activeTerminalInstanceIndex(): number { return this._activeTerminalInstanceIndex; } public get onActiveTabChanged(): Event { return this._onActiveTabChanged.event; } public get onTabDisposed(): Event { return this._onTabDisposed.event; } public get onInstanceDisposed(): Event { return this._onInstanceDisposed.event; } @@ -52,7 +49,6 @@ export abstract class TerminalService implements ITerminalService { @ILifecycleService lifecycleService: ILifecycleService ) { this._activeTabIndex = 0; - this._activeTerminalInstanceIndex = 0; this._isShuttingDown = false; this._onActiveTabChanged = new Emitter(); @@ -137,12 +133,10 @@ export abstract class TerminalService implements ITerminalService { } private _getActiveTab(): ITerminalTab { - // TODO: TerminalService needs to track the active tab - // TODO: The tab should track its active instance - if (this.activeTerminalInstanceIndex < 0 || this.activeTerminalInstanceIndex >= this._terminalTabs.length) { + if (this._activeTabIndex < 0 || this._activeTabIndex >= this._terminalTabs.length) { return null; } - return this._terminalTabs[this.activeTerminalInstanceIndex]; + return this._terminalTabs[this._activeTabIndex]; } public getActiveInstance(): ITerminalInstance { @@ -169,6 +163,7 @@ export abstract class TerminalService implements ITerminalService { if (tabIndex >= this._terminalTabs.length) { return; } + console.log('setActiveTabByIndex', tabIndex); const didTabChange = this._activeTabIndex !== tabIndex; this._activeTabIndex = tabIndex; @@ -179,61 +174,80 @@ export abstract class TerminalService implements ITerminalService { } } + private _getInstanceFromGlobalInstanceIndex(index: number): { tab: ITerminalTab, tabIndex: number, instance: ITerminalInstance, localInstanceIndex: number } { + let currentTabIndex = 0; + while (index >= 0 && currentTabIndex < this._terminalTabs.length) { + const tab = this._terminalTabs[currentTabIndex]; + const count = tab.terminalInstances.length; + if (index < count) { + return { + tab, + tabIndex: currentTabIndex, + instance: tab.terminalInstances[index], + localInstanceIndex: index + }; + } + index -= count; + currentTabIndex++; + } + return null; + } + // TODO: Remove setActiveInstanceByIndex? public setActiveInstanceByIndex(terminalIndex: number): void { - if (terminalIndex >= this._terminalInstances.length) { + const query = this._getInstanceFromGlobalInstanceIndex(terminalIndex); + if (!query) { return; } - const didInstanceChange = this._activeTerminalInstanceIndex !== terminalIndex; - this._activeTerminalInstanceIndex = terminalIndex; - // TODO: Optimize - const activeInstance = this.terminalInstances[this.activeTerminalInstanceIndex]; - this._terminalTabs.forEach((t, i) => { - const isTabActive = t.terminalInstances.indexOf(activeInstance) !== -1; - t.setVisible(isTabActive); - if (isTabActive) { - this._activeTabIndex = i; - } - }); + console.log('setActiveInstanceByIndex', terminalIndex, query); + const didInstanceChange = query.tab.setActiveInstanceByIndex(query.localInstanceIndex); + const didTabChange = this._activeTabIndex !== query.tabIndex; + this._activeTabIndex = query.tabIndex; + this._terminalTabs.forEach((t, i) => t.setVisible(i === query.tabIndex)); - // this._terminalInstances.forEach((terminalInstance, i) => { - // terminalInstance.setVisible(i === terminalIndex); - // }); + // TOOD: Should this live in TerminalTab now? // Only fire the event if there was a change - if (didInstanceChange) { - // TODO: If this method is being kept this should only fire when the tab is actually changed + if (didInstanceChange || didTabChange) { + // TODO: If this method is being kept this should only fire when the _tab_ is actually changed this._onActiveTabChanged.fire(); } } - public setActiveInstanceToNext(): void { - if (this.terminalInstances.length <= 1) { + public setActiveTabToNext(): void { + if (this._terminalTabs.length <= 1) { return; } - let newIndex = this._activeTerminalInstanceIndex + 1; - if (newIndex >= this.terminalInstances.length) { + let newIndex = this._activeTabIndex + 1; + if (newIndex >= this._terminalTabs.length) { newIndex = 0; } - this.setActiveInstanceByIndex(newIndex); + this.setActiveTabByIndex(newIndex); } - public setActiveInstanceToPrevious(): void { - if (this.terminalInstances.length <= 1) { + public setActiveTabToPrevious(): void { + if (this._terminalTabs.length <= 1) { return; } - let newIndex = this._activeTerminalInstanceIndex - 1; + let newIndex = this._activeTabIndex - 1; if (newIndex < 0) { - newIndex = this.terminalInstances.length - 1; + newIndex = this._terminalTabs.length - 1; } - this.setActiveInstanceByIndex(newIndex); + this.setActiveTabByIndex(newIndex); } public splitInstanceVertically(instanceToSplit: ITerminalInstance): void { + console.log('splitting instance', instanceToSplit); const tab = this._getTabForInstance(instanceToSplit); if (!tab) { return; } + + if (tab.terminalInstances.length === 2) { + console.warn('Only a single split in the terminal is supported currently'); + return; + } + const instance = tab.split(this._terminalFocusContextKey, this.configHelper, {}); // TOOD: The below should be shared with ITerminalService.createInstance tab.addDisposable(tab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed)); @@ -242,20 +256,15 @@ export abstract class TerminalService implements ITerminalService { instance.addDisposable(instance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady)); this._onInstancesChanged.fire(); - // TODO: This shouldn't be needed - tab.setVisible(true); + this._terminalTabs.forEach((t, i) => t.setVisible(i === this._activeTabIndex)); } private _getTabForInstance(instance: ITerminalInstance): ITerminalTab { - let instanceIndex = this._activeTabIndex; - let currentTabIndex = 0; - while (instanceIndex >= 0 && currentTabIndex < this._terminalTabs.length) { - const tab = this._terminalTabs[currentTabIndex]; - const count = tab.terminalInstances.length; - if (instanceIndex < count) { + for (let i = 0; i < this._terminalTabs.length; i++) { + const tab = this._terminalTabs[i]; + if (tab.terminalInstances.indexOf(instance) !== -1) { return tab; } - instanceIndex -= count; } return null; } 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 b30f7df8efe..d0c56802751 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css @@ -21,6 +21,10 @@ box-sizing: border-box; } +.monaco-workbench .panel.integrated-terminal .terminal-tab { + height: 100%; +} + .monaco-workbench .panel.integrated-terminal .terminal-wrapper { display: none; } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index dc6b7e3054b..fdda2a6d33e 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -99,9 +99,9 @@ export class QuickKillTerminalAction extends Action { if (terminal) { terminal.dispose(); } - if (this.terminalService.terminalInstances.length > 0 && this.terminalService.activeTerminalInstanceIndex !== terminalIndex) { - this.terminalService.setActiveInstanceByIndex(Math.min(terminalIndex, this.terminalService.terminalInstances.length - 1)); - } + // if (this.terminalService.terminalInstances.length > 0 && this.terminalService.activeTerminalInstanceIndex !== terminalIndex) { + // this.terminalService.setActiveInstanceByIndex(Math.min(terminalIndex, this.terminalService.terminalInstances.length - 1)); + // } return TPromise.timeout(50).then(result => this.quickOpenService.show(TERMINAL_PICKER_PREFIX, null)); } } @@ -352,7 +352,7 @@ export class FocusNextTerminalAction extends Action { } public run(event?: any): TPromise { - this.terminalService.setActiveInstanceToNext(); + this.terminalService.setActiveTabToNext(); return this.terminalService.showPanel(true); } } @@ -370,7 +370,7 @@ export class FocusPreviousTerminalAction extends Action { } public run(event?: any): TPromise { - this.terminalService.setActiveInstanceToPrevious(); + this.terminalService.setActiveTabToPrevious(); return this.terminalService.showPanel(true); } } @@ -481,6 +481,7 @@ export class RunActiveFileInTerminalAction extends Action { } } +// TODO: Change name to tab export class SwitchTerminalInstanceAction extends Action { public static readonly ID = 'workbench.action.terminal.switchTerminalInstance'; @@ -498,8 +499,8 @@ export class SwitchTerminalInstanceAction extends Action { if (!item || !item.split) { return TPromise.as(null); } - const selectedTerminalIndex = parseInt(item.split(':')[0], 10) - 1; - this.terminalService.setActiveInstanceByIndex(selectedTerminalIndex); + const selectedTabIndex = parseInt(item.split(':')[0], 10) - 1; + this.terminalService.setActiveTabByIndex(selectedTabIndex); return this.terminalService.showPanel(true); } } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts index 458dd2c0d21..1ed9492f412 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts @@ -40,9 +40,7 @@ class SplitPane implements IView { protected branch(container: HTMLElement, orientation: Orientation, instance: ITerminalInstance): void { this.orientation = orientation; - while (container.children.length > 0) { - container.removeChild(container.firstChild); - } + container.removeChild((this.instance)._wrapperElement); this._splitView = new SplitView(container, { orientation }); this.layout(this._size); @@ -172,6 +170,7 @@ class RootSplitPane extends SplitPane { export class TerminalTab extends Disposable implements ITerminalTab { private _terminalInstances: ITerminalInstance[] = []; private _rootSplitPane: RootSplitPane; + private _tabElement: HTMLElement; private _activeInstanceIndex: number; @@ -201,8 +200,10 @@ export class TerminalTab extends Disposable implements ITerminalTab { this._rootSplitPane = new RootSplitPane(); this._rootSplitPane.instance = instance; - // TODO: Only render if it's visible? - this._rootSplitPane.render(this._container); + + if (this._container) { + this.attachToElement(this._container); + } // TODO: Listen to instance focus and update activeInstanceIndex accordingly } @@ -230,7 +231,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { // Adjust focus if the instance was active if (wasActiveInstance && this._terminalInstances.length > 0) { let newIndex = index < this._terminalInstances.length ? index : this._terminalInstances.length - 1; - this._setActiveInstanceByIndex(newIndex); + this.setActiveInstanceByIndex(newIndex); if (instance.hadFocusOnExit) { this.activeInstance.focus(true); } @@ -262,13 +263,26 @@ export class TerminalTab extends Disposable implements ITerminalTab { return null; } - private _setActiveInstanceByIndex(index: number): void { + public setActiveInstanceByIndex(index: number): boolean { + // Check for invalid value + if (index >= this._terminalInstances.length) { + return false; + } + + const didInstanceChange = this._activeInstanceIndex !== index; this._activeInstanceIndex = index; + // TODO: Fire events like in TerminalService.setActiveInstanceByIndex? + + return didInstanceChange; } public attachToElement(element: HTMLElement): void { this._container = element; + this._tabElement = document.createElement('div'); + this._tabElement.classList.add('terminal-tab'); + this._container.appendChild(this._tabElement); + this._rootSplitPane.render(this._tabElement); } public get title(): string { @@ -280,8 +294,8 @@ export class TerminalTab extends Disposable implements ITerminalTab { } public setVisible(visible: boolean): void { - if (this._container) { - this._container.style.display = visible ? 'block' : 'none'; + if (this._tabElement) { + this._tabElement.style.display = visible ? '' : 'none'; } // TODO: probably don't need to tell terminal instances about visiblility anymore? this.terminalInstances.forEach(i => i.setVisible(visible)); @@ -302,6 +316,9 @@ export class TerminalTab extends Disposable implements ITerminalTab { this._rootSplitPane.orientation = Orientation.HORIZONTAL; this._rootSplitPane.split(instance); + if (this._tabElement) { + this._rootSplitPane.render(this._tabElement); + } // TOOD: Set this correctly this._activeInstanceIndex = 1; From 3b4c3d2801c9343a64e61492e5d92a67dc8a3b71 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 12 Feb 2018 11:51:10 -0800 Subject: [PATCH 033/362] Pick up new version of marked Fixes #43420 --- src/vs/base/common/marked/OSSREADME.json | 2 +- src/vs/base/common/marked/raw.marked.js | 301 +++++++++++++++-------- 2 files changed, 199 insertions(+), 104 deletions(-) diff --git a/src/vs/base/common/marked/OSSREADME.json b/src/vs/base/common/marked/OSSREADME.json index 5bfc34a583b..010e42bd7b8 100644 --- a/src/vs/base/common/marked/OSSREADME.json +++ b/src/vs/base/common/marked/OSSREADME.json @@ -3,6 +3,6 @@ [{ "name": "chjj-marked", "repositoryURL": "https://github.com/npmcomponent/chjj-marked", - "version": "0.3.6", + "version": "0.3.12", "license": "MIT" }] diff --git a/src/vs/base/common/marked/raw.marked.js b/src/vs/base/common/marked/raw.marked.js index 0ee6a3b1e6a..d00c4e2bb55 100644 --- a/src/vs/base/common/marked/raw.marked.js +++ b/src/vs/base/common/marked/raw.marked.js @@ -16,19 +16,26 @@ var block = { newline: /^\n+/, code: /^( {4}[^\n]+\n*)+/, fences: noop, - hr: /^( *[-*_]){3,} *(?:\n+|$)/, + hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, nptable: noop, - lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, - blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/, + blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/, - def: /^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, + def: /^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/, table: noop, - paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, + lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, + paragraph: /^([^\n]+(?:\n?(?!hr|heading|lheading| {0,3}>|tag)[^\n]+)+)/, text: /^[^\n]+/ }; +block._label = /(?:\\[\[\]]|[^\[\]])+/; +block._title = /(?:"(?:\\"|[^"]|"[^"\n]*")*"|'\n?(?:[^'\n]+\n?)*'|\([^()]*\))/; +block.def = replace(block.def) + ('label', block._label) + ('title', block._title) + (); + block.bullet = /(?:[*+-]|\d+\.)/; block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; block.item = replace(block.item, 'gm') @@ -37,23 +44,19 @@ block.item = replace(block.item, 'gm') block.list = replace(block.list) (/bull/g, block.bullet) - ('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))') + ('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))') ('def', '\\n+(?=' + block.def.source + ')') (); -block.blockquote = replace(block.blockquote) - ('def', block.def) - (); - block._tag = '(?!(?:' + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' - + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b'; + + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b'; block.html = replace(block.html) ('comment', //) ('closed', /<(tag)[\s\S]+?<\/\1>/) - ('closing', /])*?>/) + ('closing', /]*)*?\/?>/) (/tag/g, block._tag) (); @@ -61,9 +64,11 @@ block.paragraph = replace(block.paragraph) ('hr', block.hr) ('heading', block.heading) ('lheading', block.lheading) - ('blockquote', block.blockquote) ('tag', '<' + block._tag) - ('def', block.def) + (); + +block.blockquote = replace(block.blockquote) + ('paragraph', block.paragraph) (); /** @@ -77,15 +82,15 @@ block.normal = merge({}, block); */ block.gfm = merge({}, block.normal, { - fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/, + fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/, paragraph: /^/, heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/ }); block.gfm.paragraph = replace(block.paragraph) ('(?!', '(?!' - + block.gfm.fences.source.replace('\\1', '\\2') + '|' - + block.list.source.replace('\\1', '\\3') + '|') + + block.gfm.fences.source.replace('\\1', '\\2') + '|' + + block.list.source.replace('\\1', '\\3') + '|') (); /** @@ -126,7 +131,7 @@ Lexer.rules = block; * Static Lex Method */ -Lexer.lex = function(src, options) { +Lexer.lex = function (src, options) { var lexer = new Lexer(options); return lexer.lex(src); }; @@ -135,7 +140,7 @@ Lexer.lex = function(src, options) { * Preprocessing */ -Lexer.prototype.lex = function(src) { +Lexer.prototype.lex = function (src) { src = src .replace(/\r\n|\r/g, '\n') .replace(/\t/g, ' ') @@ -149,7 +154,7 @@ Lexer.prototype.lex = function(src) { * Lexing */ -Lexer.prototype.token = function(src, top, bq) { +Lexer.prototype.token = function (src, top) { var src = src.replace(/^ +$/gm, '') , next , loose @@ -159,6 +164,7 @@ Lexer.prototype.token = function(src, top, bq) { , item , space , i + , tag , l; while (src) { @@ -239,17 +245,6 @@ Lexer.prototype.token = function(src, top, bq) { continue; } - // lheading - if (cap = this.rules.lheading.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'heading', - depth: cap[2] === '=' ? 1 : 2, - text: cap[1] - }); - continue; - } - // hr if (cap = this.rules.hr.exec(src)) { src = src.substring(cap[0].length); @@ -272,7 +267,7 @@ Lexer.prototype.token = function(src, top, bq) { // Pass `top` to keep the current // "toplevel" state. This is exactly // how markdown.pl works. - this.token(cap, top, true); + this.token(cap, top); this.tokens.push({ type: 'blockquote_end' @@ -341,7 +336,7 @@ Lexer.prototype.token = function(src, top, bq) { }); // Recurse. - this.token(item, false, bq); + this.token(item, false); this.tokens.push({ type: 'list_item_end' @@ -370,12 +365,16 @@ Lexer.prototype.token = function(src, top, bq) { } // def - if ((!bq && top) && (cap = this.rules.def.exec(src))) { + if (top && (cap = this.rules.def.exec(src))) { src = src.substring(cap[0].length); - this.tokens.links[cap[1].toLowerCase()] = { - href: cap[2], - title: cap[3] - }; + if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1); + tag = cap[1].toLowerCase(); + if (!this.tokens.links[tag]) { + this.tokens.links[tag] = { + href: cap[2], + title: cap[3] + }; + } continue; } @@ -413,6 +412,17 @@ Lexer.prototype.token = function(src, top, bq) { continue; } + // lheading + if (cap = this.rules.lheading.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'heading', + depth: cap[2] === '=' ? 1 : 2, + text: cap[1] + }); + continue; + } + // top-level paragraph if (top && (cap = this.rules.paragraph.exec(src))) { src = src.substring(cap[0].length); @@ -451,21 +461,29 @@ Lexer.prototype.token = function(src, top, bq) { var inline = { escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, - autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, + autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/, url: noop, - tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, + tag: /^|^<\/?[a-zA-Z0-9\-]+(?:"[^"]*"|'[^']*'|\s[^<'">\/]*)*?\/?>/, link: /^!?\[(inside)\]\(href\)/, reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, - nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, + nolink: /^!?\[((?:\[[^\]]*\]|\\[\[\]]|[^\[\]])*)\]/, strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, - em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/, - code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/, + em: /^_([^\s_](?:[^_]|__)+?[^\s_])_\b|^\*((?:\*\*|[^*])+?)\*(?!\*)/, + code: /^(`+)(\s*)([\s\S]*?[^`]?)\2\1(?!`)/, br: /^ {2,}\n(?!\s*$)/, del: noop, - text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/; inline.link = replace(inline.link) @@ -498,11 +516,14 @@ inline.pedantic = merge({}, inline.normal, { inline.gfm = merge({}, inline.normal, { escape: replace(inline.escape)('])', '~|])')(), - url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/, + url: replace(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/) + ('email', inline._email) + (), + _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/, del: /^~~(?=\S)([\s\S]*?\S)~~/, text: replace(inline.text) (']|', '~]|') - ('|', '|https?://|') + ('|', '|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&\'*+/=?^_`{\\|}~-]+@|') () }); @@ -552,7 +573,7 @@ InlineLexer.rules = inline; * Static Lexing/Compiling Method */ -InlineLexer.output = function(src, links, options) { +InlineLexer.output = function (src, links, options) { var inline = new InlineLexer(links, options); return inline.output(src); }; @@ -561,7 +582,7 @@ InlineLexer.output = function(src, links, options) { * Lexing/Compiling */ -InlineLexer.prototype.output = function(src) { +InlineLexer.prototype.output = function (src) { var out = '' , link , text @@ -580,10 +601,8 @@ InlineLexer.prototype.output = function(src) { if (cap = this.rules.autolink.exec(src)) { src = src.substring(cap[0].length); if (cap[2] === '@') { - text = cap[1].charAt(6) === ':' - ? this.mangle(cap[1].substring(7)) - : this.mangle(cap[1]); - href = this.mangle('mailto:') + text; + text = escape(this.mangle(cap[1])); + href = 'mailto:' + text; } else { text = escape(cap[1]); href = text; @@ -594,9 +613,19 @@ InlineLexer.prototype.output = function(src) { // url (gfm) if (!this.inLink && (cap = this.rules.url.exec(src))) { + cap[0] = this.rules._backpedal.exec(cap[0])[0]; src = src.substring(cap[0].length); - text = escape(cap[1]); - href = text; + if (cap[2] === '@') { + text = escape(cap[0]); + href = 'mailto:' + text; + } else { + text = escape(cap[0]); + if (cap[1] === 'www.') { + href = 'http://' + text; + } else { + href = text; + } + } out += this.renderer.link(href, null, text); continue; } @@ -631,7 +660,7 @@ InlineLexer.prototype.output = function(src) { // reflink, nolink if ((cap = this.rules.reflink.exec(src)) - || (cap = this.rules.nolink.exec(src))) { + || (cap = this.rules.nolink.exec(src))) { src = src.substring(cap[0].length); link = (cap[2] || cap[1]).replace(/\s+/g, ' '); link = this.links[link.toLowerCase()]; @@ -663,7 +692,7 @@ InlineLexer.prototype.output = function(src) { // code if (cap = this.rules.code.exec(src)) { src = src.substring(cap[0].length); - out += this.renderer.codespan(escape(cap[2], true)); + out += this.renderer.codespan(escape(cap[3].trim(), true)); continue; } @@ -701,7 +730,7 @@ InlineLexer.prototype.output = function(src) { * Compile Link */ -InlineLexer.prototype.outputLink = function(cap, link) { +InlineLexer.prototype.outputLink = function (cap, link) { var href = escape(link.href) , title = link.title ? escape(link.title) : null; @@ -714,7 +743,7 @@ InlineLexer.prototype.outputLink = function(cap, link) { * Smartypants Transformations */ -InlineLexer.prototype.smartypants = function(text) { +InlineLexer.prototype.smartypants = function (text) { if (!this.options.smartypants) return text; return text // em-dashes @@ -737,7 +766,7 @@ InlineLexer.prototype.smartypants = function(text) { * Mangle Links */ -InlineLexer.prototype.mangle = function(text) { +InlineLexer.prototype.mangle = function (text) { if (!this.options.mangle) return text; var out = '' , l = text.length @@ -763,7 +792,7 @@ function Renderer(options) { this.options = options || {}; } -Renderer.prototype.code = function(code, lang, escaped) { +Renderer.prototype.code = function (code, lang, escaped) { if (this.options.highlight) { var out = this.options.highlight(code, lang); if (out != null && out !== code) { @@ -786,15 +815,15 @@ Renderer.prototype.code = function(code, lang, escaped) { + '\n\n'; }; -Renderer.prototype.blockquote = function(quote) { +Renderer.prototype.blockquote = function (quote) { return '
\n' + quote + '
\n'; }; -Renderer.prototype.html = function(html) { +Renderer.prototype.html = function (html) { return html; }; -Renderer.prototype.heading = function(text, level, raw) { +Renderer.prototype.heading = function (text, level, raw) { return '' @@ -848,39 +877,42 @@ Renderer.prototype.tablecell = function(content, flags) { }; // span level renderer -Renderer.prototype.strong = function(text) { +Renderer.prototype.strong = function (text) { return '' + text + ''; }; -Renderer.prototype.em = function(text) { +Renderer.prototype.em = function (text) { return '' + text + ''; }; -Renderer.prototype.codespan = function(text) { +Renderer.prototype.codespan = function (text) { return '' + text + ''; }; -Renderer.prototype.br = function() { +Renderer.prototype.br = function () { return this.options.xhtml ? '
' : '
'; }; -Renderer.prototype.del = function(text) { +Renderer.prototype.del = function (text) { return '' + text + ''; }; -Renderer.prototype.link = function(href, title, text) { +Renderer.prototype.link = function (href, title, text) { if (this.options.sanitize) { try { var prot = decodeURIComponent(unescape(href)) .replace(/[^\w:]/g, '') .toLowerCase(); } catch (e) { - return ''; + return text; } if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { - return ''; + return text; } } + if (this.options.baseUrl && !originIndependentUrl.test(href)) { + href = resolveUrl(this.options.baseUrl, href); + } var out = ' Date: Mon, 12 Feb 2018 13:47:18 -0800 Subject: [PATCH 034/362] Revert "Don't close waitOnExit terminals when pressing modifier keys" This reverts commit 2f5aba68099dc1052bddae37f79f425e73675925. Undoing this in favor of reverting the PR that allowed arrows Part of #42066 --- .../parts/terminal/electron-browser/terminalInstance.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 63c8088d148..d32487fdb4e 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -781,12 +781,6 @@ export class TerminalInstance implements ITerminalInstance { private _attachPressAnyKeyToCloseListener() { this._processDisposables.push(dom.addDisposableListener(this._xterm.textarea, 'keydown', (event: KeyboardEvent) => { - switch (event.key) { - case 'Meta': - case 'Shift': - case 'Alt': - case 'Control': return; - } this.dispose(); event.preventDefault(); })); From 2c43e3a66f4fe1e763963dbc19bf3b6622b2c880 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 12 Feb 2018 13:48:06 -0800 Subject: [PATCH 035/362] Revert "Use keydown instead of keypress to close terminal" This reverts commit f32ed90bebdeef4713ba62fc2c4a663e693a3905. This caused problems with clicking/navigating/copying task output. Fixes #42066 --- .../parts/terminal/electron-browser/terminalInstance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index d32487fdb4e..c53fd61d6c9 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -780,7 +780,7 @@ export class TerminalInstance implements ITerminalInstance { } private _attachPressAnyKeyToCloseListener() { - this._processDisposables.push(dom.addDisposableListener(this._xterm.textarea, 'keydown', (event: KeyboardEvent) => { + this._processDisposables.push(dom.addDisposableListener(this._xterm.textarea, 'keypress', (event: KeyboardEvent) => { this.dispose(); event.preventDefault(); })); From 3e4d6da03ae30fea71da30d08d18d71d3f9be9e4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 12 Feb 2018 14:19:12 -0800 Subject: [PATCH 036/362] Get panel resize affecting inner terminals --- .../electron-browser/terminalPanel.ts | 1 + .../terminal/electron-browser/terminalTab.ts | 30 ++++++++++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts index 407beda708a..493b518507c 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts @@ -95,6 +95,7 @@ export class TerminalPanel extends Panel { // this._terminalService.terminalInstances.forEach((t) => { // t.layout(dimension); // }); + console.log('panel layout!', dimension); this._terminalService.terminalTabs.forEach(t => t.layout(dimension.width, dimension.height)); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts index 1ed9492f412..49654d285f4 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalTab.ts @@ -97,14 +97,11 @@ class SplitPane implements IView { return; } this._container = container; - console.log('render'); // throw new Error("Method not implemented."); if (!this._isContainerSet && this.instance) { if (this._needsReattach) { - console.log('reattachToElement'); (this.instance).reattachToElement(container); } else { - console.log('attachToElement'); this.instance.attachToElement(container); } this._isContainerSet = true; @@ -112,24 +109,29 @@ class SplitPane implements IView { } public layout(size: number): void { - // Only layout when both sizes are known and the SplitPane owns an instance + // Only layout when both sizes are known this._size = size; - if (!this._size || !this.orthogonalSize || !this.instance) { + if (!this._size || !this.orthogonalSize) { + console.log('SplitPane.layout', this._size, this.orthogonalSize, this.instance); return; } - console.log('layout', size, this.orthogonalSize); + if (this.instance) { + if (this.orientation === Orientation.VERTICAL) { + this.instance.layout({ width: this.orthogonalSize, height: this._size }); + } else { + this.instance.layout({ width: this._size, height: this.orthogonalSize }); + } + return; + } - if (this.orientation === Orientation.VERTICAL) { - this.instance.layout({ width: this.orthogonalSize, height: this._size }); - } else { - this.instance.layout({ width: this._size, height: this.orthogonalSize }); + for (const child of this.children) { + child.orthogonalLayout(this._size); } } public orthogonalLayout(size: number): void { this.orthogonalSize = size; - console.log('orthogonalLayout', this._size, this.orthogonalSize); if (this._splitView) { this._splitView.layout(this.orthogonalSize); @@ -154,6 +156,7 @@ class RootSplitPane extends SplitPane { } public layoutBox(width: number, height: number): void { + console.log('layoutBox', width, height); if (this.orientation === Orientation.VERTICAL) { this.layout(width); this.orthogonalLayout(height); @@ -163,6 +166,7 @@ class RootSplitPane extends SplitPane { } else { this._width = width; this._height = height; + this.instance.layout({ width, height }); } } } @@ -200,6 +204,9 @@ export class TerminalTab extends Disposable implements ITerminalTab { this._rootSplitPane = new RootSplitPane(); this._rootSplitPane.instance = instance; + this._rootSplitPane.onDidChange(e => { + console.log('onDidChange', e); + }); if (this._container) { this.attachToElement(this._container); @@ -330,6 +337,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { } public layout(width: number, height: number): void { + console.log('TerminalTab.layout', width, height); this._rootSplitPane.layoutBox(width, height); } } From 768f6c8e2f1caf41adaabd8d54858be21071fd79 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Mon, 12 Feb 2018 14:41:17 -0800 Subject: [PATCH 037/362] Add logging to issue reporter --- .../issue/issueReporterMain.ts | 18 ++++++++++++++---- .../issue/electron-main/issueService.ts | 8 ++++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index d3a82fe2377..dce948d5427 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -15,6 +15,8 @@ import { escape } from 'vs/base/common/strings'; import product from 'vs/platform/node/product'; import pkg from 'vs/platform/node/package'; import * as os from 'os'; +import { debounce } from 'vs/base/common/decorators'; +import * as platform from 'vs/base/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; import { Client as ElectronIPCClient } from 'vs/base/parts/ipc/electron-browser/ipc.electron-browser'; import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; @@ -34,8 +36,9 @@ import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporter import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures } from 'vs/platform/issue/common/issue'; import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage'; import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { debounce } from 'vs/base/common/decorators'; -import * as platform from 'vs/base/common/platform'; +import { createSpdLogService } from 'vs/platform/log/node/spdlogService'; +import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; +import { ILogService, getLogLevel } from 'vs/platform/log/common/log'; const MAX_URL_LENGTH = 5400; @@ -59,6 +62,7 @@ export function startup(configuration: IssueReporterConfiguration) { export class IssueReporter extends Disposable { private environmentService: IEnvironmentService; private telemetryService: ITelemetryService; + private logService: ILogService; private issueReporterModel: IssueReporterModel; private shouldQueueSearch = true; private features: IssueReporterFeatures; @@ -88,6 +92,7 @@ export class IssueReporter extends Disposable { this.features = configuration.features; ipcRenderer.on('issuePerformanceInfoResponse', (event, info) => { + this.logService.trace('issueReporter: Received performance data'); this.issueReporterModel.update(info); this.receivedPerformanceInfo = true; @@ -98,6 +103,7 @@ export class IssueReporter extends Disposable { }); ipcRenderer.on('issueSystemInfoResponse', (event, info) => { + this.logService.trace('issueReporter: Received system data'); this.issueReporterModel.update({ systemInfo: info }); this.receivedSystemInfo = true; @@ -107,6 +113,7 @@ export class IssueReporter extends Disposable { ipcRenderer.send('issueSystemInfoRequest'); ipcRenderer.send('issuePerformanceInfoRequest'); + this.logService.trace('issueReporter: Sent data requests'); if (window.document.documentElement.lang !== 'en') { show(document.getElementById('english')); @@ -266,6 +273,10 @@ export class IssueReporter extends Disposable { serviceCollection.set(IWindowsService, new WindowsChannelClient(windowsChannel)); this.environmentService = new EnvironmentService(configuration, configuration.execPath); + const logService = createSpdLogService(`issuereporter${configuration.windowId}`, getLogLevel(this.environmentService), this.environmentService.logsPath); + const logLevelClient = new LogLevelSetterChannelClient(mainProcessClient.getChannel('loglevel')); + this.logService = new FollowerLogService(logLevelClient, logService); + const sharedProcess = (serviceCollection.get(IWindowsService)).whenSharedProcessReady() .then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${configuration.windowId}`)); @@ -532,8 +543,7 @@ export class IssueReporter extends Disposable { } private logSearchError(error: Error) { - // TODO: Use LogService here. - console.log(error); + this.logService.warn('issueReporter#search ', error.message); /* __GDPR__ "issueReporterSearchError" : { "message" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } diff --git a/src/vs/platform/issue/electron-main/issueService.ts b/src/vs/platform/issue/electron-main/issueService.ts index ff083572f83..d706dcd5ff9 100644 --- a/src/vs/platform/issue/electron-main/issueService.ts +++ b/src/vs/platform/issue/electron-main/issueService.ts @@ -15,7 +15,8 @@ import { ILaunchService } from 'vs/code/electron-main/launch'; import { getPerformanceInfo, PerformanceInfo, getSystemInfo, SystemInfo } from 'vs/code/electron-main/diagnostics'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { isMacintosh } from 'vs/base/common/platform'; -import { IConfigurationService } from '../../configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILogService } from 'vs/platform/log/common/log'; const DEFAULT_BACKGROUND_COLOR = '#1E1E1E'; @@ -28,7 +29,8 @@ export class IssueService implements IIssueService { private machineId: string, @IEnvironmentService private environmentService: IEnvironmentService, @ILaunchService private launchService: ILaunchService, - @IConfigurationService private configurationService: IConfigurationService + @IConfigurationService private configurationService: IConfigurationService, + @ILogService private logService: ILogService ) { } openReporter(data: IssueReporterData): TPromise { @@ -67,6 +69,7 @@ export class IssueService implements IIssueService { useDuplicateSearch: this.configurationService.getValue('issueReporter.searchDuplicates') }; + this.logService.trace('issueService#openReporter: opening issue reporter'); this._issueWindow.loadURL(this.getIssueReporterPath(data, features)); return TPromise.as(null); @@ -158,6 +161,7 @@ export class IssueService implements IIssueService { resolve(diagnosticInfo); }) .catch(err => { + this.logService.warn('issueService#getPerformanceInfo ', err.message); reject(err); }); }); From bfbbe132cf8b4bcc1c90f74cf88c348b4e5b181d Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Mon, 12 Feb 2018 14:30:19 -0800 Subject: [PATCH 038/362] Fix css emmet abbreviations with : Fixes #43544 --- extensions/emmet/src/abbreviationActions.ts | 6 +++-- .../src/test/cssAbbreviationAction.test.ts | 22 +++++++++---------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts index f0349269969..0fdfacd4116 100644 --- a/extensions/emmet/src/abbreviationActions.ts +++ b/extensions/emmet/src/abbreviationActions.ts @@ -253,12 +253,14 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen if (propertyNode.terminatorToken && propertyNode.separator && position.isAfterOrEqual(propertyNode.separatorToken.end) - && position.isBeforeOrEqual(propertyNode.terminatorToken.start)) { + && position.isBeforeOrEqual(propertyNode.terminatorToken.start) + && abbreviation.indexOf(':') === -1) { return hexColorRegex.test(abbreviation); } if (!propertyNode.terminatorToken && propertyNode.separator - && position.isAfterOrEqual(propertyNode.separatorToken.end)) { + && position.isAfterOrEqual(propertyNode.separatorToken.end) + && abbreviation.indexOf(':') === -1) { return hexColorRegex.test(abbreviation); } } diff --git a/extensions/emmet/src/test/cssAbbreviationAction.test.ts b/extensions/emmet/src/test/cssAbbreviationAction.test.ts index 39fc7df5755..4f07358638b 100644 --- a/extensions/emmet/src/test/cssAbbreviationAction.test.ts +++ b/extensions/emmet/src/test/cssAbbreviationAction.test.ts @@ -14,9 +14,9 @@ const completionProvider = new DefaultCompletionItemProvider(); const cssContents = ` .boo { margin: 20px 10px; - m10 + pos:f background-image: url('tryme.png'); - m10 + pos:f } .boo .hoo { @@ -57,9 +57,9 @@ suite('Tests for Expand Abbreviations (CSS)', () => { test('Expand abbreviation (CSS)', () => { return withRandomFileEditor(cssContents, 'css', (editor, doc) => { - editor.selections = [new Selection(3, 1, 3, 4), new Selection(5, 1, 5, 4)]; + editor.selections = [new Selection(3, 1, 3, 6), new Selection(5, 1, 5, 6)]; return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), cssContents.replace(/m10/g, 'margin: 10px;')); + assert.equal(editor.document.getText(), cssContents.replace(/pos:f/g, 'position: fixed;')); return Promise.resolve(); }); }); @@ -243,22 +243,22 @@ nav# }); test('Expand abbreviation in completion list (CSS)', () => { - const abbreviation = 'm10'; - const expandedText = 'margin: 10px;'; + const abbreviation = 'pos:f'; + const expandedText = 'position: fixed;'; return withRandomFileEditor(cssContents, 'css', (editor, doc) => { - editor.selection = new Selection(3, 1, 3, 4); + editor.selection = new Selection(3, 1, 3, 6); const cancelSrc = new CancellationTokenSource(); - const completionPromise1 = completionProvider.provideCompletionItems(editor.document, new Position(3, 4), cancelSrc.token); - const completionPromise2 = completionProvider.provideCompletionItems(editor.document, new Position(5, 4), cancelSrc.token); + const completionPromise1 = completionProvider.provideCompletionItems(editor.document, new Position(3, 6), cancelSrc.token); + const completionPromise2 = completionProvider.provideCompletionItems(editor.document, new Position(5, 6), cancelSrc.token); if (!completionPromise1 || !completionPromise2) { - assert.equal(1, 2, `Problem with expanding m10`); + assert.equal(1, 2, `Problem with expanding pos:f`); return Promise.resolve(); } const callBack = (completionList: CompletionList) => { if (!completionList.items || !completionList.items.length) { - assert.equal(1, 2, `Problem with expanding m10`); + assert.equal(1, 2, `Problem with expanding pos:f`); return; } const emmetCompletionItem = completionList.items[0]; From 38cfb0438e2c831fb01d8a6df511957c1f018dac Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 12 Feb 2018 17:49:45 -0800 Subject: [PATCH 039/362] Update markdown grammar --- .../syntaxes/markdown.tmLanguage.json | 1232 ++++++++--------- 1 file changed, 616 insertions(+), 616 deletions(-) diff --git a/extensions/markdown/syntaxes/markdown.tmLanguage.json b/extensions/markdown/syntaxes/markdown.tmLanguage.json index 86b940a8927..11022350c6e 100644 --- a/extensions/markdown/syntaxes/markdown.tmLanguage.json +++ b/extensions/markdown/syntaxes/markdown.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/1ba9efd21c29c70dd87d8402a92ef64666dcb25e", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/d88bcfff33c41e4bd3628e2fa0746ded43cac8f6", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -194,621 +194,6 @@ ], "while": "(^|\\G)\\s*(>) ?" }, - "fenced_code_block_unknown": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?=([^`~]*)?$)", - "beginCaptures": { - "3": { - "name": "punctuation.definition.markdown" - }, - "4": { - "name": "fenced_code.block.language" - } - }, - "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", - "endCaptures": { - "3": { - "name": "punctuation.definition.markdown" - } - }, - "name": "markup.fenced_code.block.markdown" - }, - "heading": { - "begin": "(?:^|\\G)[ ]{0,3}(#{1,6})\\s*(?=[\\S[^#]])", - "captures": { - "1": { - "name": "punctuation.definition.heading.markdown" - } - }, - "contentName": "entity.name.section.markdown", - "end": "\\s*(#{1,6})?$\\n?", - "name": "markup.heading.markdown", - "patterns": [ - { - "include": "#inline" - } - ] - }, - "heading-setext": { - "patterns": [ - { - "match": "^(={3,})(?=[ \\t]*$\\n?)", - "name": "markup.heading.setext.1.markdown" - }, - { - "match": "^(-{3,})(?=[ \\t]*$\\n?)", - "name": "markup.heading.setext.2.markdown" - } - ] - }, - "html": { - "patterns": [ - { - "begin": "(^|\\G)\\s*()", - "name": "comment.block.html" - }, - { - "begin": "(^|\\G)\\s*(?=<(script|style|pre)(\\s|$|>)(?!.*?))", - "end": "(?=.*)", - "patterns": [ - { - "begin": "(\\s*|$)", - "patterns": [ - { - "include": "text.html.basic" - } - ], - "while": "^(?!.*)" - } - ] - }, - { - "begin": "(^|\\G)\\s*(?=))", - "patterns": [ - { - "include": "text.html.basic" - } - ], - "while": "^(?!\\s*$)" - }, - { - "begin": "(^|\\G)\\s*(?=(<[a-zA-Z0-9\\-](/?>|\\s.*?>)|)\\s*$)", - "patterns": [ - { - "include": "text.html.basic" - } - ], - "while": "^(?!\\s*$)" - } - ] - }, - "link-def": { - "captures": { - "1": { - "name": "punctuation.definition.constant.markdown" - }, - "2": { - "name": "constant.other.reference.link.markdown" - }, - "3": { - "name": "punctuation.definition.constant.markdown" - }, - "4": { - "name": "punctuation.separator.key-value.markdown" - }, - "5": { - "name": "punctuation.definition.link.markdown" - }, - "6": { - "name": "markup.underline.link.markdown" - }, - "7": { - "name": "punctuation.definition.link.markdown" - }, - "8": { - "name": "string.other.link.description.title.markdown" - }, - "9": { - "name": "punctuation.definition.string.begin.markdown" - }, - "10": { - "name": "punctuation.definition.string.end.markdown" - }, - "11": { - "name": "string.other.link.description.title.markdown" - }, - "12": { - "name": "punctuation.definition.string.begin.markdown" - }, - "13": { - "name": "punctuation.definition.string.end.markdown" - } - }, - "match": "^(?x:\n \\s*\t\t\t\t\t\t# Leading whitespace\n (\\[)(.+?)(\\])(:)\t\t# Reference name\n [ \\t]*\t\t\t\t\t# Optional whitespace\n (?)\t\t\t# The url\n [ \\t]*\t\t\t\t\t# Optional whitespace\n (?:\n ((\\().+?(\\)))\t\t# Match title in quotes…\n | ((\").+?(\"))\t\t# or in parens.\n )?\t\t\t\t\t\t# Title is optional\n \\s*\t\t\t\t\t\t# Optional whitespace\n $\n)\n", - "name": "meta.link.reference.def.markdown" - }, - "list_paragraph": { - "begin": "(^|\\G)(?=\\S)(?![*+->]\\s|[0-9]+\\.\\s)", - "name": "meta.paragraph.markdown", - "patterns": [ - { - "include": "#inline" - }, - { - "include": "text.html.basic" - }, - { - "include": "#heading-setext" - } - ], - "while": "(^|\\G)(?!\\s*$|#|[ ]{0,3}([-*_>][ ]{2,}){3,}[ \\t]*$\\n?|[ ]{0,3}[*+->]|[ ]{0,3}[0-9]+\\.)" - }, - "lists": { - "patterns": [ - { - "begin": "(^|\\G)([ ]{0,3})([*+-])([ ]{1,3}|\\t)", - "beginCaptures": { - "3": { - "name": "beginning.punctuation.definition.list.markdown" - } - }, - "comment": "Currently does not support un-indented second lines.", - "name": "markup.list.unnumbered.markdown", - "patterns": [ - { - "include": "#block" - }, - { - "include": "#list_paragraph" - } - ], - "while": "((^|\\G)([ ]{4}|\\t))|(^[ \\t]*$)" - }, - { - "begin": "(^|\\G)([ ]{0,3})([0-9]+\\.)([ ]{1,3}|\\t)", - "beginCaptures": { - "3": { - "name": "beginning.punctuation.definition.list.markdown" - } - }, - "name": "markup.list.numbered.markdown", - "patterns": [ - { - "include": "#block" - }, - { - "include": "#list_paragraph" - } - ], - "while": "((^|\\G)([ ]{4}|\\t))|(^[ \\t]*$)" - } - ] - }, - "paragraph": { - "begin": "(^|\\G)[ ]{0,3}(?=\\S)", - "name": "meta.paragraph.markdown", - "patterns": [ - { - "include": "#inline" - }, - { - "include": "text.html.basic" - }, - { - "include": "#heading-setext" - } - ], - "while": "(^|\\G)((?=\\s*[-=]{3,}\\s*$)|[ ]{4,}(?=\\S))" - }, - "raw_block": { - "begin": "(^|\\G)([ ]{4}|\\t)", - "name": "markup.raw.block.markdown", - "while": "(^|\\G)([ ]{4}|\\t)" - }, - "separator": { - "match": "(^|\\G)[ ]{0,3}([*-_])([ ]{0,2}\\2){2,}[ \\t]*$\\n?", - "name": "meta.separator.markdown" - } - } - }, - "frontMatter": { - "begin": "\\A-{3}\\s*$", - "contentName": "meta.embedded.block.frontmatter", - "patterns": [ - { - "include": "source.yaml" - } - ], - "while": "^(?!(-{3}|\\.{3})\\s*$)" - }, - "inline": { - "patterns": [ - { - "include": "#ampersand" - }, - { - "include": "#bracket" - }, - { - "include": "#bold" - }, - { - "include": "#italic" - }, - { - "include": "#raw" - }, - { - "include": "#escape" - }, - { - "include": "#image-inline" - }, - { - "include": "#image-ref" - }, - { - "include": "#link-email" - }, - { - "include": "#link-inet" - }, - { - "include": "#link-inline" - }, - { - "include": "#link-ref" - }, - { - "include": "#link-ref-literal" - } - ], - "repository": { - "ampersand": { - "comment": "Markdown will convert this for us. We match it so that the HTML grammar will not mark it up as invalid.", - "match": "&(?!([a-zA-Z0-9]+|#[0-9]+|#x[0-9a-fA-F]+);)", - "name": "meta.other.valid-ampersand.markdown" - }, - "bold": { - "begin": "(?x)\n (\\*\\*|__)(?=\\S)\t\t\t\t\t\t\t\t# Open\n (?=\n (\n <[^>]*+>\t\t\t\t\t\t\t# HTML tags\n | (?`+)([^`]|(?!(?(?!`))`)*+\\k\n # Raw\n | \\\\[\\\\`*_{}\\[\\]()#.!+\\->]?+\t\t\t# Escapes\n | \\[\n (\n (?\t\t\t\t\t# Named group\n [^\\[\\]\\\\]\t\t\t\t# Match most chars\n | \\\\.\t\t\t\t\t\t# Escaped chars\n | \\[ \\g*+ \\]\t\t# Nested brackets\n )*+\n \\]\n (\n (\t\t\t\t\t\t\t# Reference Link\n [ ]?\t\t\t\t\t# Optional space\n \\[[^\\]]*+\\]\t\t\t\t# Ref name\n )\n | (\t\t\t\t\t\t\t# Inline Link\n \\(\t\t\t\t\t\t# Opening paren\n [ \\t]*+\t\t\t\t# Optional whitespace\n ?\t\t\t# URL\n [ \\t]*+\t\t\t\t# Optional whitespace\n (\t\t\t\t\t# Optional Title\n (?['\"])\n (.*?)\n \\k<title>\n )?\n \\)\n )\n )\n )\n | (?!(?<=\\S)\\1).\t\t\t\t\t\t# Everything besides\n # style closer\n )++\n (?<=\\S)\\1\t\t\t\t\t\t\t\t# Close\n )\n", - "captures": { - "1": { - "name": "punctuation.definition.bold.markdown" - } - }, - "end": "(?<=\\S)(\\1)", - "name": "markup.bold.markdown", - "patterns": [ - { - "applyEndPatternLast": 1, - "begin": "(?=<[^>]*?>)", - "end": "(?<=>)", - "patterns": [ - { - "include": "text.html.basic" - } - ] - }, - { - "include": "#escape" - }, - { - "include": "#ampersand" - }, - { - "include": "#bracket" - }, - { - "include": "#raw" - }, - { - "include": "#italic" - }, - { - "include": "#image-inline" - }, - { - "include": "#link-inline" - }, - { - "include": "#link-inet" - }, - { - "include": "#link-email" - }, - { - "include": "#image-ref" - }, - { - "include": "#link-ref-literal" - }, - { - "include": "#link-ref" - } - ] - }, - "bracket": { - "comment": "Markdown will convert this for us. We match it so that the HTML grammar will not mark it up as invalid.", - "match": "<(?![a-z/?\\$!])", - "name": "meta.other.valid-bracket.markdown" - }, - "escape": { - "match": "\\\\[-`*_#+.!(){}\\[\\]\\\\>]", - "name": "constant.character.escape.markdown" - }, - "image-inline": { - "captures": { - "1": { - "name": "punctuation.definition.string.begin.markdown" - }, - "2": { - "name": "string.other.link.description.markdown" - }, - "4": { - "name": "punctuation.definition.string.end.markdown" - }, - "5": { - "name": "punctuation.definition.metadata.markdown" - }, - "6": { - "name": "punctuation.definition.link.markdown" - }, - "7": { - "name": "markup.underline.link.image.markdown" - }, - "8": { - "name": "punctuation.definition.link.markdown" - }, - "9": { - "name": "string.other.link.description.title.markdown" - }, - "10": { - "name": "punctuation.definition.string.markdown" - }, - "11": { - "name": "punctuation.definition.string.markdown" - }, - "12": { - "name": "string.other.link.description.title.markdown" - }, - "13": { - "name": "punctuation.definition.string.markdown" - }, - "14": { - "name": "punctuation.definition.string.markdown" - }, - "15": { - "name": "punctuation.definition.metadata.markdown" - } - }, - "match": "(?x:\n (\\!\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])\n # Match the link text.\n (\\()\t\t\t\t\t\t# Opening paren for url\n (<?)(\\S+?)(>?)\t\t\t# The url\n [ \\t]*\t\t\t\t\t# Optional whitespace\n (?:\n ((\\().+?(\\)))\t\t# Match title in parens…\n | ((\").+?(\"))\t\t# or in quotes.\n )?\t\t\t\t\t\t# Title is optional\n \\s*\t\t\t\t\t\t# Optional whitespace\n (\\))\n)\n", - "name": "meta.image.inline.markdown" - }, - "image-ref": { - "captures": { - "1": { - "name": "punctuation.definition.string.begin.markdown" - }, - "2": { - "name": "string.other.link.description.markdown" - }, - "4": { - "name": "punctuation.definition.string.begin.markdown" - }, - "5": { - "name": "punctuation.definition.constant.markdown" - }, - "6": { - "name": "constant.other.reference.link.markdown" - }, - "7": { - "name": "punctuation.definition.constant.markdown" - } - }, - "match": "(\\!\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])[ ]?(\\[)(.*?)(\\])", - "name": "meta.image.reference.markdown" - }, - "italic": { - "begin": "(?x) (\\*\\b|\\b_)(?=\\S)\t\t\t\t\t\t\t\t# Open\n (?=\n (\n <[^>]*+>\t\t\t\t\t\t\t# HTML tags\n | (?<raw>`+)([^`]|(?!(?<!`)\\k<raw>(?!`))`)*+\\k<raw>\n # Raw\n | \\\\[\\\\`*_{}\\[\\]()#.!+\\->]?+\t\t\t# Escapes\n | \\[\n (\n (?<square>\t\t\t\t\t# Named group\n [^\\[\\]\\\\]\t\t\t\t# Match most chars\n | \\\\.\t\t\t\t\t\t# Escaped chars\n | \\[ \\g<square>*+ \\]\t\t# Nested brackets\n )*+\n \\]\n (\n (\t\t\t\t\t\t\t# Reference Link\n [ ]?\t\t\t\t\t# Optional space\n \\[[^\\]]*+\\]\t\t\t\t# Ref name\n )\n | (\t\t\t\t\t\t\t# Inline Link\n \\(\t\t\t\t\t\t# Opening paren\n [ \\t]*+\t\t\t\t# Optional whtiespace\n <?(.*?)>?\t\t\t# URL\n [ \\t]*+\t\t\t\t# Optional whtiespace\n (\t\t\t\t\t# Optional Title\n (?<title>['\"])\n (.*?)\n \\k<title>\n )?\n \\)\n )\n )\n )\n | \\1\\1\t\t\t\t\t\t\t\t# Must be bold closer\n | (?!(?<=\\S)\\1).\t\t\t\t\t\t# Everything besides\n # style closer\n )++\n (?<=\\S)\\1\t\t\t\t\t\t\t\t# Close\n )\n", - "captures": { - "1": { - "name": "punctuation.definition.italic.markdown" - } - }, - "end": "(?<=\\S)(\\1)((?!\\1)|(?=\\1\\1))", - "name": "markup.italic.markdown", - "patterns": [ - { - "applyEndPatternLast": 1, - "begin": "(?=<[^>]*?>)", - "end": "(?<=>)", - "patterns": [ - { - "include": "text.html.basic" - } - ] - }, - { - "include": "#escape" - }, - { - "include": "#ampersand" - }, - { - "include": "#bracket" - }, - { - "include": "#raw" - }, - { - "include": "#bold" - }, - { - "include": "#image-inline" - }, - { - "include": "#link-inline" - }, - { - "include": "#link-inet" - }, - { - "include": "#link-email" - }, - { - "include": "#image-ref" - }, - { - "include": "#link-ref-literal" - }, - { - "include": "#link-ref" - } - ] - }, - "link-email": { - "captures": { - "1": { - "name": "punctuation.definition.link.markdown" - }, - "2": { - "name": "markup.underline.link.markdown" - }, - "4": { - "name": "punctuation.definition.link.markdown" - } - }, - "match": "(<)((?:mailto:)?[-.\\w]+@[-a-z0-9]+(\\.[-a-z0-9]+)*\\.[a-z]+)(>)", - "name": "meta.link.email.lt-gt.markdown" - }, - "link-inet": { - "captures": { - "1": { - "name": "punctuation.definition.link.markdown" - }, - "2": { - "name": "markup.underline.link.markdown" - }, - "3": { - "name": "punctuation.definition.link.markdown" - } - }, - "match": "(<)((?:https?|ftp)://.*?)(>)", - "name": "meta.link.inet.markdown" - }, - "link-inline": { - "captures": { - "1": { - "name": "punctuation.definition.string.begin.markdown" - }, - "2": { - "name": "string.other.link.title.markdown" - }, - "4": { - "name": "punctuation.definition.string.end.markdown" - }, - "5": { - "name": "punctuation.definition.metadata.markdown" - }, - "6": { - "name": "punctuation.definition.link.markdown" - }, - "7": { - "name": "markup.underline.link.markdown" - }, - "8": { - "name": "punctuation.definition.link.markdown" - }, - "9": { - "name": "string.other.link.description.title.markdown" - }, - "10": { - "name": "punctuation.definition.string.begin.markdown" - }, - "11": { - "name": "punctuation.definition.string.end.markdown" - }, - "12": { - "name": "string.other.link.description.title.markdown" - }, - "13": { - "name": "punctuation.definition.string.begin.markdown" - }, - "14": { - "name": "punctuation.definition.string.end.markdown" - }, - "15": { - "name": "punctuation.definition.metadata.markdown" - } - }, - "match": "(?x:\n (\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])\n # Match the link text.\n (\\()\t\t\t\t\t\t# Opening paren for url\n (<?)(.*?)(>?)\t\t\t# The url\n [ \\t]*\t\t\t\t\t# Optional whitespace\n (?:\n ((\\().+?(\\)))\t\t# Match title in parens…\n | ((\").+?(\"))\t\t# or in quotes.\n )?\t\t\t\t\t\t# Title is optional\n \\s*\t\t\t\t\t\t# Optional whitespace\n (\\))\n )\n", - "name": "meta.link.inline.markdown" - }, - "link-ref": { - "captures": { - "1": { - "name": "punctuation.definition.string.begin.markdown" - }, - "2": { - "name": "string.other.link.title.markdown" - }, - "4": { - "name": "punctuation.definition.string.end.markdown" - }, - "5": { - "name": "punctuation.definition.constant.begin.markdown" - }, - "6": { - "name": "constant.other.reference.link.markdown" - }, - "7": { - "name": "punctuation.definition.constant.end.markdown" - } - }, - "match": "(\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])(\\[)([^\\]]*+)(\\])", - "name": "meta.link.reference.markdown" - }, - "link-ref-literal": { - "captures": { - "1": { - "name": "punctuation.definition.string.begin.markdown" - }, - "2": { - "name": "string.other.link.title.markdown" - }, - "4": { - "name": "punctuation.definition.string.end.markdown" - }, - "5": { - "name": "punctuation.definition.constant.begin.markdown" - }, - "6": { - "name": "punctuation.definition.constant.end.markdown" - } - }, - "match": "(\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])[ ]?(\\[)(\\])", - "name": "meta.link.reference.literal.markdown" - }, - "raw": { - "captures": { - "1": { - "name": "punctuation.definition.raw.markdown" - }, - "3": { - "name": "punctuation.definition.raw.markdown" - } - }, - "match": "(`+)([^`]|(?!(?<!`)\\1(?!`))`)*+(\\1)", - "name": "markup.inline.raw.string.markdown" - }, "fenced_code_block_css": { "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(css|css.erb)(\\s+[^`~]*)?$)", "name": "markup.fenced_code.block.markdown", @@ -2263,6 +1648,621 @@ ] } ] + }, + "fenced_code_block_unknown": { + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?=([^`~]*)?$)", + "beginCaptures": { + "3": { + "name": "punctuation.definition.markdown" + }, + "4": { + "name": "fenced_code.block.language" + } + }, + "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", + "endCaptures": { + "3": { + "name": "punctuation.definition.markdown" + } + }, + "name": "markup.fenced_code.block.markdown" + }, + "heading": { + "begin": "(?:^|\\G)[ ]{0,3}(#{1,6})\\s*(?=[\\S[^#]])", + "captures": { + "1": { + "name": "punctuation.definition.heading.markdown" + } + }, + "contentName": "entity.name.section.markdown", + "end": "\\s*(#{1,6})?$\\n?", + "name": "markup.heading.markdown", + "patterns": [ + { + "include": "#inline" + } + ] + }, + "heading-setext": { + "patterns": [ + { + "match": "^(={3,})(?=[ \\t]*$\\n?)", + "name": "markup.heading.setext.1.markdown" + }, + { + "match": "^(-{3,})(?=[ \\t]*$\\n?)", + "name": "markup.heading.setext.2.markdown" + } + ] + }, + "html": { + "patterns": [ + { + "begin": "(^|\\G)\\s*(<!--)", + "captures": { + "1": { + "name": "punctuation.definition.comment.html" + }, + "2": { + "name": "punctuation.definition.comment.html" + } + }, + "end": "(-->)", + "name": "comment.block.html" + }, + { + "begin": "(^|\\G)\\s*(?=<(script|style|pre)(\\s|$|>)(?!.*?</(script|style|pre)>))", + "end": "(?=.*</(script|style|pre)>)", + "patterns": [ + { + "begin": "(\\s*|$)", + "patterns": [ + { + "include": "text.html.basic" + } + ], + "while": "^(?!.*</(script|style|pre)>)" + } + ] + }, + { + "begin": "(^|\\G)\\s*(?=</?(address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(\\s|$|/?>))", + "patterns": [ + { + "include": "text.html.basic" + } + ], + "while": "^(?!\\s*$)" + }, + { + "begin": "(^|\\G)\\s*(?=(<[a-zA-Z0-9\\-](/?>|\\s.*?>)|</[a-zA-Z0-9\\-]>)\\s*$)", + "patterns": [ + { + "include": "text.html.basic" + } + ], + "while": "^(?!\\s*$)" + } + ] + }, + "link-def": { + "captures": { + "1": { + "name": "punctuation.definition.constant.markdown" + }, + "2": { + "name": "constant.other.reference.link.markdown" + }, + "3": { + "name": "punctuation.definition.constant.markdown" + }, + "4": { + "name": "punctuation.separator.key-value.markdown" + }, + "5": { + "name": "punctuation.definition.link.markdown" + }, + "6": { + "name": "markup.underline.link.markdown" + }, + "7": { + "name": "punctuation.definition.link.markdown" + }, + "8": { + "name": "string.other.link.description.title.markdown" + }, + "9": { + "name": "punctuation.definition.string.begin.markdown" + }, + "10": { + "name": "punctuation.definition.string.end.markdown" + }, + "11": { + "name": "string.other.link.description.title.markdown" + }, + "12": { + "name": "punctuation.definition.string.begin.markdown" + }, + "13": { + "name": "punctuation.definition.string.end.markdown" + } + }, + "match": "^(?x:\n \\s*\t\t\t\t\t\t# Leading whitespace\n (\\[)(.+?)(\\])(:)\t\t# Reference name\n [ \\t]*\t\t\t\t\t# Optional whitespace\n (<?)(\\S+?)(>?)\t\t\t# The url\n [ \\t]*\t\t\t\t\t# Optional whitespace\n (?:\n ((\\().+?(\\)))\t\t# Match title in quotes…\n | ((\").+?(\"))\t\t# or in parens.\n )?\t\t\t\t\t\t# Title is optional\n \\s*\t\t\t\t\t\t# Optional whitespace\n $\n)\n", + "name": "meta.link.reference.def.markdown" + }, + "list_paragraph": { + "begin": "(^|\\G)(?=\\S)(?![*+->]\\s|[0-9]+\\.\\s)", + "name": "meta.paragraph.markdown", + "patterns": [ + { + "include": "#inline" + }, + { + "include": "text.html.basic" + }, + { + "include": "#heading-setext" + } + ], + "while": "(^|\\G)(?!\\s*$|#|[ ]{0,3}([-*_>][ ]{2,}){3,}[ \\t]*$\\n?|[ ]{0,3}[*+->]|[ ]{0,3}[0-9]+\\.)" + }, + "lists": { + "patterns": [ + { + "begin": "(^|\\G)([ ]{0,3})([*+-])([ ]{1,3}|\\t)", + "beginCaptures": { + "3": { + "name": "beginning.punctuation.definition.list.markdown" + } + }, + "comment": "Currently does not support un-indented second lines.", + "name": "markup.list.unnumbered.markdown", + "patterns": [ + { + "include": "#block" + }, + { + "include": "#list_paragraph" + } + ], + "while": "((^|\\G)([ ]{4}|\\t))|(^[ \\t]*$)" + }, + { + "begin": "(^|\\G)([ ]{0,3})([0-9]+\\.)([ ]{1,3}|\\t)", + "beginCaptures": { + "3": { + "name": "beginning.punctuation.definition.list.markdown" + } + }, + "name": "markup.list.numbered.markdown", + "patterns": [ + { + "include": "#block" + }, + { + "include": "#list_paragraph" + } + ], + "while": "((^|\\G)([ ]{4}|\\t))|(^[ \\t]*$)" + } + ] + }, + "paragraph": { + "begin": "(^|\\G)[ ]{0,3}(?=\\S)", + "name": "meta.paragraph.markdown", + "patterns": [ + { + "include": "#inline" + }, + { + "include": "text.html.basic" + }, + { + "include": "#heading-setext" + } + ], + "while": "(^|\\G)((?=\\s*[-=]{3,}\\s*$)|[ ]{4,}(?=\\S))" + }, + "raw_block": { + "begin": "(^|\\G)([ ]{4}|\\t)", + "name": "markup.raw.block.markdown", + "while": "(^|\\G)([ ]{4}|\\t)" + }, + "separator": { + "match": "(^|\\G)[ ]{0,3}([*-_])([ ]{0,2}\\2){2,}[ \\t]*$\\n?", + "name": "meta.separator.markdown" + } + } + }, + "frontMatter": { + "begin": "\\A-{3}\\s*$", + "contentName": "meta.embedded.block.frontmatter", + "patterns": [ + { + "include": "source.yaml" + } + ], + "while": "^(?!(-{3}|\\.{3})\\s*$)" + }, + "inline": { + "patterns": [ + { + "include": "#ampersand" + }, + { + "include": "#bracket" + }, + { + "include": "#bold" + }, + { + "include": "#italic" + }, + { + "include": "#raw" + }, + { + "include": "#escape" + }, + { + "include": "#image-inline" + }, + { + "include": "#image-ref" + }, + { + "include": "#link-email" + }, + { + "include": "#link-inet" + }, + { + "include": "#link-inline" + }, + { + "include": "#link-ref" + }, + { + "include": "#link-ref-literal" + } + ], + "repository": { + "ampersand": { + "comment": "Markdown will convert this for us. We match it so that the HTML grammar will not mark it up as invalid.", + "match": "&(?!([a-zA-Z0-9]+|#[0-9]+|#x[0-9a-fA-F]+);)", + "name": "meta.other.valid-ampersand.markdown" + }, + "bold": { + "begin": "(?x)\n (\\*\\*|__)(?=\\S)\t\t\t\t\t\t\t\t# Open\n (?=\n (\n <[^>]*+>\t\t\t\t\t\t\t# HTML tags\n | (?<raw>`+)([^`]|(?!(?<!`)\\k<raw>(?!`))`)*+\\k<raw>\n # Raw\n | \\\\[\\\\`*_{}\\[\\]()#.!+\\->]?+\t\t\t# Escapes\n | \\[\n (\n (?<square>\t\t\t\t\t# Named group\n [^\\[\\]\\\\]\t\t\t\t# Match most chars\n | \\\\.\t\t\t\t\t\t# Escaped chars\n | \\[ \\g<square>*+ \\]\t\t# Nested brackets\n )*+\n \\]\n (\n (\t\t\t\t\t\t\t# Reference Link\n [ ]?\t\t\t\t\t# Optional space\n \\[[^\\]]*+\\]\t\t\t\t# Ref name\n )\n | (\t\t\t\t\t\t\t# Inline Link\n \\(\t\t\t\t\t\t# Opening paren\n [ \\t]*+\t\t\t\t# Optional whitespace\n <?(.*?)>?\t\t\t# URL\n [ \\t]*+\t\t\t\t# Optional whitespace\n (\t\t\t\t\t# Optional Title\n (?<title>['\"])\n (.*?)\n \\k<title>\n )?\n \\)\n )\n )\n )\n | (?!(?<=\\S)\\1).\t\t\t\t\t\t# Everything besides\n # style closer\n )++\n (?<=\\S)\\1\t\t\t\t\t\t\t\t# Close\n )\n", + "captures": { + "1": { + "name": "punctuation.definition.bold.markdown" + } + }, + "end": "(?<=\\S)(\\1)", + "name": "markup.bold.markdown", + "patterns": [ + { + "applyEndPatternLast": 1, + "begin": "(?=<[^>]*?>)", + "end": "(?<=>)", + "patterns": [ + { + "include": "text.html.basic" + } + ] + }, + { + "include": "#escape" + }, + { + "include": "#ampersand" + }, + { + "include": "#bracket" + }, + { + "include": "#raw" + }, + { + "include": "#italic" + }, + { + "include": "#image-inline" + }, + { + "include": "#link-inline" + }, + { + "include": "#link-inet" + }, + { + "include": "#link-email" + }, + { + "include": "#image-ref" + }, + { + "include": "#link-ref-literal" + }, + { + "include": "#link-ref" + } + ] + }, + "bracket": { + "comment": "Markdown will convert this for us. We match it so that the HTML grammar will not mark it up as invalid.", + "match": "<(?![a-z/?\\$!])", + "name": "meta.other.valid-bracket.markdown" + }, + "escape": { + "match": "\\\\[-`*_#+.!(){}\\[\\]\\\\>]", + "name": "constant.character.escape.markdown" + }, + "image-inline": { + "captures": { + "1": { + "name": "punctuation.definition.string.begin.markdown" + }, + "2": { + "name": "string.other.link.description.markdown" + }, + "4": { + "name": "punctuation.definition.string.end.markdown" + }, + "5": { + "name": "punctuation.definition.metadata.markdown" + }, + "6": { + "name": "punctuation.definition.link.markdown" + }, + "7": { + "name": "markup.underline.link.image.markdown" + }, + "8": { + "name": "punctuation.definition.link.markdown" + }, + "9": { + "name": "string.other.link.description.title.markdown" + }, + "10": { + "name": "punctuation.definition.string.markdown" + }, + "11": { + "name": "punctuation.definition.string.markdown" + }, + "12": { + "name": "string.other.link.description.title.markdown" + }, + "13": { + "name": "punctuation.definition.string.markdown" + }, + "14": { + "name": "punctuation.definition.string.markdown" + }, + "15": { + "name": "punctuation.definition.metadata.markdown" + } + }, + "match": "(?x:\n (\\!\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])\n # Match the link text.\n (\\()\t\t\t\t\t\t# Opening paren for url\n (<?)(\\S+?)(>?)\t\t\t# The url\n [ \\t]*\t\t\t\t\t# Optional whitespace\n (?:\n ((\\().+?(\\)))\t\t# Match title in parens…\n | ((\").+?(\"))\t\t# or in quotes.\n )?\t\t\t\t\t\t# Title is optional\n \\s*\t\t\t\t\t\t# Optional whitespace\n (\\))\n)\n", + "name": "meta.image.inline.markdown" + }, + "image-ref": { + "captures": { + "1": { + "name": "punctuation.definition.string.begin.markdown" + }, + "2": { + "name": "string.other.link.description.markdown" + }, + "4": { + "name": "punctuation.definition.string.begin.markdown" + }, + "5": { + "name": "punctuation.definition.constant.markdown" + }, + "6": { + "name": "constant.other.reference.link.markdown" + }, + "7": { + "name": "punctuation.definition.constant.markdown" + } + }, + "match": "(\\!\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])[ ]?(\\[)(.*?)(\\])", + "name": "meta.image.reference.markdown" + }, + "italic": { + "begin": "(?x) (\\*\\b|\\b_)(?=\\S)\t\t\t\t\t\t\t\t# Open\n (?=\n (\n <[^>]*+>\t\t\t\t\t\t\t# HTML tags\n | (?<raw>`+)([^`]|(?!(?<!`)\\k<raw>(?!`))`)*+\\k<raw>\n # Raw\n | \\\\[\\\\`*_{}\\[\\]()#.!+\\->]?+\t\t\t# Escapes\n | \\[\n (\n (?<square>\t\t\t\t\t# Named group\n [^\\[\\]\\\\]\t\t\t\t# Match most chars\n | \\\\.\t\t\t\t\t\t# Escaped chars\n | \\[ \\g<square>*+ \\]\t\t# Nested brackets\n )*+\n \\]\n (\n (\t\t\t\t\t\t\t# Reference Link\n [ ]?\t\t\t\t\t# Optional space\n \\[[^\\]]*+\\]\t\t\t\t# Ref name\n )\n | (\t\t\t\t\t\t\t# Inline Link\n \\(\t\t\t\t\t\t# Opening paren\n [ \\t]*+\t\t\t\t# Optional whtiespace\n <?(.*?)>?\t\t\t# URL\n [ \\t]*+\t\t\t\t# Optional whtiespace\n (\t\t\t\t\t# Optional Title\n (?<title>['\"])\n (.*?)\n \\k<title>\n )?\n \\)\n )\n )\n )\n | \\1\\1\t\t\t\t\t\t\t\t# Must be bold closer\n | (?!(?<=\\S)\\1).\t\t\t\t\t\t# Everything besides\n # style closer\n )++\n (?<=\\S)\\1\t\t\t\t\t\t\t\t# Close\n )\n", + "captures": { + "1": { + "name": "punctuation.definition.italic.markdown" + } + }, + "end": "(?<=\\S)(\\1)((?!\\1)|(?=\\1\\1))", + "name": "markup.italic.markdown", + "patterns": [ + { + "applyEndPatternLast": 1, + "begin": "(?=<[^>]*?>)", + "end": "(?<=>)", + "patterns": [ + { + "include": "text.html.basic" + } + ] + }, + { + "include": "#escape" + }, + { + "include": "#ampersand" + }, + { + "include": "#bracket" + }, + { + "include": "#raw" + }, + { + "include": "#bold" + }, + { + "include": "#image-inline" + }, + { + "include": "#link-inline" + }, + { + "include": "#link-inet" + }, + { + "include": "#link-email" + }, + { + "include": "#image-ref" + }, + { + "include": "#link-ref-literal" + }, + { + "include": "#link-ref" + } + ] + }, + "link-email": { + "captures": { + "1": { + "name": "punctuation.definition.link.markdown" + }, + "2": { + "name": "markup.underline.link.markdown" + }, + "4": { + "name": "punctuation.definition.link.markdown" + } + }, + "match": "(<)((?:mailto:)?[-.\\w]+@[-a-z0-9]+(\\.[-a-z0-9]+)*\\.[a-z]+)(>)", + "name": "meta.link.email.lt-gt.markdown" + }, + "link-inet": { + "captures": { + "1": { + "name": "punctuation.definition.link.markdown" + }, + "2": { + "name": "markup.underline.link.markdown" + }, + "3": { + "name": "punctuation.definition.link.markdown" + } + }, + "match": "(<)((?:https?|ftp)://.*?)(>)", + "name": "meta.link.inet.markdown" + }, + "link-inline": { + "captures": { + "1": { + "name": "punctuation.definition.string.begin.markdown" + }, + "2": { + "name": "string.other.link.title.markdown" + }, + "4": { + "name": "punctuation.definition.string.end.markdown" + }, + "5": { + "name": "punctuation.definition.metadata.markdown" + }, + "6": { + "name": "punctuation.definition.link.markdown" + }, + "7": { + "name": "markup.underline.link.markdown" + }, + "8": { + "name": "punctuation.definition.link.markdown" + }, + "9": { + "name": "string.other.link.description.title.markdown" + }, + "10": { + "name": "punctuation.definition.string.begin.markdown" + }, + "11": { + "name": "punctuation.definition.string.end.markdown" + }, + "12": { + "name": "string.other.link.description.title.markdown" + }, + "13": { + "name": "punctuation.definition.string.begin.markdown" + }, + "14": { + "name": "punctuation.definition.string.end.markdown" + }, + "15": { + "name": "punctuation.definition.metadata.markdown" + } + }, + "match": "(?x:\n (\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])\n # Match the link text.\n (\\()\t\t\t\t\t\t# Opening paren for url\n (<?)(.*?)(>?)\t\t\t# The url\n [ \\t]*\t\t\t\t\t# Optional whitespace\n (?:\n ((\\().+?(\\)))\t\t# Match title in parens…\n | ((\").+?(\"))\t\t# or in quotes.\n )?\t\t\t\t\t\t# Title is optional\n \\s*\t\t\t\t\t\t# Optional whitespace\n (\\))\n )\n", + "name": "meta.link.inline.markdown" + }, + "link-ref": { + "captures": { + "1": { + "name": "punctuation.definition.string.begin.markdown" + }, + "2": { + "name": "string.other.link.title.markdown" + }, + "4": { + "name": "punctuation.definition.string.end.markdown" + }, + "5": { + "name": "punctuation.definition.constant.begin.markdown" + }, + "6": { + "name": "constant.other.reference.link.markdown" + }, + "7": { + "name": "punctuation.definition.constant.end.markdown" + } + }, + "match": "(\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])(\\[)([^\\]]*+)(\\])", + "name": "meta.link.reference.markdown" + }, + "link-ref-literal": { + "captures": { + "1": { + "name": "punctuation.definition.string.begin.markdown" + }, + "2": { + "name": "string.other.link.title.markdown" + }, + "4": { + "name": "punctuation.definition.string.end.markdown" + }, + "5": { + "name": "punctuation.definition.constant.begin.markdown" + }, + "6": { + "name": "punctuation.definition.constant.end.markdown" + } + }, + "match": "(\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])[ ]?(\\[)(\\])", + "name": "meta.link.reference.literal.markdown" + }, + "raw": { + "captures": { + "1": { + "name": "punctuation.definition.raw.markdown" + }, + "3": { + "name": "punctuation.definition.raw.markdown" + } + }, + "match": "(`+)([^`]|(?!(?<!`)\\1(?!`))`)*+(\\1)", + "name": "markup.inline.raw.string.markdown" } } } From 4d122017332a1ed12e083d79f85a054ac3f32fcf Mon Sep 17 00:00:00 2001 From: Matt Bierner <matb@microsoft.com> Date: Mon, 12 Feb 2018 18:06:17 -0800 Subject: [PATCH 040/362] Update markdown grammar --- extensions/markdown/syntaxes/markdown.tmLanguage.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/extensions/markdown/syntaxes/markdown.tmLanguage.json b/extensions/markdown/syntaxes/markdown.tmLanguage.json index 11022350c6e..bad82f2269a 100644 --- a/extensions/markdown/syntaxes/markdown.tmLanguage.json +++ b/extensions/markdown/syntaxes/markdown.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/d88bcfff33c41e4bd3628e2fa0746ded43cac8f6", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/73687e96144eff4db45a9a00655bfe87c73b023d", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -521,7 +521,7 @@ "include": "text.html.basic" }, { - "include": "text.html.php#language" + "include": "source.php" } ] } @@ -1964,6 +1964,9 @@ { "include": "#raw" }, + { + "include": "#bold" + }, { "include": "#italic" }, From d0a6369f3975f8928d6c41866aa1864e8dc25c3a Mon Sep 17 00:00:00 2001 From: Sriram Desikan <sriramdesikan205@gmail.com> Date: Tue, 13 Feb 2018 07:43:33 +0530 Subject: [PATCH 041/362] #42557 fix --- .../execution.contribution.ts | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts b/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts index 869a9d8c7c1..60435c80426 100644 --- a/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts +++ b/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts @@ -24,7 +24,7 @@ import { ResourceContextKey } from 'vs/workbench/common/resources'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IFileService } from 'vs/platform/files/common/files'; import { IListService } from 'vs/platform/list/browser/listService'; -import { getResourceForCommand } from 'vs/workbench/parts/files/browser/files'; +import { getMultiSelectedResources } from 'vs/workbench/parts/files/browser/files'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { Schemas } from 'vs/base/common/network'; @@ -85,20 +85,26 @@ CommandsRegistry.registerCommand({ const fileService = accessor.get(IFileService); const integratedTerminalService = accessor.get(IIntegratedTerminalService); const terminalService = accessor.get(ITerminalService); - resource = getResourceForCommand(resource, accessor.get(IListService), editorService); - - return fileService.resolveFile(resource).then(stat => { - return stat.isDirectory ? stat.resource.fsPath : paths.dirname(stat.resource.fsPath); - }).then(directoryToOpen => { - if (configurationService.getValue<ITerminalConfiguration>().terminal.explorerKind === 'integrated') { - const instance = integratedTerminalService.createInstance({ cwd: directoryToOpen }, true); - if (instance) { - integratedTerminalService.setActiveInstance(instance); - integratedTerminalService.showPanel(true); + let resources:uri[] = []; + let directoryMap:Map<string, boolean> = new Map(); + resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService); + return resources.map((resource) => { + return fileService.resolveFile(resource).then(stat => { + return stat.isDirectory ? stat.resource.fsPath : paths.dirname(stat.resource.fsPath); + }).then(directoryToOpen => { + if (!directoryMap.has(directoryToOpen)) { + directoryMap.set(directoryToOpen, true); + if (configurationService.getValue<ITerminalConfiguration>().terminal.explorerKind === 'integrated') { + const instance = integratedTerminalService.createInstance({ cwd: directoryToOpen }, true); + if (instance) { + integratedTerminalService.setActiveInstance(instance); + integratedTerminalService.showPanel(true); + } + } else { + terminalService.openTerminal(directoryToOpen); + } } - } else { - terminalService.openTerminal(directoryToOpen); - } + }); }); } }); From 00f2d8ed7196606d31cc1759a171486d1cc00deb Mon Sep 17 00:00:00 2001 From: Ramya Rao <ramya.rao.a@outlook.com> Date: Mon, 12 Feb 2018 21:40:10 -0800 Subject: [PATCH 042/362] Get emmet completions from css extension (#41652) * Get emmet completions from html,css extensions * Resolve extensionsPath, use emmet results even if empty * Support css abbr with : * Refactoring * Add some basic emmet tests * Refactoring * More tests --- extensions/css/client/src/cssMain.ts | 2 +- extensions/css/client/tsconfig.json | 4 +- extensions/css/package.json | 4 +- extensions/css/server/.vscode/launch.json | 1 + extensions/css/server/package.json | 7 +- extensions/css/server/src/cssServerMain.ts | 64 +++++++++++++++-- extensions/css/server/src/test/emmet.test.ts | 68 ++++++++++++++++++ extensions/css/server/test/mocha.opts | 3 + extensions/css/server/yarn.lock | 52 ++++++++++---- extensions/css/yarn.lock | 30 ++++---- extensions/emmet/package.json | 2 +- extensions/emmet/src/extension.ts | 3 +- extensions/emmet/src/util.ts | 12 +--- extensions/emmet/yarn.lock | 12 ++-- extensions/html/client/src/htmlMain.ts | 2 +- extensions/html/client/tsconfig.json | 4 +- extensions/html/package.json | 14 ++-- extensions/html/server/.vscode/launch.json | 1 + extensions/html/server/package.json | 6 +- extensions/html/server/src/htmlServerMain.ts | 66 ++++++++++++++--- extensions/html/server/src/modes/cssMode.ts | 5 +- extensions/html/server/src/modes/htmlMode.ts | 21 +++++- .../html/server/src/modes/languageModes.ts | 2 + extensions/html/server/src/test/emmet.test.ts | 70 +++++++++++++++++++ extensions/html/server/yarn.lock | 46 ++++++++---- extensions/html/yarn.lock | 30 ++++---- 26 files changed, 415 insertions(+), 116 deletions(-) create mode 100644 extensions/css/server/src/test/emmet.test.ts create mode 100644 extensions/css/server/test/mocha.opts create mode 100644 extensions/html/server/src/test/emmet.test.ts diff --git a/extensions/css/client/src/cssMain.ts b/extensions/css/client/src/cssMain.ts index 930deba4a6e..5cc90fff537 100644 --- a/extensions/css/client/src/cssMain.ts +++ b/extensions/css/client/src/cssMain.ts @@ -35,7 +35,7 @@ export function activate(context: ExtensionContext) { let clientOptions: LanguageClientOptions = { documentSelector, synchronize: { - configurationSection: ['css', 'scss', 'less'] + configurationSection: ['css', 'scss', 'less', 'emmet'] }, initializationOptions: { } diff --git a/extensions/css/client/tsconfig.json b/extensions/css/client/tsconfig.json index 7f8b647d04b..ee67f8333d4 100644 --- a/extensions/css/client/tsconfig.json +++ b/extensions/css/client/tsconfig.json @@ -1,11 +1,11 @@ { "compilerOptions": { - "target": "es5", + "target": "es6", "module": "commonjs", "outDir": "./out", "noUnusedLocals": true, "lib": [ - "es5", "es2015.promise" + "es2016" ], "strict": true }, diff --git a/extensions/css/package.json b/extensions/css/package.json index 8893cc34b93..b4f94d937fd 100644 --- a/extensions/css/package.json +++ b/extensions/css/package.json @@ -713,10 +713,10 @@ ] }, "dependencies": { - "vscode-languageclient": "^3.5.0", + "vscode-languageclient": "^4.0.0-next.8", "vscode-nls": "^3.2.1" }, "devDependencies": { "@types/node": "7.0.43" } -} \ No newline at end of file +} diff --git a/extensions/css/server/.vscode/launch.json b/extensions/css/server/.vscode/launch.json index 68a18d7bb9e..624ac31332f 100644 --- a/extensions/css/server/.vscode/launch.json +++ b/extensions/css/server/.vscode/launch.json @@ -7,6 +7,7 @@ "type": "node", "request": "attach", "port": 6044, + "protocol": "inspector", "sourceMaps": true, "outFiles": ["${workspaceFolder}/out/**/*.js"] }, diff --git a/extensions/css/server/package.json b/extensions/css/server/package.json index bd00c7a2c6d..d789802f6ff 100644 --- a/extensions/css/server/package.json +++ b/extensions/css/server/package.json @@ -9,9 +9,11 @@ }, "dependencies": { "vscode-css-languageservice": "^3.0.5", - "vscode-languageserver": "^3.5.0" + "vscode-languageserver": "^4.0.0-next.4", + "vscode-emmet-helper": "1.1.34" }, "devDependencies": { + "@types/mocha": "2.2.33", "@types/node": "7.0.43" }, "scripts": { @@ -20,6 +22,7 @@ "install-service-next": "yarn add vscode-css-languageservice@next", "install-service-local": "npm install ../../../../vscode-css-languageservice -f", "install-server-next": "yarn add vscode-languageserver@next", - "install-server-local": "npm install ../../../../vscode-languageserver-node/server -f" + "install-server-local": "npm install ../../../../vscode-languageserver-node/server -f", + "test": "../../../node_modules/.bin/mocha" } } diff --git a/extensions/css/server/src/cssServerMain.ts b/extensions/css/server/src/cssServerMain.ts index c388bebe832..d70ae5011d9 100644 --- a/extensions/css/server/src/cssServerMain.ts +++ b/extensions/css/server/src/cssServerMain.ts @@ -5,22 +5,26 @@ 'use strict'; import { - createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities + createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, CompletionTriggerKind } from 'vscode-languageserver'; -import { TextDocument } from 'vscode-languageserver-types'; +import { TextDocument, CompletionList } from 'vscode-languageserver-types'; import { ConfigurationRequest } from 'vscode-languageserver-protocol/lib/protocol.configuration.proposed'; +import { WorkspaceFolder } from 'vscode-languageserver-protocol/lib/protocol.workspaceFolders.proposed'; import { DocumentColorRequest, ServerCapabilities as CPServerCapabilities, ColorPresentationRequest } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed'; -import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet } from 'vscode-css-languageservice'; +import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet, ICompletionParticipant } from 'vscode-css-languageservice'; import { getLanguageModelCache } from './languageModelCache'; import { formatError, runSafe } from './utils/errors'; +import { doComplete as emmetDoComplete, updateExtensionsPath as updateEmmetExtensionsPath, getEmmetCompletionParticipants } from 'vscode-emmet-helper'; +import uri from 'vscode-uri'; export interface Settings { css: LanguageSettings; less: LanguageSettings; scss: LanguageSettings; + emmet: { [key: string]: any }; } // Create a connection for the server. @@ -49,9 +53,22 @@ connection.onShutdown(() => { }); let scopedSettingsSupport = false; +let workspaceFolders: WorkspaceFolder[] | undefined; +let emmetSettings = {}; +let currentEmmetExtensionsPath: string; +const emmetTriggerCharacters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + // After the server has started the client sends an initilize request. The server receives // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { + workspaceFolders = (<any>params).workspaceFolders; + if (!Array.isArray(workspaceFolders)) { + workspaceFolders = []; + if (params.rootPath) { + workspaceFolders.push({ name: '', uri: uri.file(params.rootPath).toString() }); + } + } + function hasClientCapability(name: string) { let keys = name.split('.'); let c: any = params.capabilities; @@ -65,7 +82,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { let capabilities: ServerCapabilities & CPServerCapabilities = { // Tell the client that the server works in FULL text document sync mode textDocumentSync: documents.syncKind, - completionProvider: snippetSupport ? { resolveProvider: false } : undefined, + completionProvider: snippetSupport ? { resolveProvider: false, triggerCharacters: emmetTriggerCharacters } : undefined, hoverProvider: true, documentSymbolProvider: true, referencesProvider: true, @@ -124,6 +141,13 @@ function updateConfiguration(settings: Settings) { documentSettings = {}; // Revalidate any open text documents documents.all().forEach(triggerValidation); + + emmetSettings = settings.emmet; + if (currentEmmetExtensionsPath !== emmetSettings['extensionsPath']) { + currentEmmetExtensionsPath = emmetSettings['extensionsPath']; + const workspaceUri = (workspaceFolders && workspaceFolders.length === 1) ? uri.parse(workspaceFolders[0].uri) : null; + updateEmmetExtensionsPath(currentEmmetExtensionsPath, workspaceUri ? workspaceUri.fsPath : null); + } } let pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {}; @@ -169,11 +193,39 @@ function validateTextDocument(textDocument: TextDocument): void { }); } +let cachedCompletionList: CompletionList; connection.onCompletion(textDocumentPosition => { return runSafe(() => { let document = documents.get(textDocumentPosition.textDocument.uri); - let stylesheet = stylesheets.get(document); - return getLanguageService(document).doComplete(document, textDocumentPosition.position, stylesheet)!; /* TODO: remove ! once LS has null annotations */ + if (cachedCompletionList + && !cachedCompletionList.isIncomplete + && textDocumentPosition.context + && textDocumentPosition.context.triggerKind === CompletionTriggerKind.TriggerForIncompleteCompletions + ) { + let result: CompletionList = emmetDoComplete(document, textDocumentPosition.position, document.languageId, emmetSettings); + if (result && result.items) { + result.items.push(...cachedCompletionList.items); + } else { + result = cachedCompletionList; + cachedCompletionList = null; + } + return result; + } + + cachedCompletionList = null; + let emmetCompletionList: CompletionList = { + isIncomplete: true, + items: undefined + }; + const emmetCompletionParticipant: ICompletionParticipant = getEmmetCompletionParticipants(document, textDocumentPosition.position, document.languageId, emmetSettings, emmetCompletionList); + getLanguageService(document).setCompletionParticipants([emmetCompletionParticipant]); + + const result = getLanguageService(document).doComplete(document, textDocumentPosition.position, stylesheets.get(document))!; /* TODO: remove ! once LS has null annotations */ + if (emmetCompletionList && emmetCompletionList.items) { + cachedCompletionList = result; + return { isIncomplete: true, items: [...emmetCompletionList.items, ...result.items] }; + } + return result; }, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`); }); diff --git a/extensions/css/server/src/test/emmet.test.ts b/extensions/css/server/src/test/emmet.test.ts new file mode 100644 index 00000000000..ef8441153d7 --- /dev/null +++ b/extensions/css/server/src/test/emmet.test.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * 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 'mocha'; +import * as assert from 'assert'; +import { getCSSLanguageService, getSCSSLanguageService } from 'vscode-css-languageservice/lib/cssLanguageService'; +import { TextDocument, CompletionList } from 'vscode-languageserver-types'; +import { getEmmetCompletionParticipants } from 'vscode-emmet-helper'; + +suite('Emmet Support', () => { + + const cssLanguageService = getCSSLanguageService(); + const scssLanguageService = getSCSSLanguageService(); + + function assertCompletions(syntax: string, value: string, expectedProposal: string, expectedProposalDoc: string): void { + const offset = value.indexOf('|'); + value = value.substr(0, offset) + value.substr(offset + 1); + + const document = TextDocument.create('test://test/test.' + syntax, syntax, 0, value); + const position = document.positionAt(offset); + const emmetCompletionList: CompletionList = { + isIncomplete: true, + items: undefined + } + const languageService = syntax === 'scss' ? scssLanguageService : cssLanguageService; + languageService.setCompletionParticipants([getEmmetCompletionParticipants(document, position, document.languageId, {}, emmetCompletionList)]) + const stylesheet = languageService.parseStylesheet(document); + const list = languageService.doComplete!(document, position, stylesheet); + + assert.ok(list); + assert.ok(emmetCompletionList) + + if (expectedProposal && expectedProposalDoc) { + let actualLabels = emmetCompletionList!.items.map(c => c.label).sort(); + let actualDocs = emmetCompletionList!.items.map(c => c.documentation).sort(); + assert.ok(actualLabels.indexOf(expectedProposal) !== -1, 'Not found:' + expectedProposal + ' is ' + actualLabels.join(', ')); + assert.ok(actualDocs.indexOf(expectedProposalDoc) !== -1, 'Not found:' + expectedProposalDoc + ' is ' + actualDocs.join(', ')); + } else { + assert.ok(!emmetCompletionList || !emmetCompletionList.items); + } + } + + test('Css Emmet Completions', function (): any { + assertCompletions('css', '.foo { display: none; m10| }', 'margin: 10px;', 'margin: 10px;'); + assertCompletions('css', 'foo { display: none; pos:f| }', 'position: fixed;', 'position: fixed;'); + assertCompletions('css', 'foo { display: none; margin: a| }', null, null); + assertCompletions('css', 'foo| { display: none; }', null, null); + assertCompletions('css', 'foo {| display: none; }', null, null); + assertCompletions('css', 'foo { display: none;| }', null, null); + assertCompletions('css', 'foo { display: none|; }', null, null); + assertCompletions('css', '.foo { display: none; -m-m10| }', 'margin: 10px;', '-moz-margin: 10px;\nmargin: 10px;'); + }); + + test('Scss Emmet Completions', function (): any { + assertCompletions('scss', '.foo { display: none; .bar { m10| } }', 'margin: 10px;', 'margin: 10px;'); + assertCompletions('scss', 'foo { display: none; .bar { pos:f| } }', 'position: fixed;', 'position: fixed;'); + assertCompletions('scss', 'foo { display: none; margin: a| .bar {}}', null, null); + assertCompletions('scss', 'foo| { display: none; }', null, null); + assertCompletions('scss', 'foo {| display: none; }', null, null); + assertCompletions('scss', 'foo { display: none;| }', null, null); + assertCompletions('scss', 'foo { display: none|; }', null, null); + assertCompletions('scss', '.foo { display: none; -m-m10| }', 'margin: 10px;', '-moz-margin: 10px;\nmargin: 10px;'); + }); + +}); \ No newline at end of file diff --git a/extensions/css/server/test/mocha.opts b/extensions/css/server/test/mocha.opts new file mode 100644 index 00000000000..97e8b723ae2 --- /dev/null +++ b/extensions/css/server/test/mocha.opts @@ -0,0 +1,3 @@ +--ui tdd +--useColors true +./out/test \ No newline at end of file diff --git a/extensions/css/server/yarn.lock b/extensions/css/server/yarn.lock index 5e8284700f9..6c2bc300879 100644 --- a/extensions/css/server/yarn.lock +++ b/extensions/css/server/yarn.lock @@ -2,10 +2,22 @@ # yarn lockfile v1 +"@emmetio/extract-abbreviation@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@emmetio/extract-abbreviation/-/extract-abbreviation-0.1.4.tgz#f5be070db97901ecc37e5204f2ace68242cdcefa" + +"@types/mocha@2.2.33": + version "2.2.33" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.33.tgz#d79a0061ec270379f4d9e225f4096fb436669def" + "@types/node@7.0.43": version "7.0.43" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c" +jsonc-parser@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.0.tgz#ddcc864ae708e60a7a6dd36daea00172fa8d9272" + vscode-css-languageservice@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.5.tgz#8470989c07bbe740db4fa621423e98384d2c342f" @@ -13,26 +25,38 @@ vscode-css-languageservice@^3.0.5: vscode-languageserver-types "3.5.0" vscode-nls "^2.0.1" -vscode-jsonrpc@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa" - -vscode-languageserver-protocol@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.0.tgz#067c5cbe27709795398d119692c97ebba1452209" +vscode-emmet-helper@1.1.34: + version "1.1.34" + resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-1.1.34.tgz#1e2d3e26c389efc6f73ceb4c0ed797ae07291874" dependencies: - vscode-jsonrpc "^3.5.0" - vscode-languageserver-types "^3.5.0" + "@emmetio/extract-abbreviation" "^0.1.4" + jsonc-parser "^1.0.0" + vscode-languageserver-types "^3.6.0-next.1" -vscode-languageserver-types@3.5.0, vscode-languageserver-types@^3.5.0: +vscode-jsonrpc@^3.6.0-next.1: + version "3.6.0-next.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0-next.1.tgz#3cb463dffe5842d6aec16718ca9252708cd6aabe" + +vscode-languageserver-protocol@^3.6.0-next.5: + version "3.6.0-next.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.6.0-next.5.tgz#ed2ec2db759826f753c0a13977dfb2bedc4d31b3" + dependencies: + vscode-jsonrpc "^3.6.0-next.1" + vscode-languageserver-types "^3.6.0-next.1" + +vscode-languageserver-types@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374" -vscode-languageserver@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-3.5.0.tgz#d28099bc6ddda8c1dd16b707e454e1b1ddae0dba" +vscode-languageserver-types@^3.6.0-next.1: + version "3.6.0-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0-next.1.tgz#98e488d3f87b666b4ee1a3d89f0023e246d358f3" + +vscode-languageserver@^4.0.0-next.4: + version "4.0.0-next.4" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-4.0.0-next.4.tgz#162440b15bedaab07e1676f046e4d9b8578b3d92" dependencies: - vscode-languageserver-protocol "^3.5.0" + vscode-languageserver-protocol "^3.6.0-next.5" vscode-uri "^1.0.1" vscode-nls@^2.0.1: diff --git a/extensions/css/yarn.lock b/extensions/css/yarn.lock index d7ec451f0e1..6e06462fdb0 100644 --- a/extensions/css/yarn.lock +++ b/extensions/css/yarn.lock @@ -6,26 +6,26 @@ version "7.0.43" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c" -vscode-jsonrpc@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa" +vscode-jsonrpc@^3.6.0-next.1: + version "3.6.0-next.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0-next.1.tgz#3cb463dffe5842d6aec16718ca9252708cd6aabe" -vscode-languageclient@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.0.tgz#36d02cc186a8365a4467719a290fb200a9ae490a" +vscode-languageclient@^4.0.0-next.8: + version "4.0.0-next.8" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-4.0.0-next.8.tgz#eb8eef0bf08399924f8fa520cb0b37071086e7c0" dependencies: - vscode-languageserver-protocol "^3.5.0" + vscode-languageserver-protocol "^3.6.0-next.5" -vscode-languageserver-protocol@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.0.tgz#067c5cbe27709795398d119692c97ebba1452209" +vscode-languageserver-protocol@^3.6.0-next.5: + version "3.6.0-next.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.6.0-next.5.tgz#ed2ec2db759826f753c0a13977dfb2bedc4d31b3" dependencies: - vscode-jsonrpc "^3.5.0" - vscode-languageserver-types "^3.5.0" + vscode-jsonrpc "^3.6.0-next.1" + vscode-languageserver-types "^3.6.0-next.1" -vscode-languageserver-types@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374" +vscode-languageserver-types@^3.6.0-next.1: + version "3.6.0-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0-next.1.tgz#98e488d3f87b666b4ee1a3d89f0023e246d358f3" vscode-nls@^3.2.1: version "3.2.1" diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index 61134ba415a..c63f9791858 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -337,7 +337,7 @@ "@emmetio/html-matcher": "^0.3.3", "@emmetio/css-parser": "ramya-rao-a/css-parser#vscode", "@emmetio/math-expression": "^0.1.1", - "vscode-emmet-helper": "^1.1.25", + "vscode-emmet-helper": "^1.1.32", "vscode-languageserver-types": "^3.5.0", "image-size": "^0.5.2", "vscode-nls": "3.2.1" diff --git a/extensions/emmet/src/extension.ts b/extensions/emmet/src/extension.ts index 3079a1764fe..0f7364a7de8 100644 --- a/extensions/emmet/src/extension.ts +++ b/extensions/emmet/src/extension.ts @@ -142,6 +142,7 @@ export function activate(context: vscode.ExtensionContext) { */ const languageMappingForCompletionProviders: Map<string, string> = new Map<string, string>(); const completionProvidersMapping: Map<string, vscode.Disposable> = new Map<string, vscode.Disposable>(); +const languagesToSkipCompletionProviders = ['html', 'css', 'scss', 'less']; function registerCompletionProviders(context: vscode.ExtensionContext) { let completionProvider = new DefaultCompletionItemProvider(); @@ -169,7 +170,7 @@ function registerCompletionProviders(context: vscode.ExtensionContext) { }); Object.keys(LANGUAGE_MODES).forEach(language => { - if (!languageMappingForCompletionProviders.has(language)) { + if (languagesToSkipCompletionProviders.indexOf(language) === -1 && !languageMappingForCompletionProviders.has(language)) { const provider = vscode.languages.registerCompletionItemProvider(language, completionProvider, ...LANGUAGE_MODES[language]); context.subscriptions.push(provider); diff --git a/extensions/emmet/src/util.ts b/extensions/emmet/src/util.ts index fde2c573dd3..5ed8c9e55f6 100644 --- a/extensions/emmet/src/util.ts +++ b/extensions/emmet/src/util.ts @@ -8,7 +8,6 @@ import parse from '@emmetio/html-matcher'; import parseStylesheet from '@emmetio/css-parser'; import { Node, HtmlNode, CssToken, Property, Rule } from 'EmmetNode'; import { DocumentStreamReader } from './bufferStream'; -import * as path from 'path'; let _emmetHelper: any; let _currentExtensionsPath: string | undefined = undefined; @@ -26,17 +25,9 @@ export function resolveUpdateExtensionsPath() { return; } let extensionsPath = vscode.workspace.getConfiguration('emmet')['extensionsPath']; - if (extensionsPath && !path.isAbsolute(extensionsPath)) { - extensionsPath = path.join(vscode.workspace.rootPath || '', extensionsPath); - } if (_currentExtensionsPath !== extensionsPath) { _currentExtensionsPath = extensionsPath; - if (_currentExtensionsPath && !path.isAbsolute(_currentExtensionsPath)) { - vscode.window.showErrorMessage('The path provided in emmet.extensionsPath setting should be absolute path'); - _emmetHelper.updateExtensionsPath(); - return; - } - _emmetHelper.updateExtensionsPath(_currentExtensionsPath).then(null, (err: string) => vscode.window.showErrorMessage(err)); + _emmetHelper.updateExtensionsPath(extensionsPath, vscode.workspace.rootPath).then(null, (err: string) => vscode.window.showErrorMessage(err)); } } @@ -63,7 +54,6 @@ const emmetModes = ['html', 'pug', 'slim', 'haml', 'xml', 'xsl', 'jsx', 'css', ' // For other languages, users will have to use `emmet.includeLanguages` or // language specific extensions can provide emmet completion support export const MAPPED_MODES: Object = { - 'handlebars': 'html', 'php': 'html' }; diff --git a/extensions/emmet/yarn.lock b/extensions/emmet/yarn.lock index 9162b566dc1..b4cc87e65e9 100644 --- a/extensions/emmet/yarn.lock +++ b/extensions/emmet/yarn.lock @@ -2052,17 +2052,13 @@ vinyl@~2.0.1: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -vscode-emmet-helper@^1.1.25: - version "1.1.25" - resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-1.1.25.tgz#2c43df3e8502d420fb91032826fc0cb6efee5611" +vscode-emmet-helper@^1.1.32: + version "1.1.32" + resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-1.1.32.tgz#5a67c6aa34255129d8b6564f5182f016a2b5b0da" dependencies: "@emmetio/extract-abbreviation" "^0.1.4" jsonc-parser "^1.0.0" - vscode-languageserver-types "^3.0.3" - -vscode-languageserver-types@^3.0.3: - version "3.3.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.3.0.tgz#8964dc7c2247536fbefd2d6836bf3febac80dd00" + vscode-languageserver-types "^3.5.0" vscode-languageserver-types@^3.5.0: version "3.5.0" diff --git a/extensions/html/client/src/htmlMain.ts b/extensions/html/client/src/htmlMain.ts index 6bcce08f9d6..107ac7ce11d 100644 --- a/extensions/html/client/src/htmlMain.ts +++ b/extensions/html/client/src/htmlMain.ts @@ -53,7 +53,7 @@ export function activate(context: ExtensionContext) { let clientOptions: LanguageClientOptions = { documentSelector, synchronize: { - configurationSection: ['html', 'css', 'javascript'], // the settings to synchronize + configurationSection: ['html', 'css', 'javascript', 'emmet'], // the settings to synchronize }, initializationOptions: { embeddedLanguages diff --git a/extensions/html/client/tsconfig.json b/extensions/html/client/tsconfig.json index e8d14302454..28f991bea7b 100644 --- a/extensions/html/client/tsconfig.json +++ b/extensions/html/client/tsconfig.json @@ -1,11 +1,11 @@ { "compilerOptions": { - "target": "es5", + "target": "es6", "module": "commonjs", "outDir": "./out", "noUnusedLocals": true, "lib": [ - "es5", "es2015.promise" + "es2016" ], "strict": true } diff --git a/extensions/html/package.json b/extensions/html/package.json index 21fc673bf67..f3da0649944 100644 --- a/extensions/html/package.json +++ b/extensions/html/package.json @@ -73,10 +73,12 @@ "end": "^\\s*<!--\\s*#endregion\\b\\s*(.*?)-->/" } }, - "snippets": [{ - "language": "html", - "path": "./snippets/html.snippets.json" - }], + "snippets": [ + { + "language": "html", + "path": "./snippets/html.snippets.json" + } + ], "configuration": { "id": "html", "order": 20, @@ -225,10 +227,10 @@ }, "dependencies": { "vscode-extension-telemetry": "0.0.11", - "vscode-languageclient": "^3.5.0", + "vscode-languageclient": "^4.0.0-next.8", "vscode-nls": "^3.2.1" }, "devDependencies": { "@types/node": "7.0.43" } -} \ No newline at end of file +} diff --git a/extensions/html/server/.vscode/launch.json b/extensions/html/server/.vscode/launch.json index 8ca29fac876..b22e91c41ef 100644 --- a/extensions/html/server/.vscode/launch.json +++ b/extensions/html/server/.vscode/launch.json @@ -7,6 +7,7 @@ "type": "node", "request": "attach", "port": 6045, + "protocol": "inspector", "sourceMaps": true, "outFiles": ["${workspaceFolder}/out/**/*.js"] }, diff --git a/extensions/html/server/package.json b/extensions/html/server/package.json index 1afa6e9672d..db666179d8f 100644 --- a/extensions/html/server/package.json +++ b/extensions/html/server/package.json @@ -10,9 +10,11 @@ "dependencies": { "vscode-css-languageservice": "^3.0.5", "vscode-html-languageservice": "^2.0.15", - "vscode-languageserver": "^3.5.0", + "vscode-languageserver": "^4.0.0-next.4", + "vscode-languageserver-types": "^3.6.0-next.1", "vscode-nls": "^3.2.1", - "vscode-uri": "^1.0.1" + "vscode-uri": "^1.0.1", + "vscode-emmet-helper": "1.1.34" }, "devDependencies": { "@types/mocha": "2.2.33", diff --git a/extensions/html/server/src/htmlServerMain.ts b/extensions/html/server/src/htmlServerMain.ts index cf03f00d561..274a5d126bf 100644 --- a/extensions/html/server/src/htmlServerMain.ts +++ b/extensions/html/server/src/htmlServerMain.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType, DocumentRangeFormattingRequest, Disposable, DocumentSelector, TextDocumentPositionParams, ServerCapabilities, Position } from 'vscode-languageserver'; -import { TextDocument, Diagnostic, DocumentLink, SymbolInformation } from 'vscode-languageserver-types'; +import { createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType, DocumentRangeFormattingRequest, Disposable, DocumentSelector, TextDocumentPositionParams, ServerCapabilities, Position, CompletionTriggerKind } from 'vscode-languageserver'; +import { TextDocument, Diagnostic, DocumentLink, SymbolInformation, CompletionList } from 'vscode-languageserver-types'; import { getLanguageModes, LanguageModes, Settings } from './modes/languageModes'; import { ConfigurationRequest, ConfigurationParams } from 'vscode-languageserver-protocol/lib/protocol.configuration.proposed'; @@ -17,6 +17,7 @@ import { pushAll } from './utils/arrays'; import { getDocumentContext } from './utils/documentContext'; import uri from 'vscode-uri'; import { formatError, runSafe } from './utils/errors'; +import { doComplete as emmetDoComplete, updateExtensionsPath as updateEmmetExtensionsPath, getEmmetCompletionParticipants } from 'vscode-emmet-helper'; namespace TagCloseRequest { export const type: RequestType<TextDocumentPositionParams, string | null, any, any> = new RequestType('html/tag'); @@ -69,6 +70,10 @@ function getDocumentSettings(textDocument: TextDocument, needsDocumentSettings: return Promise.resolve(void 0); } +let emmetSettings = {}; +let currentEmmetExtensionsPath: string; +const emmetTriggerCharacters = ['!', '.', '}', ':', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + // After the server has started the client sends an initilize request. The server receives // in the passed params the rootPath of the workspace plus the client capabilites connection.onInitialize((params: InitializeParams): InitializeResult => { @@ -105,7 +110,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { let capabilities: ServerCapabilities & CPServerCapabilities = { // Tell the client that the server works in FULL text document sync mode textDocumentSync: documents.syncKind, - completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['.', ':', '<', '"', '=', '/'] } : undefined, + completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: [...emmetTriggerCharacters, '.', ':', '<', '"', '=', '/'] } : undefined, hoverProvider: true, documentHighlightProvider: true, documentRangeFormattingProvider: false, @@ -167,6 +172,12 @@ connection.onDidChangeConfiguration((change) => { } } + emmetSettings = globalSettings.emmet; + if (currentEmmetExtensionsPath !== emmetSettings['extensionsPath']) { + currentEmmetExtensionsPath = emmetSettings['extensionsPath']; + const workspaceUri = (workspaceFolders && workspaceFolders.length === 1) ? uri.parse(workspaceFolders[0].uri) : null; + updateEmmetExtensionsPath(currentEmmetExtensionsPath, workspaceUri ? workspaceUri.fsPath : null); + } }); let pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {}; @@ -226,19 +237,52 @@ async function validateTextDocument(textDocument: TextDocument) { } } +let cachedCompletionList: CompletionList; connection.onCompletion(async textDocumentPosition => { return runSafe(async () => { let document = documents.get(textDocumentPosition.textDocument.uri); let mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); - if (mode && mode.doComplete) { - let doComplete = mode.doComplete; - if (mode.getId() !== 'html') { - connection.telemetry.logEvent({ key: 'html.embbedded.complete', value: { languageId: mode.getId() } }); - } - let settings = await getDocumentSettings(document, () => doComplete.length > 2); - return doComplete(document, textDocumentPosition.position, settings); + if (!mode || !mode.doComplete) { + return { isIncomplete: true, items: [] }; } - return { isIncomplete: true, items: [] }; + + if (cachedCompletionList + && !cachedCompletionList.isIncomplete + && (mode.getId() === 'html' || mode.getId() === 'css') + && textDocumentPosition.context + && textDocumentPosition.context.triggerKind === CompletionTriggerKind.TriggerForIncompleteCompletions + ) { + let result: CompletionList = emmetDoComplete(document, textDocumentPosition.position, mode.getId(), emmetSettings); + if (result && result.items) { + result.items.push(...cachedCompletionList.items); + } else { + result = cachedCompletionList; + cachedCompletionList = null; + } + return result; + } + + if (mode.getId() !== 'html') { + connection.telemetry.logEvent({ key: 'html.embbedded.complete', value: { languageId: mode.getId() } }); + } + + cachedCompletionList = null; + let emmetCompletionList: CompletionList = { + isIncomplete: true, + items: undefined + }; + if (mode.setCompletionParticipants) { + const emmetCompletionParticipant = getEmmetCompletionParticipants(document, textDocumentPosition.position, mode.getId(), emmetSettings, emmetCompletionList); + mode.setCompletionParticipants([emmetCompletionParticipant]); + } + + let settings = await getDocumentSettings(document, () => mode.doComplete.length > 2); + let result = mode.doComplete(document, textDocumentPosition.position, settings); + if (emmetCompletionList && emmetCompletionList.items) { + cachedCompletionList = result; + return { isIncomplete: true, items: [...emmetCompletionList.items, ...result.items] }; + } + return result; }, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`); }); diff --git a/extensions/html/server/src/modes/cssMode.ts b/extensions/html/server/src/modes/cssMode.ts index 79565426a32..10d1a48646d 100644 --- a/extensions/html/server/src/modes/cssMode.ts +++ b/extensions/html/server/src/modes/cssMode.ts @@ -6,7 +6,7 @@ import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache'; import { TextDocument, Position, Range } from 'vscode-languageserver-types'; -import { getCSSLanguageService, Stylesheet } from 'vscode-css-languageservice'; +import { getCSSLanguageService, Stylesheet, ICompletionParticipant } from 'vscode-css-languageservice'; import { LanguageMode, Settings } from './languageModes'; import { HTMLDocumentRegions, CSS_STYLE_RULE } from './embeddedSupport'; import { Color } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed'; @@ -31,6 +31,9 @@ export function getCSSMode(documentRegions: LanguageModelCache<HTMLDocumentRegio let embedded = embeddedCSSDocuments.get(document); return cssLanguageService.doComplete(embedded, position, cssStylesheets.get(embedded)); }, + setCompletionParticipants(registeredCompletionParticipants: ICompletionParticipant[]) { + cssLanguageService.setCompletionParticipants(registeredCompletionParticipants); + }, doHover(document: TextDocument, position: Position) { let embedded = embeddedCSSDocuments.get(document); return cssLanguageService.doHover(embedded, position, cssStylesheets.get(embedded)); diff --git a/extensions/html/server/src/modes/htmlMode.ts b/extensions/html/server/src/modes/htmlMode.ts index c0146423d67..24dcd14d49c 100644 --- a/extensions/html/server/src/modes/htmlMode.ts +++ b/extensions/html/server/src/modes/htmlMode.ts @@ -5,13 +5,14 @@ 'use strict'; import { getLanguageModelCache } from '../languageModelCache'; -import { LanguageService as HTMLLanguageService, HTMLDocument, DocumentContext, FormattingOptions, HTMLFormatConfiguration } from 'vscode-html-languageservice'; +import { LanguageService as HTMLLanguageService, HTMLDocument, DocumentContext, FormattingOptions, HTMLFormatConfiguration, TokenType } from 'vscode-html-languageservice'; import { TextDocument, Position, Range } from 'vscode-languageserver-types'; import { LanguageMode, Settings } from './languageModes'; export function getHTMLMode(htmlLanguageService: HTMLLanguageService): LanguageMode { let globalSettings: Settings = {}; let htmlDocuments = getLanguageModelCache<HTMLDocument>(10, 60, document => htmlLanguageService.parseHTMLDocument(document)); + let completionParticipants = []; return { getId() { return 'html'; @@ -25,7 +26,23 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService): LanguageM if (doAutoComplete) { options.hideAutoCompleteProposals = true; } - return htmlLanguageService.doComplete(document, position, htmlDocuments.get(document), options); + + const htmlDocument = htmlDocuments.get(document); + const offset = document.offsetAt(position); + const node = htmlDocument.findNodeBefore(offset); + const scanner = htmlLanguageService.createScanner(document.getText(), node.start); + let token = scanner.scan(); + while (token !== TokenType.EOS && scanner.getTokenOffset() <= offset) { + if (token === TokenType.Content && offset <= scanner.getTokenEnd()) { + completionParticipants.forEach(participant => { if (participant.onHtmlContent) { participant.onHtmlContent(); } }); + break; + } + token = scanner.scan(); + } + return htmlLanguageService.doComplete(document, position, htmlDocument, options); + }, + setCompletionParticipants(registeredCompletionParticipants: any[]) { + completionParticipants = registeredCompletionParticipants; }, doHover(document: TextDocument, position: Position) { return htmlLanguageService.doHover(document, position, htmlDocuments.get(document)); diff --git a/extensions/html/server/src/modes/languageModes.ts b/extensions/html/server/src/modes/languageModes.ts index d1a461a1648..721ee9a4e65 100644 --- a/extensions/html/server/src/modes/languageModes.ts +++ b/extensions/html/server/src/modes/languageModes.ts @@ -24,6 +24,7 @@ export interface Settings { css?: any; html?: any; javascript?: any; + emmet?: { [key: string]: any }; } export interface SettingProvider { @@ -35,6 +36,7 @@ export interface LanguageMode { configure?: (options: Settings) => void; doValidation?: (document: TextDocument, settings?: Settings) => Diagnostic[]; doComplete?: (document: TextDocument, position: Position, settings?: Settings) => CompletionList | null; + setCompletionParticipants?: (registeredCompletionParticipants: any[]) => void; doResolve?: (document: TextDocument, item: CompletionItem) => CompletionItem | null; doHover?: (document: TextDocument, position: Position) => Hover | null; doSignatureHelp?: (document: TextDocument, position: Position) => SignatureHelp | null; diff --git a/extensions/html/server/src/test/emmet.test.ts b/extensions/html/server/src/test/emmet.test.ts new file mode 100644 index 00000000000..3f0b9f8407b --- /dev/null +++ b/extensions/html/server/src/test/emmet.test.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import 'mocha'; +import * as assert from 'assert'; +import { getHTMLMode } from '../modes/htmlMode'; +import { TextDocument, CompletionList } from 'vscode-languageserver-types'; +import { getLanguageModelCache } from '../languageModelCache'; + +import { getLanguageService } from 'vscode-html-languageservice'; +import * as embeddedSupport from '../modes/embeddedSupport'; +import { getEmmetCompletionParticipants } from 'vscode-emmet-helper'; +import { getCSSMode } from '../modes/cssMode'; + +suite('Emmet Support', () => { + + const htmlLanguageService = getLanguageService(); + + function assertCompletions(syntax: string, value: string, expectedProposal: string, expectedProposalDoc: string): void { + const offset = value.indexOf('|'); + value = value.substr(0, offset) + value.substr(offset + 1); + + const document = TextDocument.create('test://test/test.' + syntax, syntax, 0, value); + const position = document.positionAt(offset); + const documentRegions = getLanguageModelCache<embeddedSupport.HTMLDocumentRegions>(10, 60, document => embeddedSupport.getDocumentRegions(htmlLanguageService, document)); + const mode = syntax == 'html' ? getHTMLMode(htmlLanguageService) : getCSSMode(documentRegions); + const emmetCompletionList: CompletionList = { + isIncomplete: true, + items: undefined + } + mode.setCompletionParticipants([getEmmetCompletionParticipants(document, position, document.languageId, {}, emmetCompletionList)]) + + const list = mode.doComplete!(document, position); + assert.ok(list); + assert.ok(emmetCompletionList) + + + if (expectedProposal && expectedProposalDoc) { + let actualLabels = emmetCompletionList!.items.map(c => c.label).sort(); + let actualDocs = emmetCompletionList!.items.map(c => c.documentation).sort(); + assert.ok(actualLabels.indexOf(expectedProposal) !== -1, 'Not found:' + expectedProposal + ' is ' + actualLabels.join(', ')); + assert.ok(actualDocs.indexOf(expectedProposalDoc) !== -1, 'Not found:' + expectedProposalDoc + ' is ' + actualDocs.join(', ')); + } else { + assert.ok(!emmetCompletionList || !emmetCompletionList.items); + } + + } + + test('Html Emmet Completions', function (): any { + assertCompletions('html', 'ul|', 'ul', '<ul>|</ul>'); + assertCompletions('html', '<ul|', null, null); + assertCompletions('html', '<html>ul|</html>', 'ul', '<ul>|</ul>'); + assertCompletions('html', '<img src=|', null, null); + assertCompletions('html', '<div class=|/>', null, null); + }); + + test('Css Emmet Completions', function (): any { + assertCompletions('css', '<style>.foo { display: none; m10| }</style>', 'margin: 10px;', 'margin: 10px;'); + assertCompletions('css', '<style>foo { display: none; pos:f| }</style>', 'position: fixed;', 'position: fixed;'); + assertCompletions('css', '<style>foo { display: none; margin: a| }</style>', null, null); + assertCompletions('css', '<style>foo| { display: none; }</style>', null, null); + assertCompletions('css', '<style>foo {| display: none; }</style>', null, null); + assertCompletions('css', '<style>foo { display: none;| }</style>', null, null); + assertCompletions('css', '<style>foo { display: none|; }</style>', null, null); + assertCompletions('css', '<style>.foo { display: none; -m-m10| }</style>', 'margin: 10px;', '-moz-margin: 10px;\nmargin: 10px;'); + }); +}); \ No newline at end of file diff --git a/extensions/html/server/yarn.lock b/extensions/html/server/yarn.lock index bd284a09edd..ca465b2f24b 100644 --- a/extensions/html/server/yarn.lock +++ b/extensions/html/server/yarn.lock @@ -2,6 +2,10 @@ # yarn lockfile v1 +"@emmetio/extract-abbreviation@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@emmetio/extract-abbreviation/-/extract-abbreviation-0.1.4.tgz#f5be070db97901ecc37e5204f2ace68242cdcefa" + "@types/mocha@2.2.33": version "2.2.33" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.33.tgz#d79a0061ec270379f4d9e225f4096fb436669def" @@ -10,6 +14,10 @@ version "7.0.43" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c" +jsonc-parser@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.0.tgz#ddcc864ae708e60a7a6dd36daea00172fa8d9272" + vscode-css-languageservice@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.5.tgz#8470989c07bbe740db4fa621423e98384d2c342f" @@ -17,6 +25,14 @@ vscode-css-languageservice@^3.0.5: vscode-languageserver-types "3.5.0" vscode-nls "^2.0.1" +vscode-emmet-helper@1.1.34: + version "1.1.34" + resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-1.1.34.tgz#1e2d3e26c389efc6f73ceb4c0ed797ae07291874" + dependencies: + "@emmetio/extract-abbreviation" "^0.1.4" + jsonc-parser "^1.0.0" + vscode-languageserver-types "^3.6.0-next.1" + vscode-html-languageservice@^2.0.15: version "2.0.15" resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-2.0.15.tgz#e3ec34c4d62bc636f8a16c9b474e5851ca13aab0" @@ -25,26 +41,30 @@ vscode-html-languageservice@^2.0.15: vscode-nls "^2.0.2" vscode-uri "^1.0.1" -vscode-jsonrpc@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa" +vscode-jsonrpc@^3.6.0-next.1: + version "3.6.0-next.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0-next.1.tgz#3cb463dffe5842d6aec16718ca9252708cd6aabe" -vscode-languageserver-protocol@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.0.tgz#067c5cbe27709795398d119692c97ebba1452209" +vscode-languageserver-protocol@^3.6.0-next.5: + version "3.6.0-next.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.6.0-next.5.tgz#ed2ec2db759826f753c0a13977dfb2bedc4d31b3" dependencies: - vscode-jsonrpc "^3.5.0" - vscode-languageserver-types "^3.5.0" + vscode-jsonrpc "^3.6.0-next.1" + vscode-languageserver-types "^3.6.0-next.1" -vscode-languageserver-types@3.5.0, vscode-languageserver-types@^3.5.0: +vscode-languageserver-types@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374" -vscode-languageserver@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-3.5.0.tgz#d28099bc6ddda8c1dd16b707e454e1b1ddae0dba" +vscode-languageserver-types@^3.6.0-next.1: + version "3.6.0-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0-next.1.tgz#98e488d3f87b666b4ee1a3d89f0023e246d358f3" + +vscode-languageserver@^4.0.0-next.4: + version "4.0.0-next.4" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-4.0.0-next.4.tgz#162440b15bedaab07e1676f046e4d9b8578b3d92" dependencies: - vscode-languageserver-protocol "^3.5.0" + vscode-languageserver-protocol "^3.6.0-next.5" vscode-uri "^1.0.1" vscode-nls@^2.0.1, vscode-nls@^2.0.2: diff --git a/extensions/html/yarn.lock b/extensions/html/yarn.lock index 47c5c2e51b7..47534aeb2d2 100644 --- a/extensions/html/yarn.lock +++ b/extensions/html/yarn.lock @@ -34,26 +34,26 @@ vscode-extension-telemetry@0.0.11: dependencies: applicationinsights "1.0.1" -vscode-jsonrpc@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa" +vscode-jsonrpc@^3.6.0-next.1: + version "3.6.0-next.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0-next.1.tgz#3cb463dffe5842d6aec16718ca9252708cd6aabe" -vscode-languageclient@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.0.tgz#36d02cc186a8365a4467719a290fb200a9ae490a" +vscode-languageclient@^4.0.0-next.8: + version "4.0.0-next.8" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-4.0.0-next.8.tgz#eb8eef0bf08399924f8fa520cb0b37071086e7c0" dependencies: - vscode-languageserver-protocol "^3.5.0" + vscode-languageserver-protocol "^3.6.0-next.5" -vscode-languageserver-protocol@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.0.tgz#067c5cbe27709795398d119692c97ebba1452209" +vscode-languageserver-protocol@^3.6.0-next.5: + version "3.6.0-next.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.6.0-next.5.tgz#ed2ec2db759826f753c0a13977dfb2bedc4d31b3" dependencies: - vscode-jsonrpc "^3.5.0" - vscode-languageserver-types "^3.5.0" + vscode-jsonrpc "^3.6.0-next.1" + vscode-languageserver-types "^3.6.0-next.1" -vscode-languageserver-types@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374" +vscode-languageserver-types@^3.6.0-next.1: + version "3.6.0-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0-next.1.tgz#98e488d3f87b666b4ee1a3d89f0023e246d358f3" vscode-nls@^3.2.1: version "3.2.1" From b709b4de9f205cfe3f71a3c3ae062be99e186f98 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero <benjpas@microsoft.com> Date: Tue, 13 Feb 2018 07:27:40 +0100 Subject: [PATCH 043/362] notifications - some polish and validation --- .../notification/common/notification.ts | 17 +++++---- src/vs/workbench/electron-browser/actions.ts | 11 ++++-- .../browser/media/notificationList.css | 9 ++--- .../notification/browser/notificationList.ts | 36 ++++++++----------- .../browser/notificationService.ts | 9 ++--- .../notification/common/notificationsModel.ts | 30 +++++++++++++++- 6 files changed, 69 insertions(+), 43 deletions(-) diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index 71ea3b33df4..46bbdb2c8b6 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -8,21 +8,24 @@ import { Severity } from 'vs/platform/message/common/message'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { IAction } from 'vs/base/common/actions'; export const INotificationService = createDecorator<INotificationService>('notificationService'); -export interface INotification extends IDisposable { +export interface INotification { + severity: Severity; + message: string | IMarkdownString | Error; + source?: string; + actions?: IAction[]; +} +export interface INotificationHandle extends IDisposable { } export interface INotificationService { _serviceBrand: any; - notify(sev: Severity, message: string): INotification; - - // notify(sev: Severity, message: Error): () => void; - // notify(sev: Severity, message: string[]): () => void; - // notify(sev: Severity, message: Error[]): () => void; - // notify(sev: Severity, message: IMessageWithAction): () => void; + notify(notification: INotification): INotificationHandle; } \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index 27adc685ece..b6ce54c7704 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -1578,9 +1578,14 @@ export class ShowAboutDialogAction extends Action { } run(): TPromise<void> { - this.notificationService.notify(Severity.Info, 'This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com).'); - this.notificationService.notify(Severity.Warning, 'This is a warning message with a [link](https://code.visualstudio.com).'); - this.notificationService.notify(Severity.Error, 'This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com).This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com).'); + this.notificationService.notify({ severity: Severity.Info, message: 'This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com).' }); + this.notificationService.notify({ + severity: Severity.Warning, message: 'This is a warning message with a [link](https://code.visualstudio.com).', actions: [ + new Action('id.reload', 'Reload Window', null, true, () => { console.log('Reload Window'); return void 0; }), + new Action('id.cancel', 'Cancel', null, true, () => { console.log('Cancel'); return void 0; }) + ] + }); + this.notificationService.notify({ severity: Severity.Error, message: 'This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com).This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com).' }); return TPromise.as(undefined); } diff --git a/src/vs/workbench/services/notification/browser/media/notificationList.css b/src/vs/workbench/services/notification/browser/media/notificationList.css index e25b80abb88..eb2c3717cce 100644 --- a/src/vs/workbench/services/notification/browser/media/notificationList.css +++ b/src/vs/workbench/services/notification/browser/media/notificationList.css @@ -10,6 +10,7 @@ left: 50%; margin-left: -300px; display: none; + background: #1E1E1E; /* TODO make themable */ } .monaco-workbench > .notifications-list-container.visible { @@ -17,13 +18,13 @@ } .monaco-workbench > .notifications-list-container .monaco-list-row { - border-left: 2px solid grey; /* TODO make themable */ - border-right: 2px solid grey; - border-bottom: 2px solid grey; + border-left: 2px solid #323232; /* TODO make themable */ + border-right: 2px solid #323232; + border-bottom: 2px solid #323232; } .monaco-workbench > .notifications-list-container .monaco-list-row:first { - border-top: 2px solid grey; + border-top: 2px solid #323232; } /** Notification: Container */ diff --git a/src/vs/workbench/services/notification/browser/notificationList.ts b/src/vs/workbench/services/notification/browser/notificationList.ts index 6c0e7fcb8f8..000eba372d5 100644 --- a/src/vs/workbench/services/notification/browser/notificationList.ts +++ b/src/vs/workbench/services/notification/browser/notificationList.ts @@ -6,7 +6,6 @@ 'use strict'; import 'vs/css!./media/notificationList'; -import { Severity } from 'vs/platform/message/common/message'; import { addClass } from 'vs/base/browser/dom'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -14,13 +13,15 @@ import { INotificationViewItem, NotificationViewItem } from 'vs/workbench/servic import { NotificationRenderer, NotificationsDelegate } from 'vs/workbench/services/notification/browser/notificationViewer'; import { IListOptions } from 'vs/base/browser/ui/list/listWidget'; import { localize } from 'vs/nls'; -import { Themable, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND } from 'vs/workbench/common/theme'; +import { Themable } from 'vs/workbench/common/theme'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { contrastBorder, widgetShadow, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { Action } from 'vs/base/common/actions'; +import { INotification, INotificationHandle } from 'vs/platform/notification/common/notification'; export class NotificationList extends Themable { + + private static NO_OP_NOTIFICATION: INotificationHandle = { dispose: () => void 0 }; + private listContainer: HTMLElement; private list: WorkbenchList<INotificationViewItem>; @@ -36,12 +37,6 @@ export class NotificationList extends Themable { protected updateStyles(): void { if (this.listContainer) { - const background = this.getColor(NOTIFICATIONS_BACKGROUND); - this.listContainer.style.background = background ? background.toString() : null; - - const foreground = this.getColor(NOTIFICATIONS_FOREGROUND); - this.listContainer.style.color = foreground ? foreground.toString() : null; - const outlineColor = this.getColor(contrastBorder); this.listContainer.style.outlineColor = outlineColor ? outlineColor.toString() : null; @@ -84,21 +79,18 @@ export class NotificationList extends Themable { this.updateStyles(); } - public show(severity: Severity, notification: string): void { + public show(notification: INotification): INotificationHandle { + const viewItem = NotificationViewItem.create(notification); + if (!viewItem) { + return NotificationList.NO_OP_NOTIFICATION; + } + addClass(this.listContainer, 'visible'); - this.list.splice(0, 0, [ - new NotificationViewItem( - severity, - { value: notification, isTrusted: true } as IMarkdownString, - 'VS Code Core', - [ - new Action('id.reload', 'Reload Window', null, true, () => { console.log('Reload Window'); return void 0; }), - new Action('id.cancel', 'Cancel', null, true, () => { console.log('Cancel'); return void 0; }) - ] - ) - ]); + this.list.splice(0, 0, [viewItem]); this.list.layout(); + + return { dispose: () => void 0 }; } } diff --git a/src/vs/workbench/services/notification/browser/notificationService.ts b/src/vs/workbench/services/notification/browser/notificationService.ts index c5462542494..6187fbaa0a4 100644 --- a/src/vs/workbench/services/notification/browser/notificationService.ts +++ b/src/vs/workbench/services/notification/browser/notificationService.ts @@ -5,8 +5,7 @@ 'use strict'; -import { INotificationService, INotification } from 'vs/platform/notification/common/notification'; -import { Severity } from 'vs/platform/message/common/message'; +import { INotificationService, INotification, INotificationHandle } from 'vs/platform/notification/common/notification'; import { NotificationList } from 'vs/workbench/services/notification/browser/notificationList'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -27,13 +26,11 @@ export class NotificationService implements INotificationService { this.handler = this.instantiationService.createInstance(NotificationList, document.getElementById('workbench.main.container')); } - public notify(sev: Severity, message: string): INotification { + public notify(notification: INotification): INotificationHandle { if (!this.handler) { this.createHandler(); } - this.handler.show(sev, message); - - return { dispose: () => void 0 }; + return this.handler.show(notification); } } \ No newline at end of file diff --git a/src/vs/workbench/services/notification/common/notificationsModel.ts b/src/vs/workbench/services/notification/common/notificationsModel.ts index a4f25c63ff3..6a30039a042 100644 --- a/src/vs/workbench/services/notification/common/notificationsModel.ts +++ b/src/vs/workbench/services/notification/common/notificationsModel.ts @@ -8,6 +8,9 @@ import { Severity } from 'vs/platform/message/common/message'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IAction } from 'vs/base/common/actions'; +import { INotification } from 'vs/platform/notification/common/notification'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { localize } from 'vs/nls'; export class INotificationsModel { @@ -29,9 +32,34 @@ export interface INotificationViewItem { } export class NotificationViewItem implements INotificationViewItem { - private _expanded: boolean = false; + + private static DEFAULT_SOURCE = localize('product', "Product"); + + private _expanded: boolean; constructor(private _severity: Severity, private _message: IMarkdownString, private _source: string, private _actions: IAction[]) { + this._expanded = _actions.length > 0; + } + + public static create(notification: INotification): INotificationViewItem { + if (!notification || !notification.message) { + return null; // we need a message to show + } + + let message: IMarkdownString; + if (notification.message instanceof Error) { + message = { value: toErrorMessage(notification.message, false), isTrusted: true }; + } else if (typeof notification.message === 'string') { + message = { value: notification.message, isTrusted: true }; + } else if (notification.message.value) { + message = notification.message; + } + + if (!message) { + return null; // we need a message to show + } + + return new NotificationViewItem(notification.severity, message, notification.source || NotificationViewItem.DEFAULT_SOURCE, notification.actions || []); } public get expanded(): boolean { From d36606826a2af43fe45295325cb79789d0872cf4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero <benjpas@microsoft.com> Date: Tue, 13 Feb 2018 08:10:06 +0100 Subject: [PATCH 044/362] notifications - allow to click actions and links without expanding --- .../standalone/browser/simpleServices.ts | 15 ++++++----- src/vs/platform/list/browser/listService.ts | 23 +++++++++-------- src/vs/workbench/electron-browser/actions.ts | 14 ++--------- src/vs/workbench/electron-browser/shell.ts | 2 +- .../browser/media/notificationList.css | 7 +++++- .../notification/browser/notificationList.ts | 25 ++++++++++++++++--- .../browser/notificationService.ts | 13 ++++++++++ .../browser/notificationViewer.ts | 12 ++++++++- 8 files changed, 74 insertions(+), 37 deletions(-) diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index bcd770656cb..f6160b4ac21 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -39,7 +39,7 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe import { OS } from 'vs/base/common/platform'; import { IRange } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; -import { INotificationService, INotification } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotification, INotificationHandle } from 'vs/platform/notification/common/notification'; export class SimpleEditor implements IEditor { @@ -285,19 +285,18 @@ export class SimpleNotificationService implements INotificationService { public _serviceBrand: any; - private static readonly Empty: INotification = { dispose: () => undefined }; + private static readonly Empty: INotificationHandle = { dispose: () => undefined }; - public notify(sev: Severity, message: string): INotification { - - switch (sev) { + public notify(notification: INotification): INotificationHandle { + switch (notification.severity) { case Severity.Error: - console.error(message); + console.error(notification.message); break; case Severity.Warning: - console.warn(message); + console.warn(notification.message); break; default: - console.log(message); + console.log(notification.message); break; } diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 9fa1ab9b76b..7e0c5c9871f 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -24,6 +24,7 @@ import { DefaultController, IControllerOptions, OpenMode, ClickBehavior } from ' import { isUndefinedOrNull } from 'vs/base/common/types'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import Event, { Emitter } from 'vs/base/common/event'; + export type ListWidget = List<any> | PagedList<any> | ITree; export const IListService = createDecorator<IListService>('listService'); @@ -84,9 +85,7 @@ export const WorkbenchListSupportsMultiSelectContextKey = new RawContextKey<bool export const WorkbenchListFocusContextKey = ContextKeyExpr.and(RawWorkbenchListFocusContextKey, ContextKeyExpr.not(InputFocusedContextKey)); export const WorkbenchListDoubleSelection = new RawContextKey<boolean>('listDoubleSelection', false); -export type Widget = List<any> | PagedList<any> | ITree; - -function createScopedContextKeyService(contextKeyService: IContextKeyService, widget: Widget): IContextKeyService { +function createScopedContextKeyService(contextKeyService: IContextKeyService, widget: ListWidget): IContextKeyService { const result = contextKeyService.createScoped(widget.getHTMLElement()); if (widget instanceof List || widget instanceof PagedList) { @@ -127,7 +126,7 @@ class MultipleSelectionController<T> implements IMultipleSelectionController<T> class WorkbenchOpenController implements IOpenController { - constructor(private configurationService: IConfigurationService) { } + constructor(private configurationService: IConfigurationService, private existingOpenController?: IOpenController) { } shouldOpen(event: UIEvent): boolean { if (event instanceof MouseEvent) { @@ -136,10 +135,14 @@ class WorkbenchOpenController implements IOpenController { return false; } - return event.button === 0 /* left mouse button */ || event.button === 1 /* middle mouse button */; + if (event.button === 0 /* left mouse button */ || event.button === 1 /* middle mouse button */) { + return this.existingOpenController ? this.existingOpenController.shouldOpen(event) : true; + } + + return false; } - return true; + return this.existingOpenController ? this.existingOpenController.shouldOpen(event) : true; } } @@ -148,7 +151,7 @@ function handleListControllers<T>(options: IListOptions<T>, configurationService options.multipleSelectionController = new MultipleSelectionController(configurationService); } - options.openController = new WorkbenchOpenController(configurationService); + options.openController = new WorkbenchOpenController(configurationService, options.openController); return options; } @@ -179,7 +182,7 @@ export class WorkbenchList<T> extends List<T> { @IThemeService themeService: IThemeService, @IConfigurationService private configurationService: IConfigurationService ) { - super(container, delegate, renderers, mixin(handleListControllers(options, configurationService), { keyboardSupport: false, selectOnMouseDown: true } as IListOptions<any>, false)); + super(container, delegate, renderers, mixin(handleListControllers(options, configurationService), { keyboardSupport: false, selectOnMouseDown: true } as IListOptions<T>, false)); this.contextKeyService = createScopedContextKeyService(contextKeyService, this); this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService); @@ -221,13 +224,13 @@ export class WorkbenchPagedList<T> extends PagedList<T> { container: HTMLElement, delegate: IDelegate<number>, renderers: IPagedRenderer<T, any>[], - options: IListOptions<any>, + options: IListOptions<T>, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @IConfigurationService private configurationService: IConfigurationService ) { - super(container, delegate, renderers, mixin(handleListControllers(options, configurationService), { keyboardSupport: false, selectOnMouseDown: true } as IListOptions<any>, false)); + super(container, delegate, renderers, mixin(handleListControllers(options, configurationService), { keyboardSupport: false, selectOnMouseDown: true } as IListOptions<T>, false)); this.contextKeyService = createScopedContextKeyService(contextKeyService, this); diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index b6ce54c7704..f04c6bdbf0e 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -51,7 +51,6 @@ import { getDomNodePagePosition, createStyleSheet, createCSSRule } from 'vs/base import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Context } from 'vs/platform/contextkey/browser/contextKeyService'; import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; -import { INotificationService } from 'vs/platform/notification/common/notification'; // --- actions @@ -1572,22 +1571,13 @@ export class ShowAboutDialogAction extends Action { constructor( id: string, label: string, - @INotificationService private notificationService: INotificationService + @IWindowsService private windowsService: IWindowsService ) { super(id, label); } run(): TPromise<void> { - this.notificationService.notify({ severity: Severity.Info, message: 'This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com).' }); - this.notificationService.notify({ - severity: Severity.Warning, message: 'This is a warning message with a [link](https://code.visualstudio.com).', actions: [ - new Action('id.reload', 'Reload Window', null, true, () => { console.log('Reload Window'); return void 0; }), - new Action('id.cancel', 'Cancel', null, true, () => { console.log('Cancel'); return void 0; }) - ] - }); - this.notificationService.notify({ severity: Severity.Error, message: 'This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com).This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com).' }); - - return TPromise.as(undefined); + return this.windowsService.openAboutDialog(); } } diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index e382649668d..252ef71f037 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -403,7 +403,7 @@ export class WorkbenchShell { serviceCollection.set(IMessageService, this.messageService); serviceCollection.set(IChoiceService, this.messageService); - serviceCollection.set(INotificationService, new SyncDescriptor(NotificationService, container)); + serviceCollection.set(INotificationService, instantiationService.createInstance(NotificationService, container)); const lifecycleService = instantiationService.createInstance(LifecycleService); this.toUnbind.push(lifecycleService.onShutdown(reason => this.dispose(reason))); diff --git a/src/vs/workbench/services/notification/browser/media/notificationList.css b/src/vs/workbench/services/notification/browser/media/notificationList.css index eb2c3717cce..301187034fb 100644 --- a/src/vs/workbench/services/notification/browser/media/notificationList.css +++ b/src/vs/workbench/services/notification/browser/media/notificationList.css @@ -30,13 +30,18 @@ /** Notification: Container */ .monaco-workbench > .notifications-list-container .notification-list-item { + display: flex; + flex-direction: column; padding: 5px; + height: 100%; + box-sizing: border-box; } /** Notification: Main Row */ .monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-main-row { display: flex; + flex-grow: 1; } /** Notification: Icon */ @@ -83,7 +88,7 @@ text-overflow: ellipsis; } -.monaco-workbench > .notifications-list-container .notification-list-item.expanded > .notification-list-item-message { +.monaco-workbench > .notifications-list-container .notification-list-item.expanded .notification-list-item-message { white-space: normal; } diff --git a/src/vs/workbench/services/notification/browser/notificationList.ts b/src/vs/workbench/services/notification/browser/notificationList.ts index 000eba372d5..7009c60611f 100644 --- a/src/vs/workbench/services/notification/browser/notificationList.ts +++ b/src/vs/workbench/services/notification/browser/notificationList.ts @@ -57,12 +57,16 @@ export class NotificationList extends Themable { this.listContainer, new NotificationsDelegate(), [this.instantiationService.createInstance(NotificationRenderer)], - { ariaLabel: localize('notificationsList', "Notifications List") } as IListOptions<INotificationViewItem> + { + ariaLabel: localize('notificationsList', "Notifications List"), + openController: { shouldOpen: e => this.shouldExpand(e) } + } as IListOptions<INotificationViewItem> ); - this.list.onOpen(notifications => { - const notification = notifications.elements[0]; - const index = notifications.indexes[0]; + // Expand/Collapse + this.list.onOpen(e => { + const notification = e.elements[0]; + const index = e.indexes[0]; if (notification.expanded) { notification.collapse(); @@ -72,6 +76,10 @@ export class NotificationList extends Themable { this.list.splice(index, 1, [notification]); this.list.layout(); + this.list.setSelection([index]); + this.list.setFocus([index]); + + setTimeout(() => this.list.domFocus()); // TODO why? }); this.container.appendChild(this.listContainer); @@ -79,6 +87,15 @@ export class NotificationList extends Themable { this.updateStyles(); } + private shouldExpand(event: UIEvent): boolean { + const target = event.target as HTMLElement; + if (target.tagName.toLowerCase() === 'a') { + return false; // do not overwrite links/buttons + } + + return true; + } + public show(notification: INotification): INotificationHandle { const viewItem = NotificationViewItem.create(notification); if (!viewItem) { diff --git a/src/vs/workbench/services/notification/browser/notificationService.ts b/src/vs/workbench/services/notification/browser/notificationService.ts index 6187fbaa0a4..a03abcf13b9 100644 --- a/src/vs/workbench/services/notification/browser/notificationService.ts +++ b/src/vs/workbench/services/notification/browser/notificationService.ts @@ -8,6 +8,8 @@ import { INotificationService, INotification, INotificationHandle } from 'vs/platform/notification/common/notification'; import { NotificationList } from 'vs/workbench/services/notification/browser/notificationList'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { Severity } from 'vs/platform/message/common/message'; +import { Action } from 'vs/base/common/actions'; export class NotificationService implements INotificationService { @@ -19,6 +21,17 @@ export class NotificationService implements INotificationService { container: HTMLElement, @IInstantiationService private instantiationService: IInstantiationService ) { + // TODO remove me + setTimeout(() => { + this.notify({ severity: Severity.Info, message: 'This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com).' }); + this.notify({ + severity: Severity.Warning, message: 'This is a warning message with a [link](https://code.visualstudio.com).', actions: [ + new Action('id.reload', 'Yes OK', null, true, () => { console.log('OK'); return void 0; }), + new Action('id.cancel', 'No, not OK!', null, true, () => { console.log('NOT OK'); return void 0; }) + ] + }); + this.notify({ severity: Severity.Error, message: 'This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com).This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com). This is a error message with a [link](https://code.visualstudio.com).' }); + }, 500); } private createHandler(): void { diff --git a/src/vs/workbench/services/notification/browser/notificationViewer.ts b/src/vs/workbench/services/notification/browser/notificationViewer.ts index a4ae742e776..33125f0d613 100644 --- a/src/vs/workbench/services/notification/browser/notificationViewer.ts +++ b/src/vs/workbench/services/notification/browser/notificationViewer.ts @@ -21,8 +21,18 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; export class NotificationsDelegate implements IDelegate<INotificationViewItem> { + private static readonly ROW_HEIGHT = 32; + public getHeight(element: INotificationViewItem): number { - return element.expanded ? 70 : 32; + if (!element.expanded) { + return NotificationsDelegate.ROW_HEIGHT; + } + + if (element.actions.length === 0) { + return NotificationsDelegate.ROW_HEIGHT * 2; + } + + return NotificationsDelegate.ROW_HEIGHT * 3; } public getTemplateId(element: INotificationViewItem): string { From 1e6039f5a1cc021bd195e1cb9a7e4baaa5b2425b Mon Sep 17 00:00:00 2001 From: Joao Moreno <joao.moreno@microsoft.com> Date: Tue, 13 Feb 2018 10:39:42 +0100 Subject: [PATCH 045/362] move inno updater into tools fixes #43503 --- build/win32/code.iss | 4 ++-- build/win32/inno_updater.exe | Bin 311808 -> 310784 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/win32/code.iss b/build/win32/code.iss index 22c0096e0c5..b8339edadaf 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -70,7 +70,7 @@ Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{ [Files] Source: "*"; Excludes: "inno_updater.exe"; DestDir: "{code:GetDestDir}"; Flags: ignoreversion recursesubdirs createallsubdirs -Source: "inno_updater.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "inno_updater.exe"; DestDir: "{app}\tools"; Flags: ignoreversion recursesubdirs createallsubdirs [Icons] Name: "{group}\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; AppUserModelID: "{#AppUserId}" @@ -1027,7 +1027,7 @@ begin Sleep(1000); end; - Exec(ExpandConstant('{app}\inno_updater.exe'), ExpandConstant('"{app}\{#ExeBasename}.exe" ' + BoolToStr(LockFileExists())), '', SW_SHOW, ewWaitUntilTerminated, UpdateResultCode); + Exec(ExpandConstant('{app}\tools\inno_updater.exe'), ExpandConstant('"{app}\{#ExeBasename}.exe" ' + BoolToStr(LockFileExists())), '', SW_SHOW, ewWaitUntilTerminated, UpdateResultCode); end; end; diff --git a/build/win32/inno_updater.exe b/build/win32/inno_updater.exe index ffc04fb766193a705b63fd8ba6416b693e2d31ee..05441e94e159fac683eb94f98c1cded042ba4f04 100644 GIT binary patch delta 98781 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�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ÐthBzxdL8!ZbvU5c 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 delta 100240 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 Q%RDQ{q_4OIOZbxi56Uop$p8QV From f965a2c51ef42f9d0cc2385d5c03955e6fadddab Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu <sasomava@microsoft.com> Date: Tue, 13 Feb 2018 11:34:56 +0100 Subject: [PATCH 046/362] Fix #43197 --- .../common/configurationRegistry.ts | 10 ++++++++-- src/vs/workbench/api/node/extHost.protocol.ts | 2 +- .../workbench/api/node/extHostConfiguration.ts | 18 +++--------------- .../electron-browser/extensionHost.ts | 4 ++-- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 518898d9d85..34b7a40b1c3 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -336,7 +336,13 @@ export function validateProperty(property: string): string { return null; } -export function getScopes(keys: string[]): ConfigurationScope[] { +export function getScopes(): { [key: string]: ConfigurationScope } { + const scopes = {}; const configurationProperties = configurationRegistry.getConfigurationProperties(); - return keys.map(key => configurationProperties[key].scope); + for (const key of Object.keys(configurationProperties)) { + scopes[key] = configurationProperties[key].scope; + } + scopes['launch'] = ConfigurationScope.RESOURCE; + scopes['task'] = ConfigurationScope.RESOURCE; + return scopes; } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 07a6fc237ad..3a62eda7a27 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -83,7 +83,7 @@ export interface IInitData { } export interface IConfigurationInitData extends IConfigurationData { - configurationScopes: ConfigurationScope[]; + configurationScopes: { [key: string]: ConfigurationScope }; } export interface IWorkspaceConfigurationChangeEventData { diff --git a/src/vs/workbench/api/node/extHostConfiguration.ts b/src/vs/workbench/api/node/extHostConfiguration.ts index db1ab8b650f..e89decf0b5b 100644 --- a/src/vs/workbench/api/node/extHostConfiguration.ts +++ b/src/vs/workbench/api/node/extHostConfiguration.ts @@ -41,14 +41,14 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { private readonly _onDidChangeConfiguration = new Emitter<vscode.ConfigurationChangeEvent>(); private readonly _proxy: MainThreadConfigurationShape; private readonly _extHostWorkspace: ExtHostWorkspace; - private _configurationScopes: Map<string, ConfigurationScope>; + private _configurationScopes: { [key: string]: ConfigurationScope }; private _configuration: Configuration; constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationInitData) { this._proxy = proxy; this._extHostWorkspace = extHostWorkspace; this._configuration = Configuration.parse(data); - this._readConfigurationScopes(data.configurationScopes); + this._configurationScopes = data.configurationScopes; } get onDidChangeConfiguration(): Event<vscode.ConfigurationChangeEvent> { @@ -129,7 +129,7 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { } private _validateConfigurationAccess(key: string, resource: URI, extensionId: string): void { - const scope = this._configurationScopes.get(key); + const scope = this._configurationScopes[key]; const extensionIdText = extensionId ? `[${extensionId}] ` : ''; if (ConfigurationScope.RESOURCE === scope) { if (resource === void 0) { @@ -145,18 +145,6 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { } } - private _readConfigurationScopes(scopes: ConfigurationScope[]): void { - this._configurationScopes = new Map<string, ConfigurationScope>(); - if (scopes.length) { - const defaultKeys = this._configuration.keys(this._extHostWorkspace.workspace).default; - if (defaultKeys.length === scopes.length) { - for (let i = 0; i < defaultKeys.length; i++) { - this._configurationScopes.set(defaultKeys[i], scopes[i]); - } - } - } - } - private _toConfigurationChangeEvent(data: IWorkspaceConfigurationChangeEventData): vscode.ConfigurationChangeEvent { const changedConfiguration = new ConfigurationModel(data.changedConfiguration.contents, data.changedConfiguration.keys, data.changedConfiguration.overrides); const changedConfigurationByResource: StrictResourceMap<ConfigurationModel> = new StrictResourceMap<ConfigurationModel>(); diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 8079fd1e198..a02b5cf7211 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -358,7 +358,7 @@ export class ExtensionHostProcessWorker { private _createExtHostInitData(): TPromise<IInitData> { return TPromise.join<any>([this._telemetryService.getTelemetryInfo(), this._extensionService.getExtensions()]).then(([telemetryInfo, extensionDescriptions]) => { - const configurationData: IConfigurationInitData = { ...this._configurationService.getConfigurationData(), configurationScopes: [] }; + const configurationData: IConfigurationInitData = { ...this._configurationService.getConfigurationData(), configurationScopes: {} }; const r: IInitData = { parentPid: process.pid, environment: { @@ -376,7 +376,7 @@ export class ExtensionHostProcessWorker { workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : <IWorkspaceData>this._contextService.getWorkspace(), extensions: extensionDescriptions, // Send configurations scopes only in development mode. - configuration: !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment ? { ...configurationData, configurationScopes: getScopes(this._configurationService.keys().default) } : configurationData, + configuration: !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment ? { ...configurationData, configurationScopes: getScopes() } : configurationData, telemetryInfo, args: this._environmentService.args, execPath: this._environmentService.execPath, From 4d032aad0ad99a637f2e02641195012669f6fd12 Mon Sep 17 00:00:00 2001 From: Joao Moreno <joao.moreno@microsoft.com> Date: Tue, 13 Feb 2018 11:42:23 +0100 Subject: [PATCH 047/362] remove bad empty dirs --- build/win32/code.iss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build/win32/code.iss b/build/win32/code.iss index b8339edadaf..34b6c7a0905 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -57,7 +57,6 @@ Type: files; Name: "{app}\resources\app\Credits_45.0.2454.85.html"; Check: IsNot [UninstallDelete] Type: filesandordirs; Name: "{app}\_" -Type: filesandordirs; Name: "{app}\old" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked @@ -70,7 +69,7 @@ Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{ [Files] Source: "*"; Excludes: "inno_updater.exe"; DestDir: "{code:GetDestDir}"; Flags: ignoreversion recursesubdirs createallsubdirs -Source: "inno_updater.exe"; DestDir: "{app}\tools"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "inno_updater.exe"; DestDir: "{app}\tools"; Flags: ignoreversion [Icons] Name: "{group}\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; AppUserModelID: "{#AppUserId}" From c31c65951a2a1b6903d1a6721478c7748737d8ff Mon Sep 17 00:00:00 2001 From: Benjamin Pasero <benjpas@microsoft.com> Date: Tue, 13 Feb 2018 11:45:43 +0100 Subject: [PATCH 048/362] notifications - proper message size computation --- .../browser/media/notificationList.css | 1 + .../notification/browser/notificationList.ts | 2 +- .../browser/notificationViewer.ts | 74 +++++++++++++++---- 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/services/notification/browser/media/notificationList.css b/src/vs/workbench/services/notification/browser/media/notificationList.css index 301187034fb..898df38375f 100644 --- a/src/vs/workbench/services/notification/browser/media/notificationList.css +++ b/src/vs/workbench/services/notification/browser/media/notificationList.css @@ -105,6 +105,7 @@ opacity: 0.7; flex: 1; font-size: 12px; + padding-left: 4px; /* aligns with severity icon */ } /** Notification: Actions */ diff --git a/src/vs/workbench/services/notification/browser/notificationList.ts b/src/vs/workbench/services/notification/browser/notificationList.ts index 7009c60611f..de36270a701 100644 --- a/src/vs/workbench/services/notification/browser/notificationList.ts +++ b/src/vs/workbench/services/notification/browser/notificationList.ts @@ -55,7 +55,7 @@ export class NotificationList extends Themable { this.list = this.instantiationService.createInstance( WorkbenchList, this.listContainer, - new NotificationsDelegate(), + new NotificationsDelegate(this.listContainer), [this.instantiationService.createInstance(NotificationRenderer)], { ariaLabel: localize('notificationsList', "Notifications List"), diff --git a/src/vs/workbench/services/notification/browser/notificationViewer.ts b/src/vs/workbench/services/notification/browser/notificationViewer.ts index 33125f0d613..d856e5fdc58 100644 --- a/src/vs/workbench/services/notification/browser/notificationViewer.ts +++ b/src/vs/workbench/services/notification/browser/notificationViewer.ts @@ -18,26 +18,62 @@ import { localize } from 'vs/nls'; import { Button } from 'vs/base/browser/ui/button/button'; import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; export class NotificationsDelegate implements IDelegate<INotificationViewItem> { private static readonly ROW_HEIGHT = 32; + private offsetHelper: HTMLElement; + + constructor(container: HTMLElement) { + this.offsetHelper = this.createOffsetHelper(container); + } + + private createOffsetHelper(container: HTMLElement): HTMLElement { + const offsetHelper = document.createElement('div'); + offsetHelper.style.opacity = '0'; + offsetHelper.style.position = 'absolute'; // do not mess with the visual layout + offsetHelper.style.width = '100%'; // ensure to fill contauner to measure true width + offsetHelper.style.overflow = 'hidden'; // do not overflow + offsetHelper.style.whiteSpace = 'nowrap'; // do not wrap to measure true width + + container.appendChild(offsetHelper); + + return offsetHelper; + } + public getHeight(element: INotificationViewItem): number { if (!element.expanded) { return NotificationsDelegate.ROW_HEIGHT; } - if (element.actions.length === 0) { - return NotificationsDelegate.ROW_HEIGHT * 2; - } + const preferredMessageRows = this.computePreferredRows(element.message); - return NotificationsDelegate.ROW_HEIGHT * 3; + return NotificationsDelegate.ROW_HEIGHT * (preferredMessageRows + 1); + } + + private computePreferredRows(message: IMarkdownString): number { + + // Render message markdown into offset helper + const renderedMessage = NotificationMarkdownRenderer.render(message); + this.offsetHelper.appendChild(renderedMessage); + + // Compute message width taking overflow into account + const messageWidth = Math.max(renderedMessage.scrollWidth, renderedMessage.offsetWidth); + + // One row per exceeding the total width of the container + const preferredRows = Math.ceil(messageWidth / this.offsetHelper.offsetWidth); + + // Always clear offset helper after use + clearNode(this.offsetHelper); + + return preferredRows; } public getTemplateId(element: INotificationViewItem): string { if (element instanceof NotificationViewItem) { - return NotificationRenderer.ID; + return NotificationRenderer.TEMPLATE_ID; } return void 0; @@ -56,11 +92,8 @@ export interface INotificationTemplateData { actionsContainer: HTMLElement; } -export class NotificationRenderer implements IRenderer<INotificationViewItem, INotificationTemplateData> { +class NotificationMarkdownRenderer { - public static readonly ID = 'notification'; - - private static readonly SEVERITIES: ('info' | 'warning' | 'error')[] = ['info', 'warning', 'error']; private static readonly MARKED_NOOP = (text?: string) => text || ''; private static readonly MARKED_NOOP_TARGETS = [ 'blockquote', 'br', 'code', 'codespan', 'del', 'em', 'heading', 'hr', 'html', @@ -68,6 +101,21 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN 'tablerow' ]; + public static render(markdown: IMarkdownString, actionCallback?: (content: string) => void): HTMLElement { + return renderMarkdown(markdown, { + inline: true, + joinRendererConfiguration: renderer => NotificationMarkdownRenderer.MARKED_NOOP_TARGETS.forEach(fn => renderer[fn] = NotificationMarkdownRenderer.MARKED_NOOP), + actionCallback + }); + } +} + +export class NotificationRenderer implements IRenderer<INotificationViewItem, INotificationTemplateData> { + + public static readonly TEMPLATE_ID = 'notification'; + + private static readonly SEVERITIES: ('info' | 'warning' | 'error')[] = ['info', 'warning', 'error']; + constructor( @IOpenerService private openerService: IOpenerService, @IThemeService private themeService: IThemeService @@ -75,7 +123,7 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN } public get templateId() { - return NotificationRenderer.ID; + return NotificationRenderer.TEMPLATE_ID; } public renderTemplate(container: HTMLElement): INotificationTemplateData { @@ -146,11 +194,7 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN // Message (simple markdown with links support) clearNode(data.message); - data.message.appendChild(renderMarkdown(element.message, { - inline: true, - joinRendererConfiguration: renderer => NotificationRenderer.MARKED_NOOP_TARGETS.forEach(fn => renderer[fn] = NotificationRenderer.MARKED_NOOP), - actionCallback: (content: string) => this.openerService.open(URI.parse(content)).then(void 0, onUnexpectedError) - })); + data.message.appendChild(NotificationMarkdownRenderer.render(element.message, (content: string) => this.openerService.open(URI.parse(content)).then(void 0, onUnexpectedError))); // Source if (element.expanded) { From 6458cbc4c93408f92d4edcb8d18c969a8969a843 Mon Sep 17 00:00:00 2001 From: isidor <inikolic@microsoft.com> Date: Tue, 13 Feb 2018 11:47:29 +0100 Subject: [PATCH 049/362] debug: null guard --- src/vs/workbench/parts/debug/electron-browser/debugService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 8aee38d766b..4d9b673c02d 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -661,7 +661,7 @@ export class DebugService implements debug.IDebugService { public startDebugging(launch: debug.ILaunch, configOrName?: debug.IConfig | string, noDebug = false): TPromise<any> { // make sure to save all files and that the configuration is up to date - return this.extensionService.activateByEvent('onDebug').then(() => this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration(launch.workspace).then(() => + return this.extensionService.activateByEvent('onDebug').then(() => this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration(launch ? launch.workspace : undefined).then(() => this.extensionService.whenInstalledExtensionsRegistered().then(() => { if (this.model.getProcesses().length === 0) { this.removeReplExpressions(); From 64b699b54ac3b375aa679992f840a394eb3156fd Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu <sasomava@microsoft.com> Date: Tue, 13 Feb 2018 12:00:21 +0100 Subject: [PATCH 050/362] Fix compilation errors --- .../electron-browser/api/extHostConfiguration.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts index 9449ad6c56a..4377228a0b8 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -40,7 +40,7 @@ suite('ExtHostConfiguration', function () { user: new ConfigurationModel(contents), workspace: new ConfigurationModel(), folders: Object.create(null), - configurationScopes: [] + configurationScopes: {} }; } @@ -149,7 +149,7 @@ suite('ExtHostConfiguration', function () { }, ['editor.wordWrap']), workspace: new ConfigurationModel({}, []), folders: Object.create(null), - configurationScopes: [] + configurationScopes: {} } ); @@ -195,7 +195,7 @@ suite('ExtHostConfiguration', function () { }, ['editor.wordWrap']), workspace, folders, - configurationScopes: [] + configurationScopes: {} } ); @@ -269,7 +269,7 @@ suite('ExtHostConfiguration', function () { }, ['editor.wordWrap']), workspace, folders, - configurationScopes: [] + configurationScopes: {} } ); From ae6677bcf432f607e554b736977c389b00e60904 Mon Sep 17 00:00:00 2001 From: Joao Moreno <jomo@microsoft.com> Date: Tue, 13 Feb 2018 11:52:51 +0100 Subject: [PATCH 051/362] contextview: logs related to #41680 --- src/vs/platform/contextview/browser/contextViewService.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/contextview/browser/contextViewService.ts b/src/vs/platform/contextview/browser/contextViewService.ts index f1086b3f6f0..684802017cc 100644 --- a/src/vs/platform/contextview/browser/contextViewService.ts +++ b/src/vs/platform/contextview/browser/contextViewService.ts @@ -8,6 +8,7 @@ import { IContextViewService, IContextViewDelegate } from './contextView'; import { ContextView } from 'vs/base/browser/ui/contextview/contextview'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IMessageService } from 'vs/platform/message/common/message'; +import { ILogService } from 'vs/platform/log/common/log'; export class ContextViewService implements IContextViewService { public _serviceBrand: any; @@ -17,7 +18,8 @@ export class ContextViewService implements IContextViewService { constructor( container: HTMLElement, @ITelemetryService telemetryService: ITelemetryService, - @IMessageService messageService: IMessageService + @IMessageService messageService: IMessageService, + @ILogService private logService: ILogService ) { this.contextView = new ContextView(container); } @@ -29,10 +31,12 @@ export class ContextViewService implements IContextViewService { // ContextView public setContainer(container: HTMLElement): void { + this.logService.trace('ContextViewService#setContainer'); this.contextView.setContainer(container); } public showContextView(delegate: IContextViewDelegate): void { + this.logService.trace('ContextViewService#showContextView'); this.contextView.show(delegate); } @@ -41,6 +45,7 @@ export class ContextViewService implements IContextViewService { } public hideContextView(data?: any): void { + this.logService.trace('ContextViewService#hideContextView'); this.contextView.hide(data); } } \ No newline at end of file From 7923fa940ad3ea4aeac5eda1b3decb07bca96a57 Mon Sep 17 00:00:00 2001 From: Joao Moreno <jomo@microsoft.com> Date: Tue, 13 Feb 2018 11:57:51 +0100 Subject: [PATCH 052/362] request service: logs related to #41680 --- src/vs/platform/request/node/requestService.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index 8b6f3fabd19..8571028ceb6 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -11,6 +11,7 @@ import { IRequestOptions, IRequestContext, IRequestFunction, request } from 'vs/ import { getProxyAgent } from 'vs/base/node/proxy'; import { IRequestService, IHTTPConfiguration } from 'vs/platform/request/node/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILogService } from '../../log/common/log'; /** * This service exposes the `request` API, while using the global @@ -26,7 +27,8 @@ export class RequestService implements IRequestService { private disposables: IDisposable[] = []; constructor( - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @ILogService private logService: ILogService ) { this.configure(configurationService.getValue<IHTTPConfiguration>()); configurationService.onDidChangeConfiguration(() => this.configure(configurationService.getValue()), this, this.disposables); @@ -39,6 +41,8 @@ export class RequestService implements IRequestService { } async request(options: IRequestOptions, requestFn: IRequestFunction = request): TPromise<IRequestContext> { + this.logService.trace('RequestService#request', options.url); + const { proxyUrl, strictSSL } = this; options.agent = options.agent || await getProxyAgent(options.url, { proxyUrl, strictSSL }); From e7208c05e41607ecc590ff3ab37667c75443298d Mon Sep 17 00:00:00 2001 From: Joao Moreno <jomo@microsoft.com> Date: Tue, 13 Feb 2018 12:16:28 +0100 Subject: [PATCH 053/362] fix compile error --- src/vs/editor/standalone/browser/standaloneServices.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 552156aca88..bb53a47c1fd 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -181,7 +181,7 @@ export class DynamicStandaloneServices extends Disposable { ensure(IKeybindingService, () => this._register(new StandaloneKeybindingService(contextKeyService, commandService, telemetryService, messageService, domElement))); - let contextViewService = ensure(IContextViewService, () => this._register(new ContextViewService(domElement, telemetryService, messageService))); + let contextViewService = ensure(IContextViewService, () => this._register(new ContextViewService(domElement, telemetryService, messageService, new NullLogService()))); ensure(IContextMenuService, () => this._register(new ContextMenuService(domElement, telemetryService, messageService, contextViewService))); From 433c624560cc13e9d80c048d4c3c356fbf165cfc Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu <sasomava@microsoft.com> Date: Tue, 13 Feb 2018 12:35:44 +0100 Subject: [PATCH 054/362] Fix #43466 --- src/vs/workbench/api/node/extHostTreeViews.ts | 2 +- src/vs/workbench/browser/parts/views/customView.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index cbf0e1a75f1..063ea101fd0 100644 --- a/src/vs/workbench/api/node/extHostTreeViews.ts +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -197,7 +197,7 @@ class ExtHostTreeView<T> extends Disposable { } const prefix = parentHandle ? parentHandle : ExtHostTreeView.LABEL_HANDLE_PREFIX; - let elementId = label ? label : basename(resourceUri.path); + let elementId = label ? label : resourceUri ? basename(resourceUri.path) : ''; elementId = elementId.indexOf('/') !== -1 ? elementId.replace('/', '//') : elementId; const existingHandle = this.nodes.has(element) ? this.nodes.get(element).handle : void 0; diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index fd26b0a3372..10fccde4b0d 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -404,7 +404,7 @@ class TreeRenderer implements IRenderer { public renderElement(tree: ITree, node: ITreeItem, templateId: string, templateData: ITreeExplorerTemplateData): void { const resource = node.resourceUri ? URI.revive(node.resourceUri) : null; - const name = node.label || basename(resource.path); + const name = node.label ? node.label : resource ? basename(resource.path) : ''; const icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark; // reset From 72c76bfb093b5f4b8c77485f464ec65549aed204 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann <martinae@microsoft.com> Date: Tue, 13 Feb 2018 13:03:03 +0100 Subject: [PATCH 055/362] [html] update client (for #43317) --- extensions/html/package.json | 2 +- extensions/html/server/package.json | 8 ++++---- extensions/html/server/yarn.lock | 20 ++++++++------------ extensions/html/yarn.lock | 6 +++--- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/extensions/html/package.json b/extensions/html/package.json index f3da0649944..cbbb9640e17 100644 --- a/extensions/html/package.json +++ b/extensions/html/package.json @@ -227,7 +227,7 @@ }, "dependencies": { "vscode-extension-telemetry": "0.0.11", - "vscode-languageclient": "^4.0.0-next.8", + "vscode-languageclient": "^4.0.0-next.9", "vscode-nls": "^3.2.1" }, "devDependencies": { diff --git a/extensions/html/server/package.json b/extensions/html/server/package.json index db666179d8f..9c7681c864b 100644 --- a/extensions/html/server/package.json +++ b/extensions/html/server/package.json @@ -8,13 +8,13 @@ "node": "*" }, "dependencies": { - "vscode-css-languageservice": "^3.0.5", - "vscode-html-languageservice": "^2.0.15", + "vscode-css-languageservice": "^3.0.6", + "vscode-emmet-helper": "1.1.34", + "vscode-html-languageservice": "^2.0.16", "vscode-languageserver": "^4.0.0-next.4", "vscode-languageserver-types": "^3.6.0-next.1", "vscode-nls": "^3.2.1", - "vscode-uri": "^1.0.1", - "vscode-emmet-helper": "1.1.34" + "vscode-uri": "^1.0.1" }, "devDependencies": { "@types/mocha": "2.2.33", diff --git a/extensions/html/server/yarn.lock b/extensions/html/server/yarn.lock index ca465b2f24b..4da15e5bd70 100644 --- a/extensions/html/server/yarn.lock +++ b/extensions/html/server/yarn.lock @@ -18,11 +18,11 @@ jsonc-parser@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.0.tgz#ddcc864ae708e60a7a6dd36daea00172fa8d9272" -vscode-css-languageservice@^3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.5.tgz#8470989c07bbe740db4fa621423e98384d2c342f" +vscode-css-languageservice@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.6.tgz#0e9230347040ba0161fba941885195215ec9be40" dependencies: - vscode-languageserver-types "3.5.0" + vscode-languageserver-types "^3.6.0-next.1" vscode-nls "^2.0.1" vscode-emmet-helper@1.1.34: @@ -33,11 +33,11 @@ vscode-emmet-helper@1.1.34: jsonc-parser "^1.0.0" vscode-languageserver-types "^3.6.0-next.1" -vscode-html-languageservice@^2.0.15: - version "2.0.15" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-2.0.15.tgz#e3ec34c4d62bc636f8a16c9b474e5851ca13aab0" +vscode-html-languageservice@^2.0.16: + version "2.0.16" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-2.0.16.tgz#db5e8307caddedc1acdb5a1fffb919bb9a1184cb" dependencies: - vscode-languageserver-types "3.5.0" + vscode-languageserver-types "^3.6.0-next.1" vscode-nls "^2.0.2" vscode-uri "^1.0.1" @@ -52,10 +52,6 @@ vscode-languageserver-protocol@^3.6.0-next.5: vscode-jsonrpc "^3.6.0-next.1" vscode-languageserver-types "^3.6.0-next.1" -vscode-languageserver-types@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374" - vscode-languageserver-types@^3.6.0-next.1: version "3.6.0-next.1" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0-next.1.tgz#98e488d3f87b666b4ee1a3d89f0023e246d358f3" diff --git a/extensions/html/yarn.lock b/extensions/html/yarn.lock index 47534aeb2d2..607fbdfd6cb 100644 --- a/extensions/html/yarn.lock +++ b/extensions/html/yarn.lock @@ -38,9 +38,9 @@ vscode-jsonrpc@^3.6.0-next.1: version "3.6.0-next.1" resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0-next.1.tgz#3cb463dffe5842d6aec16718ca9252708cd6aabe" -vscode-languageclient@^4.0.0-next.8: - version "4.0.0-next.8" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-4.0.0-next.8.tgz#eb8eef0bf08399924f8fa520cb0b37071086e7c0" +vscode-languageclient@^4.0.0-next.9: + version "4.0.0-next.9" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-4.0.0-next.9.tgz#2a06568f46ee9de3490f85e227d3740a21a03d3a" dependencies: vscode-languageserver-protocol "^3.6.0-next.5" From ea1d638ea721d2ae883f23e8dfc94f5e2fa08acb Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann <martinae@microsoft.com> Date: Tue, 13 Feb 2018 13:03:16 +0100 Subject: [PATCH 056/362] [json] update client (for #43317) --- extensions/json/package.json | 2 +- extensions/json/yarn.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/extensions/json/package.json b/extensions/json/package.json index f551c4f5de8..4bc84caf35a 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -163,7 +163,7 @@ }, "dependencies": { "vscode-extension-telemetry": "0.0.11", - "vscode-languageclient": "^4.0.0-next.7", + "vscode-languageclient": "^4.0.0-next.9", "vscode-nls": "^3.2.1" }, "devDependencies": { diff --git a/extensions/json/yarn.lock b/extensions/json/yarn.lock index 8c75b70d8f0..607fbdfd6cb 100644 --- a/extensions/json/yarn.lock +++ b/extensions/json/yarn.lock @@ -38,15 +38,15 @@ vscode-jsonrpc@^3.6.0-next.1: version "3.6.0-next.1" resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0-next.1.tgz#3cb463dffe5842d6aec16718ca9252708cd6aabe" -vscode-languageclient@^4.0.0-next.7: - version "4.0.0-next.7" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-4.0.0-next.7.tgz#d4d22937a46ad6c0884a678158c4d4a2269a747e" +vscode-languageclient@^4.0.0-next.9: + version "4.0.0-next.9" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-4.0.0-next.9.tgz#2a06568f46ee9de3490f85e227d3740a21a03d3a" dependencies: - vscode-languageserver-protocol "^3.6.0-next.4" + vscode-languageserver-protocol "^3.6.0-next.5" -vscode-languageserver-protocol@^3.6.0-next.4: - version "3.6.0-next.4" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.6.0-next.4.tgz#5b9940e4d6afafd5b63f9731dbd3a9bcc65b3719" +vscode-languageserver-protocol@^3.6.0-next.5: + version "3.6.0-next.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.6.0-next.5.tgz#ed2ec2db759826f753c0a13977dfb2bedc4d31b3" dependencies: vscode-jsonrpc "^3.6.0-next.1" vscode-languageserver-types "^3.6.0-next.1" From 2cb15ab3bb9785cb47fdedeb0d919a23eba506bc Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann <martinae@microsoft.com> Date: Tue, 13 Feb 2018 13:03:25 +0100 Subject: [PATCH 057/362] [css] update client (for #43317) --- extensions/css/package.json | 2 +- extensions/css/server/package.json | 6 +++--- extensions/css/server/yarn.lock | 12 ++++-------- extensions/css/yarn.lock | 6 +++--- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/extensions/css/package.json b/extensions/css/package.json index b4f94d937fd..21b3f36d871 100644 --- a/extensions/css/package.json +++ b/extensions/css/package.json @@ -713,7 +713,7 @@ ] }, "dependencies": { - "vscode-languageclient": "^4.0.0-next.8", + "vscode-languageclient": "^4.0.0-next.9", "vscode-nls": "^3.2.1" }, "devDependencies": { diff --git a/extensions/css/server/package.json b/extensions/css/server/package.json index d789802f6ff..3adb09fc67b 100644 --- a/extensions/css/server/package.json +++ b/extensions/css/server/package.json @@ -8,9 +8,9 @@ "node": "*" }, "dependencies": { - "vscode-css-languageservice": "^3.0.5", - "vscode-languageserver": "^4.0.0-next.4", - "vscode-emmet-helper": "1.1.34" + "vscode-css-languageservice": "^3.0.6", + "vscode-emmet-helper": "1.1.34", + "vscode-languageserver": "^4.0.0-next.4" }, "devDependencies": { "@types/mocha": "2.2.33", diff --git a/extensions/css/server/yarn.lock b/extensions/css/server/yarn.lock index 6c2bc300879..632fac47270 100644 --- a/extensions/css/server/yarn.lock +++ b/extensions/css/server/yarn.lock @@ -18,11 +18,11 @@ jsonc-parser@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.0.tgz#ddcc864ae708e60a7a6dd36daea00172fa8d9272" -vscode-css-languageservice@^3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.5.tgz#8470989c07bbe740db4fa621423e98384d2c342f" +vscode-css-languageservice@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.6.tgz#0e9230347040ba0161fba941885195215ec9be40" dependencies: - vscode-languageserver-types "3.5.0" + vscode-languageserver-types "^3.6.0-next.1" vscode-nls "^2.0.1" vscode-emmet-helper@1.1.34: @@ -44,10 +44,6 @@ vscode-languageserver-protocol@^3.6.0-next.5: vscode-jsonrpc "^3.6.0-next.1" vscode-languageserver-types "^3.6.0-next.1" -vscode-languageserver-types@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374" - vscode-languageserver-types@^3.6.0-next.1: version "3.6.0-next.1" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0-next.1.tgz#98e488d3f87b666b4ee1a3d89f0023e246d358f3" diff --git a/extensions/css/yarn.lock b/extensions/css/yarn.lock index 6e06462fdb0..7ab2a57b25f 100644 --- a/extensions/css/yarn.lock +++ b/extensions/css/yarn.lock @@ -10,9 +10,9 @@ vscode-jsonrpc@^3.6.0-next.1: version "3.6.0-next.1" resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0-next.1.tgz#3cb463dffe5842d6aec16718ca9252708cd6aabe" -vscode-languageclient@^4.0.0-next.8: - version "4.0.0-next.8" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-4.0.0-next.8.tgz#eb8eef0bf08399924f8fa520cb0b37071086e7c0" +vscode-languageclient@^4.0.0-next.9: + version "4.0.0-next.9" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-4.0.0-next.9.tgz#2a06568f46ee9de3490f85e227d3740a21a03d3a" dependencies: vscode-languageserver-protocol "^3.6.0-next.5" From 6c7165424abaada920a86a27a05be7dcc32ed4d2 Mon Sep 17 00:00:00 2001 From: Joao Moreno <jomo@microsoft.com> Date: Tue, 13 Feb 2018 15:32:11 +0100 Subject: [PATCH 058/362] ship vcruntime140 along with inno_updater --- build/win32/code.iss | 1 + 1 file changed, 1 insertion(+) diff --git a/build/win32/code.iss b/build/win32/code.iss index 34b6c7a0905..ab4a702e9d5 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -70,6 +70,7 @@ Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{ [Files] Source: "*"; Excludes: "inno_updater.exe"; DestDir: "{code:GetDestDir}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "inno_updater.exe"; DestDir: "{app}\tools"; Flags: ignoreversion +Source: "vcruntime140.dll"; DestDir: "{app}\tools"; Flags: ignoreversion [Icons] Name: "{group}\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; AppUserModelID: "{#AppUserId}" From f9ab6993948c720bc43e650a511c50339e16ba20 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero <benjpas@microsoft.com> Date: Tue, 13 Feb 2018 15:43:11 +0100 Subject: [PATCH 059/362] notifications - add a toolbar with close action --- src/vs/base/common/actions.ts | 3 +- .../browser/media/close-inverse.svg | 1 + .../notification/browser/media/close.svg | 1 + .../browser/media/notificationList.css | 54 ++++++++++--- .../notification/browser/notificationList.ts | 2 +- .../browser/notificationService.ts | 4 +- .../browser/notificationViewer.ts | 75 +++++++++++++++++-- 7 files changed, 118 insertions(+), 22 deletions(-) create mode 100644 src/vs/workbench/services/notification/browser/media/close-inverse.svg create mode 100644 src/vs/workbench/services/notification/browser/media/close.svg diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index bf62845dc0d..691139b6c2f 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -233,6 +233,7 @@ export class ActionRunner implements IActionRunner { } public dispose(): void { - // noop + this._onDidBeforeRun.dispose(); + this._onDidRun.dispose(); } } diff --git a/src/vs/workbench/services/notification/browser/media/close-inverse.svg b/src/vs/workbench/services/notification/browser/media/close-inverse.svg new file mode 100644 index 00000000000..751e89b3b02 --- /dev/null +++ b/src/vs/workbench/services/notification/browser/media/close-inverse.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#e8e8e8" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg> \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/media/close.svg b/src/vs/workbench/services/notification/browser/media/close.svg new file mode 100644 index 00000000000..fde34404d4e --- /dev/null +++ b/src/vs/workbench/services/notification/browser/media/close.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#424242" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg> \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/media/notificationList.css b/src/vs/workbench/services/notification/browser/media/notificationList.css index 898df38375f..1d072aabeeb 100644 --- a/src/vs/workbench/services/notification/browser/media/notificationList.css +++ b/src/vs/workbench/services/notification/browser/media/notificationList.css @@ -10,7 +10,7 @@ left: 50%; margin-left: -300px; display: none; - background: #1E1E1E; /* TODO make themable */ + background: #1E1E1E; /* TODO@notification make themable */ } .monaco-workbench > .notifications-list-container.visible { @@ -18,7 +18,7 @@ } .monaco-workbench > .notifications-list-container .monaco-list-row { - border-left: 2px solid #323232; /* TODO make themable */ + border-left: 2px solid #323232; /* TODO@notification make themable */ border-right: 2px solid #323232; border-bottom: 2px solid #323232; } @@ -32,7 +32,7 @@ .monaco-workbench > .notifications-list-container .notification-list-item { display: flex; flex-direction: column; - padding: 5px; + padding: 10px 5px; height: 100%; box-sizing: border-box; } @@ -47,13 +47,13 @@ /** Notification: Icon */ .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon { + flex: 0 0 16px; height: 22px; margin-right: 4px; margin-left: 4px; background-position: center; background-repeat: no-repeat; background-size: cover; - flex: 0 0 16px; } .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-info { @@ -68,15 +68,18 @@ background-image: url('notification-error.svg'); } -.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-info { +.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-info, +.hc-black .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-info { background-image: url('notification-info-inverse.svg'); } -.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-warning { +.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-warning, +.hc-black .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-warning { background-image: url('notification-warning-inverse.svg'); } -.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-error { +.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-error, +.hc-black .mocnaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-icon.icon-error { background-image: url('notification-error-inverse.svg'); } @@ -86,17 +89,49 @@ line-height: 22px; overflow: hidden; text-overflow: ellipsis; + flex: 1; /* let the message always grow */ } .monaco-workbench > .notifications-list-container .notification-list-item.expanded .notification-list-item-message { white-space: normal; } +/** Notification: Toolbar Container */ + +.monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-toolbar-container { + display: none; +} + +.monaco-workbench > .notifications-list-container .notification-list-item:hover .notification-list-item-toolbar-container, +.monaco-workbench > .notifications-list-container .notification-list-item.expanded .notification-list-item-toolbar-container { + display: block; +} + +.monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .action-label { + flex: 0 0 16px; + width: 16px; + height: 22px; + margin-right: 4px; + margin-left: 4px; + background-position: center; + background-repeat: no-repeat; +} + +.vs .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .close-notification-action { + background-image: url('close.svg'); +} + +.vs-dark .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .close-notification-action, +.hc-black .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .close-notification-action { + background-image: url('close-inverse.svg'); +} + /** Notification: Details Row */ .monaco-workbench > .notifications-list-container .notification-list-item > .notification-list-item-details-row { display: flex; align-items: center; + padding: 0 5px; } /** Notification: Source */ @@ -105,16 +140,15 @@ opacity: 0.7; flex: 1; font-size: 12px; - padding-left: 4px; /* aligns with severity icon */ } /** Notification: Actions */ .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-actions-container .monaco-button { max-width: fit-content; - padding: 5px; + padding: 5px 10px; } .monaco-workbench > .notifications-list-container .notification-list-item .notification-list-item-actions-container .monaco-button:not(:first-of-type) { - margin-left: 5px; + margin-left: 10px; } \ No newline at end of file diff --git a/src/vs/workbench/services/notification/browser/notificationList.ts b/src/vs/workbench/services/notification/browser/notificationList.ts index de36270a701..ee9117b9b5d 100644 --- a/src/vs/workbench/services/notification/browser/notificationList.ts +++ b/src/vs/workbench/services/notification/browser/notificationList.ts @@ -79,7 +79,7 @@ export class NotificationList extends Themable { this.list.setSelection([index]); this.list.setFocus([index]); - setTimeout(() => this.list.domFocus()); // TODO why? + setTimeout(() => this.list.domFocus()); // TODO@notification why? }); this.container.appendChild(this.listContainer); diff --git a/src/vs/workbench/services/notification/browser/notificationService.ts b/src/vs/workbench/services/notification/browser/notificationService.ts index a03abcf13b9..9799042372c 100644 --- a/src/vs/workbench/services/notification/browser/notificationService.ts +++ b/src/vs/workbench/services/notification/browser/notificationService.ts @@ -21,7 +21,7 @@ export class NotificationService implements INotificationService { container: HTMLElement, @IInstantiationService private instantiationService: IInstantiationService ) { - // TODO remove me + // TODO@notification remove me setTimeout(() => { this.notify({ severity: Severity.Info, message: 'This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com). This is a info message with a [link](https://code.visualstudio.com).' }); this.notify({ @@ -35,7 +35,7 @@ export class NotificationService implements INotificationService { } private createHandler(): void { - // TODO should this be a setter to pass in from outside? + // TODO@notification should this be a setter to pass in from outside? this.handler = this.instantiationService.createInstance(NotificationList, document.getElementById('workbench.main.container')); } diff --git a/src/vs/workbench/services/notification/browser/notificationViewer.ts b/src/vs/workbench/services/notification/browser/notificationViewer.ts index d856e5fdc58..6e7ab34782c 100644 --- a/src/vs/workbench/services/notification/browser/notificationViewer.ts +++ b/src/vs/workbench/services/notification/browser/notificationViewer.ts @@ -19,10 +19,16 @@ import { Button } from 'vs/base/browser/ui/button/button'; import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Action, ActionRunner, IAction } from 'vs/base/common/actions'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; export class NotificationsDelegate implements IDelegate<INotificationViewItem> { - private static readonly ROW_HEIGHT = 32; + private static readonly DEFAULT_HEIGHT = 42; + private static readonly LINE_HEIGHT = 22; private offsetHelper: HTMLElement; @@ -45,12 +51,17 @@ export class NotificationsDelegate implements IDelegate<INotificationViewItem> { public getHeight(element: INotificationViewItem): number { if (!element.expanded) { - return NotificationsDelegate.ROW_HEIGHT; + return NotificationsDelegate.DEFAULT_HEIGHT; } - const preferredMessageRows = this.computePreferredRows(element.message); + let expandedHeight = NotificationsDelegate.DEFAULT_HEIGHT * 2 /* make extra room for source/buttons */; - return NotificationsDelegate.ROW_HEIGHT * (preferredMessageRows + 1); + const preferredMessageRows = this.computePreferredRows(element.message); + if (preferredMessageRows > 1) { + expandedHeight += preferredMessageRows * NotificationsDelegate.LINE_HEIGHT; + } + + return expandedHeight; } private computePreferredRows(message: IMarkdownString): number { @@ -82,10 +93,12 @@ export class NotificationsDelegate implements IDelegate<INotificationViewItem> { export interface INotificationTemplateData { container: HTMLElement; + toDispose: IDisposable[]; mainRow: HTMLElement; icon: HTMLElement; message: HTMLElement; + toolbar: ActionBar; detailsRow: HTMLElement; source: HTMLElement; @@ -110,16 +123,48 @@ class NotificationMarkdownRenderer { } } +class CloseNotificationAction extends Action { + + public static readonly ID = 'workbench.action.closeNotification'; + public static readonly LABEL = localize('closeNotification', "Close Notification"); + + constructor( + id: string, + label: string + ) { + super(id, label, 'close-notification-action'); + } + + public run(context?: any): TPromise<any> { + return TPromise.as(void 0); // TODO@notification + } +} + +class CloseNotificationActionRunner extends ActionRunner { + + constructor(private context: INotificationViewItem) { + super(); + } + + public run(action: IAction, context?: any): TPromise<void> { + return super.run(action, this.context); + } +} + export class NotificationRenderer implements IRenderer<INotificationViewItem, INotificationTemplateData> { public static readonly TEMPLATE_ID = 'notification'; private static readonly SEVERITIES: ('info' | 'warning' | 'error')[] = ['info', 'warning', 'error']; + private closeNotificationAction: CloseNotificationAction; + constructor( @IOpenerService private openerService: IOpenerService, - @IThemeService private themeService: IThemeService + @IThemeService private themeService: IThemeService, + @IInstantiationService instantiationService: IInstantiationService ) { + this.closeNotificationAction = instantiationService.createInstance(CloseNotificationAction, CloseNotificationAction.ID, CloseNotificationAction.LABEL); } public get templateId() { @@ -128,6 +173,7 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN public renderTemplate(container: HTMLElement): INotificationTemplateData { const data: INotificationTemplateData = Object.create(null); + data.toDispose = []; // Container data.container = document.createElement('div'); @@ -145,6 +191,13 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN data.message = document.createElement('div'); addClass(data.message, 'notification-list-item-message'); + // Toolbar + const toolbarContainer = document.createElement('div'); + addClass(toolbarContainer, 'notification-list-item-toolbar-container'); + + data.toolbar = new ActionBar(toolbarContainer, { ariaLabel: localize('notificationActions', "Notification actions") }); + data.toolbar.push(this.closeNotificationAction, { icon: true, label: false }); + // Details Row data.detailsRow = document.createElement('div'); addClass(data.detailsRow, 'notification-list-item-details-row'); @@ -153,7 +206,7 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN data.source = document.createElement('div'); addClass(data.source, 'notification-list-item-source'); - // Actions Container + // Buttons Container data.actionsContainer = document.createElement('div'); addClass(data.actionsContainer, 'notification-list-item-actions-container'); @@ -162,6 +215,7 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN data.container.appendChild(data.mainRow); data.mainRow.appendChild(data.icon); data.mainRow.appendChild(data.message); + data.mainRow.appendChild(toolbarContainer); data.container.appendChild(data.detailsRow); data.detailsRow.appendChild(data.source); @@ -196,6 +250,10 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN clearNode(data.message); data.message.appendChild(NotificationMarkdownRenderer.render(element.message, (content: string) => this.openerService.open(URI.parse(content)).then(void 0, onUnexpectedError))); + // Toolbar + data.toolbar.actionRunner = new CloseNotificationActionRunner(element); + data.toDispose.push(data.toolbar.actionRunner); + // Source if (element.expanded) { data.source.innerText = localize('notificationSource', "Source: {0}", element.source); @@ -208,7 +266,7 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN if (element.expanded) { element.actions.forEach(action => { const button = new Button(data.actionsContainer); - attachButtonStyler(button, this.themeService); // TODO dispose + data.toDispose.push(attachButtonStyler(button, this.themeService)); button.label = action.label; button.onDidClick(() => action.run()); @@ -217,6 +275,7 @@ export class NotificationRenderer implements IRenderer<INotificationViewItem, IN } public disposeTemplate(templateData: INotificationTemplateData): void { - // Method not implemented + templateData.toolbar.dispose(); + templateData.toDispose = dispose(templateData.toDispose); } } \ No newline at end of file From 5361361944ef14dbd8750292ff1d2d2d3300d68b Mon Sep 17 00:00:00 2001 From: Daniel Imms <daimms@microsoft.com> Date: Tue, 13 Feb 2018 07:14:20 -0800 Subject: [PATCH 060/362] Reuse listener code between create and split --- .../parts/terminal/common/terminalService.ts | 12 +++++++----- .../terminal/electron-browser/terminalService.ts | 9 +++------ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index 7dcdd2acc2d..5e6008c3665 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -249,16 +249,18 @@ export abstract class TerminalService implements ITerminalService { } const instance = tab.split(this._terminalFocusContextKey, this.configHelper, {}); - // TOOD: The below should be shared with ITerminalService.createInstance - tab.addDisposable(tab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed)); - instance.addDisposable(instance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed)); - instance.addDisposable(instance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged)); - instance.addDisposable(instance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady)); + this._initInstanceListeners(instance); this._onInstancesChanged.fire(); this._terminalTabs.forEach((t, i) => t.setVisible(i === this._activeTabIndex)); } + protected _initInstanceListeners(instance: ITerminalInstance): void { + instance.addDisposable(instance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed)); + instance.addDisposable(instance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged)); + instance.addDisposable(instance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady)); + } + private _getTabForInstance(instance: ITerminalInstance): ITerminalTab { for (let i = 0; i < this._terminalTabs.length; i++) { const tab = this._terminalTabs[i]; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 53ebe938812..bae27a7004c 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -58,19 +58,16 @@ export class TerminalService extends AbstractTerminalService implements ITermina this._terminalContainer, shell); this._terminalTabs.push(terminalTab); - const terminalInstance = terminalTab.terminalInstances[0]; + const instance = terminalTab.terminalInstances[0]; terminalTab.addDisposable(terminalTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed)); - terminalInstance.addDisposable(terminalInstance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed)); - terminalInstance.addDisposable(terminalInstance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged)); - terminalInstance.addDisposable(terminalInstance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady)); - this.terminalInstances.push(terminalInstance); + this._initInstanceListeners(instance); if (this.terminalInstances.length === 1) { // It's the first instance so it should be made active automatically this.setActiveInstanceByIndex(0); } this._onInstancesChanged.fire(); this._suggestShellChange(wasNewTerminalAction); - return terminalInstance; + return instance; } public focusFindWidget(): TPromise<void> { From 9ec86ada93038915310ebead5d17d65b8a053382 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero <benjpas@microsoft.com> Date: Tue, 13 Feb 2018 16:21:02 +0100 Subject: [PATCH 061/362] notifications - add more actions to expand/collapse --- .../browser/media/down-inverse.svg | 1 + .../notification/browser/media/down.svg | 1 + .../browser/media/notificationList.css | 33 ++++++++-- .../notification/browser/media/up-inverse.svg | 1 + .../notification/browser/media/up.svg | 1 + .../notification/browser/notificationList.ts | 18 ++++-- .../browser/notificationViewer.ts | 62 +++++++++++++++++-- .../notification/common/notificationsModel.ts | 22 +++++++ 8 files changed, 122 insertions(+), 17 deletions(-) create mode 100755 src/vs/workbench/services/notification/browser/media/down-inverse.svg create mode 100755 src/vs/workbench/services/notification/browser/media/down.svg create mode 100755 src/vs/workbench/services/notification/browser/media/up-inverse.svg create mode 100755 src/vs/workbench/services/notification/browser/media/up.svg diff --git a/src/vs/workbench/services/notification/browser/media/down-inverse.svg b/src/vs/workbench/services/notification/browser/media/down-inverse.svg new file mode 100755 index 00000000000..f914c8bff14 --- /dev/null +++ b/src/vs/workbench/services/notification/browser/media/down-inverse.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#2d2d30;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>CollapseChevronDown_md_16x + `; + + return withRandomFileEditor(contents, 'html', (editor, doc) => { + editor.selections = [new Selection(1, 2, 1, 2)]; + const promise = wrapWithAbbreviation({ abbreviation: 'ul>li>a' }); + if (!promise) { + assert.equal(1, 2, 'Wrap returned undefined instead of promise.'); + return Promise.resolve(); + } + return promise.then(() => { + assert.equal(editor.document.getText(), expectedContents); + return Promise.resolve(); + }); + }); + }); + test('Wrap individual lines with abbreviation', () => { const contents = `