diff --git a/.github/new_release.yml b/.github/new_release.yml index a17089e852d8e..7482b60b108ea 100644 --- a/.github/new_release.yml +++ b/.github/new_release.yml @@ -1,6 +1,6 @@ { newReleaseLabel: 'new release', newReleaseColor: '006b75', - daysAfterRelease: 7, + daysAfterRelease: 5, perform: true -} \ No newline at end of file +} diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index 3ec638c2f2292..acc7d90bfb7f2 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -20,7 +20,6 @@ const sourcemaps = require('gulp-sourcemaps'); const nlsDev = require('vscode-nls-dev'); const root = path.dirname(__dirname); const commit = util.getVersion(root); -const i18n = require('./lib/i18n'); const plumber = require('gulp-plumber'); const extensionsPath = path.join(path.dirname(__dirname), 'extensions'); @@ -32,8 +31,6 @@ 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 tasks = compilations.map(function (tsconfigFile) { const absolutePath = path.join(extensionsPath, tsconfigFile); const relativeDirname = path.dirname(tsconfigFile); @@ -58,7 +55,6 @@ const tasks = compilations.map(function (tsconfigFile) { const srcBase = path.join(root, 'src'); const src = path.join(srcBase, '**'); const out = path.join(root, 'out'); - const i18nPath = path.join(__dirname, '..', 'i18n'); const baseUrl = getBaseUrl(out); let headerId, headerOut; @@ -102,9 +98,9 @@ const tasks = compilations.map(function (tsconfigFile) { sourceRoot: '../src' })) .pipe(tsFilter.restore) - .pipe(build ? nlsDev.createAdditionalLanguageFiles(languages, i18nPath, out) : es.through()) .pipe(build ? nlsDev.bundleMetaDataFiles(headerId, headerOut) : es.through()) - .pipe(build ? nlsDev.bundleLanguageFiles() : es.through()) + // Filter out *.nls.json file. We needed them only to bundle meta data file. + .pipe(filter(['**', '!**/*.nls.json'])) .pipe(reporter.end(emitError)); return es.duplex(input, output); diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 30a1563d7b9c1..47f243676b54d 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -17,14 +17,12 @@ const vfs = require('vinyl-fs'); const rename = require('gulp-rename'); const replace = require('gulp-replace'); const filter = require('gulp-filter'); -const buffer = require('gulp-buffer'); const json = require('gulp-json-editor'); const _ = require('underscore'); const util = require('./lib/util'); const ext = require('./lib/extensions'); const buildfile = require('../src/buildfile'); 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 @@ -98,8 +96,6 @@ const BUNDLED_FILE_HEADER = [ ' *--------------------------------------------------------*/' ].join('\n'); -const languages = i18n.defaultLanguages.concat([]); // i18n.defaultLanguages.concat(process.env.VSCODE_QUALITY !== 'stable' ? i18n.extraLanguages : []); - gulp.task('clean-optimized-vscode', util.rimraf('out-vscode')); gulp.task('optimize-vscode', ['clean-optimized-vscode', 'compile-build', 'compile-extensions-build'], common.optimizeTask({ entryPoints: vscodeEntryPoints, @@ -108,7 +104,6 @@ gulp.task('optimize-vscode', ['clean-optimized-vscode', 'compile-build', 'compil loaderConfig: common.loaderConfig(nodeModules), header: BUNDLED_FILE_HEADER, out: 'out-vscode', - languages: languages, bundleInfo: undefined })); @@ -244,15 +239,8 @@ function packageTask(platform, arch, opts) { .filter(({ name }) => builtInExtensions.every(b => b.name !== name)); const localExtensions = es.merge(...localExtensionDescriptions.map(extension => { - const nlsFilter = filter('**/*.nls.json', { restore: true }); - return ext.fromLocal(extension.path) - .pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`)) - // // TODO@Dirk: this filter / buffer is here to make sure the nls.json files are buffered - .pipe(nlsFilter) - .pipe(buffer()) - .pipe(nlsDev.createAdditionalLanguageFiles(languages, path.join(__dirname, '..', 'i18n'))) - .pipe(nlsFilter.restore); + .pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`)); })); const localExtensionDependencies = gulp.src('extensions/node_modules/**', { base: '.' }); diff --git a/build/lib/optimize.js b/build/lib/optimize.js index 2693d9f519aae..3599086a4b43b 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -17,7 +17,6 @@ var concat = require("gulp-concat"); var VinylFile = require("vinyl"); var bundle = require("./bundle"); var util = require("./util"); -var i18n = require("./i18n"); var gulpUtil = require("gulp-util"); var flatmap = require("gulp-flatmap"); var pump = require("pump"); @@ -163,10 +162,6 @@ function optimizeTask(opts) { sourceRoot: null, addComment: true, includeContent: true - })) - .pipe(i18n.processNlsFiles({ - fileHeader: bundledFileHeader, - languages: opts.languages })) .pipe(gulp.dest(out)); }; diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index b5636ffb8ef2d..7c5f15309c3c6 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -18,7 +18,6 @@ import * as concat from 'gulp-concat'; import * as VinylFile from 'vinyl'; import * as bundle from './bundle'; import * as util from './util'; -import * as i18n from './i18n'; import * as gulpUtil from 'gulp-util'; import * as flatmap from 'gulp-flatmap'; import * as pump from 'pump'; @@ -160,11 +159,8 @@ export interface IOptimizeTaskOpts { * (out folder name) */ out: string; - /** - * (languages to process) - */ - languages: i18n.Language[]; } + export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStream { const entryPoints = opts.entryPoints; const otherSources = opts.otherSources; @@ -233,10 +229,6 @@ export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStr addComment: true, includeContent: true })) - .pipe(i18n.processNlsFiles({ - fileHeader: bundledFileHeader, - languages: opts.languages - })) .pipe(gulp.dest(out)); }; } diff --git a/build/tfs/linux/release.sh b/build/tfs/linux/release.sh index 8843da6773426..ad74ce0a38561 100755 --- a/build/tfs/linux/release.sh +++ b/build/tfs/linux/release.sh @@ -31,10 +31,10 @@ DEB_PATH="$REPO/.build/linux/deb/$DEB_ARCH/deb/$DEB_FILENAME" node build/tfs/common/publish.js $VSCODE_QUALITY $PLATFORM_DEB package $DEB_FILENAME $VERSION true $DEB_PATH -# RPM_FILENAME="$(ls $REPO/.build/linux/rpm/$RPM_ARCH/ | grep .rpm)" -# RPM_PATH="$REPO/.build/linux/rpm/$RPM_ARCH/$RPM_FILENAME" +RPM_FILENAME="$(ls $REPO/.build/linux/rpm/$RPM_ARCH/ | grep .rpm)" +RPM_PATH="$REPO/.build/linux/rpm/$RPM_ARCH/$RPM_FILENAME" -# node build/tfs/common/publish.js $VSCODE_QUALITY $PLATFORM_RPM package $RPM_FILENAME $VERSION true $RPM_PATH +node build/tfs/common/publish.js $VSCODE_QUALITY $PLATFORM_RPM package $RPM_FILENAME $VERSION true $RPM_PATH # SNAP_FILENAME="$(ls $REPO/.build/linux/snap/$ARCH/ | grep .snap)" # SNAP_PATH="$REPO/.build/linux/snap/$ARCH/$SNAP_FILENAME" diff --git a/build/tfs/product-build.yml b/build/tfs/product-build.yml index 3e1a0246c74ed..4e8f85af18f18 100644 --- a/build/tfs/product-build.yml +++ b/build/tfs/product-build.yml @@ -278,7 +278,7 @@ phases: - script: | set -e npm run gulp -- "vscode-linux-$(VSCODE_ARCH)-build-deb" - # npm run gulp -- "vscode-linux-$(VSCODE_ARCH)-build-rpm" + npm run gulp -- "vscode-linux-$(VSCODE_ARCH)-build-rpm" #npm run gulp -- "vscode-linux-$(VSCODE_ARCH)-build-snap" AZURE_DOCUMENTDB_MASTERKEY="$(AZURE_DOCUMENTDB_MASTERKEY)" \ @@ -334,7 +334,7 @@ phases: - script: | set -e npm run gulp -- "vscode-linux-$(VSCODE_ARCH)-build-deb" - # npm run gulp -- "vscode-linux-$(VSCODE_ARCH)-build-rpm" + npm run gulp -- "vscode-linux-$(VSCODE_ARCH)-build-rpm" #npm run gulp -- "vscode-linux-$(VSCODE_ARCH)-build-snap" AZURE_DOCUMENTDB_MASTERKEY="$(AZURE_DOCUMENTDB_MASTERKEY)" \ diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index 049257e3c67e7..30c1ca68b1c52 100644 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScript.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/TypeScript-TmLanguage/commit/5629dc0e736de7e26b7f86db725bea2e3a67eb80", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/7bf8960f7042474b10b519f39339fc527907ce16", "name": "JavaScript (with React support)", "scopeName": "source.js", "patterns": [ @@ -337,7 +337,7 @@ "patterns": [ { "name": "meta.var-single-variable.expr.js", - "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*([\\(]\\s*([\\{\\[]\\s*)?$)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.js entity.name.function.js" @@ -577,7 +577,7 @@ } }, { - "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*([\\(]\\s*([\\{\\[]\\s*)?$)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js" @@ -809,7 +809,7 @@ "include": "#comment" }, { - "match": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\?)?(?=(\\?\\s*)?\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\?)?(?=(\\?\\s*)?\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*([\\(]\\s*([\\{\\[]\\s*)?$)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.js entity.name.function.js" @@ -2536,11 +2536,11 @@ }, { "name": "keyword.operator.expression.in.js", - "match": "(?)\n ))\n ))\n)) |\n(:\\s*([\\(]\\s*([\\{\\[]\\s*)?$)))", + "captures": { + "1": { + "name": "storage.modifier.js" + }, + "2": { + "name": "keyword.operator.rest.js" + }, + "3": { + "name": "entity.name.function.js variable.language.this.js" + }, + "4": { + "name": "entity.name.function.js" + }, + "5": { + "name": "keyword.operator.optional.js" + } + } + }, + { + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*([\\(]\\s*([\\{\\[]\\s*)?$)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.js.jsx entity.name.function.js.jsx" @@ -577,7 +577,7 @@ } }, { - "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*([\\(]\\s*([\\{\\[]\\s*)?$)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js.jsx" @@ -809,7 +809,7 @@ "include": "#comment" }, { - "match": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\?)?(?=(\\?\\s*)?\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\?)?(?=(\\?\\s*)?\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*([\\(]\\s*([\\{\\[]\\s*)?$)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.js.jsx entity.name.function.js.jsx" @@ -2536,11 +2536,11 @@ }, { "name": "keyword.operator.expression.in.js.jsx", - "match": "(?)\n ))\n ))\n)) |\n(:\\s*([\\(]\\s*([\\{\\[]\\s*)?$)))", + "captures": { + "1": { + "name": "storage.modifier.js.jsx" + }, + "2": { + "name": "keyword.operator.rest.js.jsx" + }, + "3": { + "name": "entity.name.function.js.jsx variable.language.this.js.jsx" + }, + "4": { + "name": "entity.name.function.js.jsx" + }, + "5": { + "name": "keyword.operator.optional.js.jsx" + } + } + }, + { + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.ts entity.name.function.ts" @@ -574,7 +574,7 @@ } }, { - "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.ts" @@ -806,7 +806,7 @@ "include": "#comment" }, { - "match": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\?)?(?=(\\?\\s*)?\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\?)?(?=(\\?\\s*)?\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.ts entity.name.function.ts" @@ -2570,11 +2570,11 @@ }, { "name": "keyword.operator.expression.in.ts", - "match": "(?)\n ))\n ))\n)) |\n(:\\s*((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?[\\(]\\s*([\\{\\[]\\s*)?$)))", + "captures": { + "1": { + "name": "storage.modifier.ts" + }, + "2": { + "name": "keyword.operator.rest.ts" + }, + "3": { + "name": "entity.name.function.ts variable.language.this.ts" + }, + "4": { + "name": "entity.name.function.ts" + }, + "5": { + "name": "keyword.operator.optional.ts" + } + } + }, + { + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "begin": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*([\\(]\\s*([\\{\\[]\\s*)?$)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "beginCaptures": { "1": { "name": "meta.definition.variable.tsx entity.name.function.tsx" @@ -577,7 +577,7 @@ } }, { - "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(?:(?)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*([\\(]\\s*([\\{\\[]\\s*)?$)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "storage.modifier.tsx" @@ -809,7 +809,7 @@ "include": "#comment" }, { - "match": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\?)?(?=(\\?\\s*)?\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)([_$[:alpha:]][_$[:alnum:]]*)(\\?)?(?=(\\?\\s*)?\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*([\\(]\\s*([\\{\\[]\\s*)?$)) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ([\\(]\\s*([\\{\\[]\\s*)?$) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.tsx entity.name.function.tsx" @@ -2536,11 +2536,11 @@ }, { "name": "keyword.operator.expression.in.tsx", - "match": "(?)\n ))\n ))\n)) |\n(:\\s*([\\(]\\s*([\\{\\[]\\s*)?$)))", + "captures": { + "1": { + "name": "storage.modifier.tsx" + }, + "2": { + "name": "keyword.operator.rest.tsx" + }, + "3": { + "name": "entity.name.function.tsx variable.language.this.tsx" + }, + "4": { + "name": "entity.name.function.tsx" + }, + "5": { + "name": "keyword.operator.optional.tsx" + } + } + }, + { + "match": "(?x)(?:(? { +export default class TypeScriptDefinitionProvider extends DefinitionProviderBase implements vscode.DefinitionProvider { + constructor( + client: ITypeScriptServiceClient + ) { + super(client); + } + + public async provideDefinition() { + // Implemented by provideDefinition2 + return undefined; + } + + public async provideDefinition2( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken | boolean + ): Promise { + if (this.client.apiVersion.gte(API.v270)) { + const filepath = this.client.toPath(document.uri); + if (!filepath) { + return undefined; + } + + const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position); + try { + const response = await this.client.execute('definitionAndBoundSpan', args, token); + const locations: Proto.FileSpan[] = (response && response.body && response.body.definitions) || []; + if (!locations) { + return undefined; + } + + const span = response.body.textSpan ? typeConverters.Range.fromTextSpan(response.body.textSpan) : undefined; + return locations + .map(location => { + const loc = typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location); + return { + origin: span, + ...loc, + }; + }); + } catch { + return []; + } + } + return this.getSymbolLocations('definition', document, position, token); } } diff --git a/extensions/typescript-language-features/src/features/organizeImports.ts b/extensions/typescript-language-features/src/features/organizeImports.ts index 7d8697903a7d8..944fcf7c58582 100644 --- a/extensions/typescript-language-features/src/features/organizeImports.ts +++ b/extensions/typescript-language-features/src/features/organizeImports.ts @@ -39,7 +39,7 @@ class OrganizeImportsCommand implements Command { return false; } - const edits = typeconverts.WorkspaceEdit.fromFromFileCodeEdits(this.client, response.body); + const edits = typeconverts.WorkspaceEdit.fromFileCodeEdits(this.client, response.body); return await vscode.workspace.applyEdit(edits); } } diff --git a/extensions/typescript-language-features/src/features/quickFix.ts b/extensions/typescript-language-features/src/features/quickFix.ts index 8a9762def39fb..56232596b22fd 100644 --- a/extensions/typescript-language-features/src/features/quickFix.ts +++ b/extensions/typescript-language-features/src/features/quickFix.ts @@ -92,7 +92,7 @@ class ApplyFixAllCodeAction implements Command { return; } - const edit = typeConverters.WorkspaceEdit.fromFromFileCodeEdits(this.client, combinedCodeFixesResponse.body.changes); + const edit = typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, combinedCodeFixesResponse.body.changes); await vscode.workspace.applyEdit(edit); if (combinedCodeFixesResponse.command) { diff --git a/extensions/typescript-language-features/src/features/refactor.ts b/extensions/typescript-language-features/src/features/refactor.ts index 7fc60d4b2476b..5feec27bcfc2c 100644 --- a/extensions/typescript-language-features/src/features/refactor.ts +++ b/extensions/typescript-language-features/src/features/refactor.ts @@ -4,15 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as fs from 'fs'; - import * as Proto from '../protocol'; import { ITypeScriptServiceClient } from '../typescriptService'; +import API from '../utils/api'; +import { Command, CommandManager } from '../utils/commandManager'; +import { VersionDependentRegistration } from '../utils/dependentRegistration'; import * as typeConverters from '../utils/typeConverters'; import FormattingOptionsManager from './fileConfigurationManager'; -import { CommandManager, Command } from '../utils/commandManager'; -import { VersionDependentRegistration } from '../utils/dependentRegistration'; -import API from '../utils/api'; + class ApplyRefactoringCommand implements Command { public static readonly ID = '_typescript.applyRefactoring'; @@ -35,30 +34,17 @@ class ApplyRefactoringCommand implements Command { action }; const response = await this.client.execute('getEditsForRefactor', args); - if (!response || !response.body || !response.body.edits.length) { + const body = response && response.body; + if (!body || !body.edits.length) { return false; } - for (const edit of response.body.edits) { - try { - await vscode.workspace.openTextDocument(edit.fileName); - } catch { - try { - if (!fs.existsSync(edit.fileName)) { - fs.writeFileSync(edit.fileName, ''); - } - } catch { - // noop - } - } - } - - const edit = typeConverters.WorkspaceEdit.fromFromFileCodeEdits(this.client, response.body.edits); - if (!(await vscode.workspace.applyEdit(edit))) { + const workspaceEdit = await this.toWorkspaceEdit(body); + if (!(await vscode.workspace.applyEdit(workspaceEdit))) { return false; } - const renameLocation = response.body.renameLocation; + const renameLocation = body.renameLocation; if (renameLocation) { await vscode.commands.executeCommand('editor.action.rename', [ document.uri, @@ -67,6 +53,15 @@ class ApplyRefactoringCommand implements Command { } return true; } + + private async toWorkspaceEdit(body: Proto.RefactorEditInfo) { + const workspaceEdit = new vscode.WorkspaceEdit(); + for (const edit of body.edits) { + workspaceEdit.createFile(this.client.toResource(edit.fileName), { ignoreIfExists: true }); + } + typeConverters.WorkspaceEdit.withFileCodeEdits(workspaceEdit, this.client, body.edits); + return workspaceEdit; + } } class SelectRefactorCommand implements Command { diff --git a/extensions/typescript-language-features/src/features/tagCompletion.ts b/extensions/typescript-language-features/src/features/tagCompletion.ts new file mode 100644 index 0000000000000..8a8495180ca37 --- /dev/null +++ b/extensions/typescript-language-features/src/features/tagCompletion.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as Proto from '../protocol'; +import { ITypeScriptServiceClient } from '../typescriptService'; +import API from '../utils/api'; +import { VersionDependentRegistration } from '../utils/dependentRegistration'; +import * as typeConverters from '../utils/typeConverters'; + +class TypeScriptTagCompletion implements vscode.CompletionItemProvider { + constructor( + private readonly client: ITypeScriptServiceClient + ) { } + + async provideCompletionItems( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken, + _context: vscode.CompletionContext + ): Promise { + const filepath = this.client.toPath(document.uri); + if (!filepath) { + return undefined; + } + + const args: Proto.JsxClosingTagRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position); + let body: Proto.TextInsertion | undefined = undefined; + try { + const response = await this.client.execute('jsxClosingTag', args, token); + body = response && response.body; + if (!body) { + return undefined; + } + } catch { + return undefined; + } + + return [this.getCompletion(body)]; + } + + private getCompletion(body: Proto.TextInsertion) { + const completion = new vscode.CompletionItem(body.newText); + completion.insertText = this.getTagSnippet(body); + return completion; + } + + private getTagSnippet(closingTag: Proto.TextInsertion): vscode.SnippetString { + const snippet = new vscode.SnippetString(); + snippet.appendPlaceholder('', 0); + snippet.appendText(closingTag.newText); + return snippet; + } +} + +export function register( + selector: vscode.DocumentSelector, + client: ITypeScriptServiceClient, +) { + return new VersionDependentRegistration(client, API.v300, () => + vscode.languages.registerCompletionItemProvider(selector, + new TypeScriptTagCompletion(client), + '>')); +} diff --git a/extensions/typescript-language-features/src/features/updatePathsOnRename.ts b/extensions/typescript-language-features/src/features/updatePathsOnRename.ts index c8d16ab255903..4e9c0398fea25 100644 --- a/extensions/typescript-language-features/src/features/updatePathsOnRename.ts +++ b/extensions/typescript-language-features/src/features/updatePathsOnRename.ts @@ -34,8 +34,8 @@ export class UpdateImportsOnFileRenameHandler { private readonly fileConfigurationManager: FileConfigurationManager, private readonly _handles: (uri: vscode.Uri) => Promise, ) { - this._onDidRenameSub = vscode.workspace.onDidRenameResource(e => { - this.doRename(e.oldResource, e.newResource); + this._onDidRenameSub = vscode.workspace.onDidRenameFile(e => { + this.doRename(e.oldUri, e.newUri); }); } @@ -227,7 +227,7 @@ export class UpdateImportsOnFileRenameHandler { for (const edit of response.body) { edits.push(await this.fixEdit(edit, isDirectoryRename, oldFile, newFile)); } - return typeConverters.WorkspaceEdit.fromFromFileCodeEdits(this.client, edits); + return typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, edits); } private async fixEdit( diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index a00f7e9a47045..1776fd1b8fc5b 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -91,6 +91,7 @@ export default class LanguageProvider { this.disposables.push((await import('./features/referencesCodeLens')).register(selector, this.description.id, this.client, cachedResponse)); this.disposables.push((await import('./features/rename')).register(selector, this.client)); this.disposables.push((await import('./features/signatureHelp')).register(selector, this.client)); + this.disposables.push((await import('./features/tagCompletion')).register(selector, this.client)); this.disposables.push((await import('./features/typeDefinitions')).register(selector, this.client)); this.disposables.push((await import('./features/workspaceSymbols')).register(this.client, this.description.modeIds)); } diff --git a/extensions/typescript-language-features/src/protocol.d.ts b/extensions/typescript-language-features/src/protocol.d.ts index 0015b80d9276e..6e926eb8d7ee2 100644 --- a/extensions/typescript-language-features/src/protocol.d.ts +++ b/extensions/typescript-language-features/src/protocol.d.ts @@ -1,2 +1,2 @@ import * as Proto from 'typescript/lib/protocol'; -export = Proto; \ No newline at end of file +export = Proto; diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index ef58af1e6350a..67be9e314c06d 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -11,6 +11,11 @@ import { TypeScriptServiceConfiguration } from './utils/configuration'; import Logger from './utils/logger'; import BufferSyncSupport from './features/bufferSyncSupport'; +declare module './protocol' { + export type JsxClosingTagRequestArgs = any; + export type JsxClosingTagResponse = any; +} + export interface ITypeScriptServiceClient { /** * Convert a resource (VS Code) to a normalized path (TypeScript). @@ -54,6 +59,7 @@ export interface ITypeScriptServiceClient { execute(command: 'completionEntryDetails', args: Proto.CompletionDetailsRequestArgs, token?: CancellationToken): Promise; execute(command: 'signatureHelp', args: Proto.SignatureHelpRequestArgs, token?: CancellationToken): Promise; execute(command: 'definition', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise; + execute(command: 'definitionAndBoundSpan', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise; execute(command: 'implementation', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise; execute(command: 'typeDefinition', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise; execute(command: 'references', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise; @@ -78,6 +84,7 @@ export interface ITypeScriptServiceClient { execute(command: 'organizeImports', args: Proto.OrganizeImportsRequestArgs, token?: CancellationToken): Promise; execute(command: 'getOutliningSpans', args: Proto.FileRequestArgs, token: CancellationToken): Promise; execute(command: 'getEditsForFileRename', args: Proto.GetEditsForFileRenameRequestArgs): Promise; + execute(command: 'jsxClosingTag', args: Proto.JsxClosingTagRequestArgs, token: CancellationToken): Promise; execute(command: string, args: any, expectedResult: boolean | CancellationToken, token?: CancellationToken): Promise; executeAsync(command: 'geterr', args: Proto.GeterrRequestArgs, token: CancellationToken): Promise; diff --git a/extensions/typescript-language-features/src/utils/codeAction.ts b/extensions/typescript-language-features/src/utils/codeAction.ts index 984ea6bc9f3f1..d31a40d602d6d 100644 --- a/extensions/typescript-language-features/src/utils/codeAction.ts +++ b/extensions/typescript-language-features/src/utils/codeAction.ts @@ -13,7 +13,7 @@ export function getEditForCodeAction( action: Proto.CodeAction ): WorkspaceEdit | undefined { return action.changes && action.changes.length - ? typeConverters.WorkspaceEdit.fromFromFileCodeEdits(client, action.changes) + ? typeConverters.WorkspaceEdit.fromFileCodeEdits(client, action.changes) : undefined; } diff --git a/extensions/typescript-language-features/src/utils/typeConverters.ts b/extensions/typescript-language-features/src/utils/typeConverters.ts index d65b1468e9d26..8c87338893d94 100644 --- a/extensions/typescript-language-features/src/utils/typeConverters.ts +++ b/extensions/typescript-language-features/src/utils/typeConverters.ts @@ -50,11 +50,18 @@ export namespace TextEdit { } export namespace WorkspaceEdit { - export function fromFromFileCodeEdits( + export function fromFileCodeEdits( + client: ITypeScriptServiceClient, + edits: Iterable + ): vscode.WorkspaceEdit { + return withFileCodeEdits(new vscode.WorkspaceEdit(), client, edits); + + } + export function withFileCodeEdits( + workspaceEdit: vscode.WorkspaceEdit, client: ITypeScriptServiceClient, edits: Iterable ): vscode.WorkspaceEdit { - const workspaceEdit = new vscode.WorkspaceEdit(); for (const edit of edits) { for (const textChange of edit.textChanges) { workspaceEdit.replace(client.toResource(edit.fileName), diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts index 31fe9e34810e8..7211b8f397cce 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/quickInput.test.ts @@ -9,6 +9,13 @@ import * as assert from 'assert'; import { window, commands } from 'vscode'; import { closeAllEditors } from '../utils'; +interface QuickPickExpected { + events: string[]; + activeItems: string[][]; + selectionItems: string[][]; + acceptedItems: string[][]; +} + suite('window namespace tests', function () { suite('QuickInput tests', function () { @@ -20,59 +27,87 @@ suite('window namespace tests', function () { _done(err); }; - const expectedEvents = ['active', 'active', 'selection', 'accept', 'hide']; - const expectedActiveItems = [['eins'], ['zwei']]; - const expectedSelectionItems = [['zwei']]; + const quickPick = createQuickPick({ + events: ['active', 'active', 'selection', 'accept', 'hide'], + activeItems: [['eins'], ['zwei']], + selectionItems: [['zwei']], + acceptedItems: [['zwei']], + }, done); + quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label })); + quickPick.show(); - const quickPick = window.createQuickPick(); - quickPick.onDidChangeActive(items => { - try { - assert.equal('active', expectedEvents.shift()); - const expected = expectedActiveItems.shift(); - assert.deepEqual(items.map(item => item.label), expected); - assert.deepEqual(quickPick.activeItems.map(item => item.label), expected); - } catch (err) { - done(err); - } - }); - quickPick.onDidChangeSelection(items => { - try { - assert.equal('selection', expectedEvents.shift()); - const expected = expectedSelectionItems.shift(); - assert.deepEqual(items.map(item => item.label), expected); - assert.deepEqual(quickPick.selectedItems.map(item => item.label), expected); - } catch (err) { - done(err); - } - }); - quickPick.onDidAccept(() => { - try { - assert.equal('accept', expectedEvents.shift()); - const expected = ['zwei']; - assert.deepEqual(quickPick.activeItems.map(item => item.label), expected); - assert.deepEqual(quickPick.selectedItems.map(item => item.label), expected); - quickPick.dispose(); - } catch (err) { - done(err); - } - }); - quickPick.onDidHide(() => { - try { - assert.equal('hide', expectedEvents.shift()); - done(); - } catch (err) { - done(err); - } - }); + (async () => { + await commands.executeCommand('workbench.action.quickOpenSelectNext'); + await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); + })() + .catch(err => done(err)); + }); + test('createQuickPick, focus second', function (_done) { + let done = (err?: any) => { + done = () => {}; + _done(err); + }; + + const quickPick = createQuickPick({ + events: ['active', 'selection', 'accept', 'hide'], + activeItems: [['zwei']], + selectionItems: [['zwei']], + acceptedItems: [['zwei']], + }, done); quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label })); + quickPick.activeItems = [quickPick.items[1]]; quickPick.show(); (async () => { - await commands.executeCommand('workbench.action.quickOpenSelectNext'); await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); })() .catch(err => done(err)); }); }); }); + +function createQuickPick(expected: QuickPickExpected, done: (err?: any) => void) { + const quickPick = window.createQuickPick(); + quickPick.onDidChangeActive(items => { + try { + assert.equal('active', expected.events.shift()); + const expectedItems = expected.activeItems.shift(); + assert.deepEqual(items.map(item => item.label), expectedItems); + assert.deepEqual(quickPick.activeItems.map(item => item.label), expectedItems); + } catch (err) { + done(err); + } + }); + quickPick.onDidChangeSelection(items => { + try { + assert.equal('selection', expected.events.shift()); + const expectedItems = expected.selectionItems.shift(); + assert.deepEqual(items.map(item => item.label), expectedItems); + assert.deepEqual(quickPick.selectedItems.map(item => item.label), expectedItems); + } catch (err) { + done(err); + } + }); + quickPick.onDidAccept(() => { + try { + assert.equal('accept', expected.events.shift()); + const expectedItems = expected.acceptedItems.shift(); + assert.deepEqual(quickPick.activeItems.map(item => item.label), expectedItems); + assert.deepEqual(quickPick.selectedItems.map(item => item.label), expectedItems); + quickPick.dispose(); + } catch (err) { + done(err); + } + }); + quickPick.onDidHide(() => { + try { + assert.equal('hide', expected.events.shift()); + done(); + } catch (err) { + done(err); + } + }); + + return quickPick; +} \ No newline at end of file diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index 449cc78536a93..87734062b8a00 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -440,6 +440,18 @@ suite('window namespace tests', () => { assert.deepStrictEqual(await picks, ['eins', 'zwei']); }); + test('showQuickPick, keep selection (Microsoft/vscode-azure-account#67)', async function () { + const picks = window.showQuickPick([ + { label: 'eins' }, + { label: 'zwei', picked: true }, + { label: 'drei', picked: true } + ], { + canPickMany: true + }); + await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); + assert.deepStrictEqual((await picks)!.map(pick => pick.label), ['zwei', 'drei']); + }); + test('showQuickPick, undefined on cancel', function () { const source = new CancellationTokenSource(); const p = window.showQuickPick(['eins', 'zwei', 'drei'], undefined, source.token); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 373fc9b1f68dc..1f42a34e5976a 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -7,9 +7,10 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; -import { createRandomFile, deleteFile, closeAllEditors, pathEquals } from '../utils'; -import { join, basename } from 'path'; +import { createRandomFile, deleteFile, closeAllEditors, pathEquals, rndName } from '../utils'; +import { join, posix, basename } from 'path'; import * as fs from 'fs'; +import * as os from 'os'; suite('workspace-namespace', () => { @@ -519,7 +520,6 @@ suite('workspace-namespace', () => { }); }); - test('applyEdit should fail when editing deleted resource', async () => { const resource = await createRandomFile(); @@ -564,7 +564,130 @@ suite('workspace-namespace', () => { let success = await vscode.workspace.applyEdit(edit); assert.equal(success, true); - // let doc = await vscode.workspace.openTextDocument(newUri); - // assert.equal(doc.getText(), 'AFTERBEFORE'); + let doc = await vscode.workspace.openTextDocument(newUri); + assert.equal(doc.getText(), 'AFTERBEFORE'); + }); + + function nameWithUnderscore(uri: vscode.Uri) { + return uri.with({ path: posix.join(posix.dirname(uri.path), `_${posix.basename(uri.path)}`) }); + } + + test('WorkspaceEdit: applying edits before and after rename duplicates resource #42633', async function () { + let docUri = await createRandomFile(); + let newUri = nameWithUnderscore(docUri); + + let we = new vscode.WorkspaceEdit(); + we.insert(docUri, new vscode.Position(0, 0), 'Hello'); + we.insert(docUri, new vscode.Position(0, 0), 'Foo'); + we.renameFile(docUri, newUri); + we.insert(newUri, new vscode.Position(0, 0), 'Bar'); + + assert.ok(await vscode.workspace.applyEdit(we)); + let doc = await vscode.workspace.openTextDocument(newUri); + assert.equal(doc.getText(), 'BarHelloFoo'); + }); + + test('WorkspaceEdit: Problem recreating a renamed resource #42634', async function () { + let docUri = await createRandomFile(); + let newUri = nameWithUnderscore(docUri); + + let we = new vscode.WorkspaceEdit(); + we.insert(docUri, new vscode.Position(0, 0), 'Hello'); + we.insert(docUri, new vscode.Position(0, 0), 'Foo'); + we.renameFile(docUri, newUri); + + we.createFile(docUri); + we.insert(docUri, new vscode.Position(0, 0), 'Bar'); + + assert.ok(await vscode.workspace.applyEdit(we)); + + let newDoc = await vscode.workspace.openTextDocument(newUri); + assert.equal(newDoc.getText(), 'HelloFoo'); + // let doc = await vscode.workspace.openTextDocument(docUri); + // assert.equal(doc.getText(), 'Bar'); + }); + + test('WorkspaceEdit api - after saving a deleted file, it still shows up as deleted. #42667', async function () { + let docUri = await createRandomFile(); + let we = new vscode.WorkspaceEdit(); + we.deleteFile(docUri); + we.insert(docUri, new vscode.Position(0, 0), 'InsertText'); + + assert.ok(!(await vscode.workspace.applyEdit(we))); + try { + await vscode.workspace.openTextDocument(docUri); + assert.ok(false); + } catch (e) { + assert.ok(true); + } + }); + + test('WorkspaceEdit: edit and rename parent folder duplicates resource #42641', async function () { + + let dir = join(os.tmpdir(), 'before-' + rndName()); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } + + let docUri = await createRandomFile('', dir); + let docParent = docUri.with({ path: posix.dirname(docUri.path) }); + let newParent = nameWithUnderscore(docParent); + + let we = new vscode.WorkspaceEdit(); + we.insert(docUri, new vscode.Position(0, 0), 'Hello'); + we.renameFile(docParent, newParent); + + assert.ok(await vscode.workspace.applyEdit(we)); + + try { + await vscode.workspace.openTextDocument(docUri); + assert.ok(false); + } catch (e) { + assert.ok(true); + } + + let newUri = newParent.with({ path: posix.join(newParent.path, posix.basename(docUri.path)) }); + let doc = await vscode.workspace.openTextDocument(newUri); + assert.ok(doc); + + assert.equal(doc.getText(), 'Hello'); + }); + + test('WorkspaceEdit: rename resource followed by edit does not work #42638', async function () { + let docUri = await createRandomFile(); + let newUri = nameWithUnderscore(docUri); + + let we = new vscode.WorkspaceEdit(); + we.renameFile(docUri, newUri); + we.insert(newUri, new vscode.Position(0, 0), 'Hello'); + + assert.ok(await vscode.workspace.applyEdit(we)); + + let doc = await vscode.workspace.openTextDocument(newUri); + assert.equal(doc.getText(), 'Hello'); + }); + + test('WorkspaceEdit: create & override', async function () { + + let docUri = await createRandomFile('before'); + + let we = new vscode.WorkspaceEdit(); + we.createFile(docUri); + assert.ok(!await vscode.workspace.applyEdit(we)); + assert.equal((await vscode.workspace.openTextDocument(docUri)).getText(), 'before'); + + we = new vscode.WorkspaceEdit(); + we.createFile(docUri, { overwrite: true }); + assert.ok(await vscode.workspace.applyEdit(we)); + // todo@ben + // assert.equal((await vscode.workspace.openTextDocument(docUri)).getText(), ''); + }); + + test('WorkspaceEdit: create & ignoreIfExists', async function () { + let docUri = await createRandomFile('before'); + let we = new vscode.WorkspaceEdit(); + we.createFile(docUri, { ignoreIfExists: true }); + assert.ok(await vscode.workspace.applyEdit(we)); + assert.equal((await vscode.workspace.openTextDocument(docUri)).getText(), 'before'); }); }); diff --git a/extensions/vscode-api-tests/src/utils.ts b/extensions/vscode-api-tests/src/utils.ts index 0d869ed787df7..f2b3511598c38 100644 --- a/extensions/vscode-api-tests/src/utils.ts +++ b/extensions/vscode-api-tests/src/utils.ts @@ -10,13 +10,13 @@ import * as fs from 'fs'; import * as os from 'os'; import { join } from 'path'; -function rndName() { +export function rndName() { return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10); } -export function createRandomFile(contents = ''): Thenable { +export function createRandomFile(contents = '', dir: string = os.tmpdir()): Thenable { return new Promise((resolve, reject) => { - const tmpFile = join(os.tmpdir(), rndName()); + const tmpFile = join(dir, rndName()); fs.writeFile(tmpFile, contents, (error) => { if (error) { return reject(error); diff --git a/package.json b/package.json index add4abb298022..7e92f6392d7ed 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "vscode-nsfw": "1.0.17", "vscode-ripgrep": "^1.0.1", "vscode-textmate": "^4.0.0-next.2", - "vscode-xterm": "3.5.0-beta12", + "vscode-xterm": "3.5.0-beta15", "yauzl": "^2.9.1" }, "devDependencies": { diff --git a/src/main.js b/src/main.js index 268e143d53cf0..135637642741f 100644 --- a/src/main.js +++ b/src/main.js @@ -230,50 +230,21 @@ function getNLSConfiguration(locale) { let initialLocale = locale; - function resolveLocale(locale) { - while (locale) { - let candidate = path.join(__dirname, 'vs', 'code', 'electron-main', 'main.nls.') + locale + '.js'; - if (fs.existsSync(candidate)) { - return { locale: initialLocale, availableLanguages: { '*': locale } }; - } else { - let index = locale.lastIndexOf('-'); - if (index > 0) { - locale = locale.substring(0, index); - } else { - locale = undefined; - } - } - } - return undefined; - } - perf.mark('nlsGeneration:start'); - let defaultResult = function (locale) { - let isCoreLanguage = true; - if (locale) { - isCoreLanguage = ['de', 'es', 'fr', 'it', 'ja', 'ko', 'ru', 'zh-cn', 'zh-tw'].some((language) => { - return locale === language || locale.startsWith(language + '-'); - }); - } - if (isCoreLanguage) { - let result = resolveLocale(locale); - perf.mark('nlsGeneration:end'); - return Promise.resolve(result); - } else { - perf.mark('nlsGeneration:end'); - return Promise.resolve({ locale: locale, availableLanguages: {} }); - } + + let defaultResult = function(locale) { + perf.mark('nlsGeneration:end'); + return Promise.resolve({ locale: locale, availableLanguages: {} }); }; try { let commit = product.commit; if (!commit) { - return defaultResult(locale); + return defaultResult(initialLocale); } let configs = getLanguagePackConfigurations(); if (!configs) { - return defaultResult(locale); + return defaultResult(initialLocale); } - let initialLocale = locale; locale = resolveLanguagePackLocale(configs, locale); if (!locale) { return defaultResult(initialLocale); @@ -281,11 +252,11 @@ function getNLSConfiguration(locale) { let packConfig = configs[locale]; let mainPack; if (!packConfig || typeof packConfig.hash !== 'string' || !packConfig.translations || typeof (mainPack = packConfig.translations['vscode']) !== 'string') { - return defaultResult(locale); + return defaultResult(initialLocale); } return exists(mainPack).then((fileExists) => { if (!fileExists) { - return defaultResult(locale); + return defaultResult(initialLocale); } let packId = packConfig.hash + '.' + locale; let cacheRoot = path.join(userData, 'clp', packId); diff --git a/src/typings/vscode-xterm.d.ts b/src/typings/vscode-xterm.d.ts index 356aee471e3a7..8661eec2dd73d 100644 --- a/src/typings/vscode-xterm.d.ts +++ b/src/typings/vscode-xterm.d.ts @@ -670,19 +670,38 @@ declare module 'vscode-xterm' { // Modifications to official .d.ts below declare module 'vscode-xterm' { - interface Terminal { + interface TerminalCore { buffer: { y: number; ybase: number; ydisp: number; x: number; + lines: any[]; + + translateBufferLineToString(lineIndex: number, trimRight: boolean): string; }; + send(text: string): void; + /** * Emit an event on the terminal. */ emit(type: string, data: any): void; + charMeasure?: { height: number, width: number }; + + renderer: { + _renderLayers: any[]; + onIntersectionChange: any; + }; + } + + interface Terminal { + _core: TerminalCore; + + webLinksInit(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void; + winptyCompatInit(): void; + /** * Find the next instance of the term, then scroll to and select it. If it * doesn't exist, do nothing. @@ -698,9 +717,5 @@ declare module 'vscode-xterm' { * @return Whether a result was found. */ findPrevious(term: string): boolean; - - webLinksInit(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void; - winptyCompatInit(): void; - charMeasure?: { height: number, width: number }; } } diff --git a/src/vs/base/browser/ui/centered/centeredViewLayout.ts b/src/vs/base/browser/ui/centered/centeredViewLayout.ts new file mode 100644 index 0000000000000..345a723e5828d --- /dev/null +++ b/src/vs/base/browser/ui/centered/centeredViewLayout.ts @@ -0,0 +1,129 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { SplitView, Orientation, ISplitViewStyles, IView as ISplitViewView } from 'vs/base/browser/ui/splitview/splitview'; +import { $ } from 'vs/base/browser/dom'; +import { Event, mapEvent } from 'vs/base/common/event'; +import { IView } from 'vs/base/browser/ui/grid/gridview'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; + +export interface CenteredViewState { + leftMarginRatio: number; + rightMarginRatio: number; +} + +const GOLDEN_RATIO = { + leftMarginRatio: 0.1909, + rightMarginRatio: 0.1909 +}; + +function createEmptyView() { + return { + element: $('.centered-layout-margin'), + layout: () => undefined, + minimumSize: 60, + maximumSize: Number.POSITIVE_INFINITY, + onDidChange: Event.None + }; +} + +function toSplitViewView(view: IView, getHeight: () => number): ISplitViewView { + return { + element: view.element, + get maximumSize() { return view.maximumWidth; }, + get minimumSize() { return view.minimumWidth; }, + onDidChange: mapEvent(view.onDidChange, e => e && e.width), + layout: size => view.layout(size, getHeight()) + }; +} + +export class CenteredViewLayout { + + private splitView: SplitView; + private width: number = 0; + private height: number = 0; + private style: ISplitViewStyles; + private didLayout = false; + private splitViewDisposables: IDisposable[] = []; + + constructor(private container: HTMLElement, private view: IView, public readonly state: CenteredViewState = GOLDEN_RATIO) { + this.container.appendChild(this.view.element); + } + + layout(width: number, height: number): void { + this.width = width; + this.height = height; + if (this.splitView) { + this.splitView.layout(width); + if (!this.didLayout) { + this.resizeMargins(); + } + } else { + this.view.layout(width, height); + } + this.didLayout = true; + } + + private resizeMargins(): void { + this.splitView.resizeView(0, this.state.leftMarginRatio * this.width); + this.splitView.resizeView(2, this.state.rightMarginRatio * this.width); + } + + isActive(): boolean { + return !!this.splitView; + } + + styles(style: ISplitViewStyles): void { + this.style = style; + if (this.splitView) { + this.splitView.style(this.style); + } + } + + activate(active: boolean): void { + if (active === this.isActive()) { + return; + } + + if (active) { + this.container.removeChild(this.view.element); + this.splitView = new SplitView(this.container, { + inverseAltBehavior: true, + orientation: Orientation.HORIZONTAL, + styles: this.style + }); + + this.splitViewDisposables.push(this.splitView.onDidSashChange(() => { + this.state.leftMarginRatio = this.splitView.getViewSize(0) / this.width; + this.state.rightMarginRatio = this.splitView.getViewSize(2) / this.width; + })); + this.splitViewDisposables.push(this.splitView.onDidSashReset(() => { + this.state.leftMarginRatio = GOLDEN_RATIO.leftMarginRatio; + this.state.rightMarginRatio = GOLDEN_RATIO.rightMarginRatio; + this.resizeMargins(); + })); + + this.splitView.layout(this.width); + this.splitView.addView(toSplitViewView(this.view, () => this.height), 0); + this.splitView.addView(createEmptyView(), this.state.leftMarginRatio * this.width, 0); + this.splitView.addView(createEmptyView(), this.state.rightMarginRatio * this.width, 2); + } else { + this.container.removeChild(this.splitView.el); + this.splitViewDisposables = dispose(this.splitViewDisposables); + this.splitView.dispose(); + this.splitView = undefined; + this.container.appendChild(this.view.element); + } + } + + dispose(): void { + this.splitViewDisposables = dispose(this.splitViewDisposables); + + if (this.splitView) { + this.splitView.dispose(); + this.splitView = undefined; + } + } +} diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index e9b941a942657..15e572c303001 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -467,7 +467,6 @@ export class SerializableGrid extends Grid { result.orientation = orientation; result.restoreViews(firstLeaf.view, orientation, root); result.initialLayoutContext = { width, height, root }; - result.gridview.trySet2x2(); return result; } @@ -496,6 +495,8 @@ export class SerializableGrid extends Grid { this.restoreViewsSize([], this.initialLayoutContext.root, this.orientation, widthScale, heightScale); this.initialLayoutContext = undefined; + + this.gridview.trySet2x2(); } } diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index 9c04e1a08085a..6f40d22b0a0d9 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -75,7 +75,7 @@ export interface IGridViewOptions { class BranchNode implements ISplitView, IDisposable { readonly element: HTMLElement; - readonly children: Node[]; + readonly children: Node[] = []; private splitview: SplitView; private _size: number; @@ -127,8 +127,9 @@ class BranchNode implements ISplitView, IDisposable { return this.orientation === Orientation.HORIZONTAL ? this.maximumSize : this.maximumOrthogonalSize; } - private _onDidChange: Emitter; - get onDidChange(): Event { return this._onDidChange.event; } + private _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; + private childrenChangeDisposable: IDisposable = EmptyDisposable; private _onDidSashReset = new Emitter(); @@ -151,9 +152,6 @@ class BranchNode implements ISplitView, IDisposable { this._size = size; this._orthogonalSize = orthogonalSize; - this._onDidChange = new Emitter(); - this.children = []; - this.element = $('.monaco-grid-branch-node'); this.splitview = new SplitView(this.element, { orientation, styles }); this.splitview.layout(size); @@ -297,11 +295,44 @@ class BranchNode implements ISplitView, IDisposable { return EmptyDisposable; } + const [firstChild, secondChild] = this.children; + const [otherFirstChild, otherSecondChild] = other.children; + + if (!(firstChild instanceof LeafNode) || !(secondChild instanceof LeafNode)) { + return EmptyDisposable; + } + + if (!(otherFirstChild instanceof LeafNode) || !(otherSecondChild instanceof LeafNode)) { + return EmptyDisposable; + } + + if (this.orientation === Orientation.VERTICAL) { + secondChild.linkedWidthNode = otherFirstChild.linkedHeightNode = firstChild; + firstChild.linkedWidthNode = otherSecondChild.linkedHeightNode = secondChild; + otherSecondChild.linkedWidthNode = firstChild.linkedHeightNode = otherFirstChild; + otherFirstChild.linkedWidthNode = secondChild.linkedHeightNode = otherSecondChild; + } else { + otherFirstChild.linkedWidthNode = secondChild.linkedHeightNode = firstChild; + otherSecondChild.linkedWidthNode = firstChild.linkedHeightNode = secondChild; + firstChild.linkedWidthNode = otherSecondChild.linkedHeightNode = otherFirstChild; + secondChild.linkedWidthNode = otherFirstChild.linkedHeightNode = otherSecondChild; + } + const mySash = this.splitview.sashes[0]; const otherSash = other.splitview.sashes[0]; mySash.linkedSash = otherSash; otherSash.linkedSash = mySash; - return toDisposable(() => mySash.linkedSash = otherSash.linkedSash = undefined); + + this._onDidChange.fire(); + other._onDidChange.fire(); + + return toDisposable(() => { + mySash.linkedSash = otherSash.linkedSash = undefined; + firstChild.linkedHeightNode = firstChild.linkedWidthNode = undefined; + secondChild.linkedHeightNode = secondChild.linkedWidthNode = undefined; + otherFirstChild.linkedHeightNode = otherFirstChild.linkedWidthNode = undefined; + otherSecondChild.linkedHeightNode = otherSecondChild.linkedWidthNode = undefined; + }); } dispose(): void { @@ -326,12 +357,37 @@ class LeafNode implements ISplitView, IDisposable { readonly onDidSashReset: Event = Event.None; + private _onDidLinkedWidthNodeChange = new Relay(); + private _linkedWidthNode: LeafNode | undefined = undefined; + get linkedWidthNode(): LeafNode | undefined { return this._linkedWidthNode; } + set linkedWidthNode(node: LeafNode | undefined) { + this._onDidLinkedWidthNodeChange.input = node ? node._onDidViewChange : Event.None; + this._linkedWidthNode = node; + this._onDidSetLinkedNode.fire(); + } + + private _onDidLinkedHeightNodeChange = new Relay(); + private _linkedHeightNode: LeafNode | undefined = undefined; + get linkedHeightNode(): LeafNode | undefined { return this._linkedHeightNode; } + set linkedHeightNode(node: LeafNode | undefined) { + this._onDidLinkedHeightNodeChange.input = node ? node._onDidViewChange : Event.None; + this._linkedHeightNode = node; + this._onDidSetLinkedNode.fire(); + } + + private _onDidSetLinkedNode = new Emitter(); + private _onDidViewChange: Event; + readonly onDidChange: Event; + constructor( readonly view: IView, readonly orientation: Orientation, orthogonalSize: number = 0 ) { this._orthogonalSize = orthogonalSize; + + this._onDidViewChange = mapEvent(this.view.onDidChange, this.orientation === Orientation.HORIZONTAL ? e => e && e.width : e => e && e.height); + this.onDidChange = anyEvent(this._onDidViewChange, this._onDidSetLinkedNode.event, this._onDidLinkedWidthNodeChange.event, this._onDidLinkedHeightNodeChange.event); } get width(): number { @@ -346,24 +402,36 @@ class LeafNode implements ISplitView, IDisposable { return this.view.element; } + private get minimumWidth(): number { + return this.linkedWidthNode ? Math.max(this.linkedWidthNode.view.minimumWidth, this.view.minimumWidth) : this.view.minimumWidth; + } + + private get maximumWidth(): number { + return this.linkedWidthNode ? Math.min(this.linkedWidthNode.view.maximumWidth, this.view.maximumWidth) : this.view.maximumWidth; + } + + private get minimumHeight(): number { + return this.linkedHeightNode ? Math.max(this.linkedHeightNode.view.minimumHeight, this.view.minimumHeight) : this.view.minimumHeight; + } + + private get maximumHeight(): number { + return this.linkedHeightNode ? Math.min(this.linkedHeightNode.view.maximumHeight, this.view.maximumHeight) : this.view.maximumHeight; + } + get minimumSize(): number { - return this.orientation === Orientation.HORIZONTAL ? this.view.minimumHeight : this.view.minimumWidth; + return this.orientation === Orientation.HORIZONTAL ? this.minimumHeight : this.minimumWidth; } get maximumSize(): number { - return this.orientation === Orientation.HORIZONTAL ? this.view.maximumHeight : this.view.maximumWidth; + return this.orientation === Orientation.HORIZONTAL ? this.maximumHeight : this.maximumWidth; } get minimumOrthogonalSize(): number { - return this.orientation === Orientation.HORIZONTAL ? this.view.minimumWidth : this.view.minimumHeight; + return this.orientation === Orientation.HORIZONTAL ? this.minimumWidth : this.minimumHeight; } get maximumOrthogonalSize(): number { - return this.orientation === Orientation.HORIZONTAL ? this.view.maximumWidth : this.view.maximumHeight; - } - - get onDidChange(): Event { - return mapEvent(this.view.onDidChange, this.orientation === Orientation.HORIZONTAL ? e => e && e.width : e => e && e.height); + return this.orientation === Orientation.HORIZONTAL ? this.maximumWidth : this.maximumHeight; } set orthogonalStartSash(sash: Sash) { diff --git a/src/vs/base/browser/ui/splitview/splitview.css b/src/vs/base/browser/ui/splitview/splitview.css index ed6006b4a1149..6968524236365 100644 --- a/src/vs/base/browser/ui/splitview/splitview.css +++ b/src/vs/base/browser/ui/splitview/splitview.css @@ -37,6 +37,7 @@ .monaco-split-view2 > .split-view-container > .split-view-view { white-space: initial; + flex: none; } .monaco-split-view2.vertical > .split-view-container > .split-view-view { diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 4272aa50abf90..3f593b721e0e5 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -11,7 +11,7 @@ import { Event, mapEvent, Emitter } from 'vs/base/common/event'; import * as types from 'vs/base/common/types'; import * as dom from 'vs/base/browser/dom'; import { clamp } from 'vs/base/common/numbers'; -import { range, firstIndex } from 'vs/base/common/arrays'; +import { range, firstIndex, pushToStart, pushToEnd } from 'vs/base/common/arrays'; import { Sash, Orientation, ISashEvent as IBaseSashEvent, SashState } from 'vs/base/browser/ui/sash/sash'; import { Color } from 'vs/base/common/color'; import { domEvent } from 'vs/base/browser/event'; @@ -77,44 +77,6 @@ enum State { Busy } -function pushToStart(arr: T[], value: T): T[] { - let didFindValue = false; - - const result = arr.filter(v => { - if (v === value) { - didFindValue = true; - return false; - } - - return true; - }); - - if (didFindValue) { - result.unshift(value); - } - - return result; -} - -function pushToEnd(arr: T[], value: T): T[] { - let didFindValue = false; - - const result = arr.filter(v => { - if (v === value) { - didFindValue = true; - return false; - } - - return true; - }); - - if (didFindValue) { - result.push(value); - } - - return result; -} - export type DistributeSizing = { type: 'distribute' }; export type SplitSizing = { type: 'split', index: number }; export type Sizing = DistributeSizing | SplitSizing; @@ -127,7 +89,8 @@ export namespace Sizing { export class SplitView implements IDisposable { readonly orientation: Orientation; - private el: HTMLElement; + // TODO@Joao have the same pattern as grid here + readonly el: HTMLElement; private sashContainer: HTMLElement; private viewContainer: HTMLElement; private size = 0; @@ -346,13 +309,18 @@ export class SplitView implements IDisposable { const toSize = this.getViewSize(to); const toView = this.removeView(to); const fromView = this.removeView(from); + this.addView(toView, fromSize, from); this.addView(fromView, toSize, to); } private relayout(lowPriorityIndex?: number, highPriorityIndex?: number): void { const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); - this.resizeAndLayout(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex, highPriorityIndex); + + this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex, highPriorityIndex); + this.distributeEmptySpace(); + this.layoutViews(); + this.saveProportions(); } layout(size: number): void { @@ -360,14 +328,21 @@ export class SplitView implements IDisposable { this.size = size; if (!this.proportions) { - this.resizeAndLayout(this.viewItems.length - 1, size - previousSize); + this.resize(this.viewItems.length - 1, size - previousSize); } else { for (let i = 0; i < this.viewItems.length; i++) { const item = this.viewItems[i]; item.size = clamp(Math.round(this.proportions[i] * size), item.view.minimumSize, item.view.maximumSize); } + } - this.layoutViews(); + this.distributeEmptySpace(); + this.layoutViews(); + } + + private saveProportions(): void { + if (this.contentSize > 0) { + this.proportions = this.viewItems.map(i => i.size / this.contentSize); } } @@ -382,10 +357,9 @@ export class SplitView implements IDisposable { const resetSashDragState = (start: number, alt: boolean) => { const sizes = this.viewItems.map(i => i.size); - - // TODO@Joao rename these guys - let minDelta = Number.POSITIVE_INFINITY; + let minDelta = Number.NEGATIVE_INFINITY; let maxDelta = Number.POSITIVE_INFINITY; + if (this.inverseAltBehavior) { alt = !alt; } @@ -398,11 +372,11 @@ export class SplitView implements IDisposable { if (isLastSash) { const viewItem = this.viewItems[index]; - minDelta = (viewItem.size - viewItem.view.minimumSize) / 2; + minDelta = (viewItem.view.minimumSize - viewItem.size) / 2; maxDelta = (viewItem.view.maximumSize - viewItem.size) / 2; } else { const viewItem = this.viewItems[index + 1]; - minDelta = (viewItem.view.maximumSize - viewItem.size) / 2; + minDelta = (viewItem.size - viewItem.view.maximumSize) / 2; maxDelta = (viewItem.size - viewItem.view.minimumSize) / 2; } } @@ -425,19 +399,21 @@ export class SplitView implements IDisposable { const newSizes = this.viewItems.map(i => i.size); const viewItemIndex = isLastSash ? index : index + 1; const viewItem = this.viewItems[viewItemIndex]; - const newMinDelta = (viewItem.view.maximumSize - viewItem.size); - const newMaxDelta = (viewItem.size - viewItem.view.minimumSize); + const newMinDelta = viewItem.size - viewItem.view.maximumSize; + const newMaxDelta = viewItem.size - viewItem.view.minimumSize; const resizeIndex = isLastSash ? index - 1 : index + 1; this.resize(resizeIndex, -newDelta, newSizes, undefined, undefined, newMinDelta, newMaxDelta); } + this.distributeEmptySpace(); this.layoutViews(); } private onSashEnd(index: number): void { this._onDidSashChange.fire(index); this.sashDragState.disposable.dispose(); + this.saveProportions(); } private onViewChange(item: IViewItem, size: number | undefined): void { @@ -449,8 +425,17 @@ export class SplitView implements IDisposable { size = typeof size === 'number' ? size : item.size; size = clamp(size, item.view.minimumSize, item.view.maximumSize); - item.size = size; - this.relayout(index); + + if (this.inverseAltBehavior && index > 0) { + // In this case, we want the view to grow or shrink both sides equally + // so we just resize the "left" side by half and let `resize` do the clamping magic + this.resize(index - 1, Math.floor((item.size - size) / 2)); + this.distributeEmptySpace(); + this.layoutViews(); + } else { + item.size = size; + this.relayout(index, undefined); + } } resizeView(index: number, size: number): void { @@ -475,7 +460,7 @@ export class SplitView implements IDisposable { const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0); const deltaDown = clamp(delta, -expandDown, collapseDown); - this.resizeAndLayout(index, deltaDown); + this.resize(index, deltaDown); delta -= deltaDown; } @@ -485,9 +470,12 @@ export class SplitView implements IDisposable { const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0); const deltaUp = clamp(-delta, -collapseUp, expandUp); - this.resizeAndLayout(index - 1, deltaUp); + this.resize(index - 1, deltaUp); } + this.distributeEmptySpace(); + this.layoutViews(); + this.saveProportions(); this.state = State.Idle; } @@ -513,24 +501,24 @@ export class SplitView implements IDisposable { sizes = this.viewItems.map(i => i.size), lowPriorityIndex?: number, highPriorityIndex?: number, - overloadMinDelta: number = Number.POSITIVE_INFINITY, + overloadMinDelta: number = Number.NEGATIVE_INFINITY, overloadMaxDelta: number = Number.POSITIVE_INFINITY ): number { if (index < 0 || index >= this.viewItems.length) { return 0; } - let upIndexes = range(index, -1); - let downIndexes = range(index + 1, this.viewItems.length); + const upIndexes = range(index, -1); + const downIndexes = range(index + 1, this.viewItems.length); if (typeof highPriorityIndex === 'number') { - upIndexes = pushToStart(upIndexes, highPriorityIndex); - downIndexes = pushToStart(downIndexes, highPriorityIndex); + pushToStart(upIndexes, highPriorityIndex); + pushToStart(downIndexes, highPriorityIndex); } if (typeof lowPriorityIndex === 'number') { - upIndexes = pushToEnd(upIndexes, lowPriorityIndex); - downIndexes = pushToEnd(downIndexes, lowPriorityIndex); + pushToEnd(upIndexes, lowPriorityIndex); + pushToEnd(downIndexes, lowPriorityIndex); } const upItems = upIndexes.map(i => this.viewItems[i]); @@ -539,14 +527,12 @@ export class SplitView implements IDisposable { const downItems = downIndexes.map(i => this.viewItems[i]); const downSizes = downIndexes.map(i => sizes[i]); - const collapseUp = upIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0); - const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0); - - const collapseDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0); - const expandDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0); - - const minDelta = -Math.min(collapseUp, expandDown, overloadMinDelta); - const maxDelta = Math.min(collapseDown, expandUp, overloadMaxDelta); + const minDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.minimumSize - sizes[i]), 0); + const maxDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0); + const maxDeltaDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0); + const minDeltaDown = downIndexes.length === 0 ? Number.NEGATIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.maximumSize), 0); + const minDelta = Math.max(minDeltaUp, minDeltaDown, overloadMinDelta); + const maxDelta = Math.min(maxDeltaDown, maxDeltaUp, overloadMaxDelta); delta = clamp(delta, minDelta, maxDelta); @@ -571,23 +557,7 @@ export class SplitView implements IDisposable { return delta; } - private resizeAndLayout( - index: number, - delta: number, - sizes = this.viewItems.map(i => i.size), - lowPriorityIndex?: number, - highPriorityIndex?: number - ): void { - this.resize(index, delta, sizes, lowPriorityIndex, highPriorityIndex); - this.layoutViews(); - - if (this.contentSize > 0) { - this.proportions = this.viewItems.map(i => i.size / this.contentSize); - } - } - - private layoutViews(): void { - // Rebalance empty space + private distributeEmptySpace(): void { let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); let emptyDelta = this.size - contentSize; @@ -599,7 +569,9 @@ export class SplitView implements IDisposable { emptyDelta -= viewDelta; item.size = size; } + } + private layoutViews(): void { // Save new content size this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index f9ddfdc71aaec..ef84fe95d9a28 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -463,3 +463,27 @@ export function shuffle(array: T[]): void { array[j] = temp; } } + +/** + * Pushes an element to the start of the array, if found. + */ +export function pushToStart(arr: T[], value: T): void { + const index = arr.indexOf(value); + + if (index > -1) { + arr.splice(index, 1); + arr.unshift(value); + } +} + +/** + * Pushes an element to the end of the array, if found. + */ +export function pushToEnd(arr: T[], value: T): void { + const index = arr.indexOf(value); + + if (index > -1) { + arr.splice(index, 1); + arr.push(value); + } +} \ No newline at end of file diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index ab86d970787d9..1fa02234ebc54 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -45,7 +45,7 @@ export function getPathLabel(resource: URI | string, userHomeProvider: IUserHome if (isEqual(baseResource.uri, resource, !isLinux)) { pathLabel = ''; // no label if paths are identical } else { - pathLabel = normalize(ltrim(resource.toString().substr(baseResource.uri.toString().length), nativeSep), true); + pathLabel = normalize(ltrim(resource.toString().substr(baseResource.uri.toString().length), sep), true); } if (hasMultipleRoots) { diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 0bb890d29bfca..277929ed5d022 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -46,6 +46,8 @@ if (typeof process === 'object' && typeof process.nextTick === 'function' && typ _isWindows = (process.platform === 'win32'); _isMacintosh = (process.platform === 'darwin'); _isLinux = (process.platform === 'linux'); + _locale = LANGUAGE_DEFAULT; + _language = LANGUAGE_DEFAULT; const rawNlsConfig = process.env['VSCODE_NLS_CONFIG']; if (rawNlsConfig) { try { diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index ba293981009ae..31dff819977ba 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -333,11 +333,11 @@ export function compareIgnoreCase(a: string, b: string): number { } } -function isLowerAsciiLetter(code: number): boolean { +export function isLowerAsciiLetter(code: number): boolean { return code >= CharCode.a && code <= CharCode.z; } -function isUpperAsciiLetter(code: number): boolean { +export function isUpperAsciiLetter(code: number): boolean { return code >= CharCode.A && code <= CharCode.Z; } diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 499e8a52a06a6..0be4208d0e9ac 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -31,7 +31,7 @@ export class Protocol implements IMessagePassingProtocol { readonly onMessage: Event = this._onMessage.event; - constructor(private _socket: Socket) { + constructor(private _socket: Socket, firstDataChunk?: Buffer) { let chunks: Buffer[] = []; let totalLength = 0; @@ -42,7 +42,7 @@ export class Protocol implements IMessagePassingProtocol { bodyLen: -1, }; - _socket.on('data', (data: Buffer) => { + const acceptChunk = (data: Buffer) => { chunks.push(data); totalLength += data.length; @@ -93,6 +93,23 @@ export class Protocol implements IMessagePassingProtocol { } } } + }; + + const acceptFirstDataChunk = () => { + if (firstDataChunk && firstDataChunk.length > 0) { + let tmp = firstDataChunk; + firstDataChunk = null; + acceptChunk(tmp); + } + }; + + _socket.on('data', (data: Buffer) => { + acceptFirstDataChunk(); + acceptChunk(data); + }); + + _socket.on('end', () => { + acceptFirstDataChunk(); }); } diff --git a/src/vs/base/parts/tree/browser/treeDefaults.ts b/src/vs/base/parts/tree/browser/treeDefaults.ts index 7e9a7a682a43a..b5c4ca60e62da 100644 --- a/src/vs/base/parts/tree/browser/treeDefaults.ts +++ b/src/vs/base/parts/tree/browser/treeDefaults.ts @@ -168,9 +168,8 @@ export class DefaultController implements _.IController { } protected onLeftClick(tree: _.ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean { - const payload = { origin: origin, originalEvent: eventish }; const event = eventish; - const isDoubleClick = (origin === 'mouse' && event.detail === 2); + const payload = { origin: origin, originalEvent: eventish, didClickOnTwistie: this.isClickOnTwistie(event) }; if (tree.getInput() === element) { tree.clearFocus(payload); @@ -186,7 +185,7 @@ export class DefaultController implements _.IController { tree.setSelection([element], payload); tree.setFocus(element, payload); - if (this.openOnSingleClick || isDoubleClick || this.isClickOnTwistie(event)) { + if (this.shouldToggleExpansion(element, event, origin)) { if (tree.isExpanded(element)) { tree.collapse(element).done(null, errors.onUnexpectedError); } else { @@ -198,6 +197,11 @@ export class DefaultController implements _.IController { return true; } + protected shouldToggleExpansion(element: any, event: mouse.IMouseEvent, origin: string): boolean { + const isDoubleClick = (origin === 'mouse' && event.detail === 2); + return this.openOnSingleClick || isDoubleClick || this.isClickOnTwistie(event); + } + protected setOpenMode(openMode: OpenMode) { this.options.openMode = openMode; } @@ -207,13 +211,20 @@ export class DefaultController implements _.IController { } protected isClickOnTwistie(event: mouse.IMouseEvent): boolean { - const target = event.target as HTMLElement; + let element = event.target as HTMLElement; + + if (!dom.hasClass(element, 'content')) { + return false; + } + + const twistieStyle = window.getComputedStyle(element, ':before'); + + if (twistieStyle.backgroundImage === 'none' || twistieStyle.display === 'none') { + return false; + } - // There is no way to find out if the ::before element is clicked where - // the twistie is drawn, but the
element in the - // tree item is the only thing we get back as target when the user clicks - // on the twistie. - return target && dom.hasClass(target, 'content') && dom.hasClass(target.parentElement, 'monaco-tree-row'); + const twistieWidth = parseInt(twistieStyle.width) + parseInt(twistieStyle.paddingRight); + return event.browserEvent.offsetX <= twistieWidth; } public onContextMenu(tree: _.ITree, element: any, event: _.ContextMenuEvent): boolean { diff --git a/src/vs/base/test/browser/ui/splitview/splitview.test.ts b/src/vs/base/test/browser/ui/splitview/splitview.test.ts index 3b70bf44ac1c2..619fa4dfa14b7 100644 --- a/src/vs/base/test/browser/ui/splitview/splitview.test.ts +++ b/src/vs/base/test/browser/ui/splitview/splitview.test.ts @@ -387,4 +387,48 @@ suite('Splitview', () => { view2.dispose(); view1.dispose(); }); + + test('split sizing', () => { + const view1 = new TestView(20, Number.POSITIVE_INFINITY); + const view2 = new TestView(20, Number.POSITIVE_INFINITY); + const view3 = new TestView(20, Number.POSITIVE_INFINITY); + const splitview = new SplitView(container); + splitview.layout(200); + + splitview.addView(view1, Sizing.Distribute); + assert.equal(view1.size, 200); + + splitview.addView(view2, Sizing.Split(0)); + assert.deepEqual([view1.size, view2.size], [100, 100]); + + splitview.addView(view3, Sizing.Split(1)); + assert.deepEqual([view1.size, view2.size, view3.size], [100, 50, 50]); + + splitview.dispose(); + view3.dispose(); + view2.dispose(); + view1.dispose(); + }); + + test('split sizing 2', () => { + const view1 = new TestView(20, Number.POSITIVE_INFINITY); + const view2 = new TestView(20, Number.POSITIVE_INFINITY); + const view3 = new TestView(20, Number.POSITIVE_INFINITY); + const splitview = new SplitView(container); + splitview.layout(200); + + splitview.addView(view1, Sizing.Distribute); + assert.equal(view1.size, 200); + + splitview.addView(view2, Sizing.Split(0)); + assert.deepEqual([view1.size, view2.size], [100, 100]); + + splitview.addView(view3, Sizing.Split(0)); + assert.deepEqual([view1.size, view2.size, view3.size], [50, 100, 50]); + + splitview.dispose(); + view3.dispose(); + view2.dispose(); + view1.dispose(); + }); }); \ No newline at end of file diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 964e1ed48981a..aebb464f8d21c 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -57,7 +57,7 @@ import { IIssueService } from 'vs/platform/issue/common/issue'; import { IssueChannel } from 'vs/platform/issue/common/issueIpc'; import { IssueService } from 'vs/platform/issue/electron-main/issueService'; import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; -import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; +import * as errors from 'vs/base/common/errors'; import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener'; import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver'; import { IMenubarService } from 'vs/platform/menubar/common/menubar'; @@ -97,7 +97,7 @@ export class CodeApplication { private registerListeners(): void { // We handle uncaught exceptions here to prevent electron from opening a dialog to the user - setUnexpectedErrorHandler(err => this.onUnexpectedError(err)); + errors.setUnexpectedErrorHandler(err => this.onUnexpectedError(err)); process.on('uncaughtException', err => this.onUnexpectedError(err)); app.on('will-quit', () => { diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index 220377c76035d..5a38604306832 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -676,7 +676,6 @@ export class CodeMenu { const splitEditorRight = this.createMenuItem(nls.localize({ key: 'miSplitEditorRight', comment: ['&& denotes a mnemonic'] }, "Split &&Right"), 'workbench.action.splitEditorRight'); const singleColumnEditorLayout = this.createMenuItem(nls.localize({ key: 'miSingleColumnEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Single"), 'workbench.action.editorLayoutSingle'); - const centeredEditorLayout = this.createMenuItem(nls.localize({ key: 'miCenteredEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Centered"), 'workbench.action.editorLayoutCentered'); const twoColumnsEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Two Columns"), 'workbench.action.editorLayoutTwoColumns'); const threeColumnsEditorLayout = this.createMenuItem(nls.localize({ key: 'miThreeColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&hree Columns"), 'workbench.action.editorLayoutThreeColumns'); const twoRowsEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&wo Rows"), 'workbench.action.editorLayoutTwoRows'); @@ -694,7 +693,6 @@ export class CodeMenu { splitEditorRight, __separator__(), singleColumnEditorLayout, - centeredEditorLayout, twoColumnsEditorLayout, threeColumnsEditorLayout, twoRowsEditorLayout, diff --git a/src/vs/editor/common/controller/cursorWordOperations.ts b/src/vs/editor/common/controller/cursorWordOperations.ts index 8bd73b791f02a..b69d9799b5300 100644 --- a/src/vs/editor/common/controller/cursorWordOperations.ts +++ b/src/vs/editor/common/controller/cursorWordOperations.ts @@ -10,6 +10,7 @@ import { WordCharacterClassifier, WordCharacterClass, getMapForWordSeparators } import * as strings from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; +import { CharCode } from 'vs/base/common/charCode'; interface IFindWordResult { /** @@ -235,7 +236,7 @@ export class WordOperations { return new Position(lineNumber, column); } - private static _deleteWordLeftWhitespace(model: ICursorSimpleModel, position: Position): Range { + protected static _deleteWordLeftWhitespace(model: ICursorSimpleModel, position: Position): Range { const lineContent = model.getLineContent(position.lineNumber); const startIndex = position.column - 2; const lastNonWhitespace = strings.lastNonWhitespaceIndex(lineContent, startIndex); @@ -310,7 +311,7 @@ export class WordOperations { return len; } - private static _deleteWordRightWhitespace(model: ICursorSimpleModel, position: Position): Range { + protected static _deleteWordRightWhitespace(model: ICursorSimpleModel, position: Position): Range { const lineContent = model.getLineContent(position.lineNumber); const startIndex = position.column - 1; const firstNonWhitespace = this._findFirstNonWhitespaceChar(lineContent, startIndex); @@ -463,3 +464,128 @@ export class WordOperations { return cursor.move(true, lineNumber, column, 0); } } + +export function _lastWordPartEnd(str: string, startIndex: number = str.length - 1): number { + for (let i = startIndex; i >= 0; i--) { + let chCode = str.charCodeAt(i); + if (chCode === CharCode.Space || chCode === CharCode.Tab || strings.isUpperAsciiLetter(chCode) || chCode === CharCode.Underline) { + return i - 1; + } + } + return -1; +} + +export function _nextWordPartBegin(str: string, startIndex: number = str.length - 1): number { + const checkLowerCase = str.charCodeAt(startIndex - 1) === CharCode.Space; // does a lc char count as a part start? + for (let i = startIndex; i < str.length; ++i) { + let chCode = str.charCodeAt(i); + if (chCode === CharCode.Space || chCode === CharCode.Tab || strings.isUpperAsciiLetter(chCode) || (checkLowerCase && strings.isLowerAsciiLetter(chCode))) { + return i + 1; + } + if (chCode === CharCode.Underline) { + return i + 2; + } + } + return str.length + 1; +} + +export class WordPartOperations extends WordOperations { + public static deleteWordPartLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range { + if (!selection.isEmpty()) { + return selection; + } + + const position = new Position(selection.positionLineNumber, selection.positionColumn); + const lineNumber = position.lineNumber; + const column = position.column; + + if (lineNumber === 1 && column === 1) { + // Ignore deleting at beginning of file + return null; + } + + if (whitespaceHeuristics) { + let r = WordOperations._deleteWordLeftWhitespace(model, position); + if (r) { + return r; + } + } + + const wordRange = WordOperations.deleteWordLeft(wordSeparators, model, selection, whitespaceHeuristics, wordNavigationType); + const lastWordPartEnd = _lastWordPartEnd(model.getLineContent(position.lineNumber), position.column - 2); + const wordPartRange = new Range(lineNumber, column, lineNumber, lastWordPartEnd + 2); + + if (wordPartRange.getStartPosition().isBeforeOrEqual(wordRange.getStartPosition())) { + return wordRange; + } + return wordPartRange; + } + + public static deleteWordPartRight(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range { + if (!selection.isEmpty()) { + return selection; + } + + const position = new Position(selection.positionLineNumber, selection.positionColumn); + const lineNumber = position.lineNumber; + const column = position.column; + + const lineCount = model.getLineCount(); + const maxColumn = model.getLineMaxColumn(lineNumber); + if (lineNumber === lineCount && column === maxColumn) { + // Ignore deleting at end of file + return null; + } + + if (whitespaceHeuristics) { + let r = WordOperations._deleteWordRightWhitespace(model, position); + if (r) { + return r; + } + } + + const wordRange = WordOperations.deleteWordRight(wordSeparators, model, selection, whitespaceHeuristics, wordNavigationType); + const nextWordPartBegin = _nextWordPartBegin(model.getLineContent(position.lineNumber), position.column); + const wordPartRange = new Range(lineNumber, column, lineNumber, nextWordPartBegin); + + if (wordRange.getEndPosition().isBeforeOrEqual(wordPartRange.getEndPosition())) { + return wordRange; + } + return wordPartRange; + } + + public static moveWordPartLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position, wordNavigationType: WordNavigationType): Position { + const lineNumber = position.lineNumber; + const column = position.column; + if (column === 1) { + return (lineNumber > 1 ? new Position(lineNumber - 1, model.getLineMaxColumn(lineNumber - 1)) : position); + } + + const wordPos = WordOperations.moveWordLeft(wordSeparators, model, position, wordNavigationType); + const lastWordPartEnd = _lastWordPartEnd(model.getLineContent(lineNumber), column - 2); + const wordPartPos = new Position(lineNumber, lastWordPartEnd + 2); + + if (wordPartPos.isBeforeOrEqual(wordPos)) { + return wordPos; + } + return wordPartPos; + } + + public static moveWordPartRight(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position, wordNavigationType: WordNavigationType): Position { + const lineNumber = position.lineNumber; + const column = position.column; + const maxColumn = model.getLineMaxColumn(lineNumber); + if (column === maxColumn) { + return (lineNumber < model.getLineCount() ? new Position(lineNumber + 1, 1) : position); + } + + const wordPos = WordOperations.moveWordRight(wordSeparators, model, position, wordNavigationType); + const nextWordPartBegin = _nextWordPartBegin(model.getLineContent(lineNumber), column); + const wordPartPos = new Position(lineNumber, nextWordPartBegin); + + if (wordPos.isBeforeOrEqual(wordPartPos)) { + return wordPos; + } + return wordPartPos; + } +} diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index e4a6ba3feedcb..2d33f3731f1eb 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -828,8 +828,8 @@ export interface ITextModel { getWordUntilPosition(position: IPosition): IWordAtPosition; /** - * Find the matching bracket of `request` up, counting brackets. - * @param request The bracket we're searching for + * Find the matching bracket of `bracket` up, counting brackets. + * @param bracket The bracket we're searching for * @param position The position at which to start the search. * @return The range of the matching bracket, or null if the bracket match was not found. * @internal diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 0f14cfc72265c..7905ccba0f472 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -538,6 +538,13 @@ export interface Location { */ export type Definition = Location | Location[]; +export interface DefinitionLink { + origin?: IRange; + uri: URI; + range: IRange; + selectionRange?: IRange; +} + /** * The definition provider interface defines the contract between extensions and * the [go to definition](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-definition) @@ -547,7 +554,7 @@ export interface DefinitionProvider { /** * Provide the definition of the symbol at the given position and document. */ - provideDefinition(model: model.ITextModel, position: Position, token: CancellationToken): Definition | Thenable; + provideDefinition(model: model.ITextModel, position: Position, token: CancellationToken): DefinitionLink | Thenable; } /** @@ -899,6 +906,7 @@ export function isResourceTextEdit(thing: any): thing is ResourceTextEdit { export interface ResourceFileEdit { oldUri: URI; newUri: URI; + options: { overwrite?: boolean, ignoreIfExists?: boolean }; } export interface ResourceTextEdit { diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index a38dcf406aaf5..fa58d75a59fbc 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -9,7 +9,6 @@ import 'vs/css!./media/symbol-icons'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { values } from 'vs/base/common/collections'; -import { onUnexpectedError } from 'vs/base/common/errors'; import { createMatches } from 'vs/base/common/filters'; import { TPromise } from 'vs/base/common/winjs.base'; import { IDataSource, IFilter, IRenderer, ISorter, ITree } from 'vs/base/parts/tree/browser/tree'; @@ -307,35 +306,11 @@ export class OutlineTreeState { } export class OutlineController extends WorkbenchTreeController { - - protected onLeftClick(tree: ITree, element: any, event: IMouseEvent, origin: string = 'mouse'): boolean { - - const payload = { origin: origin, originalEvent: event, didClickElement: false }; - - if (tree.getInput() === element) { - tree.clearFocus(payload); - tree.clearSelection(payload); + protected shouldToggleExpansion(element: any, event: IMouseEvent, origin: string): boolean { + if (element instanceof OutlineElement) { + return this.isClickOnTwistie(event); } else { - const isMouseDown = event && event.browserEvent && event.browserEvent.type === 'mousedown'; - if (!isMouseDown) { - event.preventDefault(); // we cannot preventDefault onMouseDown because this would break DND otherwise - } - event.stopPropagation(); - - payload.didClickElement = element instanceof OutlineElement && !this.isClickOnTwistie(event); - - tree.domFocus(); - tree.setSelection([element], payload); - tree.setFocus(element, payload); - - if (!payload.didClickElement) { - if (tree.isExpanded(element)) { - tree.collapse(element).then(null, onUnexpectedError); - } else { - tree.expand(element).then(null, onUnexpectedError); - } - } + return super.shouldToggleExpansion(element, event, origin); } - return true; } } diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinition.ts b/src/vs/editor/contrib/goToDefinition/goToDefinition.ts index f79912d832315..eee248b2cb031 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinition.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinition.ts @@ -10,7 +10,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { ITextModel } from 'vs/editor/common/model'; import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; -import { DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, Location } from 'vs/editor/common/modes'; +import { DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, Location, DefinitionLink } from 'vs/editor/common/modes'; import { CancellationToken } from 'vs/base/common/cancellation'; import { asWinJsPromise } from 'vs/base/common/async'; import { Position } from 'vs/editor/common/core/position'; @@ -21,11 +21,11 @@ function getDefinitions( position: Position, registry: LanguageFeatureRegistry, provide: (provider: T, model: ITextModel, position: Position, token: CancellationToken) => Location | Location[] | Thenable -): TPromise { +): TPromise { const provider = registry.ordered(model); // get results - const promises = provider.map((provider, idx): TPromise => { + const promises = provider.map((provider): TPromise => { return asWinJsPromise((token) => { return provide(provider, model, position, token); }).then(undefined, err => { @@ -39,7 +39,7 @@ function getDefinitions( } -export function getDefinitionsAtPosition(model: ITextModel, position: Position): TPromise { +export function getDefinitionsAtPosition(model: ITextModel, position: Position): TPromise { return getDefinitions(model, position, DefinitionProviderRegistry, (provider, model, position, token) => { return provider.provideDefinition(model, position, token); }); diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts index 13659526cab8d..26aae11ccf849 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts @@ -14,7 +14,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IModeService } from 'vs/editor/common/services/modeService'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { Location, DefinitionProviderRegistry } from 'vs/editor/common/modes'; +import { DefinitionProviderRegistry, DefinitionLink } from 'vs/editor/common/modes'; import { ICodeEditor, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { getDefinitionsAtPosition } from './goToDefinition'; @@ -25,7 +25,8 @@ import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegist import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; import { DefinitionAction, DefinitionActionConfig } from './goToDefinitionCommands'; import { ClickLinkGesture, ClickLinkMouseEvent, ClickLinkKeyboardEvent } from 'vs/editor/contrib/goToDefinition/clickLinkGesture'; -import { IWordAtPosition, IModelDeltaDecoration } from 'vs/editor/common/model'; +import { IWordAtPosition, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { Position } from 'vs/editor/common/core/position'; class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorContribution { @@ -102,7 +103,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC this.throttler.queue(() => { return state.validate(this.editor) ? this.findDefinition(mouseEvent.target) - : TPromise.wrap(null); + : TPromise.wrap(null); }).then(results => { if (!results || !results.length || !state.validate(this.editor)) { @@ -141,25 +142,17 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC return; } - const startIndent = textEditorModel.getLineFirstNonWhitespaceColumn(startLineNumber); - const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES); - let endLineNumber = startLineNumber + 1; - let minIndent = startIndent; - - for (; endLineNumber < maxLineNumber; endLineNumber++) { - let endIndent = textEditorModel.getLineFirstNonWhitespaceColumn(endLineNumber); - minIndent = Math.min(minIndent, endIndent); - if (startIndent === endIndent) { - break; - } + let wordRange; + if (result.origin) { + wordRange = Range.lift(result.origin); + } else { + wordRange = new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn); } - const previewRange = new Range(startLineNumber, 1, endLineNumber + 1, 1); - const value = textEditorModel.getValueInRange(previewRange).replace(new RegExp(`^\\s{${minIndent - 1}}`, 'gm'), '').trim(); - + const previewValue = this.getPreviewValue(textEditorModel, startLineNumber); this.addDecoration( - new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn), - new MarkdownString().appendCodeblock(this.modeService.getModeIdByFilenameOrFirstLine(textEditorModel.uri.fsPath), value) + wordRange, + new MarkdownString().appendCodeblock(this.modeService.getModeIdByFilenameOrFirstLine(textEditorModel.uri.fsPath), previewValue) ); ref.dispose(); }); @@ -167,6 +160,96 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC }).done(undefined, onUnexpectedError); } + private getPreviewValue(textEditorModel: ITextModel, startLineNumber: number) { + let rangeToUse = this.getPreviewRangeBasedOnIndentation(textEditorModel, startLineNumber); + const numberOfLinesInRange = rangeToUse.endLineNumber - rangeToUse.startLineNumber; + if (numberOfLinesInRange < 3 || numberOfLinesInRange >= GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES) { + rangeToUse = this.getPreviewRangeBasedOnBrackets(textEditorModel, startLineNumber); + } + + const previewValue = this.stripIndentationFromPreviewRange(textEditorModel, startLineNumber, rangeToUse); + return previewValue; + } + + private stripIndentationFromPreviewRange(textEditorModel: ITextModel, startLineNumber: number, previewRange: Range) { + const startIndent = textEditorModel.getLineFirstNonWhitespaceColumn(startLineNumber); + let minIndent = startIndent; + + for (let endLineNumber = startLineNumber + 1; endLineNumber < previewRange.endLineNumber; endLineNumber++) { + const endIndent = textEditorModel.getLineFirstNonWhitespaceColumn(endLineNumber); + minIndent = Math.min(minIndent, endIndent); + } + + const previewValue = textEditorModel.getValueInRange(previewRange).replace(new RegExp(`^\\s{${minIndent - 1}}`, 'gm'), '').trim(); + return previewValue; + } + + private getPreviewRangeBasedOnIndentation(textEditorModel: ITextModel, startLineNumber: number) { + const startIndent = textEditorModel.getLineFirstNonWhitespaceColumn(startLineNumber); + const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES); + let endLineNumber = startLineNumber + 1; + + for (; endLineNumber < maxLineNumber; endLineNumber++) { + let endIndent = textEditorModel.getLineFirstNonWhitespaceColumn(endLineNumber); + + if (startIndent === endIndent) { + break; + } + } + + const previewRange = new Range(startLineNumber, 1, endLineNumber + 1, 1); + + return previewRange; + + + } + + private getPreviewRangeBasedOnBrackets(textEditorModel: ITextModel, startLineNumber: number) { + const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES); + + const brackets = []; + + let ignoreFirstEmpty = true; + let currentBracket = textEditorModel.findNextBracket(new Position(startLineNumber, 1)); + while (currentBracket !== null) { + + if (brackets.length === 0) { + brackets.push(currentBracket); + } else { + const lastBracket = brackets[brackets.length - 1]; + if (lastBracket.open === currentBracket.open && lastBracket.isOpen && !currentBracket.isOpen) { + brackets.pop(); + } else { + brackets.push(currentBracket); + } + + if (brackets.length === 0) { + if (ignoreFirstEmpty) { + ignoreFirstEmpty = false; + } else { + return new Range(startLineNumber, 1, currentBracket.range.endLineNumber + 1, 1); + } + } + } + + const maxColumn = textEditorModel.getLineMaxColumn(startLineNumber); + let nextLineNumber = currentBracket.range.endLineNumber; + let nextColumn = currentBracket.range.endColumn; + if (maxColumn === currentBracket.range.endColumn) { + nextLineNumber++; + nextColumn = 1; + } + + if (nextLineNumber > maxLineNumber) { + return new Range(startLineNumber, 1, maxLineNumber + 1, 1); + } + + currentBracket = textEditorModel.findNextBracket(new Position(nextLineNumber, nextColumn)); + } + + return new Range(startLineNumber, 1, maxLineNumber + 1, 1); + } + private addDecoration(range: Range, hoverMessage: MarkdownString): void { const newDecorations: IModelDeltaDecoration = { @@ -194,7 +277,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC DefinitionProviderRegistry.has(this.editor.getModel()); } - private findDefinition(target: IMouseTarget): TPromise { + private findDefinition(target: IMouseTarget): TPromise { const model = this.editor.getModel(); if (!model) { return TPromise.as(null); diff --git a/src/vs/editor/contrib/linesOperations/linesOperations.ts b/src/vs/editor/contrib/linesOperations/linesOperations.ts index 0e2e13b66b6e1..7f2c9fb66c44a 100644 --- a/src/vs/editor/contrib/linesOperations/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/linesOperations.ts @@ -35,7 +35,7 @@ abstract class AbstractCopyLinesAction extends EditorAction { this.down = down; } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { let commands: ICommand[] = []; let selections = editor.getSelections(); @@ -93,7 +93,7 @@ abstract class AbstractMoveLinesAction extends EditorAction { this.down = down; } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { let commands: ICommand[] = []; let selections = editor.getSelections(); @@ -149,7 +149,7 @@ export abstract class AbstractSortLinesAction extends EditorAction { this.descending = descending; } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { const selections = editor.getSelections(); for (let i = 0, len = selections.length; i < len; i++) { @@ -209,7 +209,7 @@ export class TrimTrailingWhitespaceAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { let cursors: Position[] = []; if (args.reason === 'auto-save') { @@ -235,8 +235,36 @@ interface IDeleteLinesOperation { positionColumn: number; } -abstract class AbstractRemoveLinesAction extends EditorAction { - _getLinesToRemove(editor: ICodeEditor): IDeleteLinesOperation[] { +class DeleteLinesAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.deleteLines', + label: nls.localize('lines.delete', "Delete Line"), + alias: 'Delete Line', + precondition: EditorContextKeys.writable, + kbOpts: { + kbExpr: EditorContextKeys.textInputFocus, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_K + } + }); + } + + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { + + let ops = this._getLinesToRemove(editor); + + // Finally, construct the delete lines commands + let commands: ICommand[] = ops.map((op) => { + return new DeleteLinesCommand(op.startLineNumber, op.endLineNumber, op.positionColumn); + }); + + editor.pushUndoStop(); + editor.executeCommands(this.id, commands); + editor.pushUndoStop(); + } + + private _getLinesToRemove(editor: ICodeEditor): IDeleteLinesOperation[] { // Construct delete operations let operations: IDeleteLinesOperation[] = editor.getSelections().map((s) => { @@ -277,36 +305,6 @@ abstract class AbstractRemoveLinesAction extends EditorAction { } } -class DeleteLinesAction extends AbstractRemoveLinesAction { - - constructor() { - super({ - id: 'editor.action.deleteLines', - label: nls.localize('lines.delete', "Delete Line"), - alias: 'Delete Line', - precondition: EditorContextKeys.writable, - kbOpts: { - kbExpr: EditorContextKeys.textInputFocus, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_K - } - }); - } - - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - - let ops = this._getLinesToRemove(editor); - - // Finally, construct the delete lines commands - let commands: ICommand[] = ops.map((op) => { - return new DeleteLinesCommand(op.startLineNumber, op.endLineNumber, op.positionColumn); - }); - - editor.pushUndoStop(); - editor.executeCommands(this.id, commands); - editor.pushUndoStop(); - } -} - export class IndentLinesAction extends EditorAction { constructor() { super({ @@ -321,7 +319,7 @@ export class IndentLinesAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { editor.pushUndoStop(); editor.executeCommands(this.id, TypeOperations.indent(editor._getCursorConfiguration(), editor.getModel(), editor.getSelections())); editor.pushUndoStop(); @@ -342,7 +340,7 @@ class OutdentLinesAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { CoreEditingCommands.Outdent.runEditorCommand(null, editor, null); } } @@ -361,7 +359,7 @@ export class InsertLineBeforeAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { editor.pushUndoStop(); editor.executeCommands(this.id, TypeOperations.lineInsertBefore(editor._getCursorConfiguration(), editor.getModel(), editor.getSelections())); } @@ -381,14 +379,14 @@ export class InsertLineAfterAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { editor.pushUndoStop(); editor.executeCommands(this.id, TypeOperations.lineInsertAfter(editor._getCursorConfiguration(), editor.getModel(), editor.getSelections())); } } export abstract class AbstractDeleteAllToBoundaryAction extends EditorAction { - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { const primaryCursor = editor.getSelection(); let rangesToDelete = this._getRangesToDelete(editor); // merge overlapping selections @@ -408,8 +406,8 @@ export abstract class AbstractDeleteAllToBoundaryAction extends EditorAction { effectiveRanges.push(rangesToDelete[rangesToDelete.length - 1]); let endCursorState = this._getEndCursorState(primaryCursor, effectiveRanges); + let edits: IIdentifiedSingleEditOperation[] = effectiveRanges.map(range => { - endCursorState.push(new Selection(range.startLineNumber, range.startColumn, range.startLineNumber, range.startColumn)); return EditOperation.replace(range, ''); }); @@ -444,17 +442,25 @@ export class DeleteAllLeftAction extends AbstractDeleteAllToBoundaryAction { _getEndCursorState(primaryCursor: Range, rangesToDelete: Range[]): Selection[] { let endPrimaryCursor: Selection; let endCursorState: Selection[] = []; + let deletedLines = 0; - for (let i = 0, len = rangesToDelete.length; i < len; i++) { - let range = rangesToDelete[i]; - let endCursor = new Selection(rangesToDelete[i].startLineNumber, rangesToDelete[i].startColumn, rangesToDelete[i].startLineNumber, rangesToDelete[i].startColumn); + rangesToDelete.forEach(range => { + let endCursor; + if (range.endColumn === 1 && deletedLines > 0) { + let newStartLine = range.startLineNumber - deletedLines; + endCursor = new Selection(newStartLine, range.startColumn, newStartLine, range.startColumn); + } else { + endCursor = new Selection(range.startLineNumber, range.startColumn, range.startLineNumber, range.startColumn); + } + + deletedLines += range.endLineNumber - range.startLineNumber; if (range.intersectRanges(primaryCursor)) { endPrimaryCursor = endCursor; } else { endCursorState.push(endCursor); } - } + }); if (endPrimaryCursor) { endCursorState.unshift(endPrimaryCursor); @@ -465,11 +471,18 @@ export class DeleteAllLeftAction extends AbstractDeleteAllToBoundaryAction { _getRangesToDelete(editor: ICodeEditor): Range[] { let rangesToDelete: Range[] = editor.getSelections(); + let model = editor.getModel(); rangesToDelete.sort(Range.compareRangesUsingStarts); rangesToDelete = rangesToDelete.map(selection => { if (selection.isEmpty()) { - return new Range(selection.startLineNumber, 1, selection.startLineNumber, selection.startColumn); + if (selection.startColumn === 1) { + let deleteFromLine = Math.max(1, selection.startLineNumber - 1); + let deleteFromColumn = selection.startLineNumber === 1 ? 1 : model.getLineContent(deleteFromLine).length + 1; + return new Range(deleteFromLine, deleteFromColumn, selection.startLineNumber, 1); + } else { + return new Range(selection.startLineNumber, 1, selection.startLineNumber, selection.startColumn); + } } else { return selection; } @@ -551,7 +564,7 @@ export class JoinLinesAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { let selections = editor.getSelections(); let primaryCursor = editor.getSelection(); @@ -694,7 +707,7 @@ export class TransposeAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { let selections = editor.getSelections(); let model = editor.getModel(); let commands: ICommand[] = []; @@ -735,7 +748,7 @@ export class TransposeAction extends EditorAction { } export abstract class AbstractCaseAction extends EditorAction { - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { let selections = editor.getSelections(); let model = editor.getModel(); let commands: ICommand[] = []; diff --git a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts index ae01a2682eb91..31963d459865d 100644 --- a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts @@ -155,6 +155,101 @@ suite('Editor Contrib - Line Operations', () => { }); }); + test('should jump to the previous line when on first column', function () { + withTestCodeEditor( + [ + 'one', + 'two', + 'three' + ], {}, (editor, cursor) => { + let model = editor.getModel(); + let deleteAllLeftAction = new DeleteAllLeftAction(); + + editor.setSelection(new Selection(2, 1, 2, 1)); + deleteAllLeftAction.run(null, editor); + assert.equal(model.getLineContent(1), 'onetwo', '001'); + + editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 1, 2, 1)]); + deleteAllLeftAction.run(null, editor); + assert.equal(model.getLinesContent()[0], 'onetwothree'); + assert.equal(model.getLinesContent().length, 1); + + editor.setSelection(new Selection(1, 1, 1, 1)); + deleteAllLeftAction.run(null, editor); + assert.equal(model.getLinesContent()[0], 'onetwothree'); + }); + }); + + test('should keep deleting lines in multi cursor mode', function () { + withTestCodeEditor( + [ + 'hi my name is Carlos Matos', + 'BCC', + 'waso waso waso', + 'my wife doesnt believe in me', + 'nonononono', + 'bitconneeeect' + ], {}, (editor, cursor) => { + let model = editor.getModel(); + let deleteAllLeftAction = new DeleteAllLeftAction(); + + const beforeSecondWasoSelection = new Selection(3, 5, 3, 5); + const endOfBCCSelection = new Selection(2, 4, 2, 4); + const endOfNonono = new Selection(5, 11, 5, 11); + + editor.setSelections([beforeSecondWasoSelection, endOfBCCSelection, endOfNonono]); + let selections; + + deleteAllLeftAction.run(null, editor); + selections = editor.getSelections(); + + assert.equal(model.getLineContent(2), ''); + assert.equal(model.getLineContent(3), ' waso waso'); + assert.equal(model.getLineContent(5), ''); + + assert.deepEqual([ + selections[0].startLineNumber, + selections[0].startColumn, + selections[0].endLineNumber, + selections[0].endColumn + ], [3, 1, 3, 1]); + + assert.deepEqual([ + selections[1].startLineNumber, + selections[1].startColumn, + selections[1].endLineNumber, + selections[1].endColumn + ], [2, 1, 2, 1]); + + assert.deepEqual([ + selections[2].startLineNumber, + selections[2].startColumn, + selections[2].endLineNumber, + selections[2].endColumn + ], [5, 1, 5, 1]); + + deleteAllLeftAction.run(null, editor); + selections = editor.getSelections(); + + assert.equal(model.getLineContent(1), 'hi my name is Carlos Matos waso waso'); + assert.equal(selections.length, 2); + + assert.deepEqual([ + selections[0].startLineNumber, + selections[0].startColumn, + selections[0].endLineNumber, + selections[0].endColumn + ], [1, 27, 1, 27]); + + assert.deepEqual([ + selections[1].startLineNumber, + selections[1].startColumn, + selections[1].endLineNumber, + selections[1].endColumn + ], [2, 29, 2, 29]); + }); + }); + test('should work in multi cursor mode', function () { withTestCodeEditor( [ diff --git a/src/vs/editor/contrib/snippet/snippet.md b/src/vs/editor/contrib/snippet/snippet.md index 403cda7743839..6b29d44c16e0c 100644 --- a/src/vs/editor/contrib/snippet/snippet.md +++ b/src/vs/editor/contrib/snippet/snippet.md @@ -53,6 +53,26 @@ ${TM_FILENAME/(.*)\..+$/$1/} |-> resolves to the filename ``` +Placeholder-Transform +-- + +Like a Variable-Transform, a transformation of a placeholder allows changing the inserted text for the placeholder when moving to the next tab stop. +The inserted text is matched with the regular expression and the match or matches - depending on the options - are replaced with the specified replacement format text. +Every occurrence of a placeholder can define its own transformation independently using the value of the first placeholder. +The format for Placeholder-Transforms is the same as for Variable-Transforms. + +The following sample removes an underscore at the beginning of the text. `_transform` becomes `transform`. + +``` +${1/^_(.*)/$1/} + | | | |-> No options + | | | + | | |-> Replace it with the first capture group + | | + | |-> Regular expression to capture everything after the underscore + | + |-> Placeholder Index +``` Grammar -- @@ -61,12 +81,17 @@ Below is the EBNF for snippets. With `\` (backslash) you can escape `$`, `}` and ``` any ::= tabstop | placeholder | choice | variable | text -tabstop ::= '$' int | '${' int '}' +tabstop ::= '$' int + | '${' int '}' + | '${' int transform '}' placeholder ::= '${' int ':' any '}' + | '${' int ':' any transform '}' choice ::= '${' int '|' text (',' text)* '|}' + | '${' int '|' text (',' text)* '|' transform '}' variable ::= '$' var | '${' var }' | '${' var ':' any '}' - | '${' var '/' regex '/' (format | text)+ '/' options '}' + | '${' var transform '}' +transform ::= '/' regex '/' (format | text)+ '/' options format ::= '$' int | '${' int '}' | '${' int ':' '/upcase' | '/downcase' | '/capitalize' '}' | '${' int ':+' if '}' @@ -78,3 +103,5 @@ var ::= [_a-zA-Z] [_a-zA-Z0-9]* int ::= [0-9]+ text ::= .* ``` + +Transformations for placeholders and choices are an extension to the TextMate snippet grammar and only support by Visual Studio Code. \ No newline at end of file diff --git a/src/vs/editor/contrib/snippet/snippetParser.ts b/src/vs/editor/contrib/snippet/snippetParser.ts index 2ee46939849ad..7ab0d57eafeeb 100644 --- a/src/vs/editor/contrib/snippet/snippetParser.ts +++ b/src/vs/editor/contrib/snippet/snippetParser.ts @@ -214,8 +214,11 @@ export class Text extends Marker { } } -export class Placeholder extends Marker { +export abstract class TransformableMarker extends Marker { + public transform: Transform; +} +export class Placeholder extends TransformableMarker { static compareByIndex(a: Placeholder, b: Placeholder): number { if (a.index === b.index) { return 0; @@ -247,17 +250,26 @@ export class Placeholder extends Marker { } toTextmateString(): string { - if (this.children.length === 0) { + let transformString = ''; + if (this.transform) { + transformString = this.transform.toTextmateString(); + } + if (this.children.length === 0 && !this.transform) { return `\$${this.index}`; + } else if (this.children.length === 0) { + return `\${${this.index}${transformString}}`; } else if (this.choice) { - return `\${${this.index}|${this.choice.toTextmateString()}|}`; + return `\${${this.index}|${this.choice.toTextmateString()}|${transformString}}`; } else { - return `\${${this.index}:${this.children.map(child => child.toTextmateString()).join('')}}`; + return `\${${this.index}:${this.children.map(child => child.toTextmateString()).join('')}${transformString}}`; } } clone(): Placeholder { let ret = new Placeholder(this.index); + if (this.transform) { + ret.transform = this.transform.clone(); + } ret._children = this.children.map(child => child.clone()); return ret; } @@ -384,7 +396,7 @@ export class FormatString extends Marker { } } -export class Variable extends Marker { +export class Variable extends TransformableMarker { constructor(public name: string) { super(); @@ -392,9 +404,8 @@ export class Variable extends Marker { resolve(resolver: VariableResolver): boolean { let value = resolver.resolve(this); - let [firstChild] = this._children; - if (firstChild instanceof Transform && this._children.length === 1) { - value = firstChild.resolve(value || ''); + if (this.transform) { + value = this.transform.resolve(value || ''); } if (value !== undefined) { this._children = [new Text(value)]; @@ -404,15 +415,22 @@ export class Variable extends Marker { } toTextmateString(): string { + let transformString = ''; + if (this.transform) { + transformString = this.transform.toTextmateString(); + } if (this.children.length === 0) { - return `\${${this.name}}`; + return `\${${this.name}${transformString}}`; } else { - return `\${${this.name}:${this.children.map(child => child.toTextmateString()).join('')}}`; + return `\${${this.name}:${this.children.map(child => child.toTextmateString()).join('')}${transformString}}`; } } clone(): Variable { const ret = new Variable(this.name); + if (this.transform) { + ret.transform = this.transform.clone(); + } ret._children = this.children.map(child => child.clone()); return ret; } @@ -580,6 +598,7 @@ export class SnippetParser { for (const placeholder of incompletePlaceholders) { if (placeholderDefaultValues.has(placeholder.index)) { const clone = new Placeholder(placeholder.index); + clone.transform = placeholder.transform; for (const child of placeholderDefaultValues.get(placeholder.index)) { clone.appendChild(child.clone()); } @@ -696,6 +715,17 @@ export class SnippetParser { return true; } + //..///} -> transform + if (this._accept(TokenType.Forwardslash)) { + if (this._parseTransform(placeholder)) { + parent.appendChild(placeholder); + return true; + } + + this._backTo(token); + return false; + } + if (this._parse(placeholder)) { continue; } @@ -717,11 +747,20 @@ export class SnippetParser { continue; } - if (this._accept(TokenType.Pipe) && this._accept(TokenType.CurlyClose)) { - // ..|} -> done + if (this._accept(TokenType.Pipe)) { placeholder.appendChild(choice); - parent.appendChild(placeholder); - return true; + if (this._accept(TokenType.CurlyClose)) { + // ..|} -> done + parent.appendChild(placeholder); + return true; + } + if (this._accept(TokenType.Forwardslash)) { + // ...|///} -> transform + if (this._parseTransform(placeholder)) { + parent.appendChild(placeholder); + return true; + } + } } } @@ -729,6 +768,16 @@ export class SnippetParser { return false; } + } else if (this._accept(TokenType.Forwardslash)) { + // ${1///} + if (this._parseTransform(placeholder)) { + parent.appendChild(placeholder); + return true; + } + + this._backTo(token); + return false; + } else if (this._accept(TokenType.CurlyClose)) { // ${1} parent.appendChild(placeholder); @@ -829,7 +878,7 @@ export class SnippetParser { } } - private _parseTransform(parent: Variable): boolean { + private _parseTransform(parent: TransformableMarker): boolean { // ...//} let transform = new Transform(); @@ -894,7 +943,7 @@ export class SnippetParser { return false; } - parent.appendChild(transform); + parent.transform = transform; return true; } diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index 31be6b2ba1662..9d9f336c9290e 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -87,6 +87,23 @@ export class OneSnippet { this._initDecorations(); + // Transform placeholder text if necessary + if (this._placeholderGroupsIdx >= 0) { + let operations: IIdentifiedSingleEditOperation[] = []; + + for (const placeholder of this._placeholderGroups[this._placeholderGroupsIdx]) { + // Check if the placeholder has a transformation + if (placeholder.transform) { + const id = this._placeholderDecorations.get(placeholder); + const range = this._editor.getModel().getDecorationRange(id); + const currentValue = this._editor.getModel().getValueInRange(range); + + operations.push({ range: range, text: placeholder.transform.resolve(currentValue) }); + } + } + this._editor.getModel().applyEdits(operations); + } + if (fwd === true && this._placeholderGroupsIdx < this._placeholderGroups.length - 1) { this._placeholderGroupsIdx += 1; diff --git a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts index 20d5d3c96af12..5cae847a72615 100644 --- a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts @@ -240,6 +240,35 @@ suite('SnippetParser', () => { }); + test('Parser, placeholder transforms', function () { + assertTextAndMarker('${1///}', '', Placeholder); + assertTextAndMarker('${1/regex/format/gmi}', '', Placeholder); + assertTextAndMarker('${1/([A-Z][a-z])/format/}', '', Placeholder); + + // tricky regex + assertTextAndMarker('${1/m\\/atch/$1/i}', '', Placeholder); + assertMarker('${1/regex\/format/options}', Text); + + // incomplete + assertTextAndMarker('${1///', '${1///', Text); + assertTextAndMarker('${1/regex/format/options', '${1/regex/format/options', Text); + }); + + test('Parser, placeholder with defaults and transformation', () => { + assertTextAndMarker('${1:value/foo/bar/}', 'value', Placeholder); + assertTextAndMarker('${1:bar${2:foo}bar/foo/bar/}', 'barfoobar', Placeholder); + + // incomplete + assertTextAndMarker('${1:bar${2:foobar}/foo/bar/', '${1:barfoobar/foo/bar/', Text, Placeholder, Text); + }); + + test('Parser, placeholder with choice and transformation', () => { + assertTextAndMarker('${1|one,two,three|/foo/bar/}', 'one', Placeholder); + assertTextAndMarker('${1|one|/foo/bar/}', 'one', Placeholder); + assertTextAndMarker('${1|one,two,three,|/foo/bar/}', '${1|one,two,three,|/foo/bar/}', Text); + assertTextAndMarker('${1|one,/foo/bar/', '${1|one,/foo/bar/', Text); + }); + test('No way to escape forward slash in snippet regex #36715', function () { assertMarker('${TM_DIRECTORY/src\\//$1/}', Variable); }); @@ -378,6 +407,36 @@ suite('SnippetParser', () => { assert.ok(marker[0] instanceof Variable); }); + test('Parser, transform example', () => { + let marker = new SnippetParser().parse('${1:name} : ${2:type}${3: :=/\\s:=(.*)/${1:+ :=}${1}/};\n$0'); + let childs = marker.children; + + assert.ok(childs[0] instanceof Placeholder); + assert.equal(childs[0].children.length, 1); + assert.equal(childs[0].children[0].toString(), 'name'); + assert.equal((childs[0]).transform, undefined); + assert.ok(childs[1] instanceof Text); + assert.equal(childs[1].toString(), ' : '); + assert.ok(childs[2] instanceof Placeholder); + assert.equal(childs[2].children.length, 1); + assert.equal(childs[2].children[0].toString(), 'type'); + assert.ok(childs[3] instanceof Placeholder); + assert.equal(childs[3].children.length, 1); + assert.equal(childs[3].children[0].toString(), ' :='); + assert.notEqual((childs[3]).transform, undefined); + let t = (childs[3]).transform; + assert.equal(t.regexp, '/\\s:=(.*)/'); + assert.equal(t.children.length, 2); + assert.ok(t.children[0] instanceof FormatString); + assert.equal((t.children[0]).index, 1); + assert.equal((t.children[0]).ifValue, ' :='); + assert.ok(t.children[1] instanceof FormatString); + assert.equal((t.children[1]).index, 1); + assert.ok(childs[4] instanceof Text); + assert.equal(childs[4].toString(), ';\n'); + + }); + test('Parser, default placeholder values', () => { assertMarker('errorContext: `${1:err}`, error: $1', Text, Placeholder, Text, Placeholder); @@ -393,6 +452,37 @@ suite('SnippetParser', () => { assert.equal(((p2).children[0]), 'err'); }); + test('Parser, default placeholder values and one transform', () => { + + assertMarker('errorContext: `${1:err/err/ok/}`, error: $1', Text, Placeholder, Text, Placeholder); + + const [, p1, , p2] = new SnippetParser().parse('errorContext: `${1:err/err/ok/}`, error:$1').children; + + assert.equal((p1).index, '1'); + assert.equal((p1).children.length, '1'); + assert.equal(((p1).children[0]), 'err'); + assert.notEqual((p1).transform, undefined); + + assert.equal((p2).index, '1'); + assert.equal((p2).children.length, '1'); + assert.equal(((p2).children[0]), 'err'); + assert.equal((p2).transform, undefined); + + assertMarker('errorContext: `${1:err}`, error: ${1/err/ok/}', Text, Placeholder, Text, Placeholder); + + const [, p3, , p4] = new SnippetParser().parse('errorContext: `${1:err}`, error:${1/err/ok/}').children; + + assert.equal((p3).index, '1'); + assert.equal((p3).children.length, '1'); + assert.equal(((p3).children[0]), 'err'); + assert.equal((p3).transform, undefined); + + assert.equal((p4).index, '1'); + assert.equal((p4).children.length, '1'); + assert.equal(((p4).children[0]), 'err'); + assert.notEqual((p4).transform, undefined); + }); + test('Repeated snippet placeholder should always inherit, #31040', function () { assertText('${1:foo}-abc-$1', 'foo-abc-foo'); assertText('${1:foo}-abc-${1}', 'foo-abc-foo'); diff --git a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts index 6c9ea6728db92..8093aa82221b0 100644 --- a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts @@ -434,6 +434,121 @@ suite('SnippetSession', function () { assertSelections(editor, new Selection(1, 6, 1, 25)); }); + test('snippets, transform', function () { + editor.getModel().setValue(''); + editor.setSelection(new Selection(1, 1, 1, 1)); + const session = new SnippetSession(editor, '${1/foo/bar/}$0'); + session.insert(); + assertSelections(editor, new Selection(1, 1, 1, 1)); + + editor.trigger('test', 'type', { text: 'foo' }); + session.next(); + + assert.equal(model.getValue(), 'bar'); + assert.equal(session.isAtLastPlaceholder, true); + assertSelections(editor, new Selection(1, 4, 1, 4)); + }); + + test('snippets, multi placeholder same index one transform', function () { + editor.getModel().setValue(''); + editor.setSelection(new Selection(1, 1, 1, 1)); + const session = new SnippetSession(editor, '$1 baz ${1/foo/bar/}$0'); + session.insert(); + assertSelections(editor, new Selection(1, 1, 1, 1), new Selection(1, 6, 1, 6)); + + editor.trigger('test', 'type', { text: 'foo' }); + session.next(); + + assert.equal(model.getValue(), 'foo baz bar'); + assert.equal(session.isAtLastPlaceholder, true); + assertSelections(editor, new Selection(1, 12, 1, 12)); + }); + + test('snippets, transform example', function () { + editor.getModel().setValue(''); + editor.setSelection(new Selection(1, 1, 1, 1)); + const session = new SnippetSession(editor, '${1:name} : ${2:type}${3: :=/\\s:=(.*)/${1:+ :=}${1}/};\n$0'); + session.insert(); + + assertSelections(editor, new Selection(1, 1, 1, 5)); + editor.trigger('test', 'type', { text: 'clk' }); + session.next(); + + assertSelections(editor, new Selection(1, 7, 1, 11)); + editor.trigger('test', 'type', { text: 'std_logic' }); + session.next(); + + assertSelections(editor, new Selection(1, 16, 1, 19)); + session.next(); + + assert.equal(model.getValue(), 'clk : std_logic;\n'); + assert.equal(session.isAtLastPlaceholder, true); + assertSelections(editor, new Selection(2, 1, 2, 1)); + }); + + test('snippets, transform with indent', function () { + const snippet = [ + 'private readonly ${1} = new Emitter<$2>();', + 'readonly ${1/^_(.*)/$1/}: Event<$2> = this.$1.event;', + '$0' + ].join('\n'); + const expected = [ + '{', + '\tprivate readonly _prop = new Emitter();', + '\treadonly prop: Event = this._prop.event;', + '\t', + '}' + ].join('\n'); + const base = [ + '{', + '\t', + '}' + ].join('\n'); + + editor.getModel().setValue(base); + editor.getModel().updateOptions({ insertSpaces: false }); + editor.setSelection(new Selection(2, 2, 2, 2)); + + const session = new SnippetSession(editor, snippet); + session.insert(); + + assertSelections(editor, new Selection(2, 19, 2, 19), new Selection(3, 11, 3, 11), new Selection(3, 28, 3, 28)); + editor.trigger('test', 'type', { text: '_prop' }); + session.next(); + + assertSelections(editor, new Selection(2, 39, 2, 39), new Selection(3, 23, 3, 23)); + editor.trigger('test', 'type', { text: 'string' }); + session.next(); + + assert.equal(model.getValue(), expected); + assert.equal(session.isAtLastPlaceholder, true); + assertSelections(editor, new Selection(4, 2, 4, 2)); + + }); + + test('snippets, transform example hit if', function () { + editor.getModel().setValue(''); + editor.setSelection(new Selection(1, 1, 1, 1)); + const session = new SnippetSession(editor, '${1:name} : ${2:type}${3: :=/\\s:=(.*)/${1:+ :=}${1}/};\n$0'); + session.insert(); + + assertSelections(editor, new Selection(1, 1, 1, 5)); + editor.trigger('test', 'type', { text: 'clk' }); + session.next(); + + assertSelections(editor, new Selection(1, 7, 1, 11)); + editor.trigger('test', 'type', { text: 'std_logic' }); + session.next(); + + assertSelections(editor, new Selection(1, 16, 1, 19)); + editor.trigger('test', 'type', { text: ' := \'1\'' }); + session.next(); + + assert.equal(model.getValue(), 'clk : std_logic := \'1\';\n'); + assert.equal(session.isAtLastPlaceholder, true); + assertSelections(editor, new Selection(2, 1, 2, 1)); + }); + test('Snippet placeholder index incorrect after using 2+ snippets in a row that each end with a placeholder, #30769', function () { editor.getModel().setValue(''); editor.setSelection(new Selection(1, 1, 1, 1)); diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index 1d1cf7b049ccc..e073751d3f3d4 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -196,10 +196,12 @@ export class SuggestController implements IEditorContribution { const editorColumn = this._editor.getPosition().column; const columnDelta = editorColumn - position.column; + // pushing undo stops *before* additional text edits and + // *after* the main edit + this._editor.pushUndoStop(); + if (Array.isArray(suggestion.additionalTextEdits)) { - this._editor.pushUndoStop(); this._editor.executeEdits('suggestController.additionalTextEdits', suggestion.additionalTextEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); - this._editor.pushUndoStop(); } // keep item in memory @@ -213,9 +215,12 @@ export class SuggestController implements IEditorContribution { SnippetController2.get(this._editor).insert( insertText, suggestion.overwriteBefore + columnDelta, - suggestion.overwriteAfter + suggestion.overwriteAfter, + false, false ); + this._editor.pushUndoStop(); + if (!suggestion.command) { // done this._model.cancel(); diff --git a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts new file mode 100644 index 0000000000000..2074dd9cfc691 --- /dev/null +++ b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts @@ -0,0 +1,209 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as assert from 'assert'; +import { Position } from 'vs/editor/common/core/position'; +import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { + DeleteWordPartLeft, DeleteWordPartRight, + CursorWordPartLeft, CursorWordPartRight +} from 'vs/editor/contrib/wordPartOperations/wordPartOperations'; +import { EditorCommand } from 'vs/editor/browser/editorExtensions'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; + +suite('WordPartOperations', () => { + const _deleteWordPartLeft = new DeleteWordPartLeft(); + const _deleteWordPartRight = new DeleteWordPartRight(); + const _cursorWordPartLeft = new CursorWordPartLeft(); + const _cursorWordPartRight = new CursorWordPartRight(); + + function runEditorCommand(editor: ICodeEditor, command: EditorCommand): void { + command.runEditorCommand(null, editor, null); + } + function moveWordPartLeft(editor: ICodeEditor, inSelectionmode: boolean = false): void { + runEditorCommand(editor, inSelectionmode ? _cursorWordPartLeft : _cursorWordPartLeft); + } + function moveWordPartRight(editor: ICodeEditor, inSelectionmode: boolean = false): void { + runEditorCommand(editor, inSelectionmode ? _cursorWordPartLeft : _cursorWordPartRight); + } + function deleteWordPartLeft(editor: ICodeEditor): void { + runEditorCommand(editor, _deleteWordPartLeft); + } + function deleteWordPartRight(editor: ICodeEditor): void { + runEditorCommand(editor, _deleteWordPartRight); + } + + test('move word part left basic', () => { + withTestCodeEditor([ + 'start line', + 'thisIsACamelCaseVar this_is_a_snake_case_var', + 'end line' + ], {}, (editor, _) => { + editor.setPosition(new Position(3, 8)); + const expectedStops = [ + [3, 5], + [3, 4], + [3, 1], + [2, 46], + [2, 42], + [2, 37], + [2, 31], + [2, 29], + [2, 26], + [2, 22], + [2, 21], + [2, 20], + [2, 17], + [2, 13], + [2, 8], + [2, 7], + [2, 5], + [2, 1], + [1, 11], + [1, 7], + [1, 6], + [1, 1] + ]; + + let actualStops: number[][] = []; + for (let i = 0; i < expectedStops.length; i++) { + moveWordPartLeft(editor); + const pos = editor.getPosition(); + actualStops.push([pos.lineNumber, pos.column]); + } + + assert.deepEqual(actualStops, expectedStops); + }); + }); + + test('move word part right basic', () => { + withTestCodeEditor([ + 'start line', + 'thisIsACamelCaseVar this_is_a_snake_case_var', + 'end line' + ], {}, (editor, _) => { + editor.setPosition(new Position(1, 1)); + const expectedStops = [ + [1, 6], + [1, 7], + [1, 11], + [2, 1], + [2, 5], + [2, 7], + [2, 8], + [2, 13], + [2, 17], + [2, 20], + [2, 21], + [2, 22], + [2, 27], + [2, 30], + [2, 32], + [2, 38], + [2, 43], + [2, 46], + [3, 1], + [3, 4], + [3, 5], + [3, 9] + ]; + + let actualStops: number[][] = []; + for (let i = 0; i < expectedStops.length; i++) { + moveWordPartRight(editor); + const pos = editor.getPosition(); + actualStops.push([pos.lineNumber, pos.column]); + } + + assert.deepEqual(actualStops, expectedStops); + }); + }); + + test('delete word part left basic', () => { + withTestCodeEditor([ + ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var' + ], {}, (editor, _) => { + const model = editor.getModel(); + editor.setPosition(new Position(1, 84)); + + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case', '001'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake', '002'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a', '003'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is', '004'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this', '005'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar ', '006'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar', '007'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCase', '008'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamel', '009'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsA', '010'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIs', '011'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ this', '012'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ ', '013'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */', '014'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 ', '015'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3', '015bis'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-', '016'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5', '017'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +', '018'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 ', '019'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3', '019bis'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= ', '020'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+=', '021'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a', '022'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text ', '023'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text', '024'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some ', '025'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some', '026'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just ', '027'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just', '028'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* ', '029'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /*', '030'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' ', '031'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '', '032'); + }); + }); + + test('delete word part right basic', () => { + withTestCodeEditor([ + ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var' + ], {}, (editor, _) => { + const model = editor.getModel(); + editor.setPosition(new Position(1, 1)); + + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '/* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '001'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '002'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '003'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '004'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '005'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '006'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '007'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '008'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '009'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '010'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '011'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '012'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '013'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '014'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '015'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' */ thisIsACamelCaseVar this_is_a_snake_case_var', '016'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' thisIsACamelCaseVar this_is_a_snake_case_var', '017'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'thisIsACamelCaseVar this_is_a_snake_case_var', '018'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'IsACamelCaseVar this_is_a_snake_case_var', '019'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'ACamelCaseVar this_is_a_snake_case_var', '020'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'CamelCaseVar this_is_a_snake_case_var', '021'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'CaseVar this_is_a_snake_case_var', '022'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'Var this_is_a_snake_case_var', '023'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' this_is_a_snake_case_var', '024'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'this_is_a_snake_case_var', '025'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'is_a_snake_case_var', '026'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'a_snake_case_var', '027'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'snake_case_var', '028'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'case_var', '029'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'var', '030'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '', '031'); + }); + }); +}); diff --git a/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts b/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts new file mode 100644 index 0000000000000..28829ba7b317e --- /dev/null +++ b/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts @@ -0,0 +1,147 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ITextModel } from 'vs/editor/common/model'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { Selection } from 'vs/editor/common/core/selection'; +import { registerEditorCommand } from 'vs/editor/browser/editorExtensions'; +import { Range } from 'vs/editor/common/core/range'; +import { WordNavigationType, WordPartOperations } from 'vs/editor/common/controller/cursorWordOperations'; +import { WordCharacterClassifier } from 'vs/editor/common/controller/wordCharacterClassifier'; +import { DeleteWordCommand, MoveWordCommand } from '../wordOperations/wordOperations'; +import { Position } from 'vs/editor/common/core/position'; + +export class DeleteWordPartLeft extends DeleteWordCommand { + constructor() { + super({ + whitespaceHeuristics: true, + wordNavigationType: WordNavigationType.WordStart, + id: 'deleteWordPartLeft', + precondition: EditorContextKeys.writable, + kbOpts: { + kbExpr: EditorContextKeys.textInputFocus, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Backspace, + mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Backspace } + } + }); + } + + protected _delete(wordSeparators: WordCharacterClassifier, model: ITextModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range { + let r = WordPartOperations.deleteWordPartLeft(wordSeparators, model, selection, whitespaceHeuristics, wordNavigationType); + if (r) { + return r; + } + return new Range(1, 1, 1, 1); + } +} + +export class DeleteWordPartRight extends DeleteWordCommand { + constructor() { + super({ + whitespaceHeuristics: true, + wordNavigationType: WordNavigationType.WordEnd, + id: 'deleteWordPartRight', + precondition: EditorContextKeys.writable, + kbOpts: { + kbExpr: EditorContextKeys.textInputFocus, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Delete, + mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Delete } + } + }); + } + + protected _delete(wordSeparators: WordCharacterClassifier, model: ITextModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range { + let r = WordPartOperations.deleteWordPartRight(wordSeparators, model, selection, whitespaceHeuristics, wordNavigationType); + if (r) { + return r; + } + const lineCount = model.getLineCount(); + const maxColumn = model.getLineMaxColumn(lineCount); + return new Range(lineCount, maxColumn, lineCount, maxColumn); + } +} + +export class WordPartLeftCommand extends MoveWordCommand { + protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { + return WordPartOperations.moveWordPartLeft(wordSeparators, model, position, wordNavigationType); + } +} +export class CursorWordPartLeft extends WordPartLeftCommand { + constructor() { + super({ + inSelectionMode: false, + wordNavigationType: WordNavigationType.WordStart, + id: 'cursorWordPartStartLeft', + precondition: null, + kbOpts: { + kbExpr: EditorContextKeys.textInputFocus, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, + mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.LeftArrow } + } + }); + } +} +export class CursorWordPartLeftSelect extends WordPartLeftCommand { + constructor() { + super({ + inSelectionMode: true, + wordNavigationType: WordNavigationType.WordStart, + id: 'cursorWordPartStartLeftSelect', + precondition: null, + kbOpts: { + kbExpr: EditorContextKeys.textInputFocus, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.LeftArrow, + mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.LeftArrow } + } + }); + } +} + +export class WordPartRightCommand extends MoveWordCommand { + protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { + return WordPartOperations.moveWordPartRight(wordSeparators, model, position, wordNavigationType); + } +} +export class CursorWordPartRight extends WordPartRightCommand { + constructor() { + super({ + inSelectionMode: false, + wordNavigationType: WordNavigationType.WordEnd, + id: 'cursorWordPartRight', + precondition: null, + kbOpts: { + kbExpr: EditorContextKeys.textInputFocus, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, + mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.RightArrow } + } + }); + } +} +export class CursorWordPartRightSelect extends WordPartRightCommand { + constructor() { + super({ + inSelectionMode: true, + wordNavigationType: WordNavigationType.WordEnd, + id: 'cursorWordPartRightSelect', + precondition: null, + kbOpts: { + kbExpr: EditorContextKeys.textInputFocus, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.RightArrow, + mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.RightArrow } + } + }); + } +} + + +registerEditorCommand(new DeleteWordPartLeft()); +registerEditorCommand(new DeleteWordPartRight()); +registerEditorCommand(new CursorWordPartLeft()); +registerEditorCommand(new CursorWordPartLeftSelect()); +registerEditorCommand(new CursorWordPartRight()); +registerEditorCommand(new CursorWordPartRightSelect()); diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index e23a365cca772..cc32769c6a6de 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -42,3 +42,4 @@ import 'vs/editor/contrib/suggest/suggestController'; import 'vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode'; import 'vs/editor/contrib/wordHighlighter/wordHighlighter'; import 'vs/editor/contrib/wordOperations/wordOperations'; +import 'vs/editor/contrib/wordPartOperations/wordPartOperations'; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 05aad76b9ef9a..3f50cc1a7fea8 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4805,6 +4805,13 @@ declare namespace monaco.languages { */ export type Definition = Location | Location[]; + export interface DefinitionLink { + origin?: IRange; + uri: Uri; + range: IRange; + selectionRange?: IRange; + } + /** * The definition provider interface defines the contract between extensions and * the [go to definition](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-definition) @@ -4814,7 +4821,7 @@ declare namespace monaco.languages { /** * Provide the definition of the symbol at the given position and document. */ - provideDefinition(model: editor.ITextModel, position: Position, token: CancellationToken): Definition | Thenable; + provideDefinition(model: editor.ITextModel, position: Position, token: CancellationToken): DefinitionLink | Thenable; } /** @@ -5100,6 +5107,10 @@ declare namespace monaco.languages { export interface ResourceFileEdit { oldUri: Uri; newUri: Uri; + options: { + overwrite?: boolean; + ignoreIfExists?: boolean; + }; } export interface ResourceTextEdit { diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts index 5ba3a7b944490..6c723a9918187 100644 --- a/src/vs/platform/driver/electron-browser/driver.ts +++ b/src/vs/platform/driver/electron-browser/driver.ts @@ -13,6 +13,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { getTopLeftOffset, getClientArea } from 'vs/base/browser/dom'; import * as electron from 'electron'; import { IWindowService } from 'vs/platform/windows/common/windows'; +import { Terminal } from 'vscode-xterm'; function serializeElement(element: Element, recursive: boolean): IElement { const attributes = Object.create(null); @@ -172,7 +173,7 @@ class WindowDriver implements IWindowDriver { throw new Error('Terminal not found: ' + selector); } - const xterm = (element as any).xterm; + const xterm: Terminal = (element as any).xterm; if (!xterm) { throw new Error('Xterm not found: ' + selector); @@ -180,8 +181,8 @@ class WindowDriver implements IWindowDriver { const lines: string[] = []; - for (let i = 0; i < xterm.buffer.lines.length; i++) { - lines.push(xterm.buffer.translateBufferLineToString(i, true)); + for (let i = 0; i < xterm._core.buffer.lines.length; i++) { + lines.push(xterm._core.buffer.translateBufferLineToString(i, true)); } return lines; @@ -194,13 +195,13 @@ class WindowDriver implements IWindowDriver { throw new Error('Element not found'); } - const xterm = (element as any).xterm; + const xterm: Terminal = (element as any).xterm; if (!xterm) { throw new Error('Xterm not found'); } - xterm.send(text); + xterm._core.send(text); } async openDevTools(): TPromise { diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 0a35c0f0cd144..12e6500741873 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -376,6 +376,21 @@ export interface IExtensionEnablementService { setEnablement(extension: ILocalExtension, state: EnablementState): TPromise; } +export interface IIgnoredRecommendations { + global: string[]; + workspace: string[]; +} + +export interface IExtensionsConfigContent { + recommendations: string[]; + unwantedRecommendations: string[]; +} + +export type RecommendationChangeNotification = { + extensionId: string, + isRecommended: boolean +}; + export const IExtensionTipsService = createDecorator('extensionTipsService'); export interface IExtensionTipsService { @@ -387,6 +402,9 @@ export interface IExtensionTipsService { getKeymapRecommendations(): string[]; getKeywordsForExtension(extension: string): string[]; getRecommendationsForExtension(extension: string): string[]; + getAllIgnoredRecommendations(): IIgnoredRecommendations; + ignoreExtensionRecommendation(extensionId: string): void; + onRecommendationChange: Event; } export enum ExtensionRecommendationReason { diff --git a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts index 2ce7fce67fdf3..7589d4398765b 100644 --- a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts +++ b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts @@ -10,7 +10,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { ILogService } from 'vs/platform/log/common/log'; import { fork, ChildProcess } from 'child_process'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { join } from 'vs/base/common/paths'; +import { posix } from 'path'; import { Limiter } from 'vs/base/common/async'; import { fromNodeEventEmitter, anyEvent, mapEvent, debounceEvent } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; @@ -43,7 +43,7 @@ export class ExtensionsLifecycle extends Disposable { this.logService.warn(extension.identifier.id, 'Uninstall script should be a node script'); return null; } - return { uninstallHook: join(extension.location.path, uninstallScript[1]), args: uninstallScript.slice(2) || [] }; + return { uninstallHook: posix.join(extension.location.fsPath, uninstallScript[1]), args: uninstallScript.slice(2) || [] }; } return null; } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index c73240422680f..611209b69266a 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -446,7 +446,7 @@ export class ExtensionManagementService extends Disposable implements IExtension private extractAndRename(id: string, zipPath: string, extractPath: string, renamePath: string): TPromise { return this.extract(id, zipPath, extractPath) - .then(() => this.rename(id, extractPath, renamePath, Date.now() + (30 * 1000) /* Retry for 30 seconds */) + .then(() => this.rename(id, extractPath, renamePath, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */) .then( () => this.logService.info('Renamed to', renamePath), e => { diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 24188ef8c4df2..39ed62b06f6d8 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -182,7 +182,8 @@ export enum FileSystemProviderCapabilities { FileOpenReadWriteClose = 1 << 2, FileFolderCopy = 1 << 3, - PathCaseSensitive = 1 << 10 + PathCaseSensitive = 1 << 10, + Readonly = 1 << 11 } export interface IFileSystemProvider { @@ -398,6 +399,11 @@ export interface IBaseStat { * current state of the file or directory. */ etag: string; + + /** + * The resource is readonly. + */ + isReadonly?: boolean; } /** diff --git a/src/vs/platform/localizations/node/localizations.ts b/src/vs/platform/localizations/node/localizations.ts index 6a5a49b1f6220..2e7b180fc83f3 100644 --- a/src/vs/platform/localizations/node/localizations.ts +++ b/src/vs/platform/localizations/node/localizations.ts @@ -8,7 +8,6 @@ import { createHash } from 'crypto'; import { IExtensionManagementService, ILocalExtension, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { join } from 'vs/base/common/paths'; import { TPromise } from 'vs/base/common/winjs.base'; import { Limiter } from 'vs/base/common/async'; import { areSameExtensions, getGalleryExtensionIdFromLocal, getIdFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -18,6 +17,7 @@ import product from 'vs/platform/node/product'; import { distinct, equals } from 'vs/base/common/arrays'; import { Event, Emitter } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; +import { posix } from 'path'; interface ILanguagePack { hash: string; @@ -107,7 +107,7 @@ class LanguagePacksCache extends Disposable { @ILogService private logService: ILogService ) { super(); - this.languagePacksFilePath = join(environmentService.userDataPath, 'languagepacks.json'); + this.languagePacksFilePath = posix.join(environmentService.userDataPath, 'languagepacks.json'); this.languagePacksFileLimiter = new Limiter(1); } @@ -152,7 +152,7 @@ class LanguagePacksCache extends Disposable { languagePack.extensions.push({ extensionIdentifier, version: extension.manifest.version }); } for (const translation of localizationContribution.translations) { - languagePack.translations[translation.id] = join(extension.location.path, translation.path); + languagePack.translations[translation.id] = posix.join(extension.location.fsPath, translation.path); } } } diff --git a/src/vs/platform/node/product.ts b/src/vs/platform/node/product.ts index 685ccc5d3740b..c60c4d01ec620 100644 --- a/src/vs/platform/node/product.ts +++ b/src/vs/platform/node/product.ts @@ -30,7 +30,7 @@ export interface IProductConfiguration { }; extensionTips: { [id: string]: string; }; extensionImportantTips: { [id: string]: { name: string; pattern: string; }; }; - exeBasedExtensionTips: { [id: string]: any; }; + exeBasedExtensionTips: { [id: string]: { friendlyName: string, windowsPath?: string, recommendations: string[] }; }; extensionKeywords: { [extension: string]: string[]; }; extensionAllowedBadgeProviders: string[]; extensionAllowedProposedApi: string[]; diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index acfda318d5ea1..20da81e28b9d6 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -131,11 +131,11 @@ export interface IQuickPick extends IQuickInput { matchOnDetail: boolean; - readonly activeItems: ReadonlyArray; + activeItems: ReadonlyArray; readonly onDidChangeActive: Event; - readonly selectedItems: ReadonlyArray; + selectedItems: ReadonlyArray; readonly onDidChangeSelection: Event; } @@ -184,6 +184,8 @@ export interface IQuickInputService { */ input(options?: IInputOptions, token?: CancellationToken): TPromise; + backButton: IQuickInputButton; + createQuickPick(): IQuickPick; createInputBox(): IInputBox; @@ -195,5 +197,7 @@ export interface IQuickInputService { accept(): TPromise; + back(): TPromise; + cancel(): TPromise; } diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 60f7435509c80..6b291af09fab7 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2540,16 +2540,17 @@ declare module 'vscode' { export interface WorkspaceSymbolProvider { /** - * Project-wide search for a symbol matching the given query string. It is up to the provider - * how to search given the query string, like substring, indexOf etc. To improve performance implementors can - * skip the [location](#SymbolInformation.location) of symbols and implement `resolveWorkspaceSymbol` to do that - * later. + * Project-wide search for a symbol matching the given query string. * * The `query`-parameter should be interpreted in a *relaxed way* as the editor will apply its own highlighting * and scoring on the results. A good rule of thumb is to match case-insensitive and to simply check that the * characters of *query* appear in their order in a candidate symbol. Don't use prefix, substring, or similar * strict matching. * + * To improve performance implementors can implement `resolveWorkspaceSymbol` and then provide symbols with partial + * [location](#SymbolInformation.location)-objects, without a `range` defined. The editor will then call + * `resolveWorkspaceSymbol` for selected symbols only, e.g. when opening a workspace symbol. + * * @param query A non-empty query string. * @param token A cancellation token. * @return An array of document highlights or a thenable that resolves to such. The lack of a result can be @@ -6863,7 +6864,7 @@ declare module 'vscode' { * @param options Immutable metadata about the provider. * @return A [disposable](#Disposable) that unregisters this provider when being disposed. */ - export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider, options?: { isCaseSensitive?: boolean }): Disposable; + export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider, options?: { isCaseSensitive?: boolean, isReadonly?: boolean }): Disposable; } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 44c776e8f4ebb..94c9f3f88fd6b 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -523,14 +523,10 @@ declare module 'vscode' { export namespace window { - /** - * Implementation incomplete. See #49340. - */ + export const quickInputBackButton: QuickInputButton; + export function createQuickPick(): QuickPick; - /** - * Implementation incomplete. See #49340. - */ export function createInputBox(): InputBox; } @@ -579,11 +575,11 @@ declare module 'vscode' { matchOnDetail: boolean; - readonly activeItems: ReadonlyArray; + activeItems: ReadonlyArray; readonly onDidChangeActive: Event; - readonly selectedItems: ReadonlyArray; + selectedItems: ReadonlyArray; readonly onDidChangeSelection: Event; } @@ -610,8 +606,8 @@ declare module 'vscode' { } export interface QuickInputButton { - iconPath: string | Uri | { light: string | Uri; dark: string | Uri } | ThemeIcon; - tooltip?: string | undefined; + readonly iconPath: string | Uri | { light: string | Uri; dark: string | Uri } | ThemeIcon; + readonly tooltip?: string | undefined; } //#endregion @@ -619,21 +615,53 @@ declare module 'vscode' { //#region joh: https://github.com/Microsoft/vscode/issues/10659 export interface WorkspaceEdit { - createFile(uri: Uri): void; + + /** + * Create a regular file. + * + * @param uri Uri of the new file.. + * @param options Defines if an existing file should be overwritten or be ignored. + */ + createFile(uri: Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }): void; + + /** + * Delete a file or folder. + * + * @param uri The uri of the file that is to be deleted. + */ deleteFile(uri: Uri): void; - renameFile(oldUri: Uri, newUri: Uri): void; + + /** + * Rename a file or folder. + * + * @param oldUri The existing file. + * @param newUri The new location. + * @param options Defines if existing files should be overwritten. + */ + renameFile(oldUri: Uri, newUri: Uri, options?: { overwrite?: boolean }): void; + + // replaceText(uri: Uri, range: Range, newText: string): void; + // insertText(uri: Uri, position: Position, newText: string): void; + // deleteText(uri: Uri, range: Range): void; } //#endregion - //#region mjbvz: File rename events - export interface ResourceRenamedEvent { - readonly oldResource: Uri; - readonly newResource: Uri; + //#region mjbvz,joh: https://github.com/Microsoft/vscode/issues/43768 + export interface FileRenameEvent { + readonly oldUri: Uri; + readonly newUri: Uri; + } + + export interface FileWillRenameEvent { + readonly oldUri: Uri; + readonly newUri: Uri; + waitUntil(thenable: Thenable): void; } export namespace workspace { - export const onDidRenameResource: Event; + export const onWillRenameFile: Event; + export const onDidRenameFile: Event; } //#endregion @@ -641,6 +669,33 @@ declare module 'vscode' { /** * Restore webview panels that have been persisted when vscode shuts down. + * + * There are two types of webview persistence: + * + * - Persistence within a session. + * - Persistence across sessions (across restarts of VS Code). + * + * A `WebviewPanelSerializer` is only required for the second case: persisting a webview across sessions. + * + * Persistence within a session allows a webview to save its state when it becomes hidden + * and restore its content from this state when it becomes visible again. It is powered entirely + * by the webview content itself. To save off a persisted state, call `acquireVsCodeApi().setState()` with + * any json serializable object. To restore the state again, call `getState()` + * + * ```js + * // Within the webview + * const vscode = acquireVsCodeApi(); + * + * // Get existing state + * const oldState = vscode.getState() || { value: 0 }; + * + * // Update state + * setState({ value: oldState.value + 1 }) + * ``` + * + * A `WebviewPanelSerializer` extends this persistence across restarts of VS Code. When the editor is shutdown, VS Code will save off the state from `setState` of all webviews that have a serializer. When the + * webview first becomes visible after the restart, this state is passed to `deserializeWebviewPanel`. + * The extension can then restore the old `WebviewPanel` from this state. */ interface WebviewPanelSerializer { /** @@ -672,4 +727,47 @@ declare module 'vscode' { } //#endregion + + //#region Matt: Deinition range + + /** + * Information about where a symbol is defined. + * + * Provides additional metadata over normal [location](#Location) definitions, including the range of + * the defining symbol + */ + export interface DefinitionLink { + /** + * Span of the symbol being defined in the source file. + * + * Used as the underlined span for mouse definition hover. Defaults to the word range at + * the definition position. + */ + origin?: Range; + + /** + * The resource identifier of the definition. + */ + uri: Uri; + + /** + * The full range of the definition. + * + * For a class definition for example, this would be the entire body of the class definition. + */ + range: Range; + + /** + * The span of the symbol definition. + * + * For a class definition, this would be the class name itself in the class definition. + */ + selectionRange?: Range; + } + + export interface DefinitionProvider { + provideDefinition2?(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + } + + //#endregion } diff --git a/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts b/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts index a85c61c9d4e26..4474c00ee09f3 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts @@ -10,7 +10,7 @@ import { IModelService, shouldSynchronizeModel } from 'vs/editor/common/services import { IDisposable, dispose, IReference } from 'vs/base/common/lifecycle'; import { TextFileModelChangeEvent, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IFileService, FileOperation } from 'vs/platform/files/common/files'; +import { IFileService } from 'vs/platform/files/common/files'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { ExtHostContext, MainThreadDocumentsShape, ExtHostDocumentsShape, IExtHostContext } from '../node/extHost.protocol'; @@ -119,12 +119,6 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { } })); - this._toDispose.push(fileService.onAfterOperation(e => { - if (e.operation === FileOperation.MOVE) { - this._proxy.$onDidRename(e.resource, e.target.resource); - } - })); - this._modelToDisposeMap = Object.create(null); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadEditors.ts b/src/vs/workbench/api/electron-browser/mainThreadEditors.ts index 737c5de4d2507..8636585f8c237 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadEditors.ts @@ -30,6 +30,9 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; export class MainThreadTextEditors implements MainThreadTextEditorsShape { + private static INSTANCE_COUNT: number = 0; + + private _instanceId: string; private _proxy: ExtHostEditorsShape; private _documentsAndEditors: MainThreadDocumentsAndEditors; private _toDispose: IDisposable[]; @@ -45,6 +48,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { @IEditorService private readonly _editorService: IEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService ) { + this._instanceId = String(++MainThreadTextEditors.INSTANCE_COUNT); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostEditors); this._documentsAndEditors = documentsAndEditors; this._toDispose = []; @@ -167,6 +171,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { } $trySetDecorations(id: string, key: string, ranges: IDecorationOptions[]): TPromise { + key = `${this._instanceId}-${key}`; if (!this._documentsAndEditors.getEditor(id)) { return TPromise.wrapError(disposed(`TextEditor(${id})`)); } @@ -175,6 +180,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { } $trySetDecorationsFast(id: string, key: string, ranges: number[]): TPromise { + key = `${this._instanceId}-${key}`; if (!this._documentsAndEditors.getEditor(id)) { return TPromise.wrapError(disposed(`TextEditor(${id})`)); } @@ -218,11 +224,13 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { } $registerTextEditorDecorationType(key: string, options: IDecorationRenderOptions): void { + key = `${this._instanceId}-${key}`; this._registeredDecorationTypes[key] = true; this._codeEditorService.registerDecorationType(key, options); } $removeTextEditorDecorationType(key: string): void { + key = `${this._instanceId}-${key}`; delete this._registeredDecorationTypes[key]; this._codeEditorService.removeDecorationType(key); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/electron-browser/mainThreadFileSystemEventService.ts index cda620bf166dc..5b6e9cf18be4e 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadFileSystemEventService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadFileSystemEventService.ts @@ -4,29 +4,32 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { FileChangeType, IFileService } from 'vs/platform/files/common/files'; -import { ExtHostContext, ExtHostFileSystemEventServiceShape, FileSystemEvents, IExtHostContext } from '../node/extHost.protocol'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { FileChangeType, IFileService, FileOperation } from 'vs/platform/files/common/files'; import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; +import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../node/extHost.protocol'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @extHostCustomer export class MainThreadFileSystemEventService { - private readonly _listener: IDisposable; + private readonly _listener = new Array(); constructor( extHostContext: IExtHostContext, - @IFileService fileService: IFileService + @IFileService fileService: IFileService, + @ITextFileService textfileService: ITextFileService, ) { - const proxy: ExtHostFileSystemEventServiceShape = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService); + const proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService); + + // file system events - (changes the editor and other make) const events: FileSystemEvents = { created: [], changed: [], deleted: [] }; - - this._listener = fileService.onFileChanges(event => { + fileService.onFileChanges(event => { for (let change of event.changes) { switch (change.type) { case FileChangeType.ADDED: @@ -45,10 +48,22 @@ export class MainThreadFileSystemEventService { events.created.length = 0; events.changed.length = 0; events.deleted.length = 0; - }); + }, undefined, this._listener); + + // file operation events - (changes the editor makes) + fileService.onAfterOperation(e => { + if (e.operation === FileOperation.MOVE) { + proxy.$onFileRename(e.resource, e.target.resource); + } + }, undefined, this._listener); + + textfileService.onWillMove(e => { + let promise = proxy.$onWillRename(e.oldResource, e.newResource); + e.waitUntil(promise); + }, undefined, this._listener); } dispose(): void { - this._listener.dispose(); + dispose(this._listener); } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts index ba2cef0387a26..7ecf37c7311c6 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts @@ -14,7 +14,7 @@ import { wireCancellationToken } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange } from 'vs/editor/common/core/range'; -import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter } from '../node/extHost.protocol'; +import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto } from '../node/extHost.protocol'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; import { IHeapService } from './mainThreadHeapService'; @@ -72,6 +72,20 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } + private static _reviveDefinitionLinkDto(data: DefinitionLinkDto): modes.DefinitionLink; + private static _reviveDefinitionLinkDto(data: DefinitionLinkDto[]): modes.DefinitionLink[]; + private static _reviveDefinitionLinkDto(data: DefinitionLinkDto | DefinitionLinkDto[]): modes.DefinitionLink | modes.DefinitionLink[] { + if (!data) { + return data; + } else if (Array.isArray(data)) { + data.forEach(l => MainThreadLanguageFeatures._reviveDefinitionLinkDto(l)); + return data; + } else { + data.uri = URI.revive(data.uri); + return data; + } + } + private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto): search.IWorkspaceSymbol; private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto[]): search.IWorkspaceSymbol[]; private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto | WorkspaceSymbolDto[]): search.IWorkspaceSymbol | search.IWorkspaceSymbol[] { @@ -139,8 +153,8 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerDeclaractionSupport(handle: number, selector: ISerializedDocumentFilter[]): void { this._registrations[handle] = modes.DefinitionProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { - provideDefinition: (model, position, token): Thenable => { - return wireCancellationToken(token, this._proxy.$provideDefinition(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveLocationDto); + provideDefinition: (model, position, token): Thenable => { + return wireCancellationToken(token, this._proxy.$provideDefinition(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveDefinitionLinkDto); } }); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts b/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts index 0430d5a27dbed..5afe23566d3b9 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts @@ -12,6 +12,11 @@ import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, Transf import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import URI from 'vs/base/common/uri'; +interface QuickInputSession { + input: IQuickInput; + handlesToItems: Map; +} + @extHostNamedCustomer(MainContext.MainThreadQuickOpen) export class MainThreadQuickOpen implements MainThreadQuickOpenShape { @@ -114,7 +119,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { // ---- QuickInput - private sessions = new Map(); + private sessions = new Map(); $createOrUpdate(params: TransferQuickInput): TPromise { const sessionId = params.id; @@ -140,7 +145,10 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { input.onDidHide(() => { this._proxy.$onDidHide(sessionId); }); - session = input; + session = { + input, + handlesToItems: new Map() + }; } else { const input = this._quickInputService.createInputBox(); input.onDidAccept(() => { @@ -155,31 +163,51 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { input.onDidHide(() => { this._proxy.$onDidHide(sessionId); }); - session = input; + session = { + input, + handlesToItems: new Map() + }; } this.sessions.set(sessionId, session); } + const { input, handlesToItems } = session; for (const param in params) { if (param === 'id' || param === 'type') { continue; } if (param === 'visible') { if (params.visible) { - session.show(); + input.show(); } else { - session.hide(); + input.hide(); } + } else if (param === 'items') { + handlesToItems.clear(); + params[param].forEach(item => { + handlesToItems.set(item.handle, item); + }); + input[param] = params[param]; + } else if (param === 'activeItems' || param === 'selectedItems') { + input[param] = params[param] + .filter(handle => handlesToItems.has(handle)) + .map(handle => handlesToItems.get(handle)); } else if (param === 'buttons') { - params.buttons.forEach(button => { - const iconPath = button.iconPath; - iconPath.dark = URI.revive(iconPath.dark); - if (iconPath.light) { - iconPath.light = URI.revive(iconPath.light); + input[param] = params.buttons.map(button => { + if (button.handle === -1) { + return this._quickInputService.backButton; } + const { iconPath, tooltip, handle } = button; + return { + iconPath: { + dark: URI.revive(iconPath.dark), + light: iconPath.light && URI.revive(iconPath.light) + }, + tooltip, + handle + }; }); - session[param] = params[param]; } else { - session[param] = params[param]; + input[param] = params[param]; } } return TPromise.as(undefined); @@ -188,7 +216,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { $dispose(sessionId: number): TPromise { const session = this.sessions.get(sessionId); if (session) { - session.dispose(); + session.input.dispose(); this.sessions.delete(sessionId); } return TPromise.as(undefined); diff --git a/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts b/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts index d953dcb6a8c0c..421daa0c9e4f3 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts @@ -7,7 +7,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY } from 'vs/workbench/parts/terminal/common/terminal'; import { TPromise } from 'vs/base/common/winjs.base'; -import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto } from '../node/extHost.protocol'; +import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto } from 'vs/workbench/api/node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 64c5d86975620..8002c97083002 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -455,6 +455,11 @@ export function createApiFactory( registerProtocolHandler: proposedApiFunction(extension, (handler: vscode.ProtocolHandler) => { return extHostUrls.registerProtocolHandler(extension.id, handler); }), + get quickInputBackButton() { + return proposedApiFunction(extension, (): vscode.QuickInputButton => { + return extHostQuickOpen.backButton; + })(); + }, createQuickPick: proposedApiFunction(extension, (): vscode.QuickPick => { return extHostQuickOpen.createQuickPick(extension.id); }), @@ -571,8 +576,11 @@ export function createApiFactory( registerWorkspaceCommentProvider: proposedApiFunction(extension, (provider: vscode.WorkspaceCommentProvider) => { return exthostCommentProviders.registerWorkspaceCommentProvider(provider); }), - onDidRenameResource: proposedApiFunction(extension, (listener, thisArg?, disposables?) => { - return extHostDocuments.onDidRenameResource(listener, thisArg, disposables); + onDidRenameFile: proposedApiFunction(extension, (listener, thisArg?, disposables?) => { + return extHostFileSystemEvent.onDidRenameFile(listener, thisArg, disposables); + }), + onWillRenameFile: proposedApiFunction(extension, (listener, thisArg?, disposables?) => { + return extHostFileSystemEvent.onWillRenameFile(listener, thisArg, disposables); }) }; diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index c43d5a27b6eff..f1b585d490bbc 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -380,6 +380,10 @@ export interface TransferQuickPick extends BaseTransferQuickInput { items?: TransferQuickPickItems[]; + activeItems?: number[]; + + selectedItems?: number[]; + canSelectMany?: boolean; ignoreFocusOut?: boolean; @@ -614,7 +618,6 @@ export interface ExtHostDocumentsShape { $acceptModelSaved(strURL: UriComponents): void; $acceptDirtyStateChanged(strURL: UriComponents, isDirty: boolean): void; $acceptModelChanged(strURL: UriComponents, e: IModelChangedEvent, isDirty: boolean): void; - $onDidRename(oldURL: UriComponents, newURL: UriComponents): void; } export interface ExtHostDocumentSaveParticipantShape { @@ -698,6 +701,8 @@ export interface FileSystemEvents { } export interface ExtHostFileSystemEventServiceShape { $onFileEvent(events: FileSystemEvents): void; + $onFileRename(oldUri: UriComponents, newUri: UriComponents): void; + $onWillRename(oldUri: UriComponents, newUri: UriComponents): TPromise; } export interface ObjectIdentifier { @@ -747,6 +752,13 @@ export interface LocationDto { range: IRange; } +export interface DefinitionLinkDto { + origin?: IRange; + uri: UriComponents; + range: IRange; + selectionRange?: IRange; +} + export interface WorkspaceSymbolDto extends IdObject { name: string; containerName?: string; @@ -761,6 +773,7 @@ export interface WorkspaceSymbolsDto extends IdObject { export interface ResourceFileEditDto { oldUri: UriComponents; newUri: UriComponents; + options: { overwrite?: boolean, ignoreIfExists?: boolean }; } export interface ResourceTextEditDto { @@ -802,7 +815,7 @@ export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents): TPromise; $provideCodeLenses(handle: number, resource: UriComponents): TPromise; $resolveCodeLens(handle: number, resource: UriComponents, symbol: modes.ICodeLensSymbol): TPromise; - $provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise; + $provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise; $provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise; $provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise; $provideHover(handle: number, resource: UriComponents, position: IPosition): TPromise; diff --git a/src/vs/workbench/api/node/extHostApiCommands.ts b/src/vs/workbench/api/node/extHostApiCommands.ts index 3ff0a6186c004..d3467d37632d9 100644 --- a/src/vs/workbench/api/node/extHostApiCommands.ts +++ b/src/vs/workbench/api/node/extHostApiCommands.ts @@ -10,7 +10,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import * as vscode from 'vscode'; import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters'; import * as types from 'vs/workbench/api/node/extHostTypes'; -import { IRawColorInfo } from 'vs/workbench/api/node/extHost.protocol'; +import { IRawColorInfo, WorkspaceEditDto } from 'vs/workbench/api/node/extHost.protocol'; import { ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import * as search from 'vs/workbench/parts/search/common/search'; @@ -344,7 +344,7 @@ export class ExtHostApiCommands { position: position && typeConverters.Position.from(position), newName }; - return this._commands.executeCommand('_executeDocumentRenameProvider', args).then(value => { + return this._commands.executeCommand('_executeDocumentRenameProvider', args).then(value => { if (!value) { return undefined; } diff --git a/src/vs/workbench/api/node/extHostDocuments.ts b/src/vs/workbench/api/node/extHostDocuments.ts index e9122c83d72e8..7cc0f42359cc7 100644 --- a/src/vs/workbench/api/node/extHostDocuments.ts +++ b/src/vs/workbench/api/node/extHostDocuments.ts @@ -21,13 +21,11 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { private _onDidRemoveDocument = new Emitter(); private _onDidChangeDocument = new Emitter(); private _onDidSaveDocument = new Emitter(); - private _onDidRenameResource = new Emitter(); readonly onDidAddDocument: Event = this._onDidAddDocument.event; readonly onDidRemoveDocument: Event = this._onDidRemoveDocument.event; readonly onDidChangeDocument: Event = this._onDidChangeDocument.event; readonly onDidSaveDocument: Event = this._onDidSaveDocument.event; - readonly onDidRenameResource: Event = this._onDidRenameResource.event; private _toDispose: IDisposable[]; private _proxy: MainThreadDocumentsShape; @@ -150,11 +148,4 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { public setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void { setWordDefinitionFor(modeId, wordDefinition); } - - public $onDidRename(oldURL: UriComponents, newURL: UriComponents): void { - const oldResource = URI.revive(oldURL); - const newResource = URI.revive(newURL); - this._onDidRenameResource.fire({ oldResource, newResource }); - } - } diff --git a/src/vs/workbench/api/node/extHostFileSystem.ts b/src/vs/workbench/api/node/extHostFileSystem.ts index 2fa4cc8844fe1..58f06eb2188b5 100644 --- a/src/vs/workbench/api/node/extHostFileSystem.ts +++ b/src/vs/workbench/api/node/extHostFileSystem.ts @@ -83,7 +83,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { extHostLanguageFeatures.registerDocumentLinkProvider('*', this._linkProvider); } - registerFileSystemProvider(scheme: string, provider: vscode.FileSystemProvider, options: { isCaseSensitive?: boolean } = {}) { + registerFileSystemProvider(scheme: string, provider: vscode.FileSystemProvider, options: { isCaseSensitive?: boolean, isReadonly?: boolean } = {}) { if (this._usedSchemes.has(scheme)) { throw new Error(`a provider for the scheme '${scheme}' is already registered`); @@ -98,6 +98,9 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { if (options.isCaseSensitive) { capabilites += files.FileSystemProviderCapabilities.PathCaseSensitive; } + if (options.isReadonly) { + capabilites += files.FileSystemProviderCapabilities.Readonly; + } if (typeof provider.copy === 'function') { capabilites += files.FileSystemProviderCapabilities.FileFolderCopy; } diff --git a/src/vs/workbench/api/node/extHostFileSystemEventService.ts b/src/vs/workbench/api/node/extHostFileSystemEventService.ts index eeb7b256dcc77..476a96a3c0013 100644 --- a/src/vs/workbench/api/node/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/node/extHostFileSystemEventService.ts @@ -4,18 +4,19 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IRelativePattern, parse } from 'vs/base/common/glob'; +import URI, { UriComponents } from 'vs/base/common/uri'; +import * as vscode from 'vscode'; +import { ExtHostFileSystemEventServiceShape, FileSystemEvents } from './extHost.protocol'; import { Disposable } from './extHostTypes'; -import { parse, IRelativePattern } from 'vs/base/common/glob'; -import { Uri, FileSystemWatcher as _FileSystemWatcher } from 'vscode'; -import { FileSystemEvents, ExtHostFileSystemEventServiceShape } from './extHost.protocol'; -import URI from 'vs/base/common/uri'; +import { TPromise } from 'vs/base/common/winjs.base'; -class FileSystemWatcher implements _FileSystemWatcher { +class FileSystemWatcher implements vscode.FileSystemWatcher { - private _onDidCreate = new Emitter(); - private _onDidChange = new Emitter(); - private _onDidDelete = new Emitter(); + private _onDidCreate = new Emitter(); + private _onDidChange = new Emitter(); + private _onDidDelete = new Emitter(); private _disposable: Disposable; private _config: number; @@ -80,31 +81,60 @@ class FileSystemWatcher implements _FileSystemWatcher { this._disposable.dispose(); } - get onDidCreate(): Event { + get onDidCreate(): Event { return this._onDidCreate.event; } - get onDidChange(): Event { + get onDidChange(): Event { return this._onDidChange.event; } - get onDidDelete(): Event { + get onDidDelete(): Event { return this._onDidDelete.event; } } export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServiceShape { - private _emitter = new Emitter(); + private _onFileEvent = new Emitter(); + private _onDidRenameFile = new Emitter(); + private _onWillRenameFile = new Emitter(); + + readonly onDidRenameFile: Event = this._onDidRenameFile.event; + readonly onWillRenameFile: Event = this._onWillRenameFile.event; constructor() { } - public createFileSystemWatcher(globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): _FileSystemWatcher { - return new FileSystemWatcher(this._emitter.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents); + public createFileSystemWatcher(globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): vscode.FileSystemWatcher { + return new FileSystemWatcher(this._onFileEvent.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents); } $onFileEvent(events: FileSystemEvents) { - this._emitter.fire(events); + this._onFileEvent.fire(events); + } + + $onFileRename(oldUri: UriComponents, newUri: UriComponents) { + this._onDidRenameFile.fire(Object.freeze({ oldUri: URI.revive(oldUri), newUri: URI.revive(newUri) })); + } + + $onWillRename(oldUri: UriComponents, newUri: UriComponents): TPromise { + + let thenables: Thenable[] = []; + + this._onWillRenameFile.fire({ + oldUri: URI.revive(oldUri), + newUri: URI.revive(newUri), + waitUntil(thenable: Thenable): void { + if (Object.isFrozen(thenables)) { + throw new Error('waitUntil cannot be called async'); + } + thenables.push(thenable.then(undefined, err => console.error(err))); + } + }); + + Object.freeze(thenables); + + return TPromise.join(thenables); } } diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index 49120b1c2911c..f562cb6d6feea 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -150,18 +150,33 @@ class DefinitionAdapter { private readonly _provider: vscode.DefinitionProvider ) { } - provideDefinition(resource: URI, position: IPosition): TPromise { + provideDefinition(resource: URI, position: IPosition): TPromise { let doc = this._documents.getDocumentData(resource).document; let pos = typeConvert.Position.to(position); - return asWinJsPromise(token => this._provider.provideDefinition(doc, pos, token)).then(value => { + + return asWinJsPromise(token => this._provider.provideDefinition2 ? this._provider.provideDefinition2(doc, pos, token) : this._provider.provideDefinition(doc, pos, token)).then((value): modes.DefinitionLink[] => { if (Array.isArray(value)) { - return value.map(typeConvert.location.from); + return (value as (vscode.DefinitionLink | vscode.Location)[]).map(x => DefinitionAdapter.convertDefinitionLink(x)); } else if (value) { - return typeConvert.location.from(value); + return [DefinitionAdapter.convertDefinitionLink(value)]; } return undefined; }); } + + private static convertDefinitionLink(value: vscode.Location | vscode.DefinitionLink): modes.DefinitionLink { + const definitionLink = value; + return { + origin: definitionLink.origin + ? typeConvert.Range.from(definitionLink.origin) + : undefined, + uri: value.uri, + range: typeConvert.Range.from(value.range), + selectionRange: definitionLink.selectionRange + ? typeConvert.Range.from(definitionLink.selectionRange) + : undefined, + }; + } } class ImplementationAdapter { @@ -495,18 +510,18 @@ class RenameAdapter { return typeConvert.WorkspaceEdit.from(value); }, err => { if (typeof err === 'string') { - return { + return { edits: undefined, rejectReason: err }; } else if (err instanceof Error && typeof err.message === 'string') { - return { + return { edits: undefined, rejectReason: err.message }; } else { // generic error - return TPromise.wrapError(err); + return TPromise.wrapError(err); } }); } @@ -974,7 +989,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise { + $provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise { return this._withAdapter(handle, DefinitionAdapter, adapter => adapter.provideDefinition(URI.revive(resource), position)); } diff --git a/src/vs/workbench/api/node/extHostQuickOpen.ts b/src/vs/workbench/api/node/extHostQuickOpen.ts index a3445ad123958..1673e4b4876cf 100644 --- a/src/vs/workbench/api/node/extHostQuickOpen.ts +++ b/src/vs/workbench/api/node/extHostQuickOpen.ts @@ -16,6 +16,8 @@ import { ExtHostQuickOpenShape, IMainContext, MainContext, MainThreadQuickOpenSh import URI from 'vs/base/common/uri'; import { ThemeIcon } from 'vs/workbench/api/node/extHostTypes'; +const backButton: QuickInputButton = { iconPath: 'back.svg' }; + export type Item = string | QuickPickItem; export class ExtHostQuickOpen implements ExtHostQuickOpenShape { @@ -151,6 +153,8 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { // ---- QuickInput + backButton = backButton; + createQuickPick(extensionId: string): QuickPick { const session = new ExtHostQuickPick(this._proxy, extensionId, () => this._sessions.delete(session._id)); this._sessions.set(session._id, session); @@ -324,13 +328,14 @@ class ExtHostQuickInput implements QuickInput { this._buttons = buttons; this._handlesToButtons.clear(); buttons.forEach((button, i) => { - this._handlesToButtons.set(i, button); + const handle = button === backButton ? -1 : i; + this._handlesToButtons.set(handle, button); }); this.update({ buttons: buttons.map((button, i) => ({ iconPath: getIconUris(button.iconPath), tooltip: button.tooltip, - handle: i, + handle: button === backButton ? -1 : i, })) }); } @@ -450,6 +455,7 @@ class ExtHostQuickPick extends ExtHostQuickInput implements QuickPick { private _items: QuickPickItem[] = []; private _handlesToItems = new Map(); + private _itemsToHandles = new Map(); private _canSelectMany = false; private _matchOnDescription = true; private _matchOnDetail = true; @@ -474,8 +480,10 @@ class ExtHostQuickPick extends ExtHostQuickInput implements QuickPick { set items(items: QuickPickItem[]) { this._items = items; this._handlesToItems.clear(); + this._itemsToHandles.clear(); items.forEach((item, i) => { this._handlesToItems.set(i, item); + this._itemsToHandles.set(item, i); }); this.update({ items: items.map((item, i) => ({ @@ -519,12 +527,22 @@ class ExtHostQuickPick extends ExtHostQuickInput implements QuickPick { return this._activeItems; } + set activeItems(activeItems: QuickPickItem[]) { + this._activeItems = activeItems.filter(item => this._itemsToHandles.has(item)); + this.update({ activeItems: this._activeItems.map(item => this._itemsToHandles.get(item)) }); + } + onDidChangeActive = this._onDidChangeActiveEmitter.event; get selectedItems() { return this._selectedItems; } + set selectedItems(selectedItems: QuickPickItem[]) { + this._selectedItems = selectedItems.filter(item => this._itemsToHandles.has(item)); + this.update({ selectedItems: this._selectedItems.map(item => this._itemsToHandles.get(item)) }); + } + onDidChangeSelection = this._onDidChangeSelectionEmitter.event; _fireDidChangeActive(handles: number[]) { diff --git a/src/vs/workbench/api/node/extHostTypeConverters.ts b/src/vs/workbench/api/node/extHostTypeConverters.ts index 9aed1474620fb..96b275af75f39 100644 --- a/src/vs/workbench/api/node/extHostTypeConverters.ts +++ b/src/vs/workbench/api/node/extHostTypeConverters.ts @@ -271,18 +271,18 @@ export const TextEdit = { export namespace WorkspaceEdit { export function from(value: vscode.WorkspaceEdit, documents?: ExtHostDocumentsAndEditors): WorkspaceEditDto { - const result: modes.WorkspaceEdit = { + const result: WorkspaceEditDto = { edits: [] }; - for (const entry of (value as types.WorkspaceEdit).allEntries()) { + for (const entry of (value as types.WorkspaceEdit)._allEntries()) { const [uri, uriOrEdits] = entry; if (Array.isArray(uriOrEdits)) { // text edits let doc = documents ? documents.getDocument(uri.toString()) : undefined; - result.edits.push({ resource: uri, modelVersionId: doc && doc.version, edits: uriOrEdits.map(TextEdit.from) }); + result.edits.push({ resource: uri, modelVersionId: doc && doc.version, edits: uriOrEdits.map(TextEdit.from) }); } else { // resource edits - result.edits.push({ oldUri: uri, newUri: uriOrEdits }); + result.edits.push({ oldUri: uri, newUri: uriOrEdits, options: entry[2] }); } } return result; @@ -299,7 +299,8 @@ export namespace WorkspaceEdit { } else { result.renameFile( URI.revive((edit).oldUri), - URI.revive((edit).newUri) + URI.revive((edit).newUri), + (edit).options ); } } diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index 208a20e8b9601..677f2a9ccf497 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -13,6 +13,8 @@ import { isMarkdownString } from 'vs/base/common/htmlContent'; import { IRelativePattern } from 'vs/base/common/glob'; import { relative } from 'path'; import { startsWith } from 'vs/base/common/strings'; +import { values } from 'vs/base/common/map'; +import { coalesce } from 'vs/base/common/arrays'; export class Disposable { @@ -492,38 +494,38 @@ export class TextEdit { } } -export class WorkspaceEdit implements vscode.WorkspaceEdit { - private _seqPool: number = 0; +export interface IFileOperation { + _type: 1; + from: URI; + to: URI; + options?: { overwrite?: boolean, ignoreIfExists?: boolean; }; +} - private _resourceEdits: { seq: number, from: URI, to: URI }[] = []; - private _textEdits = new Map(); +export interface IFileTextEdit { + _type: 2; + uri: URI; + edit: TextEdit; +} - createFile(uri: vscode.Uri): void { - this.renameFile(undefined, uri); - } +export class WorkspaceEdit implements vscode.WorkspaceEdit { - deleteFile(uri: vscode.Uri): void { - this.renameFile(uri, undefined); + private _edits = new Array(); + + renameFile(from: vscode.Uri, to: vscode.Uri, options?: { overwrite?: boolean }): void { + this._edits.push({ _type: 1, from, to, options }); } - renameFile(from: vscode.Uri, to: vscode.Uri): void { - this._resourceEdits.push({ seq: this._seqPool++, from, to }); + createFile(uri: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }): void { + this._edits.push({ _type: 1, from: undefined, to: uri, options }); } - // resourceEdits(): [vscode.Uri, vscode.Uri][] { - // return this._resourceEdits.map(({ from, to }) => (<[vscode.Uri, vscode.Uri]>[from, to])); - // } + deleteFile(uri: vscode.Uri): void { + this._edits.push({ _type: 1, from: uri, to: undefined }); + } replace(uri: URI, range: Range, newText: string): void { - let edit = new TextEdit(range, newText); - let array = this.get(uri); - if (array) { - array.push(edit); - } else { - array = [edit]; - } - this.set(uri, array); + this._edits.push({ _type: 2, uri, edit: new TextEdit(range, newText) }); } insert(resource: URI, position: Position, newText: string): void { @@ -535,54 +537,76 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { } has(uri: URI): boolean { - return this._textEdits.has(uri.toString()); + for (const edit of this._edits) { + if (edit._type === 2 && edit.uri.toString() === uri.toString()) { + return true; + } + } + return false; } set(uri: URI, edits: TextEdit[]): void { - let data = this._textEdits.get(uri.toString()); - if (!data) { - data = { seq: this._seqPool++, uri, edits: [] }; - this._textEdits.set(uri.toString(), data); - } if (!edits) { - data.edits = undefined; + // remove all text edits for `uri` + for (let i = 0; i < this._edits.length; i++) { + const element = this._edits[i]; + if (element._type === 2 && element.uri.toString() === uri.toString()) { + this._edits[i] = undefined; + } + } + this._edits = coalesce(this._edits); } else { - data.edits = edits.slice(0); + // append edit to the end + for (const edit of edits) { + if (edit) { + this._edits.push({ _type: 2, uri, edit }); + } + } } } get(uri: URI): TextEdit[] { - if (!this._textEdits.has(uri.toString())) { + let res: TextEdit[] = []; + for (let candidate of this._edits) { + if (candidate._type === 2 && candidate.uri.toString() === uri.toString()) { + res.push(candidate.edit); + } + } + if (res.length === 0) { return undefined; } - const { edits } = this._textEdits.get(uri.toString()); - return edits ? edits.slice() : undefined; + return res; } entries(): [URI, TextEdit[]][] { - const res: [URI, TextEdit[]][] = []; - this._textEdits.forEach(value => res.push([value.uri, value.edits])); - return res.slice(); - } - - allEntries(): ([URI, TextEdit[]] | [URI, URI])[] { - // use the 'seq' the we have assigned when inserting - // the operation and use that order in the resulting - // array - const res: ([URI, TextEdit[]] | [URI, URI])[] = []; - this._textEdits.forEach(value => { - const { seq, uri, edits } = value; - res[seq] = [uri, edits]; - }); - this._resourceEdits.forEach(value => { - const { seq, from, to } = value; - res[seq] = [from, to]; - }); + let textEdits = new Map(); + for (let candidate of this._edits) { + if (candidate._type === 2) { + let textEdit = textEdits.get(candidate.uri.toString()); + if (!textEdit) { + textEdit = [candidate.uri, []]; + textEdits.set(candidate.uri.toString(), textEdit); + } + textEdit[1].push(candidate.edit); + } + } + return values(textEdits); + } + + _allEntries(): ([URI, TextEdit[]] | [URI, URI, { overwrite?: boolean, ignoreIfExists?: boolean }])[] { + let res: ([URI, TextEdit[]] | [URI, URI, { overwrite?: boolean, ignoreIfExists?: boolean }])[] = []; + for (let edit of this._edits) { + if (edit._type === 1) { + res.push([edit.from, edit.to, edit.options]); + } else { + res.push([edit.uri, [edit.edit]]); + } + } return res; } get size(): number { - return this._textEdits.size + this._resourceEdits.length; + return this.entries().length; } toJSON(): any { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index e323439ee9a42..5de79ae5dade3 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -306,10 +306,10 @@ export class ActivitybarPart extends Part { private enableCompositeActions(viewlet: ViewletDescriptor): void { const { activityAction, pinnedAction } = this.getCompositeActions(viewlet.id); if (activityAction instanceof PlaceHolderViewletActivityAction) { - activityAction.enable(viewlet); + activityAction.setActivity(viewlet); } if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { - pinnedAction.enable(viewlet); + pinnedAction.setActivity(viewlet); } } @@ -376,9 +376,8 @@ class PlaceHolderViewletActivityAction extends ViewletActivityAction { this.enabled = false; } - enable(activity: IActivity): void { - this.label = activity.name; - this.class = activity.cssClass; + setActivity(activity: IActivity): void { + this.activity = activity; this.enabled = true; } @@ -393,7 +392,7 @@ class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinnedAction this.enabled = false; } - enable(activity: IActivity): void { + setActivity(activity: IActivity): void { this.label = activity.name; this.enabled = true; } diff --git a/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts index 2da7b86088f41..7beeddb156ca5 100644 --- a/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts @@ -54,6 +54,7 @@ export interface ICompositeBar { export class ActivityAction extends Action { private badge: IBadge; private clazz: string | undefined; + private _onDidChangeActivity = new Emitter(); private _onDidChangeBadge = new Emitter(); constructor(private _activity: IActivity) { @@ -66,6 +67,15 @@ export class ActivityAction extends Action { return this._activity; } + public set activity(activity: IActivity) { + this._activity = activity; + this._onDidChangeActivity.fire(this); + } + + public get onDidChangeActivity(): Event { + return this._onDidChangeActivity.event; + } + public get onDidChangeBadge(): Event { return this._onDidChangeBadge.event; } @@ -127,6 +137,7 @@ export class ActivityActionItem extends BaseActionItem { super(null, action, options); this.themeService.onThemeChange(this.onThemeChange, this, this._callOnDispose); + action.onDidChangeActivity(this.updateActivity, this, this._callOnDispose); action.onDidChangeBadge(this.updateBadge, this, this._callOnDispose); } @@ -165,8 +176,7 @@ export class ActivityActionItem extends BaseActionItem { // Make the container tab-able for keyboard navigation this.$container = $(container).attr({ tabIndex: '0', - role: 'button', - title: this.activity.name + role: 'button' }); // Try hard to prevent keyboard only focus feedback when using mouse @@ -186,27 +196,26 @@ export class ActivityActionItem extends BaseActionItem { // Label this.$label = $('a.action-label').appendTo(this.builder); - if (this.activity.cssClass) { - this.$label.addClass(this.activity.cssClass); - } - if (!this.options.icon) { - this.$label.text(this.getAction().label); - } this.$badge = this.builder.clone().div({ 'class': 'badge' }, badge => { this.$badgeContent = badge.div({ 'class': 'badge-content' }); }); - this.$badge.hide(); + this.updateActivity(); this.updateStyles(); - this.updateBadge(); } private onThemeChange(theme: ITheme): void { this.updateStyles(); } + protected updateActivity(): void { + this.updateLabel(); + this.updateTitle(this.activity.name); + this.updateBadge(); + } + protected updateBadge(): void { const action = this.getAction(); if (!this.$badge || !this.$badgeContent || !(action instanceof ActivityAction)) { @@ -271,7 +280,19 @@ export class ActivityActionItem extends BaseActionItem { } else { title = this.activity.name; } + this.updateTitle(title); + } + + private updateLabel(): void { + if (this.activity.cssClass) { + this.$label.addClass(this.activity.cssClass); + } + if (!this.options.icon) { + this.$label.text(this.getAction().label); + } + } + private updateTitle(title: string): void { [this.$label, this.$badge, this.$container].forEach(b => { if (b) { b.attr('aria-label', title); @@ -409,6 +430,8 @@ export class CompositeActionItem extends ActivityActionItem { if (!CompositeActionItem.manageExtensionAction) { CompositeActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction); } + + compositeActivityAction.onDidChangeActivity(() => { this.compositeActivity = null; this.updateActivity(); }, this, this._callOnDispose); } protected get activity(): IActivity { diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index 8e8d1e5d97eca..040f588de895f 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -143,13 +143,6 @@ export abstract class BaseEditor extends Panel implements IEditor { this._group = group; } - /** - * Subclasses can set this to false if it does not make sense to center editor input. - */ - supportsCenteredLayout(): boolean { - return true; - } - protected getEditorMemento(storageService: IStorageService, editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento { const mementoKey = `${this.getId()}${key}`; diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index 47de56f97916b..7a66f852922a7 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -116,10 +116,6 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { return this.metadata; } - public supportsCenteredLayout(): boolean { - return false; - } - public clearInput(): void { // Clear Meta diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 51f20e2222bcd..73c180588f7ec 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -37,7 +37,7 @@ import { ShowEditorsInActiveGroupAction, MoveEditorToLastGroupAction, OpenFirstEditorInGroup, MoveGroupUpAction, MoveGroupDownAction, FocusLastGroupAction, SplitEditorLeftAction, SplitEditorRightAction, SplitEditorUpAction, SplitEditorDownAction, MoveEditorToLeftGroupAction, MoveEditorToRightGroupAction, MoveEditorToAboveGroupAction, MoveEditorToBelowGroupAction, CloseAllEditorGroupsAction, JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction, - EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoColumnsRightAction, EditorLayoutCenteredAction, NewEditorGroupLeftAction, NewEditorGroupRightAction, + EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoColumnsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction, NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction } from 'vs/workbench/browser/parts/editor/editorActions'; import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands'; @@ -371,7 +371,6 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutThreeRowsA registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoByTwoGridAction, EditorLayoutTwoByTwoGridAction.ID, EditorLayoutTwoByTwoGridAction.LABEL), 'View: Grid Editor Layout (2x2)', category); registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoColumnsRightAction, EditorLayoutTwoColumnsRightAction.ID, EditorLayoutTwoColumnsRightAction.LABEL), 'View: Two Columns Right Editor Layout', category); registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoColumnsBottomAction.ID, EditorLayoutTwoColumnsBottomAction.LABEL), 'View: Two Columns Bottom Editor Layout', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutCenteredAction, EditorLayoutCenteredAction.ID, EditorLayoutCenteredAction.LABEL), 'View: Centered Editor Layout', category); // Register Editor Picker Actions including quick navigate support const openNextEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }; diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 37e225011f0ec..184d01fe29323 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -1565,34 +1565,6 @@ export class EditorLayoutTwoColumnsRightAction extends ExecuteCommandAction { } } -export class EditorLayoutCenteredAction extends Action { - - public static readonly ID = 'workbench.action.editorLayoutCentered'; - public static readonly LABEL = nls.localize('editorLayoutCentered', "Centered Editor Layout"); - - constructor( - id: string, - label: string, - @IPartService private partService: IPartService, - @IEditorGroupsService private editorGroupService: IEditorGroupsService - ) { - super(id, label); - } - - public run(): TPromise { - - // Ensure we can enter centered editor layout even if there are more than 1 groups - if (this.editorGroupService.count > 1) { - mergeAllGroups(this.editorGroupService); - } - - // Center editor layout - this.partService.centerEditorLayout(true); - - return TPromise.as(true); - } -} - export class BaseCreateEditorGroupAction extends Action { constructor( @@ -1665,4 +1637,4 @@ export class NewEditorGroupBelowAction extends BaseCreateEditorGroupAction { ) { super(id, label, GroupDirection.DOWN, editorGroupService); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 96965c2979d50..d2dafc0cfe3e4 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -343,16 +343,20 @@ class DropOverlay extends Themable { } private doPositionOverlay(options: { top: string, left: string, width: string, height: string }): void { - this.overlay.style.top = options.top; - this.overlay.style.left = options.left; - this.overlay.style.width = options.width; + // Container const offsetHeight = this.getOverlayOffsetHeight(); if (offsetHeight) { - this.overlay.style.height = `calc(${options.height} - ${offsetHeight}px)`; + this.container.style.height = `calc(100% - ${offsetHeight}px)`; } else { - this.overlay.style.height = options.height; + this.container.style.height = '100%'; } + + // Overlay + this.overlay.style.top = options.top; + this.overlay.style.left = options.left; + this.overlay.style.width = options.width; + this.overlay.style.height = options.height; } private getOverlayOffsetHeight(): number { diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 91b37e7c19e65..40229727a6d26 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -8,12 +8,12 @@ import 'vs/workbench/browser/parts/editor/editor.contribution'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Part } from 'vs/workbench/browser/part'; -import { Dimension, isAncestor, toggleClass, addClass } from 'vs/base/browser/dom'; +import { Dimension, isAncestor, toggleClass, addClass, $ } from 'vs/base/browser/dom'; import { Event, Emitter, once, Relay, anyEvent } from 'vs/base/common/event'; import { contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { GroupDirection, IAddGroupOptions, GroupsArrangement, GroupOrientation, IMergeGroupOptions, MergeGroupMode, ICopyEditorOptions, GroupsOrder, GroupChangeKind, GroupLocation, IFindGroupScope, EditorGroupLayout, GroupLayoutArgument } from 'vs/workbench/services/group/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, ISerializedNode, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid } from 'vs/base/browser/ui/grid/grid'; +import { Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, ISerializedNode, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid } from 'vs/base/browser/ui/grid/grid'; import { GroupIdentifier, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { values } from 'vs/base/common/map'; import { EDITOR_GROUP_BORDER } from 'vs/workbench/common/theme'; @@ -34,6 +34,8 @@ import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/ import { EditorDropTarget } from 'vs/workbench/browser/parts/editor/editorDropTarget'; import { localize } from 'vs/nls'; import { Color } from 'vs/base/common/color'; +import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayout'; +import { IView } from 'vs/base/browser/ui/grid/gridview'; interface IEditorPartUIState { serializedGrid: ISerializedGrid; @@ -41,11 +43,54 @@ interface IEditorPartUIState { mostRecentActiveGroups: GroupIdentifier[]; } +class GridWidgetView implements IView { + + readonly element: HTMLElement = $('.grid-view-container'); + + get minimumWidth(): number { return this.gridWidget ? this.gridWidget.minimumWidth : 0; } + get maximumWidth(): number { return this.gridWidget ? this.gridWidget.maximumWidth : Number.POSITIVE_INFINITY; } + get minimumHeight(): number { return this.gridWidget ? this.gridWidget.minimumHeight : 0; } + get maximumHeight(): number { return this.gridWidget ? this.gridWidget.maximumHeight : Number.POSITIVE_INFINITY; } + + private _onDidChange = new Relay<{ width: number; height: number; }>(); + readonly onDidChange: Event<{ width: number; height: number; }> = this._onDidChange.event; + + private _gridWidget: Grid; + + get gridWidget(): Grid { + return this._gridWidget; + } + + set gridWidget(grid: Grid) { + this.element.innerHTML = ''; + + if (grid) { + this.element.appendChild(grid.element); + this._onDidChange.input = grid.onDidChange; + } else { + this._onDidChange.input = Event.None; + } + + this._gridWidget = grid; + } + + layout(width: number, height: number): void { + if (this.gridWidget) { + this.gridWidget.layout(width, height); + } + } + + dispose(): void { + this._onDidChange.dispose(); + } +} + export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditorGroupsAccessor { _serviceBrand: any; private static readonly EDITOR_PART_UI_STATE_STORAGE_KEY = 'editorpart.state'; + private static readonly EDITOR_PART_CENTERED_VIEW_STORAGE_KEY = 'editorpart.centeredview'; //#region Events @@ -77,6 +122,8 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor private _preferredSize: Dimension; private memento: object; + private globalMemento: object; + private _partOptions: IEditorPartOptions; private _activeGroup: IEditorGroupView; @@ -84,7 +131,9 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor private mostRecentActiveGroups: GroupIdentifier[] = []; private container: HTMLElement; + private centeredLayoutWidget: CenteredViewLayout; private gridWidget: SerializableGrid; + private gridWidgetView: GridWidgetView; private _whenRestored: TPromise; private whenRestoredComplete: TValueCallback; @@ -104,8 +153,11 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor ) { super(id, { hasTitle: false }, themeService); + this.gridWidgetView = new GridWidgetView(); + this._partOptions = getEditorPartOptions(this.configurationService.getValue()); this.memento = this.getMemento(this.storageService, Scope.WORKSPACE); + this.globalMemento = this.getMemento(this.storageService, Scope.GLOBAL); this._whenRestored = new TPromise(resolve => { this.whenRestoredComplete = resolve; @@ -699,7 +751,9 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor protected updateStyles(): void { this.container.style.backgroundColor = this.getColor(editorBackground); - this.gridWidget.style({ separatorBorder: this.gridSeparatorBorder }); + const separatorBorderStyle = { separatorBorder: this.gridSeparatorBorder }; + this.gridWidget.style(separatorBorderStyle); + this.centeredLayoutWidget.styles(separatorBorderStyle); } createContentArea(parent: HTMLElement): HTMLElement { @@ -709,15 +763,25 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor addClass(this.container, 'content'); parent.appendChild(this.container); - // Grid control + // Grid control with center layout this.doCreateGridControl(); + this.centeredLayoutWidget = this._register(new CenteredViewLayout(this.container, this.gridWidgetView, this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY])); + // Drop support this._register(this.instantiationService.createInstance(EditorDropTarget, this, this.container)); return this.container; } + centerLayout(active: boolean): void { + this.centeredLayoutWidget.activate(active); + } + + isLayoutCentered(): boolean { + return this.centeredLayoutWidget.isActive(); + } + private doCreateGridControl(): void { // Grid Widget (with previous UI state) @@ -771,6 +835,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor } private doCreateGridControlWithState(serializedGrid: ISerializedGrid, activeGroupId: GroupIdentifier, editorGroupViewsToReuse?: IEditorGroupView[]): void { + // Determine group views to reuse if any let reuseGroupViews: IEditorGroupView[]; if (editorGroupViewsToReuse) { @@ -807,9 +872,9 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor } this.gridWidget = gridWidget; + this.gridWidgetView.gridWidget = gridWidget; if (gridWidget) { - this.container.appendChild(gridWidget.element); this._onDidSizeConstraintsChange.input = gridWidget.onDidChange; } @@ -967,7 +1032,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor // Layout Grid try { - this.gridWidget.layout(this.dimension.width, this.dimension.height); + this.centeredLayoutWidget.layout(this.dimension.width, this.dimension.height); } catch (error) { this.gridError(error); } @@ -993,6 +1058,9 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor } } + // Persist centered view state + this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY] = this.centeredLayoutWidget.state; + // Forward to all groups this.groupViews.forEach(group => group.shutdown()); diff --git a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css index fe09540349336..262514ee87aa1 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css +++ b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css @@ -106,4 +106,9 @@ .monaco-workbench > .part.editor > .content .editor-group-container > .editor-container > .editor-instance { height: 100%; +} + +.monaco-workbench > .part.editor > .content .grid-view-container { + width: 100%; + height: 100%; } \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 8ae86117aa7ed..2dc523ad8aab4 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -94,11 +94,10 @@ .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container { display: block; - content: ''; position: absolute; top: 0; left: 0; - z-index: 5; + z-index: 6; /* over possible title border */ pointer-events: none; background-color: var(--tab-border-top-color); width: 100%; @@ -107,11 +106,10 @@ .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container { display: block; - content: ''; position: absolute; bottom: 0; left: 0; - z-index: 5; + z-index: 6; /* over possible title border */ pointer-events: none; background-color: var(--tab-border-bottom-color); width: 100%; diff --git a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css index 79479a2046f65..da2df0dfecace 100644 --- a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css @@ -11,11 +11,6 @@ flex: 1; } -.monaco-workbench > .part.editor > .content .editor-group-container.centered > .title .title-label { - flex-direction: row; - justify-content: center; -} - .monaco-workbench > .part.editor > .content .editor-group-container > .title .title-label a, .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab .tab-label a { text-decoration: none; diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index 21922b1f46f53..48230a73d7bfa 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -151,10 +151,6 @@ export class SideBySideEditor extends BaseEditor { return this.detailsEditor; } - supportsCenteredLayout(): boolean { - return false; - } - private updateInput(oldInput: SideBySideEditorInput, newInput: SideBySideEditorInput, options: EditorOptions, token: CancellationToken): Thenable { if (!newInput.matches(oldInput)) { if (oldInput) { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 77946f8ec86fd..8934bb9925f44 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1129,13 +1129,13 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const adjustedColor = tabHoverBackground.flatten(adjustedTabBackground); const adjustedColorDrag = tabHoverBackground.flatten(adjustedTabDragBackground); collector.addRule(` - .monaco-workbench > .part.editor > .content:not(.dragged-over) .editor-group-container > .title.active .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after { - background: linear-gradient(to left, ${adjustedColor}, transparent); + .monaco-workbench > .part.editor > .content:not(.dragged-over) .editor-group-container.active > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after { + background: linear-gradient(to left, ${adjustedColor}, transparent) !important; } - .monaco-workbench > .part.editor > .content.dragged-over .editor-group-container > .title.active .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after { - background: linear-gradient(to left, ${adjustedColorDrag}, transparent); + .monaco-workbench > .part.editor > .content.dragged-over .editor-group-container.active > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after { + background: linear-gradient(to left, ${adjustedColorDrag}, transparent) !important; } `); } @@ -1146,11 +1146,11 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const adjustedColorDrag = tabUnfocusedHoverBackground.flatten(adjustedTabDragBackground); collector.addRule(` .monaco-workbench > .part.editor > .content:not(.dragged-over) .editor-group-container > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after { - background: linear-gradient(to left, ${adjustedColor}, transparent); + background: linear-gradient(to left, ${adjustedColor}, transparent) !important; } .monaco-workbench > .part.editor > .content.dragged-over .editor-group-container > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after { - background: linear-gradient(to left, ${adjustedColorDrag}, transparent); + background: linear-gradient(to left, ${adjustedColorDrag}, transparent) !important; } `); } @@ -1161,7 +1161,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { collector.addRule(` .monaco-workbench > .part.editor > .content.dragged-over .editor-group-container.active > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.active):not(.dragged) > .tab-label::after, .monaco-workbench > .part.editor > .content.dragged-over .editor-group-container > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.dragged) > .tab-label::after { - background: linear-gradient(to left, ${adjustedColorDrag}, transparent); + background: linear-gradient(to left, ${adjustedColorDrag}, transparent) !important; } `); } @@ -1188,7 +1188,6 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const adjustedColor = tabInactiveBackground.flatten(adjustedTabBackground); const adjustedColorDrag = tabInactiveBackground.flatten(adjustedTabDragBackground); collector.addRule(` - .monaco-workbench > .part.editor > .content .editor-group-container > .title .monaco-workbench > .part.editor > .content:not(.dragged-over) .editor-group-container > .title .tabs-container > .tab.sizing-shrink:not(.dragged) > .tab-label::after { background: linear-gradient(to left, ${adjustedColor}, transparent); } diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 22c0a7008c31e..79e9914076793 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -118,7 +118,8 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { // Set Editor Model const diffEditor = this.getControl(); - diffEditor.setModel((resolvedModel).textDiffEditorModel); + const resolvedDiffEditorModel = resolvedModel; + diffEditor.setModel(resolvedDiffEditorModel.textDiffEditorModel); // Apply Options from TextOptions let optionsGotApplied = false; @@ -132,6 +133,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { hasPreviousViewState = this.restoreTextDiffEditorViewState(input); } + // Diff navigator this.diffNavigator = new DiffNavigator(diffEditor, { alwaysRevealFirst: !optionsGotApplied && !hasPreviousViewState // only reveal first change if we had no options or viewstate }); @@ -142,7 +144,11 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { this.previousDiffAction.updateEnablement(); })); + // Enablement of actions this.updateIgnoreTrimWhitespaceAction(); + + // Readonly flag + diffEditor.updateOptions({ readOnly: resolvedDiffEditorModel.isReadonly() }); }, error => { // In case we tried to open a file and the response indicates that this is not a text file, fallback to binary diff. @@ -163,10 +169,6 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { } } - public supportsCenteredLayout(): boolean { - return false; - } - private restoreTextDiffEditorViewState(input: EditorInput): boolean { if (input instanceof DiffEditorInput) { const resource = this.toDiffEditorViewStateResource(input); diff --git a/src/vs/workbench/browser/parts/menubar/menubarPart.ts b/src/vs/workbench/browser/parts/menubar/menubarPart.ts index ab7402fff28f5..6ecc50082dc13 100644 --- a/src/vs/workbench/browser/parts/menubar/menubarPart.ts +++ b/src/vs/workbench/browser/parts/menubar/menubarPart.ts @@ -369,7 +369,7 @@ export class MenubarPart extends Part { titleElement.attr('role', 'menu'); let mnemonic = (/&&(.)/g).exec(this.topLevelTitles[menuTitle])[1]; - if (mnemonic) { + if (mnemonic && this.currentEnableMenuBarMnemonics) { this.registerMnemonic(titleElement.getHTMLElement(), mnemonic); } diff --git a/src/vs/workbench/browser/parts/quickinput/media/dark/arrow-left.svg b/src/vs/workbench/browser/parts/quickinput/media/dark/arrow-left.svg new file mode 100644 index 0000000000000..0d24c218ae1c1 --- /dev/null +++ b/src/vs/workbench/browser/parts/quickinput/media/dark/arrow-left.svg @@ -0,0 +1,12 @@ + + + + arrow-left + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/quickinput/media/light/arrow-left.svg b/src/vs/workbench/browser/parts/quickinput/media/light/arrow-left.svg new file mode 100644 index 0000000000000..b8362b27458e3 --- /dev/null +++ b/src/vs/workbench/browser/parts/quickinput/media/light/arrow-left.svg @@ -0,0 +1,12 @@ + + + + arrow-left + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.contribution.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.contribution.ts index 5aea4a4cfce05..cc7fb7c75777f 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.contribution.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.contribution.ts @@ -4,7 +4,15 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { QuickPickManyToggle } from 'vs/workbench/browser/parts/quickinput/quickInput'; +import { QuickPickManyToggle, BackAction } from 'vs/workbench/browser/parts/quickinput/quickInput'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickopen'; KeybindingsRegistry.registerCommandAndKeybindingRule(QuickPickManyToggle); + +const registry = Registry.as(ActionExtensions.WorkbenchActions); +registry.registerWorkbenchAction(new SyncActionDescriptor(BackAction, BackAction.ID, BackAction.LABEL, { primary: null, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }, inQuickOpenContext, KeybindingsRegistry.WEIGHT.workbenchContrib(50)), 'Back'); diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.css b/src/vs/workbench/browser/parts/quickinput/quickInput.css index ab5a6f0cfa376..fbcdcbe684c31 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.css +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.css @@ -16,19 +16,30 @@ display: flex; } -.quick-input-title { +.quick-input-left-action-bar { + display: flex; + margin-left: 4px; flex: 1; - padding: 3px 11px; } -.quick-input-action-bar { +.quick-input-left-action-bar.monaco-action-bar .actions-container { + justify-content: flex-start; +} + +.quick-input-title { + padding: 3px 0px; + text-align: center; +} + +.quick-input-right-action-bar { display: flex; - margin-right: 6px; + margin-right: 4px; + flex: 1; } -.quick-input-action-bar .action-label.icon { - margin: 0 0 0 5px; - width: 16px; +.quick-input-titlebar .monaco-action-bar .action-label.icon { + margin: 0; + width: 19px; height: 100%; background-position: center; background-repeat: no-repeat; diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index 9ba6faf423c8f..23e9a96fc834d 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -40,20 +40,32 @@ import { ActionBar, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import URI from 'vs/base/common/uri'; import { IdGenerator } from 'vs/base/common/idGenerator'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; const $ = dom.$; +const backButton = { + iconPath: { + dark: URI.parse(require.toUrl('vs/workbench/browser/parts/quickinput/media/dark/arrow-left.svg')), + light: URI.parse(require.toUrl('vs/workbench/browser/parts/quickinput/media/light/arrow-left.svg')) + }, + tooltip: localize('quickInput.back', "Back"), + handle: -1 // TODO +}; + interface QuickInputUI { container: HTMLElement; + leftActionBar: ActionBar; title: HTMLElement; + rightActionBar: ActionBar; checkAll: HTMLInputElement; inputBox: QuickInputBox; count: CountBadge; - actionBar: ActionBar; message: HTMLElement; progressBar: ProgressBar; list: QuickInputList; onDidAccept: Event; + onDidTriggerButton: Event; ignoreFocusOut: boolean; show(controller: QuickInput): void; setVisibilities(visibilities: Visibilities): void; @@ -166,6 +178,13 @@ class QuickInput implements IQuickInput { if (this.visible) { return; } + this.disposables.push( + this.ui.onDidTriggerButton(button => { + if (this.buttons.indexOf(button) !== -1) { + this.onDidTriggerButtonEmitter.fire(button); + } + }), + ); this.ui.show(this); this.visible = true; this.update(); @@ -197,7 +216,9 @@ class QuickInput implements IQuickInput { if (this.busy && !this.busyDelay) { this.busyDelay = TPromise.timeout(800); this.busyDelay.then(() => { - this.ui.progressBar.infinite(); + if (this.visible) { + this.ui.progressBar.infinite(); + } }, () => { /* ignore */ }); } if (!this.busy && this.busyDelay) { @@ -207,19 +228,28 @@ class QuickInput implements IQuickInput { } if (this.buttonsUpdated) { this.buttonsUpdated = false; - this.ui.actionBar.clear(); - this.ui.actionBar.push(this.buttons.map((button, index) => { + this.ui.leftActionBar.clear(); + const leftButtons = this.buttons.filter(button => button === backButton); + this.ui.leftActionBar.push(leftButtons.map((button, index) => { + const action = new Action(`id-${index}`, '', getIconClass(button.iconPath), true, () => this.onDidTriggerButtonEmitter.fire(button)); + action.tooltip = button.tooltip; + return action; + }), { icon: true, label: false }); + this.ui.rightActionBar.clear(); + const rightButtons = this.buttons.filter(button => button !== backButton); + this.ui.rightActionBar.push(rightButtons.map((button, index) => { const action = new Action(`id-${index}`, '', getIconClass(button.iconPath), true, () => this.onDidTriggerButtonEmitter.fire(button)); action.tooltip = button.tooltip; return action; }), { icon: true, label: false }); } + this.ui.ignoreFocusOut = this.ignoreFocusOut; this.ui.setEnabled(this.enabled); } private getTitle() { if (this.title && this.step) { - return `${this.title} ― ${this.getSteps()}`; + return `${this.title} (${this.getSteps()})`; } if (this.title) { return this.title; @@ -232,7 +262,7 @@ class QuickInput implements IQuickInput { private getSteps() { if (this.step && this.totalSteps) { - return localize('quickInput.steps', "{0} of {1}", this.step, this.totalSteps); + return localize('quickInput.steps', "{0}/{1}", this.step, this.totalSteps); } if (this.step) { return String(this.step); @@ -258,8 +288,10 @@ class QuickPick extends QuickInput implements IQuickPick { private _matchOnDescription = true; private _matchOnDetail = true; private _activeItems: IQuickPickItem[] = []; + private activeItemsUpdated = false; private onDidChangeActiveEmitter = new Emitter(); private _selectedItems: IQuickPickItem[] = []; + private selectedItemsUpdated = false; private onDidChangeSelectionEmitter = new Emitter(); private quickNavigate = false; @@ -336,12 +368,24 @@ class QuickPick extends QuickInput implements IQuickPick { return this._activeItems; } + set activeItems(activeItems: IQuickPickItem[]) { + this._activeItems = activeItems; + this.activeItemsUpdated = true; + this.update(); + } + onDidChangeActive = this.onDidChangeActiveEmitter.event; get selectedItems() { return this._selectedItems; } + set selectedItems(selectedItems: IQuickPickItem[]) { + this._selectedItems = selectedItems; + this.selectedItemsUpdated = true; + this.update(); + } + onDidChangeSelection = this.onDidChangeSelectionEmitter.event; show() { @@ -382,6 +426,9 @@ class QuickPick extends QuickInput implements IQuickPick { this.onDidAcceptEmitter.fire(); }), this.ui.list.onDidChangeFocus(focusedItems => { + if (this.activeItemsUpdated) { + return; // Expect another event. + } // Drop initial event. if (!focusedItems.length && !this._activeItems.length) { return; @@ -425,6 +472,7 @@ class QuickPick extends QuickInput implements IQuickPick { this.ui.inputBox.placeholder = (this.placeholder || ''); } if (this.itemsUpdated) { + this.itemsUpdated = false; this.ui.list.setElements(this.items); this.ui.list.filter(this.ui.inputBox.value); this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked(); @@ -432,7 +480,6 @@ class QuickPick extends QuickInput implements IQuickPick { if (!this.canSelectMany) { this.ui.list.focus('First'); } - this.itemsUpdated = false; } if (this.ui.container.classList.contains('show-checkboxes') !== this.canSelectMany) { if (this.canSelectMany) { @@ -441,7 +488,18 @@ class QuickPick extends QuickInput implements IQuickPick { this.ui.list.focus('First'); } } - this.ui.ignoreFocusOut = this.ignoreFocusOut; + if (this.activeItemsUpdated) { + this.activeItemsUpdated = false; + this.ui.list.setFocusedElements(this.activeItems); + } + if (this.selectedItemsUpdated) { + this.selectedItemsUpdated = false; + if (this.canSelectMany) { + this.ui.list.setCheckedElements(this.selectedItems); + } else { + this.ui.list.setSelectedElements(this.selectedItems); + } + } this.ui.list.matchOnDescription = this.matchOnDescription; this.ui.list.matchOnDetail = this.matchOnDetail; this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, count: true, ok: true, list: true } : { title: !!this.title || !!this.step, inputBox: true, list: true }); @@ -642,6 +700,7 @@ export class QuickInputService extends Component implements IQuickInputService { private inQuickOpenWidgets: Record = {}; private inQuickOpenContext: IContextKey; private onDidAcceptEmitter = new Emitter(); + private onDidTriggerButtonEmitter = new Emitter(); private controller: QuickInput; @@ -652,6 +711,7 @@ export class QuickInputService extends Component implements IQuickInputService { @IPartService private partService: IPartService, @IQuickOpenService private quickOpenService: IQuickOpenService, @IEditorGroupsService private editorGroupService: IEditorGroupsService, + @IKeybindingService private keybindingService: IKeybindingService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService ) { @@ -693,11 +753,15 @@ export class QuickInputService extends Component implements IQuickInputService { this.titleBar = dom.append(container, $('.quick-input-titlebar')); + const leftActionBar = new ActionBar(this.titleBar); + leftActionBar.domNode.classList.add('quick-input-left-action-bar'); + this.toUnbind.push(leftActionBar); + const title = dom.append(this.titleBar, $('.quick-input-title')); - const actionBar = new ActionBar(this.titleBar); - actionBar.domNode.classList.add('quick-input-action-bar'); - this.toUnbind.push(actionBar); + const rightActionBar = new ActionBar(this.titleBar); + rightActionBar.domNode.classList.add('quick-input-right-action-bar'); + this.toUnbind.push(rightActionBar); const headerContainer = dom.append(container, $('.quick-input-header')); @@ -779,8 +843,12 @@ export class QuickInputService extends Component implements IQuickInputService { break; case KeyCode.Tab: if (!event.altKey && !event.ctrlKey && !event.metaKey) { - const inputs = [].slice.call(container.querySelectorAll('input')) - .filter(input => input.style.display !== 'none'); + const inputs = [].slice.call(container.querySelectorAll('.action-label.icon')); + if (container.classList.contains('show-checkboxes')) { + inputs.push(...[].slice.call(container.querySelectorAll('input'))); + } else { + inputs.push(...[].slice.call(container.querySelectorAll('input[type=text]'))); + } if (event.shiftKey && event.target === inputs[0]) { dom.EventHelper.stop(e, true); inputs[inputs.length - 1].focus(); @@ -797,15 +865,17 @@ export class QuickInputService extends Component implements IQuickInputService { this.ui = { container, + leftActionBar, title, + rightActionBar, checkAll, inputBox, count, message, - actionBar, progressBar, list, onDidAccept: this.onDidAcceptEmitter.event, + onDidTriggerButton: this.onDidTriggerButtonEmitter.event, ignoreFocusOut: false, show: controller => this.show(controller), hide: () => this.hide(), @@ -868,6 +938,9 @@ export class QuickInputService extends Component implements IQuickInputService { picks.then(items => { input.busy = false; input.items = items; + if (input.canSelectMany) { + input.selectedItems = items.filter(item => item.picked); + } }); input.show(); picks.then(null, err => { @@ -928,6 +1001,8 @@ export class QuickInputService extends Component implements IQuickInputService { }); } + backButton = backButton; + createQuickPick(): IQuickPick { this.create(); return new QuickPick(this.ui); @@ -948,14 +1023,15 @@ export class QuickInputService extends Component implements IQuickInputService { } this.setEnabled(true); + this.ui.leftActionBar.clear(); this.ui.title.textContent = ''; + this.ui.rightActionBar.clear(); this.ui.checkAll.checked = false; // this.ui.inputBox.value = ''; Avoid triggering an event. this.ui.inputBox.placeholder = ''; this.ui.inputBox.password = false; this.ui.inputBox.showDecoration(Severity.Ignore); this.ui.count.setCount(0); - this.ui.actionBar.clear(); this.ui.message.textContent = ''; this.ui.progressBar.stop(); this.ui.list.setElements([]); @@ -963,6 +1039,9 @@ export class QuickInputService extends Component implements IQuickInputService { this.ui.list.matchOnDetail = false; this.ui.ignoreFocusOut = false; + const keybinding = this.keybindingService.lookupKeybinding(BackAction.ID); + backButton.tooltip = keybinding ? localize('quickInput.backWithKeybinding', "Back ({0})", keybinding.getLabel()) : localize('quickInput.back', "Back"); + this.inQuickOpen('quickInput', true); this.ui.container.style.display = ''; @@ -985,11 +1064,14 @@ export class QuickInputService extends Component implements IQuickInputService { private setEnabled(enabled: boolean) { if (enabled !== this.enabled) { this.enabled = enabled; - this.ui.checkAll.disabled = !enabled; - this.ui.inputBox.enabled = enabled; - for (const item of this.ui.actionBar.items) { + for (const item of this.ui.leftActionBar.items) { + (item as ActionItem).getAction().enabled = enabled; + } + for (const item of this.ui.rightActionBar.items) { (item as ActionItem).getAction().enabled = enabled; } + this.ui.checkAll.disabled = !enabled; + // this.ui.inputBox.enabled = enabled; Avoid loosing focus. this.ok.enabled = enabled; this.ui.list.enabled = enabled; } @@ -1034,6 +1116,11 @@ export class QuickInputService extends Component implements IQuickInputService { return TPromise.as(undefined); } + back() { + this.onDidTriggerButtonEmitter.fire(this.backButton); + return TPromise.as(undefined); + } + cancel() { this.hide(); return TPromise.as(undefined); @@ -1111,3 +1198,18 @@ export const QuickPickManyToggle: ICommandAndKeybindingRule = { quickInputService.toggle(); } }; + +export class BackAction extends Action { + + public static readonly ID = 'workbench.action.quickInputBack'; + public static readonly LABEL = localize('back', "Back"); + + constructor(id: string, label: string, @IQuickInputService private quickInputService: IQuickInputService) { + super(id, label); + } + + public run(): TPromise { + this.quickInputService.back(); + return TPromise.as(null); + } +} diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts index 96551bd6c5cc6..6d5c5416319eb 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -149,6 +149,7 @@ export class QuickInputList { private container: HTMLElement; private list: WorkbenchList; private elements: ListElement[] = []; + private elementsToIndexes = new Map(); matchOnDescription = false; matchOnDetail = false; private _onChangedAllVisibleChecked = new Emitter(); @@ -269,9 +270,14 @@ export class QuickInputList { this.elements = elements.map((item, index) => new ListElement({ index, item, - checked: !!item.picked + checked: false })); this.elementDisposables.push(...this.elements.map(element => element.onChecked(() => this.fireCheckedEvents()))); + + this.elementsToIndexes = this.elements.reduce((map, element, index) => { + map.set(element.item, index); + return map; + }, new Map()); this.list.splice(0, this.list.length, this.elements); this.list.setFocus([]); } @@ -281,16 +287,44 @@ export class QuickInputList { .map(e => e.item); } + setFocusedElements(items: IQuickPickItem[]) { + this.list.setFocus(items + .filter(item => this.elementsToIndexes.has(item)) + .map(item => this.elementsToIndexes.get(item))); + } + getSelectedElements() { return this.list.getSelectedElements() .map(e => e.item); } + setSelectedElements(items: IQuickPickItem[]) { + this.list.setSelection(items + .filter(item => this.elementsToIndexes.has(item)) + .map(item => this.elementsToIndexes.get(item))); + } + getCheckedElements() { return this.elements.filter(e => e.checked) .map(e => e.item); } + setCheckedElements(items: IQuickPickItem[]) { + try { + this._fireCheckedEvents = false; + const checked = new Set(); + for (const item of items) { + checked.add(item); + } + for (const element of this.elements) { + element.checked = checked.has(element.item); + } + } finally { + this._fireCheckedEvents = true; + this.fireCheckedEvents(); + } + } + set enabled(value: boolean) { this.list.getHTMLElement().style.pointerEvents = value ? null : 'none'; } @@ -368,6 +402,10 @@ export class QuickInputList { return compareEntries(a, b, normalizedSearchValue); }); + this.elementsToIndexes = shownElements.reduce((map, element, index) => { + map.set(element.item, index); + return map; + }, new Map()); this.list.splice(0, this.list.length, shownElements); this.list.setFocus([]); this.list.layout(); diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index da8b3a5045467..59e9137b08ac5 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -101,7 +101,6 @@ export class QuickOpenController extends Component implements IQuickOpenService private editorHistoryHandler: EditorHistoryHandler; constructor( - @IEditorService private editorService: IEditorService, @IEditorGroupsService private editorGroupService: IEditorGroupsService, @INotificationService private notificationService: INotificationService, @IContextKeyService private contextKeyService: IContextKeyService, @@ -507,8 +506,8 @@ export class QuickOpenController extends Component implements IQuickOpenService if (!quickNavigateConfiguration) { autoFocus = { autoFocusFirstEntry: true }; } else { - const visibleEditorCount = this.editorService.visibleEditors.length; - autoFocus = { autoFocusFirstEntry: visibleEditorCount === 0, autoFocusSecondEntry: visibleEditorCount !== 0 }; + const autoFocusFirstEntry = this.editorGroupService.activeGroup.count === 0; + autoFocus = { autoFocusFirstEntry, autoFocusSecondEntry: !autoFocusFirstEntry }; } } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index a7dd3d25003d6..7619cce8d02c1 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -267,6 +267,8 @@ export class TitlebarPart extends Part implements ITitleService { this.title = $(this.titleContainer).div({ class: 'window-title' }); if (this.pendingTitle) { this.title.text(this.pendingTitle); + } else { + this.setTitle(this.getWindowTitle()); } // Maximize/Restore on doubleclick diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 2ada24f3e32aa..5a63ac0a0445d 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -34,6 +34,7 @@ import { LIGHT, FileThemeIcon, FolderThemeIcon } from 'vs/platform/theme/common/ import { FileKind } from 'vs/platform/files/common/files'; import { WorkbenchTreeController } from 'vs/platform/list/browser/listService'; import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IMouseEvent } from 'vs/base/browser/mouseEvent'; export class CustomTreeViewPanel extends ViewletPanel { @@ -373,7 +374,7 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer { } private onSelection({ payload }: any): void { - if (payload && payload.source === 'api') { + if (payload && (!!payload.didClickOnTwistie || payload.source === 'api')) { return; } const selection: ITreeItem = this.tree.getSelection()[0]; @@ -586,6 +587,10 @@ class TreeController extends WorkbenchTreeController { super({}, configurationService); } + protected shouldToggleExpansion(element: ITreeItem, event: IMouseEvent, origin: string): boolean { + return element.command ? this.isClickOnTwistie(event) : super.shouldToggleExpansion(element, event, origin); + } + public onContextMenu(tree: ITree, node: ITreeItem, event: ContextMenuEvent): boolean { event.preventDefault(); event.stopPropagation(); diff --git a/src/vs/workbench/common/editor/textDiffEditorModel.ts b/src/vs/workbench/common/editor/textDiffEditorModel.ts index dc199593c220a..70c3279e8d8e0 100644 --- a/src/vs/workbench/common/editor/textDiffEditorModel.ts +++ b/src/vs/workbench/common/editor/textDiffEditorModel.ts @@ -66,6 +66,10 @@ export class TextDiffEditorModel extends DiffEditorModel { return !!this._textDiffEditorModel; } + public isReadonly(): boolean { + return this.modifiedModel.isReadonly(); + } + public dispose(): void { // Free the diff editor model but do not propagate the dispose() call to the two models @@ -76,4 +80,4 @@ export class TextDiffEditorModel extends DiffEditorModel { super.dispose(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index 3d87656feb765..20bec65ef18bc 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -137,6 +137,10 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd return !!this.textEditorModelHandle; } + public isReadonly(): boolean { + return false; + } + public dispose(): void { if (this.modelDisposeListener) { this.modelDisposeListener.dispose(); // dispose this first because it will trigger another dispose() otherwise diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index 8ff4d6a55bf2e..b5b9b6e9ba168 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -306,6 +306,12 @@ configurationRegistry.registerConfiguration({ 'type': 'boolean', 'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings."), 'default': true + }, + 'workbench.settings.settingsSearchTocBehavior': { + 'type': 'string', + 'enum': ['hide', 'filter', 'show'], + 'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the settings editor TOC while searching."), + 'default': 'hide' } } }); diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 397fde34ad5e3..a172152be3be6 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -17,7 +17,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import * as browser from 'vs/base/browser/browser'; import * as perf from 'vs/base/common/performance'; import * as errors from 'vs/base/common/errors'; -import { BackupFileService } from 'vs/workbench/services/backup/node/backupFileService'; +import { BackupFileService, InMemoryBackupFileService } from 'vs/workbench/services/backup/node/backupFileService'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Registry } from 'vs/platform/registry/common/platform'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; @@ -110,7 +110,7 @@ import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { PreferencesService } from 'vs/workbench/services/preferences/browser/preferencesService'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService, GroupDirection, preferredSideBySideGroupDirection, GroupOrientation } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService, GroupDirection, preferredSideBySideGroupDirection } from 'vs/workbench/services/group/common/editorGroupsService'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; import { IExtensionUrlHandler, ExtensionUrlHandler } from 'vs/platform/url/electron-browser/inactiveExtensionUrlHandler'; import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; @@ -227,7 +227,6 @@ export class Workbench extends Disposable implements IPartService { private panelHidden: boolean; private menubarHidden: boolean; private zenMode: IZenMode; - private centeredEditorLayoutActive: boolean; private fontAliasing: FontAliasingOption; private hasInitialFilesToOpen: boolean; @@ -416,7 +415,11 @@ export class Workbench extends Disposable implements IPartService { this.menubarPart = this.instantiationService.createInstance(MenubarPart, Identifiers.MENUBAR_PART); // Backup File Service - this.backupFileService = this.instantiationService.createInstance(BackupFileService, this.workbenchParams.configuration.backupPath); + if (this.workbenchParams.configuration.backupPath) { + this.backupFileService = this.instantiationService.createInstance(BackupFileService, this.workbenchParams.configuration.backupPath); + } else { + this.backupFileService = new InMemoryBackupFileService(); + } serviceCollection.set(IBackupFileService, this.backupFileService); // Text File Service @@ -714,7 +717,7 @@ export class Workbench extends Disposable implements IPartService { // Restore Forced Editor Center Mode if (this.storageService.getBoolean(Workbench.centeredEditorLayoutActiveStorageKey, StorageScope.WORKSPACE, false)) { - this.centeredEditorLayoutActive = true; + this.centerEditorLayout(true); } const onRestored = (error?: Error): IWorkbenchStartedInfo => { @@ -872,9 +875,6 @@ export class Workbench extends Disposable implements IPartService { wasPanelVisible: false, transitionDisposeables: [] }; - - // Centered Editor Layout - this.centeredEditorLayoutActive = false; } private setPanelPositionFromStorageOrConfig() { @@ -1188,7 +1188,7 @@ export class Workbench extends Disposable implements IPartService { case Parts.TITLEBAR_PART: return this.getCustomTitleBarStyle() === 'custom' && !browser.isFullscreen(); case Parts.MENUBAR_PART: - return this.getCustomTitleBarStyle() === 'custom' && !this.menubarHidden; + return !isMacintosh && this.isVisible(Parts.TITLEBAR_PART) && !this.menubarHidden; case Parts.SIDEBAR_PART: return !this.sideBarHidden; case Parts.PANEL_PART: @@ -1293,39 +1293,14 @@ export class Workbench extends Disposable implements IPartService { } isEditorLayoutCentered(): boolean { - return this.centeredEditorLayoutActive; + return this.editorPart.isLayoutCentered(); } - // TODO@ben support centered editor layout using empty groups or not? functionality missing: - // - resize sashes left and right in sync - // - IEditorInput.supportsCenteredEditorLayout() no longer supported - // - should we just allow to enter layout even if groups > 1? what does it then mean to be - // actively in centered editor layout though? centerEditorLayout(active: boolean, skipLayout?: boolean): void { - this.centeredEditorLayoutActive = active; - this.storageService.store(Workbench.centeredEditorLayoutActiveStorageKey, this.centeredEditorLayoutActive, StorageScope.WORKSPACE); + this.storageService.store(Workbench.centeredEditorLayoutActiveStorageKey, active, StorageScope.WORKSPACE); // Enter Centered Editor Layout - if (active) { - if (this.editorGroupService.count === 1) { - const activeGroup = this.editorGroupService.activeGroup; - this.editorGroupService.addGroup(activeGroup, GroupDirection.LEFT); - this.editorGroupService.addGroup(activeGroup, GroupDirection.RIGHT); - - this.editorGroupService.applyLayout({ groups: [{ size: 0.2 }, { size: 0.6 }, { size: 0.2 }], orientation: GroupOrientation.HORIZONTAL }); - } - } - - // Leave Centered Editor Layout - else { - if (this.editorGroupService.count === 3) { - this.editorGroupService.groups.forEach(group => { - if (group.count === 0) { - this.editorGroupService.removeGroup(group); - } - }); - } - } + this.editorPart.centerLayout(active); if (!skipLayout) { this.layout(); @@ -1514,4 +1489,4 @@ export class Workbench extends Disposable implements IPartService { } //#endregion -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/backup/common/backupModelTracker.ts b/src/vs/workbench/parts/backup/common/backupModelTracker.ts index 4cb9b8c767083..d4b296540d171 100644 --- a/src/vs/workbench/parts/backup/common/backupModelTracker.ts +++ b/src/vs/workbench/parts/backup/common/backupModelTracker.ts @@ -36,9 +36,6 @@ export class BackupModelTracker implements IWorkbenchContribution { } private registerListeners() { - if (!this.backupFileService.backupEnabled) { - return; - } // Listen for text file model changes this.toDispose.push(this.textFileService.models.onModelContentChanged((e) => this.onTextFileModelChanged(e))); diff --git a/src/vs/workbench/parts/backup/common/backupRestorer.ts b/src/vs/workbench/parts/backup/common/backupRestorer.ts index c9fba97e14bed..70b860ad8de44 100644 --- a/src/vs/workbench/parts/backup/common/backupRestorer.ts +++ b/src/vs/workbench/parts/backup/common/backupRestorer.ts @@ -35,11 +35,9 @@ export class BackupRestorer implements IWorkbenchContribution { } private restoreBackups(): void { - if (this.backupFileService.backupEnabled) { - this.lifecycleService.when(LifecyclePhase.Running).then(() => { - this.doRestoreBackups().done(null, errors.onUnexpectedError); - }); - } + this.lifecycleService.when(LifecyclePhase.Running).then(() => { + this.doRestoreBackups().done(null, errors.onUnexpectedError); + }); } private doRestoreBackups(): TPromise { diff --git a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts index 15a5f4e83a99c..4a7902f94ecdc 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts @@ -93,8 +93,7 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi this.updateScheduler = new RunOnceScheduler(() => { const state = this.debugService.state; const toolBarLocation = this.configurationService.getValue('debug').toolBarLocation; - if (state === State.Inactive || this.configurationService.getValue('debug').hideActionBar - || toolBarLocation === 'docked' || toolBarLocation === 'hidden') { + if (state === State.Inactive || toolBarLocation === 'docked' || toolBarLocation === 'hidden') { return this.hide(); } diff --git a/src/vs/workbench/parts/debug/browser/debugStatus.ts b/src/vs/workbench/parts/debug/browser/debugStatus.ts index f72afca4b5d83..755b562c84ee1 100644 --- a/src/vs/workbench/parts/debug/browser/debugStatus.ts +++ b/src/vs/workbench/parts/debug/browser/debugStatus.ts @@ -45,15 +45,11 @@ export class DebugStatus extends Themable implements IStatusbarItem { this.toDispose.push(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('debug.showInStatusBar')) { this.showInStatusBar = configurationService.getValue('debug').showInStatusBar; - if (this.showInStatusBar === 'never' && this.statusBarItem) { - this.statusBarItem.hidden = true; - } else { - if (this.statusBarItem) { - this.statusBarItem.hidden = false; - } - if (this.showInStatusBar === 'always') { - this.doRender(); - } + if (this.showInStatusBar === 'always') { + this.doRender(); + } + if (this.statusBarItem) { + dom.toggleClass(this.statusBarItem, 'hidden', this.showInStatusBar === 'never'); } } })); @@ -99,11 +95,10 @@ export class DebugStatus extends Themable implements IStatusbarItem { if (this.label && this.statusBarItem) { const manager = this.debugService.getConfigurationManager(); const name = manager.selectedConfiguration.name; - if (name && manager.selectedConfiguration.launch) { - this.statusBarItem.style.display = 'block'; + const nameAndLaunchPresent = name && manager.selectedConfiguration.launch; + dom.toggleClass(this.statusBarItem, 'hidden', this.showInStatusBar === 'never' || !nameAndLaunchPresent); + if (nameAndLaunchPresent) { this.label.textContent = manager.getLaunches().length > 1 ? `${name} (${manager.selectedConfiguration.launch.name})` : name; - } else { - this.statusBarItem.style.display = 'none'; } } } diff --git a/src/vs/workbench/parts/debug/browser/debugViewlet.ts b/src/vs/workbench/parts/debug/browser/debugViewlet.ts index 04f9c90f3dbca..e1ea8e7a8f8d4 100644 --- a/src/vs/workbench/parts/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/parts/debug/browser/debugViewlet.ts @@ -226,7 +226,7 @@ export class FocusWatchViewAction extends Action { export class FocusCallStackViewAction extends Action { static readonly ID = 'workbench.debug.action.focusCallStackView'; - static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusCallStackView' }, 'Focus CallStack'); + static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusCallStackView' }, 'Focus Call Stack'); constructor(id: string, label: string, @IViewletService private viewletService: IViewletService diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 11a7f7d80449f..16fb543aa5ff1 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -355,7 +355,6 @@ export interface IDebugConfiguration { openDebug: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart' | 'openOnDebugBreak'; openExplorerOnEnd: boolean; inlineValues: boolean; - hideActionBar: boolean; toolBarLocation: 'floating' | 'docked' | 'hidden'; showInStatusBar: 'never' | 'always' | 'onFirstSessionStart'; internalConsoleOptions: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart'; diff --git a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts index b04ba6f0bccbd..a0dd24849eb4e 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts @@ -147,7 +147,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(FocusReplAction, Focus registry.registerWorkbenchAction(new SyncActionDescriptor(SelectAndStartAction, SelectAndStartAction.ID, SelectAndStartAction.LABEL), 'Debug: Select and Start Debugging', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(FocusVariablesViewAction, FocusVariablesViewAction.ID, FocusVariablesViewAction.LABEL), 'Debug: Focus Variables', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(FocusWatchViewAction, FocusWatchViewAction.ID, FocusWatchViewAction.LABEL), 'Debug: Focus Watch', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(FocusCallStackViewAction, FocusCallStackViewAction.ID, FocusCallStackViewAction.LABEL), 'Debug: Focus CallStack', debugCategory); +registry.registerWorkbenchAction(new SyncActionDescriptor(FocusCallStackViewAction, FocusCallStackViewAction.ID, FocusCallStackViewAction.LABEL), 'Debug: Focus Call Stack', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(FocusBreakpointsViewAction, FocusBreakpointsViewAction.ID, FocusBreakpointsViewAction.LABEL), 'Debug: Focus Breakpoints', debugCategory); diff --git a/src/vs/workbench/parts/debug/electron-browser/repl.ts b/src/vs/workbench/parts/debug/electron-browser/repl.ts index 9a318ab59e0eb..8574e52a10eb4 100644 --- a/src/vs/workbench/parts/debug/electron-browser/repl.ts +++ b/src/vs/workbench/parts/debug/electron-browser/repl.ts @@ -174,6 +174,8 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati modes.SuggestRegistry.register({ scheme: DEBUG_SCHEME, hasAccessToAllModels: true }, { triggerCharacters: ['.'], provideCompletionItems: (model: ITextModel, position: Position, _context: modes.SuggestContext, token: CancellationToken): Thenable => { + // Disable history navigation because up and down are used to navigate through the suggest widget + historyNavigationEnablement.set(false); const word = this.replInput.getModel().getWordAtPosition(position); const overwriteBefore = word ? word.word.length : 0; const text = this.replInput.getModel().getLineContent(position.lineNumber); @@ -198,7 +200,6 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati historyNavigationEnablement.set(this.replInput.getModel().getLineCount() === 1); })); - this.toUnbind.push(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.FOCUS, () => dom.addClass(this.replInputContainer, 'synthetic-focus'))); this.toUnbind.push(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.BLUR, () => dom.removeClass(this.replInputContainer, 'synthetic-focus'))); } diff --git a/src/vs/workbench/parts/extensions/common/extensionsFileTemplate.ts b/src/vs/workbench/parts/extensions/common/extensionsFileTemplate.ts index fc81cb063d1b9..a6d988dfb7ccb 100644 --- a/src/vs/workbench/parts/extensions/common/extensionsFileTemplate.ts +++ b/src/vs/workbench/parts/extensions/common/extensionsFileTemplate.ts @@ -13,10 +13,20 @@ export const ExtensionsConfigurationSchema: IJSONSchema = { allowComments: true, type: 'object', title: localize('app.extensions.json.title', "Extensions"), + additionalProperties: false, properties: { recommendations: { type: 'array', - description: localize('app.extensions.json.recommendations', "List of extensions recommendations. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."), + description: localize('app.extensions.json.recommendations', "List of extensions which should be recommended for users of this workspace. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."), + items: { + type: 'string', + pattern: EXTENSION_IDENTIFIER_PATTERN, + errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.") + }, + }, + unwantedRecommendations: { + type: 'array', + description: localize('app.extensions.json.unwantedRecommendations', "List of extensions that will be skipped from the recommendations that VS Code makes for the users of this workspace. These are extensions that you may consider to be irrelevant, redundant, or otherwise unwanted. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."), items: { type: 'string', pattern: EXTENSION_IDENTIFIER_PATTERN, @@ -31,6 +41,12 @@ export const ExtensionsConfigurationInitialContent: string = [ '\t// See http://go.microsoft.com/fwlink/?LinkId=827846', '\t// for the documentation about the extensions.json format', '\t"recommendations": [', + '\t\t// List of extensions which should be recommended for users of this workspace.', + '\t\t// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp', + '\t\t', + '\t],', + '\t"unwantedRecommendations": [', + '\t\t// List of extensions that will be skipped from the recommendations that VS Code makes for the users of this workspace. These are extensions that you may consider to be irrelevant, redundant, or otherwise unwanted.', '\t\t// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp', '\t\t', '\t]', diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts index e42c43d374906..3440e74a6ddcf 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts @@ -31,7 +31,7 @@ import { Renderer, DataSource, Controller } from 'vs/workbench/parts/extensions/ import { RatingsWidget, InstallCountWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets'; import { EditorOptions } from 'vs/workbench/common/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, ReloadAction, MaliciousStatusLabelAction, DisabledStatusLabelAction, MultiServerInstallAction, MultiServerUpdateAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; +import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, ReloadAction, MaliciousStatusLabelAction, DisabledStatusLabelAction, MultiServerInstallAction, MultiServerUpdateAction, IgnoreExtensionRecommendationAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; import { WebviewElement } from 'vs/workbench/parts/webview/electron-browser/webviewElement'; import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -164,6 +164,8 @@ export class ExtensionEditor extends BaseEditor { private navbar: NavBar; private content: HTMLElement; private recommendation: HTMLElement; + private recommendationText: any; + private ignoreActionbar: ActionBar; private header: HTMLElement; private extensionReadme: Cache; @@ -256,15 +258,24 @@ export class ExtensionEditor extends BaseEditor { return null; } }); - this.disposables.push(this.extensionActionBar); this.recommendation = append(details, $('.recommendation')); + this.recommendationText = append(this.recommendation, $('.recommendation-text')); + this.ignoreActionbar = new ActionBar(this.recommendation, { animated: false }); + + this.disposables.push(this.extensionActionBar); + this.disposables.push(this.ignoreActionbar); chain(this.extensionActionBar.onDidRun) .map(({ error }) => error) .filter(error => !!error) .on(this.onError, this, this.disposables); + chain(this.ignoreActionbar.onDidRun) + .map(({ error }) => error) + .filter(error => !!error) + .on(this.onError, this, this.disposables); + const body = append(root, $('.body')); this.navbar = new NavBar(body); @@ -295,24 +306,25 @@ export class ExtensionEditor extends BaseEditor { this.publisher.textContent = extension.publisherDisplayName; this.description.textContent = extension.description; + removeClass(this.header, 'recommendation-ignored'); const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); let recommendationsData = {}; if (extRecommendations[extension.id.toLowerCase()]) { addClass(this.header, 'recommended'); - this.recommendation.textContent = extRecommendations[extension.id.toLowerCase()].reasonText; + this.recommendationText.textContent = extRecommendations[extension.id.toLowerCase()].reasonText; recommendationsData = { recommendationReason: extRecommendations[extension.id.toLowerCase()].reasonId }; } else { removeClass(this.header, 'recommended'); - this.recommendation.textContent = ''; + this.recommendationText.textContent = ''; } /* __GDPR__ - "extensionGallery:openExtension" : { - "recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "${include}": [ - "${GalleryExtensionTelemetryData}" - ] - } + "extensionGallery:openExtension" : { + "recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "${include}": [ + "${GalleryExtensionTelemetryData}" + ] + } */ this.telemetryService.publicLog('extensionGallery:openExtension', assign(extension.telemetryData, recommendationsData)); @@ -376,6 +388,21 @@ export class ExtensionEditor extends BaseEditor { this.extensionActionBar.push([disabledStatusAction, reloadAction, updateAction, enableAction, disableAction, installAction, maliciousStatusAction], { icon: true, label: true }); this.transientDisposables.push(enableAction, updateAction, reloadAction, disableAction, installAction, maliciousStatusAction, disabledStatusAction); + const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction); + ignoreAction.extension = extension; + + this.extensionTipsService.onRecommendationChange(change => { + if (change.extensionId.toLowerCase() === extension.id.toLowerCase() && change.isRecommended === false) { + addClass(this.header, 'recommendation-ignored'); + removeClass(this.header, 'recommended'); + this.recommendationText.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."); + } + }); + + this.ignoreActionbar.clear(); + this.ignoreActionbar.push([ignoreAction], { icon: true, label: true }); + this.transientDisposables.push(ignoreAction); + this.content.innerHTML = ''; // Clear content before setting navbar actions. this.navbar.clear(); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index 71cce68cb0bb5..9c3312d585eb3 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -10,7 +10,7 @@ import { forEach } from 'vs/base/common/collections'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { match } from 'vs/base/common/glob'; import * as json from 'vs/base/common/json'; -import { IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, ExtensionRecommendationReason, LocalExtensionType, EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, ExtensionRecommendationReason, LocalExtensionType, EXTENSION_IDENTIFIER_PATTERN, IIgnoredRecommendations, IExtensionsConfigContent, RecommendationChangeNotification } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextModel } from 'vs/editor/common/model'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -36,10 +36,7 @@ import { asJson } from 'vs/base/node/request'; import { isNumber } from 'vs/base/common/types'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { INotificationService } from 'vs/platform/notification/common/notification'; - -interface IExtensionsContent { - recommendations: string[]; -} +import { Emitter, Event } from 'vs/base/common/event'; const empty: { [key: string]: any; } = Object.create(null); const milliSecondsInADay = 1000 * 60 * 60 * 24; @@ -52,6 +49,15 @@ interface IDynamicWorkspaceRecommendations { recommendations: string[]; } +function caseInsensitiveGet(obj: { [key: string]: T }, key: string): T | undefined { + for (const _key in obj) { + if (obj.hasOwnProperty(_key) && _key.toLowerCase() === key.toLowerCase()) { + return obj[_key]; + } + } + return undefined; +} + export class ExtensionTipsService extends Disposable implements IExtensionTipsService { _serviceBrand: any; @@ -61,11 +67,17 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe private _availableRecommendations: { [pattern: string]: string[] } = Object.create(null); private _allWorkspaceRecommendedExtensions: string[] = []; private _dynamicWorkspaceRecommendations: string[] = []; + private _allIgnoredRecommendations: string[] = []; + private _globallyIgnoredRecommendations: string[] = []; + private _workspaceIgnoredRecommendations: string[] = []; private _extensionsRecommendationsUrl: string; private _disposables: IDisposable[] = []; - public promptWorkspaceRecommendationsPromise: TPromise; + public loadRecommendationsPromise: TPromise; private proactiveRecommendationsFetched: boolean = false; + private readonly _onRecommendationChange: Emitter = new Emitter(); + onRecommendationChange: Event = this._onRecommendationChange.event; + constructor( @IExtensionGalleryService private readonly _galleryService: IExtensionGalleryService, @IModelService private readonly _modelService: IModelService, @@ -92,9 +104,19 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe this._extensionsRecommendationsUrl = product.extensionsGallery.recommendationsUrl; } - this.getCachedDynamicWorkspaceRecommendations(); - this._suggestFileBasedRecommendations(); - this.promptWorkspaceRecommendationsPromise = this._suggestWorkspaceRecommendations(); + let globallyIgnored = JSON.parse(this.storageService.get('extensionsAssistant/ignored_recommendations', StorageScope.GLOBAL, '[]')); + this._globallyIgnoredRecommendations = globallyIgnored.map(id => id.toLowerCase()); + + this.loadRecommendationsPromise = this.getWorkspaceRecommendations() + .then(() => { + // these must be called after workspace configs have been refreshed. + this.getCachedDynamicWorkspaceRecommendations(); + this._suggestFileBasedRecommendations(); + return this._suggestWorkspaceRecommendations(); + }).then(() => { + this._modelService.onModelAdded(this._suggest, this, this._disposables); + this._modelService.getModels().forEach(model => this._suggest(model)); + }); if (!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)) { this.fetchProactiveRecommendations(true); @@ -164,41 +186,69 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } getWorkspaceRecommendations(): TPromise { - if (!this.isEnabled()) { - return TPromise.as([]); - } - const workspace = this.contextService.getWorkspace(); - return TPromise.join([this.resolveWorkspaceRecommendations(workspace), ...workspace.folders.map(workspaceFolder => this.resolveWorkspaceFolderRecommendations(workspaceFolder))]) - .then(recommendations => { - this._allWorkspaceRecommendedExtensions = distinct(flatten(recommendations)); + if (!this.isEnabled) { return TPromise.as([]); } + + return this.fetchCombinedExtensionRecommendationConfig() + .then(content => { + this._workspaceIgnoredRecommendations = content.unwantedRecommendations; + this._allIgnoredRecommendations = distinct([...this._globallyIgnoredRecommendations, ...this._workspaceIgnoredRecommendations]); + this._allWorkspaceRecommendedExtensions = content.recommendations; + this.refilterAllRecommendations(); return this._allWorkspaceRecommendedExtensions; }); } - private resolveWorkspaceRecommendations(workspace: IWorkspace): TPromise { - if (workspace.configuration) { - return this.fileService.resolveContent(workspace.configuration) - .then(content => this.processWorkspaceRecommendations(json.parse(content.value, [])['extensions']), err => []); + private fetchCombinedExtensionRecommendationConfig(): TPromise { + const mergeExtensionRecommendationConfigs: (configs: IExtensionsConfigContent[]) => IExtensionsConfigContent = configs => ({ + recommendations: distinct(flatten(configs.map(config => config && config.recommendations || []))), + unwantedRecommendations: distinct(flatten(configs.map(config => config && config.unwantedRecommendations || []))) + }); + + const workspace = this.contextService.getWorkspace(); + return TPromise.join([this.resolveWorkspaceExtensionConfig(workspace), ...workspace.folders.map(workspaceFolder => this.resolveWorkspaceFolderExtensionConfig(workspaceFolder))]) + .then(contents => this.processConfigContent(mergeExtensionRecommendationConfigs(contents))); + } + + private resolveWorkspaceExtensionConfig(workspace: IWorkspace): TPromise { + if (!workspace.configuration) { + return TPromise.as(null); } - return TPromise.as([]); + + return this.fileService.resolveContent(workspace.configuration) + .then(content => (json.parse(content.value)['extensions']), err => null); } - private resolveWorkspaceFolderRecommendations(workspaceFolder: IWorkspaceFolder): TPromise { + private resolveWorkspaceFolderExtensionConfig(workspaceFolder: IWorkspaceFolder): TPromise { const extensionsJsonUri = workspaceFolder.toResource(paths.join('.vscode', 'extensions.json')); - return this.fileService.resolveFile(extensionsJsonUri).then(() => { - return this.fileService.resolveContent(extensionsJsonUri) - .then(content => this.processWorkspaceRecommendations(json.parse(content.value, [])), err => []); - }, err => []); + + return this.fileService.resolveFile(extensionsJsonUri) + .then(() => this.fileService.resolveContent(extensionsJsonUri)) + .then(content => json.parse(content.value), err => null); } - private processWorkspaceRecommendations(extensionsContent: IExtensionsContent): TPromise { + private processConfigContent(extensionsContent: IExtensionsConfigContent): TPromise { + if (!extensionsContent) { + return TPromise.as({ recommendations: [], unwantedRecommendations: [] }); + } + const regEx = new RegExp(EXTENSION_IDENTIFIER_PATTERN); - if (extensionsContent && extensionsContent.recommendations && extensionsContent.recommendations.length) { - let countBadRecommendations = 0; - let badRecommendationsString = ''; - let filteredRecommendations = extensionsContent.recommendations.filter((element, position) => { - if (extensionsContent.recommendations.indexOf(element) !== position) { + let countBadRecommendations = 0; + let badRecommendationsString = ''; + let errorsNotification = () => { + if (countBadRecommendations > 0 && this.notificationService) { + this.notificationService.warn( + 'The below ' + + countBadRecommendations + + ' extension(s) in workspace recommendations have issues:\n' + + badRecommendationsString + ); + } + }; + + let regexFilter = (ids: string[]) => { + return ids.filter((element, position) => { + if (ids.indexOf(element) !== position) { // This is a duplicate entry, it doesn't hurt anybody // but it shouldn't be sent in the gallery query return false; @@ -207,52 +257,76 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe badRecommendationsString += `${element} (bad format) Expected: .\n`; return false; } - return true; }); + }; + + let filteredWanted = regexFilter(extensionsContent.recommendations || []).map(x => x.toLowerCase()); + let filteredUnwanted = regexFilter(extensionsContent.unwantedRecommendations || []).map(x => x.toLowerCase()); + + if (!filteredWanted.length) { + errorsNotification(); + return TPromise.as({ recommendations: filteredWanted, unwantedRecommendations: filteredUnwanted }); + } + + return this._galleryService.query({ names: filteredWanted }).then(pager => { + let page = pager.firstPage; + let validRecommendations = page.map(extension => { + return extension.identifier.id.toLowerCase(); + }); - return this._galleryService.query({ names: filteredRecommendations }).then(pager => { - let page = pager.firstPage; - let validRecommendations = page.map(extension => { - return extension.identifier.id.toLowerCase(); + if (validRecommendations.length !== filteredWanted.length) { + filteredWanted.forEach(element => { + if (validRecommendations.indexOf(element.toLowerCase()) === -1) { + countBadRecommendations++; + badRecommendationsString += `${element} (not found in marketplace)\n`; + } }); + } - if (validRecommendations.length !== filteredRecommendations.length) { - filteredRecommendations.forEach(element => { - if (validRecommendations.indexOf(element.toLowerCase()) === -1) { - countBadRecommendations++; - badRecommendationsString += `${element} (not found in marketplace)\n`; - } - }); - } + errorsNotification(); + return { recommendations: validRecommendations, unwantedRecommendations: filteredUnwanted }; + }); + } - if (countBadRecommendations > 0 && this.notificationService) { - this.notificationService.warn( - 'The below ' + - countBadRecommendations + - ' extension(s) in workspace recommendations have issues:\n' + - badRecommendationsString - ); - } + private refilterAllRecommendations() { + this._allWorkspaceRecommendedExtensions = this._allWorkspaceRecommendedExtensions.filter((id) => this.isExtensionAllowedToBeRecommended(id)); + this._dynamicWorkspaceRecommendations = this._dynamicWorkspaceRecommendations.filter((id) => this.isExtensionAllowedToBeRecommended(id)); - return validRecommendations; - }); + this._allIgnoredRecommendations.forEach(x => { + delete this._fileBasedRecommendations[x]; + delete this._exeBasedRecommendations[x]; + }); + + if (this._availableRecommendations) { + for (const key in this._availableRecommendations) { + if (Object.prototype.hasOwnProperty.call(this._availableRecommendations, key)) { + this._availableRecommendations[key] = this._availableRecommendations[key].filter(id => this.isExtensionAllowedToBeRecommended(id)); + } + } } + } - return TPromise.as([]); + getAllIgnoredRecommendations(): IIgnoredRecommendations { + return { + workspace: this._workspaceIgnoredRecommendations, + global: this._globallyIgnoredRecommendations + }; + } + private isExtensionAllowedToBeRecommended(id: string): boolean { + return this._allIgnoredRecommendations.indexOf(id.toLowerCase()) === -1; } private onWorkspaceFoldersChanged(event: IWorkspaceFoldersChangeEvent): void { if (event.added.length) { - TPromise.join(event.added.map(workspaceFolder => this.resolveWorkspaceFolderRecommendations(workspaceFolder))) - .then(result => { - const newRecommendations = flatten(result); - // Suggest only if atleast one of the newly added recommendtations was not suggested before - if (newRecommendations.some(e => this._allWorkspaceRecommendedExtensions.indexOf(e) === -1)) { - this._suggestWorkspaceRecommendations(); - } - }); + const oldWorkspaceRecommended = this._allWorkspaceRecommendedExtensions; + this.getWorkspaceRecommendations().then(result => { + // Suggest only if at least one of the newly added recommendations was not suggested before + if (result.some(e => oldWorkspaceRecommended.indexOf(e) === -1)) { + this._suggestWorkspaceRecommendations(); + } + }); } this._dynamicWorkspaceRecommendations = []; } @@ -261,10 +335,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const fileBased = Object.keys(this._fileBasedRecommendations) .sort((a, b) => { if (this._fileBasedRecommendations[a] === this._fileBasedRecommendations[b]) { - if (!product.extensionImportantTips || product.extensionImportantTips[a]) { + if (!product.extensionImportantTips || caseInsensitiveGet(product.extensionImportantTips, a)) { return -1; } - if (product.extensionImportantTips[b]) { + if (caseInsensitiveGet(product.extensionImportantTips, b)) { return 1; } } @@ -295,29 +369,30 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe this._availableRecommendations = Object.create(null); forEach(extensionTips, entry => { let { key: id, value: pattern } = entry; - let ids = this._availableRecommendations[pattern]; - if (!ids) { - this._availableRecommendations[pattern] = [id]; - } else { - ids.push(id); + if (this.isExtensionAllowedToBeRecommended(id)) { + let ids = this._availableRecommendations[pattern]; + if (!ids) { + this._availableRecommendations[pattern] = [id.toLowerCase()]; + } else { + ids.push(id.toLowerCase()); + } } }); forEach(product.extensionImportantTips, entry => { let { key: id, value } = entry; - const { pattern } = value; - let ids = this._availableRecommendations[pattern]; - if (!ids) { - this._availableRecommendations[pattern] = [id]; - } else { - ids.push(id); + if (this.isExtensionAllowedToBeRecommended(id)) { + const { pattern } = value; + let ids = this._availableRecommendations[pattern]; + if (!ids) { + this._availableRecommendations[pattern] = [id.toLowerCase()]; + } else { + ids.push(id.toLowerCase()); + } } }); - const allRecommendations: string[] = []; - forEach(this._availableRecommendations, ({ value: ids }) => { - allRecommendations.push(...ids); - }); + const allRecommendations: string[] = flatten((Object.keys(this._availableRecommendations).map(key => this._availableRecommendations[key]))); // retrieve ids of previous recommendations const storedRecommendationsJson = JSON.parse(this.storageService.get('extensionsAssistant/recommendations', StorageScope.GLOBAL, '[]')); @@ -325,7 +400,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe if (Array.isArray(storedRecommendationsJson)) { for (let id of storedRecommendationsJson) { if (allRecommendations.indexOf(id) > -1) { - this._fileBasedRecommendations[id] = Date.now(); + this._fileBasedRecommendations[id.toLowerCase()] = Date.now(); } } } else { @@ -334,14 +409,11 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe if (typeof entry.value === 'number') { const diff = (now - entry.value) / milliSecondsInADay; if (diff <= 7 && allRecommendations.indexOf(entry.key) > -1) { - this._fileBasedRecommendations[entry.key] = entry.value; + this._fileBasedRecommendations[entry.key.toLowerCase()] = entry.value; } } }); } - - this._modelService.onModelAdded(this._suggest, this, this._disposables); - this._modelService.getModels().forEach(model => this._suggest(model)); } private getMimeTypes(path: string): TPromise { @@ -370,12 +442,16 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe // the critical path - in case glob-match is slow setImmediate(() => { + let recommendationsToSuggest: string[] = []; const now = Date.now(); forEach(this._availableRecommendations, entry => { let { key: pattern, value: ids } = entry; if (match(pattern, uri.fsPath)) { for (let id of ids) { - this._fileBasedRecommendations[id] = now; + if (Object.keys(product.extensionImportantTips || []).map(x => x.toLowerCase()).indexOf(id.toLowerCase()) > -1) { + recommendationsToSuggest.push(id); + } + this._fileBasedRecommendations[id.toLowerCase()] = now; } } }); @@ -392,8 +468,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } const importantRecommendationsIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/importantRecommendationsIgnore', StorageScope.GLOBAL, '[]')); - let recommendationsToSuggest = Object.keys(product.extensionImportantTips || []) - .filter(id => importantRecommendationsIgnoreList.indexOf(id) === -1 && match(product.extensionImportantTips[id]['pattern'], uri.fsPath)); + recommendationsToSuggest = recommendationsToSuggest.filter(id => importantRecommendationsIgnoreList.indexOf(id) === -1); const importantTipsPromise = recommendationsToSuggest.length === 0 ? TPromise.as(null) : this.extensionsService.getInstalled(LocalExtensionType.User).then(local => { recommendationsToSuggest = recommendationsToSuggest.filter(id => local.every(local => `${local.manifest.publisher}.${local.manifest.name}` !== id)); @@ -401,7 +476,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return; } const id = recommendationsToSuggest[0]; - const name = product.extensionImportantTips[id]['name']; + const name = caseInsensitiveGet(product.extensionImportantTips, id)['name']; // Indicates we have a suggested extension via the whitelist hasSuggestion = true; @@ -555,86 +630,85 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe }); } - private _suggestWorkspaceRecommendations(): TPromise { + private _suggestWorkspaceRecommendations(): void { + const allRecommendations = this._allWorkspaceRecommendedExtensions; const storageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; const config = this.configurationService.getValue(ConfigurationKey); - return this.getWorkspaceRecommendations().then(allRecommendations => { - if (!allRecommendations.length || config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { - return; + if (!allRecommendations.length || config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { + return; + } + + return this.extensionsService.getInstalled(LocalExtensionType.User).done(local => { + const recommendations = allRecommendations + .filter(id => local.every(local => `${local.manifest.publisher.toLowerCase()}.${local.manifest.name.toLowerCase()}` !== id)); + + if (!recommendations.length) { + return TPromise.as(void 0); } - return this.extensionsService.getInstalled(LocalExtensionType.User).done(local => { - const recommendations = allRecommendations - .filter(id => local.every(local => `${local.manifest.publisher.toLowerCase()}.${local.manifest.name.toLowerCase()}` !== id)); + return new TPromise(c => { + this.notificationService.prompt( + Severity.Info, + localize('workspaceRecommended', "This workspace has extension recommendations."), + [{ + label: localize('installAll', "Install All"), + run: () => { + /* __GDPR__ + "extensionWorkspaceRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'install' }); - if (!recommendations.length) { - return TPromise.as(void 0); - } + const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, localize('installAll', "Install All")); + installAllAction.run(); + installAllAction.dispose(); - return new TPromise(c => { - this.notificationService.prompt( - Severity.Info, - localize('workspaceRecommended', "This workspace has extension recommendations."), - [{ - label: localize('installAll', "Install All"), - run: () => { - /* __GDPR__ + c(void 0); + } + }, { + label: localize('showRecommendations', "Show Recommendations"), + run: () => { + /* __GDPR__ "extensionWorkspaceRecommendations:popup" : { "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } - */ - this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'install' }); - - const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, localize('installAll', "Install All")); - installAllAction.run(); - installAllAction.dispose(); - - c(void 0); - } - }, { - label: localize('showRecommendations', "Show Recommendations"), - run: () => { - /* __GDPR__ - "extensionWorkspaceRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'show' }); - - const showAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); - showAction.run(); - showAction.dispose(); + */ + this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'show' }); - c(void 0); - } - }, { - label: choiceNever, - isSecondary: true, - run: () => { - /* __GDPR__ - "extensionWorkspaceRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'neverShowAgain' }); - this.storageService.store(storageKey, true, StorageScope.WORKSPACE); + const showAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); + showAction.run(); + showAction.dispose(); - c(void 0); - } - }], - () => { + c(void 0); + } + }, { + label: choiceNever, + isSecondary: true, + run: () => { /* __GDPR__ "extensionWorkspaceRecommendations:popup" : { "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ - this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'cancelled' }); + this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'neverShowAgain' }); + this.storageService.store(storageKey, true, StorageScope.WORKSPACE); c(void 0); } - ); - }); + }], + () => { + /* __GDPR__ + "extensionWorkspaceRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'cancelled' }); + + c(void 0); + } + ); }); }); } @@ -662,9 +736,9 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe if (exists && !foundExecutables.has(exeName)) { foundExecutables.add(exeName); (product.exeBasedExtensionTips[exeName]['recommendations'] || []) - .forEach(x => { - if (product.exeBasedExtensionTips[exeName]['friendlyName']) { - this._exeBasedRecommendations[x] = product.exeBasedExtensionTips[exeName]['friendlyName']; + .forEach(extensionId => { + if (product.exeBasedExtensionTips[exeName]['friendlyName'] && this.isExtensionAllowedToBeRecommended(extensionId)) { + this._exeBasedRecommendations[extensionId.toLowerCase()] = product.exeBasedExtensionTips[exeName]['friendlyName']; } }); } @@ -723,7 +797,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe && isNumber(storedRecommendationsJson['timestamp']) && storedRecommendationsJson['timestamp'] > 0 && (Date.now() - storedRecommendationsJson['timestamp']) / milliSecondsInADay < 14) { - this._dynamicWorkspaceRecommendations = storedRecommendationsJson['recommendations']; + this._dynamicWorkspaceRecommendations = storedRecommendationsJson['recommendations'].filter(id => this.isExtensionAllowedToBeRecommended(id)); /* __GDPR__ "dynamicWorkspaceRecommendations" : { "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -764,7 +838,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe for (let j = 0; j < allRecommendations.length && !foundRemote; j++) { if (Array.isArray(allRecommendations[j].remoteSet) && allRecommendations[j].remoteSet.indexOf(hashedRemotes[i]) > -1) { foundRemote = true; - this._dynamicWorkspaceRecommendations = allRecommendations[j].recommendations || []; + this._dynamicWorkspaceRecommendations = allRecommendations[j].recommendations.filter(id => this.isExtensionAllowedToBeRecommended(id)) || []; this.storageService.store(storageKey, JSON.stringify({ recommendations: this._dynamicWorkspaceRecommendations, timestamp: Date.now() @@ -812,6 +886,31 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return Object.keys(result); } + ignoreExtensionRecommendation(extensionId: string): void { + /* __GDPR__ + "extensionsRecommendations:ignoreRecommendation" : { + "recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + const reason = this.getAllRecommendationsWithReason()[extensionId.toLowerCase()]; + if (reason && reason.reasonId) { + this.telemetryService.publicLog('extensionsRecommendations:ignoreRecommendation', { id: extensionId, recommendationReason: reason.reasonId }); + } + + + this._globallyIgnoredRecommendations = distinct( + [...JSON.parse(this.storageService.get('extensionsAssistant/ignored_recommendations', StorageScope.GLOBAL, '[]')), extensionId.toLowerCase()] + .map(id => id.toLowerCase())); + + this.storageService.store('extensionsAssistant/ignored_recommendations', JSON.stringify(this._globallyIgnoredRecommendations), StorageScope.GLOBAL); + + this._allIgnoredRecommendations = distinct([...this._globallyIgnoredRecommendations, ...this._workspaceIgnoredRecommendations]); + + this.refilterAllRecommendations(); + this._onRecommendationChange.fire({ extensionId: extensionId, isRecommended: false }); + } + dispose() { this._disposables = dispose(this._disposables); } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index 7e8ddea0e51f5..45aa343cc2139 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -1147,7 +1147,7 @@ export class ShowEnabledExtensionsAction extends Action { label: string, @IViewletService private viewletService: IViewletService ) { - super(id, label, 'clear-extensions', true); + super(id, label, null, true); } run(): TPromise { @@ -1170,7 +1170,7 @@ export class ShowInstalledExtensionsAction extends Action { label: string, @IViewletService private viewletService: IViewletService ) { - super(id, label, 'clear-extensions', true); + super(id, label, null, true); } run(): TPromise { @@ -1488,6 +1488,36 @@ export class InstallRecommendedExtensionAction extends Action { } } +export class IgnoreExtensionRecommendationAction extends Action { + + static readonly ID = 'extensions.ignore'; + + private static readonly Class = 'extension-action ignore octicon octicon-x'; + + private disposables: IDisposable[] = []; + extension: IExtension; + + constructor( + @IExtensionTipsService private extensionsTipsService: IExtensionTipsService, + ) { + super(IgnoreExtensionRecommendationAction.ID); + + this.class = IgnoreExtensionRecommendationAction.Class; + this.tooltip = localize('ignoreExtensionRecommendation', "Do not recommend this extension again"); + this.enabled = true; + } + + public run(): TPromise { + this.extensionsTipsService.ignoreExtensionRecommendation(this.extension.id); + return TPromise.as(null); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + export class ShowRecommendedKeymapExtensionsAction extends Action { @@ -2219,41 +2249,49 @@ export const extensionButtonProminentHoverBackground = registerColor('extensionB registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const foregroundColor = theme.getColor(foreground); if (foregroundColor) { - collector.addRule(`.monaco-action-bar .action-item .action-label.extension-action.built-in-status { border-color: ${foregroundColor}; }`); + collector.addRule(`.extension .monaco-action-bar .action-item .action-label.extension-action.built-in-status { border-color: ${foregroundColor}; }`); + collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action.built-in-status { border-color: ${foregroundColor}; }`); } const buttonBackgroundColor = theme.getColor(buttonBackground); if (buttonBackgroundColor) { - collector.addRule(`.monaco-action-bar .action-item .action-label.extension-action { background-color: ${buttonBackgroundColor}; }`); + collector.addRule(`.extension .monaco-action-bar .action-item .action-label.extension-action { background-color: ${buttonBackgroundColor}; }`); + collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action { background-color: ${buttonBackgroundColor}; }`); } const buttonForegroundColor = theme.getColor(buttonForeground); if (buttonForegroundColor) { - collector.addRule(`.monaco-action-bar .action-item .action-label.extension-action { color: ${buttonForegroundColor}; }`); + collector.addRule(`.extension .monaco-action-bar .action-item .action-label.extension-action { color: ${buttonForegroundColor}; }`); + collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action { color: ${buttonForegroundColor}; }`); } const buttonHoverBackgroundColor = theme.getColor(buttonHoverBackground); if (buttonHoverBackgroundColor) { - collector.addRule(`.monaco-action-bar .action-item:hover .action-label.extension-action { background-color: ${buttonHoverBackgroundColor}; }`); + collector.addRule(`.extension .monaco-action-bar .action-item:hover .action-label.extension-action { background-color: ${buttonHoverBackgroundColor}; }`); + collector.addRule(`.extension-editor .monaco-action-bar .action-item:hover .action-label.extension-action { background-color: ${buttonHoverBackgroundColor}; }`); } const contrastBorderColor = theme.getColor(contrastBorder); if (contrastBorderColor) { - collector.addRule(`.monaco-action-bar .action-item .action-label.extension-action { border: 1px solid ${contrastBorderColor}; }`); + collector.addRule(`.extension .monaco-action-bar .action-item .action-label.extension-action { border: 1px solid ${contrastBorderColor}; }`); + collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action { border: 1px solid ${contrastBorderColor}; }`); } const extensionButtonProminentBackgroundColor = theme.getColor(extensionButtonProminentBackground); if (extensionButtonProminentBackground) { - collector.addRule(`.monaco-action-bar .action-item .action-label.extension-action.prominent { background-color: ${extensionButtonProminentBackgroundColor}; }`); + collector.addRule(`.extension .monaco-action-bar .action-item .action-label.extension-action.prominent { background-color: ${extensionButtonProminentBackgroundColor}; }`); + collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action.prominent { background-color: ${extensionButtonProminentBackgroundColor}; }`); } const extensionButtonProminentForegroundColor = theme.getColor(extensionButtonProminentForeground); if (extensionButtonProminentForeground) { - collector.addRule(`.monaco-action-bar .action-item .action-label.extension-action.prominent { color: ${extensionButtonProminentForegroundColor}; }`); + collector.addRule(`.extension .monaco-action-bar .action-item .action-label.extension-action.prominent { color: ${extensionButtonProminentForegroundColor}; }`); + collector.addRule(`.extension-editor .monaco-action-bar .action-item .action-label.extension-action.prominent { color: ${extensionButtonProminentForegroundColor}; }`); } const extensionButtonProminentHoverBackgroundColor = theme.getColor(extensionButtonProminentHoverBackground); if (extensionButtonProminentHoverBackground) { - collector.addRule(`.monaco-action-bar .action-item:hover .action-label.extension-action.prominent { background-color: ${extensionButtonProminentHoverBackgroundColor}; }`); + collector.addRule(`.extension .monaco-action-bar .action-item:hover .action-label.extension-action.prominent { background-color: ${extensionButtonProminentHoverBackgroundColor}; }`); + collector.addRule(`.extension-editor .monaco-action-bar .action-item:hover .action-label.extension-action.prominent { background-color: ${extensionButtonProminentHoverBackgroundColor}; }`); } }); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts index 32a607b78f119..5b83054fba525 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts @@ -167,15 +167,12 @@ export class Renderer implements IPagedRenderer { data.icon.style.visibility = 'inherit'; } - data.root.setAttribute('aria-label', extension.displayName); - removeClass(data.root, 'recommended'); - - const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); - if (extRecommendations[extension.id.toLowerCase()]) { - data.root.setAttribute('aria-label', extension.displayName + '. ' + extRecommendations[extension.id.toLowerCase()].reasonText); - addClass(data.root, 'recommended'); - data.root.title = extRecommendations[extension.id.toLowerCase()].reasonText; - } + this.updateRecommendationStatus(extension, data); + data.extensionDisposables.push(this.extensionTipsService.onRecommendationChange(change => { + if (change.extensionId.toLowerCase() === extension.id.toLowerCase() && change.isRecommended === false) { + this.updateRecommendationStatus(extension, data); + } + })); data.name.textContent = extension.displayName; data.author.textContent = extension.publisherDisplayName; @@ -190,6 +187,20 @@ export class Renderer implements IPagedRenderer { }); } + private updateRecommendationStatus(extension: IExtension, data: ITemplateData) { + const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); + + if (!extRecommendations[extension.id.toLowerCase()]) { + data.root.setAttribute('aria-label', extension.displayName); + data.root.title = ''; + removeClass(data.root, 'recommended'); + } else { + data.root.setAttribute('aria-label', extension.displayName + '. ' + extRecommendations[extension.id]); + data.root.title = extRecommendations[extension.id.toLowerCase()].reasonText; + addClass(data.root, 'recommended'); + } + } + disposeTemplate(data: ITemplateData): void { data.disposables = dispose(data.disposables); } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index c230ffaad65f5..4ee01abef1d87 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -32,7 +32,7 @@ import { } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; import { LocalExtensionType, IExtensionManagementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; -import { ExtensionsListView, InstalledExtensionsView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, GroupByServerExtensionsView } from './extensionsViews'; +import { ExtensionsListView, InstalledExtensionsView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, GroupByServerExtensionsView, DefaultRecommendedExtensionsView } from './extensionsViews'; import { OpenGlobalSettingsAction } from 'vs/workbench/parts/preferences/browser/preferencesActions'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; @@ -182,7 +182,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio id: 'extensions.recommendedList', name: localize('recommendedExtensions', "Recommended"), container: VIEW_CONTAINER, - ctor: RecommendedExtensionsView, + ctor: DefaultRecommendedExtensionsView, when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.has('defaultRecommendedExtensions')), weight: 70, order: 2, diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts index 24240c479eab7..263423f63679e 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts @@ -55,7 +55,7 @@ export class ExtensionsListView extends ViewletPanel { @IExtensionService private extensionService: IExtensionService, @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, @IEditorService private editorService: IEditorService, - @IExtensionTipsService private tipsService: IExtensionTipsService, + @IExtensionTipsService protected tipsService: IExtensionTipsService, @IModeService private modeService: IModeService, @ITelemetryService private telemetryService: ITelemetryService, @IConfigurationService configurationService: IConfigurationService @@ -447,10 +447,10 @@ export class ExtensionsListView extends ViewletPanel { .then(recommendations => { const names = recommendations.filter(name => name.toLowerCase().indexOf(value) > -1); /* __GDPR__ - "extensionWorkspaceRecommendations:open" : { - "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ + "extensionWorkspaceRecommendations:open" : { + "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + } + */ this.telemetryService.publicLog('extensionWorkspaceRecommendations:open', { count: names.length }); if (!names.length) { @@ -645,15 +645,48 @@ export class BuiltInBasicsExtensionsView extends ExtensionsListView { } } +export class DefaultRecommendedExtensionsView extends ExtensionsListView { + + renderBody(container: HTMLElement): void { + super.renderBody(container); + + this.disposables.push(this.tipsService.onRecommendationChange(() => { + this.show(''); + })); + } + + async show(query: string): TPromise> { + return super.show('@recommended:all'); + } +} + export class RecommendedExtensionsView extends ExtensionsListView { + + renderBody(container: HTMLElement): void { + super.renderBody(container); + + this.disposables.push(this.tipsService.onRecommendationChange(() => { + this.show(''); + })); + } + async show(query: string): TPromise> { - return super.show(!query.trim() ? '@recommended:all' : '@recommended'); + return super.show('@recommended'); } } export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { + + renderBody(container: HTMLElement): void { + super.renderBody(container); + + this.disposables.push(this.tipsService.onRecommendationChange(() => { + this.show(''); + })); + } + renderHeader(container: HTMLElement): void { super.renderHeader(container); diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionActions.css b/src/vs/workbench/parts/extensions/electron-browser/media/extensionActions.css index 46757df9d56e7..b33e9f8095a1a 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensionActions.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/extensionActions.css @@ -71,4 +71,29 @@ .hc-black .extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.extension-action.manage, .vs-dark .extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.extension-action.manage { background: url('manage-inverse.svg') center center no-repeat; +} + +.extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .actions-container { + justify-content: flex-start; +} + +.extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .action-item .action-label.extension-action.ignore { + height: 13px; + width: 8px; + border: none; + outline-offset: 0; + margin-left: 6px; + padding-left: 0; + margin-top: 3px; + background-color: transparent; + color: hsl(0, 66%, 50%); +} + +.hc-black .extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .action-item .action-label.extension-action.ignore, +.vs-dark .extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .action-item .action-label.extension-action.ignore { + color: hsla(0, 66%, 77%, 1); +} + +.extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .action-item .action-label.extension-action.ignore:hover { + filter: brightness(.8); } \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css b/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css index 42e53acdd0948..d5e84bd10d440 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css @@ -20,7 +20,9 @@ font-size: 14px; } -.extension-editor > .header.recommended { +.extension-editor > .header.recommended, +.extension-editor > .header.recommendation-ignored +{ height: 140px; } @@ -128,16 +130,26 @@ padding: 1px 6px; } -.extension-editor > .header.recommended > .details > .recommendation { - display: none; -} -.extension-editor > .header.recommended > .details > .recommendation { - display: block; +.extension-editor > .header.recommended > .details > .recommendation, +.extension-editor > .header.recommendation-ignored > .details > .recommendation { + display: flex; margin-top: 2px; font-size: 13px; } +.extension-editor > .header > .details > .recommendation { + display: none; +} + +.extension-editor > .header.recommendation-ignored > .details > .recommendation > .recommendation-text { + font-style: italic; +} + +.extension-editor > .header.recommendation-ignored > .details > .recommendation > .monaco-action-bar { + display: none; +} + .extension-editor > .body { height: calc(100% - 168px); overflow: hidden; diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 1259babb2dd28..c5aaaea043000 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -39,7 +39,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { groupBy } from 'vs/base/common/collections'; import { Schemas } from 'vs/base/common/network'; -import { join } from 'vs/base/common/paths'; +import { posix } from 'path'; interface IExtensionStateProvider { (extension: Extension): T; @@ -138,7 +138,7 @@ class Extension implements IExtension { private get localIconUrl(): string { if (this.local && this.local.manifest.icon) { - return this.local.location.with({ path: join(this.local.location.path, this.local.manifest.icon) }).toString(); + return this.local.location.with({ path: posix.join(this.local.location.path, this.local.manifest.icon) }).toString(); } return null; } diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts index 3f7af9a154cba..ac659cbd8114d 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -5,6 +5,7 @@ 'use strict'; +import * as sinon from 'sinon'; import * as assert from 'assert'; import * as path from 'path'; import * as fs from 'fs'; @@ -36,7 +37,7 @@ import { IPager } from 'vs/base/common/paging'; import { assign } from 'vs/base/common/objects'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IExtensionsWorkbenchService, ConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; @@ -243,27 +244,30 @@ suite('ExtensionsTipsService Test', () => { }); }); - teardown((done) => { + teardown(done => { (testObject).dispose(); (extensionsWorkbenchService).dispose(); if (parentResource) { extfs.del(parentResource, os.tmpdir(), () => { }, done); + } else { + done(); } }); - function setUpFolderWorkspace(folderName: string, recommendedExtensions: string[]): TPromise { + function setUpFolderWorkspace(folderName: string, recommendedExtensions: string[], ignoredRecommendations: string[] = []): TPromise { const id = uuid.generateUuid(); parentResource = path.join(os.tmpdir(), 'vsctests', id); - return setUpFolder(folderName, parentResource, recommendedExtensions); + return setUpFolder(folderName, parentResource, recommendedExtensions, ignoredRecommendations); } - function setUpFolder(folderName: string, parentDir: string, recommendedExtensions: string[]): TPromise { + function setUpFolder(folderName: string, parentDir: string, recommendedExtensions: string[], ignoredRecommendations: string[] = []): TPromise { const folderDir = path.join(parentDir, folderName); const workspaceSettingsDir = path.join(folderDir, '.vscode'); return mkdirp(workspaceSettingsDir, 493).then(() => { const configPath = path.join(workspaceSettingsDir, 'extensions.json'); fs.writeFileSync(configPath, JSON.stringify({ - 'recommendations': recommendedExtensions + 'recommendations': recommendedExtensions, + 'unwantedRecommendations': ignoredRecommendations, }, null, '\t')); const myWorkspace = testWorkspace(URI.from({ scheme: 'file', path: folderDir })); @@ -276,7 +280,7 @@ suite('ExtensionsTipsService Test', () => { function testNoPromptForValidRecommendations(recommendations: string[]) { return setUpFolderWorkspace('myFolder', recommendations).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); - return testObject.promptWorkspaceRecommendationsPromise.then(() => { + return testObject.loadRecommendationsPromise.then(() => { assert.equal(Object.keys(testObject.getAllRecommendationsWithReason()).length, recommendations.length); assert.ok(!prompted); }); @@ -286,7 +290,7 @@ suite('ExtensionsTipsService Test', () => { function testNoPromptOrRecommendationsForValidRecommendations(recommendations: string[]) { return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); - assert.equal(!testObject.promptWorkspaceRecommendationsPromise, true); + assert.equal(!testObject.loadRecommendationsPromise, true); assert.ok(!prompted); return testObject.getWorkspaceRecommendations().then(() => { @@ -313,7 +317,7 @@ suite('ExtensionsTipsService Test', () => { test('ExtensionTipsService: Prompt for valid workspace recommendations', () => { return setUpFolderWorkspace('myFolder', mockTestData.recommendedExtensions).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); - return testObject.promptWorkspaceRecommendationsPromise.then(() => { + return testObject.loadRecommendationsPromise.then(() => { const recommendations = Object.keys(testObject.getAllRecommendationsWithReason()); assert.equal(recommendations.length, mockTestData.validRecommendedExtensions.length); @@ -345,7 +349,7 @@ suite('ExtensionsTipsService Test', () => { testConfigurationService.setUserConfiguration(ConfigurationKey, { showRecommendationsOnlyOnDemand: true }); return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); - return testObject.promptWorkspaceRecommendationsPromise.then(() => { + return testObject.loadRecommendationsPromise.then(() => { assert.equal(Object.keys(testObject.getAllRecommendationsWithReason()).length, 0); assert.ok(!prompted); }); @@ -357,17 +361,152 @@ suite('ExtensionsTipsService Test', () => { return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions); }); + test('ExtensionTipsService: No Recommendations of globally ignored recommendations', () => { + const storageGetterStub = (a, _, c) => { + const storedRecommendations = '["ms-vscode.csharp", "ms-python.python", "eg2.tslint"]'; + const ignoredRecommendations = '["ms-vscode.csharp", "mockpublisher2.mockextension2"]'; // ignore a stored recommendation and a workspace recommendation. + if (a === 'extensionsAssistant/recommendations') { return storedRecommendations; } + if (a === 'extensionsAssistant/ignored_recommendations') { return ignoredRecommendations; } + return c; + }; + + instantiationService.stub(IStorageService, { + get: storageGetterStub, + getBoolean: (a, _, c) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c + }); + + return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { + testObject = instantiationService.createInstance(ExtensionTipsService); + return testObject.loadRecommendationsPromise.then(() => { + const recommendations = testObject.getAllRecommendationsWithReason(); + assert.ok(!recommendations['ms-vscode.csharp']); // stored recommendation that has been globally ignored + assert.ok(recommendations['ms-python.python']); // stored recommendation + assert.ok(recommendations['mockpublisher1.mockextension1']); // workspace recommendation + assert.ok(!recommendations['mockpublisher2.mockextension2']); // workspace recommendation that has been globally ignored + }); + }); + }); + + test('ExtensionTipsService: No Recommendations of workspace ignored recommendations', () => { + const ignoredRecommendations = ['ms-vscode.csharp', 'mockpublisher2.mockextension2']; // ignore a stored recommendation and a workspace recommendation. + const storedRecommendations = '["ms-vscode.csharp", "ms-python.python"]'; + instantiationService.stub(IStorageService, { + get: (a, b, c) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c, + getBoolean: (a, _, c) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c + }); + + return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, ignoredRecommendations).then(() => { + testObject = instantiationService.createInstance(ExtensionTipsService); + return testObject.loadRecommendationsPromise.then(() => { + const recommendations = testObject.getAllRecommendationsWithReason(); + assert.ok(!recommendations['ms-vscode.csharp']); // stored recommendation that has been workspace ignored + assert.ok(recommendations['ms-python.python']); // stored recommendation + assert.ok(recommendations['mockpublisher1.mockextension1']); // workspace recommendation + assert.ok(!recommendations['mockpublisher2.mockextension2']); // workspace recommendation that has been workspace ignored + }); + }); + }); + + test('ExtensionTipsService: Able to retrieve collection of all ignored recommendations', () => { + + const storageGetterStub = (a, _, c) => { + const storedRecommendations = '["ms-vscode.csharp", "ms-python.python"]'; + const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation. + if (a === 'extensionsAssistant/recommendations') { return storedRecommendations; } + if (a === 'extensionsAssistant/ignored_recommendations') { return globallyIgnoredRecommendations; } + return c; + }; + + const workspaceIgnoredRecommendations = ['ms-vscode.csharp']; // ignore a stored recommendation and a workspace recommendation. + instantiationService.stub(IStorageService, { + get: storageGetterStub, + getBoolean: (a, _, c) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c + }); + + return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, workspaceIgnoredRecommendations).then(() => { + testObject = instantiationService.createInstance(ExtensionTipsService); + return testObject.loadRecommendationsPromise.then(() => { + const recommendations = testObject.getAllIgnoredRecommendations(); + assert.deepStrictEqual(recommendations, + { + global: ['mockpublisher2.mockextension2'], + workspace: ['ms-vscode.csharp'] + }); + }); + }); + }); + + test('ExtensionTipsService: Able to dynamically ignore global recommendations', () => { + const storageGetterStub = (a, _, c) => { + const storedRecommendations = '["ms-vscode.csharp", "ms-python.python"]'; + const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation. + if (a === 'extensionsAssistant/recommendations') { return storedRecommendations; } + if (a === 'extensionsAssistant/ignored_recommendations') { return globallyIgnoredRecommendations; } + return c; + }; + + instantiationService.stub(IStorageService, { + get: storageGetterStub, + store: () => { }, + getBoolean: (a, _, c) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c + }); + + return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { + testObject = instantiationService.createInstance(ExtensionTipsService); + return testObject.loadRecommendationsPromise.then(() => { + const recommendations = testObject.getAllIgnoredRecommendations(); + assert.deepStrictEqual(recommendations, + { + global: ['mockpublisher2.mockextension2'], + workspace: [] + }); + return testObject.ignoreExtensionRecommendation('mockpublisher1.mockextension1'); + }).then(() => { + const recommendations = testObject.getAllIgnoredRecommendations(); + assert.deepStrictEqual(recommendations, + { + global: ['mockpublisher2.mockextension2', 'mockpublisher1.mockextension1'], + workspace: [] + }); + }); + }); + }); + + test('test global extensions are modified and recommendation change event is fired when an extension is ignored', () => { + const storageSetterTarget = sinon.spy(); + const changeHandlerTarget = sinon.spy(); + const ignoredExtensionId = 'Some.Extension'; + instantiationService.stub(IStorageService, { + get: (a, b, c) => a === 'extensionsAssistant/ignored_recommendations' ? '["ms-vscode.vscode"]' : c, + store: (...args) => { + storageSetterTarget(...args); + } + }); + + return setUpFolderWorkspace('myFolder', []).then(() => { + testObject = instantiationService.createInstance(ExtensionTipsService); + testObject.onRecommendationChange(changeHandlerTarget); + testObject.ignoreExtensionRecommendation(ignoredExtensionId); + + assert.ok(changeHandlerTarget.calledOnce); + assert.ok(changeHandlerTarget.getCall(0).calledWithMatch({ extensionId: 'Some.Extension', isRecommended: false })); + assert.ok(storageSetterTarget.calledWithExactly('extensionsAssistant/ignored_recommendations', `["ms-vscode.vscode","${ignoredExtensionId.toLowerCase()}"]`, StorageScope.GLOBAL)); + }); + }); + test('ExtensionTipsService: Get file based recommendations from storage (old format)', () => { const storedRecommendations = '["ms-vscode.csharp", "ms-python.python", "eg2.tslint"]'; instantiationService.stub(IStorageService, { get: (a, b, c) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c }); return setUpFolderWorkspace('myFolder', []).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); - const recommendations = testObject.getFileBasedRecommendations(); - assert.equal(recommendations.length, 2); - assert.ok(recommendations.indexOf('ms-vscode.csharp') > -1); // stored recommendation that exists in product.extensionTips - assert.ok(recommendations.indexOf('ms-python.python') > -1); // stored recommendation that exists in product.extensionImportantTips - assert.ok(recommendations.indexOf('eg2.tslint') === -1); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips + return testObject.loadRecommendationsPromise.then(() => { + const recommendations = testObject.getFileBasedRecommendations(); + assert.equal(recommendations.length, 2); + assert.ok(recommendations.indexOf('ms-vscode.csharp') > -1); // stored recommendation that exists in product.extensionTips + assert.ok(recommendations.indexOf('ms-python.python') > -1); // stored recommendation that exists in product.extensionImportantTips + assert.ok(recommendations.indexOf('eg2.tslint') === -1); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips + }); }); }); @@ -380,12 +519,14 @@ suite('ExtensionsTipsService Test', () => { return setUpFolderWorkspace('myFolder', []).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); - const recommendations = testObject.getFileBasedRecommendations(); - assert.equal(recommendations.length, 2); - assert.ok(recommendations.indexOf('ms-vscode.csharp') > -1); // stored recommendation that exists in product.extensionTips - assert.ok(recommendations.indexOf('ms-python.python') > -1); // stored recommendation that exists in product.extensionImportantTips - assert.ok(recommendations.indexOf('eg2.tslint') === -1); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips - assert.ok(recommendations.indexOf('lukehoban.Go') === -1); //stored recommendation that is older than a week + return testObject.loadRecommendationsPromise.then(() => { + const recommendations = testObject.getFileBasedRecommendations(); + assert.equal(recommendations.length, 2); + assert.ok(recommendations.indexOf('ms-vscode.csharp') > -1); // stored recommendation that exists in product.extensionTips + assert.ok(recommendations.indexOf('ms-python.python') > -1); // stored recommendation that exists in product.extensionImportantTips + assert.ok(recommendations.indexOf('eg2.tslint') === -1); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips + assert.ok(recommendations.indexOf('lukehoban.Go') === -1); //stored recommendation that is older than a week + }); }); }); }); diff --git a/src/vs/workbench/parts/feedback/electron-browser/feedback.ts b/src/vs/workbench/parts/feedback/electron-browser/feedback.ts index 08a18f96c24ab..c9b4a6a03bc5b 100644 --- a/src/vs/workbench/parts/feedback/electron-browser/feedback.ts +++ b/src/vs/workbench/parts/feedback/electron-browser/feedback.ts @@ -139,8 +139,12 @@ export class FeedbackDropdown extends Dropdown { $('h2.title').text(nls.localize("label.sendASmile", "Tweet us your feedback.")).appendTo($form); - const cancelBtn = $('div.cancel').attr('tabindex', '0'); - cancelBtn.on(dom.EventType.MOUSE_OVER, () => { + const closeBtn = $('div.cancel').attr({ + 'tabindex': '0', + 'role': 'button', + 'title': nls.localize('close', "Close") + }); + closeBtn.on(dom.EventType.MOUSE_OVER, () => { const theme = this.themeService.getTheme(); let darkenFactor: number; switch (theme.type) { @@ -153,13 +157,13 @@ export class FeedbackDropdown extends Dropdown { } if (darkenFactor) { - cancelBtn.getHTMLElement().style.backgroundColor = darken(theme.getColor(editorWidgetBackground), darkenFactor)(theme).toString(); + closeBtn.getHTMLElement().style.backgroundColor = darken(theme.getColor(editorWidgetBackground), darkenFactor)(theme).toString(); } }); - cancelBtn.on(dom.EventType.MOUSE_OUT, () => { - cancelBtn.getHTMLElement().style.backgroundColor = null; + closeBtn.on(dom.EventType.MOUSE_OUT, () => { + closeBtn.getHTMLElement().style.backgroundColor = null; }); - this.invoke(cancelBtn, () => { + this.invoke(closeBtn, () => { this.hide(); }).appendTo($form); @@ -178,7 +182,8 @@ export class FeedbackDropdown extends Dropdown { this.smileyInput = $('div').addClass('sentiment smile').attr({ 'aria-checked': 'false', - 'aria-label': nls.localize('smileCaption', "Happy"), + 'aria-label': nls.localize('smileCaption', "Happy Feedback Sentiment"), + 'title': nls.localize('smileCaption', "Happy Feedback Sentiment"), 'tabindex': 0, 'role': 'checkbox' }); @@ -186,7 +191,8 @@ export class FeedbackDropdown extends Dropdown { this.frownyInput = $('div').addClass('sentiment frown').attr({ 'aria-checked': 'false', - 'aria-label': nls.localize('frownCaption', "Sad"), + 'aria-label': nls.localize('frownCaption', "Sad Feedback Sentiment"), + 'title': nls.localize('frownCaption', "Sad Feedback Sentiment"), 'tabindex': 0, 'role': 'checkbox' }); @@ -233,7 +239,7 @@ export class FeedbackDropdown extends Dropdown { this.feedbackDescriptionInput = $('textarea.feedback-description').attr({ rows: 3, maxlength: this.maxFeedbackCharacters, - 'aria-label': nls.localize("commentsHeader", "Comments") + 'aria-label': nls.localize("feedbackTextInput", "Tell us your feedback") }) .text(this.feedback).attr('required', 'required') .on('keyup', () => { @@ -254,6 +260,7 @@ export class FeedbackDropdown extends Dropdown { this.sendButton.label = nls.localize('tweet', "Tweet"); this.$sendButton = new Builder(this.sendButton.element); this.$sendButton.addClass('send'); + this.$sendButton.title(nls.localize('tweetFeedback', "Tweet Feedback")); this.toDispose.push(attachButtonStyler(this.sendButton, this.themeService)); this.sendButton.onDidClick(() => { diff --git a/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts b/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts index de33f2ffaeb7d..4b71f7e2b427e 100644 --- a/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts @@ -146,6 +146,9 @@ export class TextFileEditor extends BaseTextEditor { if (options && types.isFunction((options).apply)) { (options).apply(textEditor, ScrollType.Immediate); } + + // Readonly flag + textEditor.updateOptions({ readOnly: textFileModel.isReadonly() }); }, error => { // In case we tried to open a file inside the text editor and the response diff --git a/src/vs/workbench/parts/files/common/explorerModel.ts b/src/vs/workbench/parts/files/common/explorerModel.ts index ce016239d03b1..4b72173d75afb 100644 --- a/src/vs/workbench/parts/files/common/explorerModel.ts +++ b/src/vs/workbench/parts/files/common/explorerModel.ts @@ -25,7 +25,7 @@ export class Model { constructor(@IWorkspaceContextService private contextService: IWorkspaceContextService) { const setRoots = () => this._roots = this.contextService.getWorkspace().folders - .map(folder => new ExplorerItem(folder.uri, undefined, false, true, folder.name)); + .map(folder => new ExplorerItem(folder.uri, undefined, false, false, true, folder.name)); this._listener = this.contextService.onDidChangeWorkspaceFolders(() => setRoots()); setRoots(); } @@ -72,16 +72,18 @@ export class ExplorerItem { public etag: string; private _isDirectory: boolean; private _isSymbolicLink: boolean; + private _isReadonly: boolean; private children: Map; public parent: ExplorerItem; public isDirectoryResolved: boolean; - constructor(resource: URI, public root: ExplorerItem, isSymbolicLink?: boolean, isDirectory?: boolean, name: string = resources.basenameOrAuthority(resource), mtime?: number, etag?: string) { + constructor(resource: URI, public root: ExplorerItem, isSymbolicLink?: boolean, isReadonly?: boolean, isDirectory?: boolean, name: string = resources.basenameOrAuthority(resource), mtime?: number, etag?: string) { this.resource = resource; this._name = name; this.isDirectory = !!isDirectory; this._isSymbolicLink = !!isSymbolicLink; + this._isReadonly = !!isReadonly; this.etag = etag; this.mtime = mtime; @@ -100,6 +102,10 @@ export class ExplorerItem { return this._isDirectory; } + public get isReadonly(): boolean { + return this._isReadonly; + } + public set isDirectory(value: boolean) { if (value !== this._isDirectory) { this._isDirectory = value; @@ -140,7 +146,7 @@ export class ExplorerItem { } public static create(raw: IFileStat, root: ExplorerItem, resolveTo?: URI[]): ExplorerItem { - const stat = new ExplorerItem(raw.resource, root, raw.isSymbolicLink, raw.isDirectory, raw.name, raw.mtime, raw.etag); + const stat = new ExplorerItem(raw.resource, root, raw.isSymbolicLink, raw.isReadonly, raw.isDirectory, raw.name, raw.mtime, raw.etag); // Recursively add children if present if (stat.isDirectory) { @@ -188,6 +194,7 @@ export class ExplorerItem { local.mtime = disk.mtime; local.isDirectoryResolved = disk.isDirectoryResolved; local._isSymbolicLink = disk.isSymbolicLink; + local._isReadonly = disk.isReadonly; // Merge Children if resolved if (mergingDirectories && disk.isDirectoryResolved) { @@ -395,7 +402,7 @@ export class NewStatPlaceholder extends ExplorerItem { private directoryPlaceholder: boolean; constructor(isDirectory: boolean, root: ExplorerItem) { - super(URI.file(''), root, false, false, NewStatPlaceholder.NAME); + super(URI.file(''), root, false, false, false, NewStatPlaceholder.NAME); this.id = NewStatPlaceholder.ID++; this.isDirectoryResolved = isDirectory; diff --git a/src/vs/workbench/parts/files/common/files.ts b/src/vs/workbench/parts/files/common/files.ts index 26c9bd956ca00..da4464d38d338 100644 --- a/src/vs/workbench/parts/files/common/files.ts +++ b/src/vs/workbench/parts/files/common/files.ts @@ -50,10 +50,12 @@ const openEditorsVisibleId = 'openEditorsVisible'; const openEditorsFocusId = 'openEditorsFocus'; const explorerViewletFocusId = 'explorerViewletFocus'; const explorerResourceIsFolderId = 'explorerResourceIsFolder'; +const explorerResourceIsReadonly = 'explorerResourceIsReadonly'; const explorerResourceIsRootId = 'explorerResourceIsRoot'; export const ExplorerViewletVisibleContext = new RawContextKey(explorerViewletVisibleId, true); export const ExplorerFolderContext = new RawContextKey(explorerResourceIsFolderId, false); +export const ExplorerResourceIsReadonlyContext = new RawContextKey(explorerResourceIsReadonly, false); export const ExplorerRootContext = new RawContextKey(explorerResourceIsRootId, false); export const FilesExplorerFocusedContext = new RawContextKey(filesExplorerFocusId, true); export const OpenEditorsVisibleContext = new RawContextKey(openEditorsVisibleId, false); diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts index 59221606c5112..96443a0f6dcab 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts @@ -16,7 +16,7 @@ import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/c import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; -import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext } from 'vs/workbench/parts/files/common/files'; +import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceIsReadonlyContext } from 'vs/workbench/parts/files/common/files'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { OPEN_FOLDER_SETTINGS_COMMAND, OPEN_FOLDER_SETTINGS_LABEL } from 'vs/workbench/parts/preferences/browser/preferencesActions'; @@ -330,7 +330,8 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { order: 4, command: { id: NEW_FILE_COMMAND_ID, - title: NEW_FILE_LABEL + title: NEW_FILE_LABEL, + precondition: ExplorerResourceIsReadonlyContext.toNegated() }, when: ExplorerFolderContext }); @@ -340,7 +341,8 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { order: 6, command: { id: NEW_FOLDER_COMMAND_ID, - title: NEW_FOLDER_LABEL + title: NEW_FOLDER_LABEL, + precondition: ExplorerResourceIsReadonlyContext.toNegated() }, when: ExplorerFolderContext }); @@ -443,7 +445,8 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { order: 10, command: { id: RENAME_ID, - title: TRIGGER_RENAME_LABEL + title: TRIGGER_RENAME_LABEL, + precondition: ExplorerResourceIsReadonlyContext.toNegated() }, when: ExplorerRootContext.toNegated() }); @@ -453,15 +456,17 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { order: 20, command: { id: MOVE_FILE_TO_TRASH_ID, - title: MOVE_FILE_TO_TRASH_LABEL + title: MOVE_FILE_TO_TRASH_LABEL, + precondition: ExplorerResourceIsReadonlyContext.toNegated() }, alt: { id: DELETE_FILE_ID, - title: nls.localize('deleteFile', "Delete Permanently") + title: nls.localize('deleteFile', "Delete Permanently"), + precondition: ExplorerResourceIsReadonlyContext.toNegated() }, when: ExplorerRootContext.toNegated() }); // Empty Editor Group Context Menu MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: 'workbench.action.files.newUntitledFile', title: nls.localize('newFile', "New File") }, group: '1_file', order: 10 }); -MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: 'workbench.action.quickOpen', title: nls.localize('openFile', "Open File...") }, group: '1_file', order: 20 }); \ No newline at end of file +MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: 'workbench.action.quickOpen', title: nls.localize('openFile', "Open File...") }, group: '1_file', order: 20 }); diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.ts index 9c1a8580d7831..4e1fea8954b8d 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.ts @@ -234,6 +234,10 @@ export abstract class BaseRenameAction extends BaseFileAction { this.element = element; } + _isEnabled(): boolean { + return super._isEnabled() && this.element && !this.element.isReadonly; + } + public run(context?: any): TPromise { if (!context) { return TPromise.wrapError(new Error('No context provided to BaseRenameFileAction.')); @@ -332,6 +336,10 @@ export class BaseNewAction extends BaseFileAction { this.renameAction = editableAction; } + _isEnabled(): boolean { + return super._isEnabled() && (!this.presetFolder || !this.presetFolder.isReadonly); + } + public run(context?: any): TPromise { if (!context) { return TPromise.wrapError(new Error('No context provided to BaseNewAction.')); @@ -547,6 +555,10 @@ class BaseDeleteFileAction extends BaseFileAction { this._updateEnablement(); } + _isEnabled(): boolean { + return super._isEnabled() && this.elements && this.elements.every(e => !e.isReadonly); + } + public run(): TPromise { // Remove highlight diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts index d9507f86bece4..a63f0444c861f 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts @@ -14,7 +14,7 @@ import * as resources from 'vs/base/common/resources'; import * as glob from 'vs/base/common/glob'; import { Action, IAction } from 'vs/base/common/actions'; import { memoize } from 'vs/base/common/decorators'; -import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, SortOrderConfiguration, SortOrder, IExplorerView, ExplorerRootContext } from 'vs/workbench/parts/files/common/files'; +import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, SortOrderConfiguration, SortOrder, IExplorerView, ExplorerRootContext, ExplorerResourceIsReadonlyContext } from 'vs/workbench/parts/files/common/files'; import { FileOperation, FileOperationEvent, IResolveFileOptions, FileChangeType, FileChangesEvent, IFileService, FILES_EXCLUDE_CONFIG } from 'vs/platform/files/common/files'; import { RefreshViewExplorerAction, NewFolderAction, NewFileAction } from 'vs/workbench/parts/files/electron-browser/fileActions'; import { FileDragAndDrop, FileFilter, FileSorter, FileController, FileRenderer, FileDataSource, FileViewletState, FileAccessibilityProvider } from 'vs/workbench/parts/files/electron-browser/views/explorerViewer'; @@ -67,6 +67,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView private resourceContext: ResourceContextKey; private folderContext: IContextKey; + private readonlyContext: IContextKey; private rootContext: IContextKey; private fileEventsFilter: ResourceGlobMatcher; @@ -104,6 +105,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView this.resourceContext = instantiationService.createInstance(ResourceContextKey); this.folderContext = ExplorerFolderContext.bindTo(contextKeyService); + this.readonlyContext = ExplorerResourceIsReadonlyContext.bindTo(contextKeyService); this.rootContext = ExplorerRootContext.bindTo(contextKeyService); this.fileEventsFilter = instantiationService.createInstance( @@ -433,6 +435,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView const resource = e.focus ? e.focus.resource : isSingleFolder ? this.contextService.getWorkspace().folders[0].uri : undefined; this.resourceContext.set(resource); this.folderContext.set((isSingleFolder && !e.focus) || e.focus && e.focus.isDirectory); + this.readonlyContext.set(e.focus && e.focus.isReadonly); this.rootContext.set(!e.focus || (e.focus && e.focus.isRoot)); })); diff --git a/src/vs/workbench/parts/files/test/electron-browser/explorerModel.test.ts b/src/vs/workbench/parts/files/test/electron-browser/explorerModel.test.ts index 515ad42c58021..210ff1bdb780e 100644 --- a/src/vs/workbench/parts/files/test/electron-browser/explorerModel.test.ts +++ b/src/vs/workbench/parts/files/test/electron-browser/explorerModel.test.ts @@ -14,7 +14,7 @@ import { validateFileName } from 'vs/workbench/parts/files/electron-browser/file import { ExplorerItem } from 'vs/workbench/parts/files/common/explorerModel'; function createStat(path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number): ExplorerItem { - return new ExplorerItem(toResource(path), undefined, false, isFolder, name, mtime); + return new ExplorerItem(toResource(path), undefined, false, false, isFolder, name, mtime); } function toResource(path) { @@ -264,20 +264,20 @@ suite('Files - View Model', () => { test('Merge Local with Disk', function () { const d = new Date().toUTCString(); - const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), undefined, false, true, 'to', Date.now(), d); - const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), undefined, false, true, 'to', Date.now(), new Date(0).toUTCString()); + const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), undefined, false, false, true, 'to', Date.now(), d); + const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), undefined, false, false, true, 'to', Date.now(), new Date(0).toUTCString()); // Merge Properties ExplorerItem.mergeLocalWithDisk(merge2, merge1); assert.strictEqual(merge1.mtime, merge2.mtime); // Merge Child when isDirectoryResolved=false is a no-op - merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), undefined, false, true, 'foo.html', Date.now(), d)); + merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), undefined, false, false, true, 'foo.html', Date.now(), d)); ExplorerItem.mergeLocalWithDisk(merge2, merge1); assert.strictEqual(merge1.getChildrenArray().length, 0); // Merge Child with isDirectoryResolved=true - const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), undefined, false, true, 'foo.html', Date.now(), d); + const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), undefined, false, false, true, 'foo.html', Date.now(), d); merge2.removeChild(child); merge2.addChild(child); merge2.isDirectoryResolved = true; diff --git a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts index 52b9944bbb65e..44db10318fba8 100644 --- a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts +++ b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts @@ -549,7 +549,7 @@ export class OutlinePanel extends ViewletPanel { // feature: reveal outline selection in editor // on change -> reveal/select defining range this._editorDisposables.push(this._tree.onDidChangeSelection(e => { - if (e.payload === this || e.payload && !e.payload.didClickElement) { + if (e.payload === this || e.payload && e.payload.didClickOnTwistie) { return; } let [first] = e.selection; diff --git a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css index ed7f7d4b70816..67068eb9e6c64 100644 --- a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css @@ -5,45 +5,40 @@ .settings-editor { padding-top: 11px; - display: flex; - margin: auto; max-width: 1000px; -} - -.settings-editor > .settings-editor-right { - flex: 1; + margin: auto; } /* header styling */ -.settings-editor > .settings-editor-right > .settings-header { +.settings-editor > .settings-header { padding-left: 5px; padding-right: 5px; - max-width: 1000px; box-sizing: border-box; + margin: auto; } -.settings-editor > .settings-editor-right > .settings-header > .settings-preview-header { +.settings-editor > .settings-header > .settings-preview-header { margin-bottom: 5px; } -.settings-editor > .settings-editor-right > .settings-header > .settings-preview-header .settings-preview-label { +.settings-editor > .settings-header > .settings-preview-header .settings-preview-label { opacity: .7; } -.settings-editor > .settings-editor-right > .settings-header > .settings-advanced-customization .open-settings-button, -.settings-editor > .settings-editor-right > .settings-header > .settings-advanced-customization .open-settings-button:hover, -.settings-editor > .settings-editor-right > .settings-header > .settings-advanced-customization .open-settings-button:active { +.settings-editor > .settings-header > .settings-advanced-customization .open-settings-button, +.settings-editor > .settings-header > .settings-advanced-customization .open-settings-button:hover, +.settings-editor > .settings-header > .settings-advanced-customization .open-settings-button:active { padding: 0; text-decoration: underline; display: inline; } -.settings-editor > .settings-editor-right > .settings-header > .settings-advanced-customization { +.settings-editor > .settings-header > .settings-advanced-customization { opacity: .7; margin-top: 10px; } -.settings-editor > .settings-editor-right > .settings-header > .settings-preview-header > .settings-preview-warning { +.settings-editor > .settings-header > .settings-preview-header > .settings-preview-warning { text-align: right; text-transform: uppercase; background: rgba(136, 136, 136, 0.3); @@ -53,140 +48,136 @@ margin-right: 7px; } -.settings-editor > .settings-editor-right > .settings-header > .search-container { +.settings-editor > .settings-header > .search-container { position: relative; } -.settings-editor > .settings-editor-right > .settings-header .search-container > .settings-search-input { +.settings-editor > .settings-header .search-container > .settings-search-input { vertical-align: middle; } -.settings-editor > .settings-editor-right > .settings-header .search-container > .settings-search-input > .monaco-inputbox { +.settings-editor > .settings-header .search-container > .settings-search-input > .monaco-inputbox { height: 30px; width: 100%; } -.settings-editor > .settings-editor-right > .settings-header .search-container > .settings-search-input > .monaco-inputbox .input { +.settings-editor > .settings-header .search-container > .settings-search-input > .monaco-inputbox .input { font-size: 14px; padding-left: 10px; } -.settings-editor > .settings-editor-right > .settings-header > .settings-header-controls { +.settings-editor > .settings-header > .settings-header-controls { margin-top: 2px; height: 30px; display: flex; } -.settings-editor > .settings-editor-right > .settings-header .settings-tabs-widget > .monaco-action-bar .action-item:not(:first-child) .action-label { +.settings-editor > .settings-header .settings-tabs-widget > .monaco-action-bar .action-item:not(:first-child) .action-label { margin-left: 14px; } -.settings-editor > .settings-editor-right > .settings-header .settings-tabs-widget .monaco-action-bar .action-item .dropdown-icon { +.settings-editor > .settings-header .settings-tabs-widget .monaco-action-bar .action-item .dropdown-icon { /** The tab widget container height is shorter than elsewhere, need to tweak this */ padding-top: 3px; } -.settings-editor > .settings-editor-right > .settings-header > .settings-header-controls .settings-header-controls-right { +.settings-editor > .settings-header > .settings-header-controls .settings-header-controls-right { margin-left: auto; padding-top: 3px; display: flex; } -.settings-editor > .settings-editor-right > .settings-header > .settings-header-controls .settings-header-controls-right #configured-only-checkbox { +.settings-editor > .settings-header > .settings-header-controls .settings-header-controls-right #configured-only-checkbox { flex-shrink: 0; } -.settings-editor > .settings-editor-right > .settings-header > .settings-header-controls .settings-header-controls-right .configured-only-label { +.settings-editor > .settings-header > .settings-header-controls .settings-header-controls-right .configured-only-label { white-space: nowrap; margin-right: 10px; margin-left: 2px; opacity: 0.7; } -.settings-editor > .settings-editor-right > .settings-body { +.settings-editor > .settings-body { display: flex; + margin: auto; max-width: 1000px; } -.settings-editor > .settings-editor-right > .settings-body .settings-tree-container .monaco-tree-wrapper { - max-width: 800px; -} - -.settings-editor > .settings-editor-right > .settings-body .settings-tree-container .monaco-scrollable-element .shadow.top-left-corner { - left: calc((100% - 800px)/2); -} - -.settings-editor > .settings-editor-right > .settings-body .settings-tree-container .monaco-scrollable-element .shadow { - left: calc((100% - 794px)/2); - width: 800px; -} - -.settings-editor > .settings-editor-right > .settings-body .settings-tree-container .monaco-tree::before { +.settings-editor > .settings-body .settings-tree-container .monaco-tree::before { outline: none !important; } -.settings-editor > .settings-toc-container { +.settings-editor > .settings-body .settings-toc-container { width: 175px; - margin-top: 114px; margin-right: 5px; } -.settings-editor.search-mode > .settings-toc-container .monaco-tree { +.search-mode .settings-toc-container { display: none; } -.settings-editor > .settings-toc-container .monaco-tree-row .settings-toc-entry { +.settings-editor > .settings-body .settings-toc-container .monaco-tree-row .settings-toc-entry { overflow: hidden; text-overflow: ellipsis; line-height: 22px; } -.settings-editor > .settings-editor-right > .settings-body .settings-tree-container { +.settings-editor > .settings-body .settings-toc-container .monaco-tree-row .settings-toc-entry.no-results { + opacity: 0.5; +} + +.settings-editor > .settings-body .settings-tree-container { flex: 1; border-spacing: 0; border-collapse: separate; position: relative; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item { - padding-top: 4px; - padding-left: 5px; +.settings-editor > .settings-body > .settings-tree-container .setting-item { + padding-top: 5px; cursor: default; white-space: normal; height: 100%; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item.odd:not(.focused):not(.selected):not(:hover), -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .monaco-tree:not(:focus) .setting-item.focused.odd:not(.selected):not(:hover), -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .monaco-tree:not(.focused) .setting-item.focused.odd:not(.selected):not(:hover) { +.settings-editor > .settings-body > .settings-tree-container .setting-item.odd:not(.focused):not(.selected):not(:hover), +.settings-editor > .settings-body > .settings-tree-container .monaco-tree:not(:focus) .setting-item.focused.odd:not(.selected):not(:hover), +.settings-editor > .settings-body > .settings-tree-container .monaco-tree:not(.focused) .setting-item.focused.odd:not(.selected):not(:hover) { background-color: rgba(130, 130, 130, 0.04); } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-title .setting-item-is-configured-label { +.settings-editor > .settings-body > .settings-tree-container .setting-item > .setting-item-left { + flex: 1; + padding-top: 3px; + padding-bottom: 12px; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-title .setting-item-is-configured-label { font-style: italic; opacity: 0.8; margin-right: 7px; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-title .setting-item-overrides { +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-title .setting-item-overrides { opacity: 0.5; font-style: italic; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-label { +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-label { margin-right: 7px; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-label, -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-category { +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-label, +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-category { font-weight: bold; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-category { +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-category { opacity: 0.7; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-description { +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description { opacity: 0.7; margin-top: 3px; overflow: hidden; @@ -195,59 +186,51 @@ height: 18px; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-measure-container.monaco-tree-row { - padding-left: 15px; - opacity: 0; - max-width: 800px; -} - -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item.is-expanded .setting-item-description, -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item.setting-measure-helper .setting-item-description { +.settings-editor > .settings-body > .settings-tree-container .setting-item.is-expanded .setting-item-description, +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-measure-helper .setting-item-description { height: initial; white-space: pre-wrap; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value { +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value { margin-top: 5px; margin-bottom: 7px; /* Needed when measuring an expanded row */ display: flex; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control.setting-type-number { +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control.setting-type-number { min-width: 200px; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control.setting-type-enum, -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control.setting-type-string { +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control.setting-type-enum, +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control.setting-type-string { flex: 1; min-width: initial; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control.setting-type-enum > *:first-child { +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control.setting-type-enum > *:first-child { width: 100%; - min-width: 250px; - display: inline-block; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control > .edit-in-settings-button, -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control > .edit-in-settings-button:hover, -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control > .edit-in-settings-button:active { +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .edit-in-settings-button, +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .edit-in-settings-button:hover, +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .edit-in-settings-button:active { margin: auto; text-align: left; text-decoration: underline; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control > .edit-in-settings-button + .setting-reset-button.monaco-button { +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .edit-in-settings-button + .setting-reset-button.monaco-button { display: none; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .monaco-select-box { +.settings-editor > .settings-body > .settings-tree-container .setting-item .monaco-select-box { width: initial; font: inherit; height: 26px; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-reset-button.monaco-button { +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-reset-button.monaco-button { text-align: left; display: inline-block; visibility: hidden; @@ -256,39 +239,43 @@ padding-top: 2px; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item.is-configured .setting-item-value > .setting-reset-button.monaco-button { +.settings-editor > .settings-body > .settings-tree-container .setting-item.is-configured .setting-item-value > .setting-reset-button.monaco-button { visibility: visible; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .all-settings { +.settings-editor > .settings-body > .settings-tree-container .all-settings { display: flex; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .all-settings .all-settings-button { +.settings-editor > .settings-body > .settings-tree-container .all-settings .all-settings-button { margin: auto; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .all-settings .all-settings-button .monaco-button { +.settings-editor > .settings-body > .settings-tree-container .all-settings .all-settings-button .monaco-button { padding-left: 10px; padding-right: 10px; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .settings-group-title-label { +.settings-editor > .settings-body > .settings-tree-container .group-title, +.settings-editor > .settings-body > .settings-tree-container .setting-item { + padding-left: 5px; +} + +.settings-editor > .settings-body > .settings-tree-container .settings-group-title-label { margin: 0px; - padding-left: 5px !important; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .settings-group-level-1 { +.settings-editor > .settings-body > .settings-tree-container .settings-group-level-1 { padding-top: 16px; font-size: 24px; } -.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .settings-group-level-2 { +.settings-editor > .settings-body > .settings-tree-container .settings-group-level-2 { padding-top: 16px; font-size: 20px; } -.settings-editor > .settings-editor-right > .settings-body .settings-feedback-button { +.settings-editor > .settings-body .settings-feedback-button { color: rgb(255, 255, 255); background-color: rgb(14, 99, 156); position: absolute; diff --git a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts index aa23dcd4ca1bb..61669bfa5e3b0 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts @@ -82,6 +82,8 @@ export class PreferencesEditor extends BaseEditor { set minimumWidth(value: number) { /*noop*/ } set maximumWidth(value: number) { /*noop*/ } + readonly minimumHeight = 260; + private _onDidCreateWidget = new Emitter<{ width: number; height: number; }>(); readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; }> = this._onDidCreateWidget.event; @@ -205,10 +207,6 @@ export class PreferencesEditor extends BaseEditor { super.clearInput(); } - public supportsCenteredLayout(): boolean { - return false; - } - protected setEditorVisible(visible: boolean, group: IEditorGroup): void { this.sideBySidePreferencesWidget.setEditorVisible(visible, group); super.setEditorVisible(visible, group); @@ -1053,10 +1051,6 @@ export class DefaultPreferencesEditor extends BaseTextEditor { this.getControl().layout(dimension); } - public supportsCenteredLayout(): boolean { - return false; - } - protected getAriaLabel(): string { return nls.localize('preferencesAriaLabel', "Default preferences. Readonly text editor."); } diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index a9f7a982a6e84..c024a63eb7ff0 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -31,7 +31,7 @@ import { EditorOptions, IEditor } from 'vs/workbench/common/editor'; import { SearchWidget, SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; import { tocData, commonlyUsedData } from 'vs/workbench/parts/preferences/browser/settingsLayout'; import { ISettingsEditorViewState, SearchResultIdx, SearchResultModel, SettingsAccessibilityProvider, SettingsDataSource, SettingsRenderer, SettingsTreeController, SettingsTreeElement, SettingsTreeFilter, SettingsTreeModel, SettingsTreeSettingElement, SettingsTreeGroupElement, resolveSettingsTree, NonExpandableTree } from 'vs/workbench/parts/preferences/browser/settingsTree'; -import { TOCDataSource, TOCRenderer } from 'vs/workbench/parts/preferences/browser/tocTree'; +import { TOCDataSource, TOCRenderer, TOCTreeModel } from 'vs/workbench/parts/preferences/browser/tocTree'; import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, IPreferencesSearchService, ISearchProvider } from 'vs/workbench/parts/preferences/common/preferences'; import { IPreferencesService, ISearchResult, ISettingsEditorModel } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; @@ -55,6 +55,7 @@ export class SettingsEditor2 extends BaseEditor { private settingsTreeContainer: HTMLElement; private settingsTree: WorkbenchTree; private treeDataSource: SettingsDataSource; + private tocTreeModel: TOCTreeModel; private settingsTreeModel: SettingsTreeModel; private tocTreeContainer: HTMLElement; @@ -102,11 +103,9 @@ export class SettingsEditor2 extends BaseEditor { createEditor(parent: HTMLElement): void { this.rootElement = DOM.append(parent, $('.settings-editor')); - this.createTOC(this.rootElement); - const settingsEditorRightElement = DOM.append(this.rootElement, $('.settings-editor-right')); - this.createHeader(settingsEditorRightElement); - this.createBody(settingsEditorRightElement); + this.createHeader(this.rootElement); + this.createBody(this.rootElement); } setInput(input: SettingsEditor2Input, options: EditorOptions, token: CancellationToken): Thenable { @@ -217,6 +216,7 @@ export class SettingsEditor2 extends BaseEditor { private createBody(parent: HTMLElement): void { const bodyContainer = DOM.append(parent, $('.settings-body')); + this.createTOC(bodyContainer); this.createSettingsTree(bodyContainer); if (this.environmentService.appQuality !== 'stable') { @@ -227,21 +227,27 @@ export class SettingsEditor2 extends BaseEditor { private createTOC(parent: HTMLElement): void { this.tocTreeContainer = DOM.append(parent, $('.settings-toc-container')); - const tocTreeDataSource = this.instantiationService.createInstance(TOCDataSource); - const renderer = this.instantiationService.createInstance(TOCRenderer); + const tocDataSource = this.instantiationService.createInstance(TOCDataSource); + const tocRenderer = this.instantiationService.createInstance(TOCRenderer); + this.tocTreeModel = new TOCTreeModel(); this.tocTree = this.instantiationService.createInstance(WorkbenchTree, this.tocTreeContainer, { - dataSource: tocTreeDataSource, - renderer, + dataSource: tocDataSource, + renderer: tocRenderer, filter: this.instantiationService.createInstance(SettingsTreeFilter, this.viewState) }, { - showLoading: false + showLoading: false, + twistiePixels: 15 }); this._register(this.tocTree.onDidChangeSelection(e => { - if (this.settingsTreeModel) { + if (this.searchResultModel) { + const element = e.selection[0]; + this.viewState.filterToCategory = element; + this.refreshTreeAndMaintainFocus(); + } else if (this.settingsTreeModel) { const element = e.selection[0]; const currentSelection = this.settingsTree.getSelection()[0]; const isEqualOrParent = (element: SettingsTreeElement, candidate: SettingsTreeElement) => { @@ -291,12 +297,12 @@ export class SettingsEditor2 extends BaseEditor { this._register(registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const activeBorderColor = theme.getColor(listActiveSelectionBackground); if (activeBorderColor) { - collector.addRule(`.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .monaco-tree:focus .monaco-tree-row.focused {outline: solid 1px ${activeBorderColor}; outline-offset: -1px; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .monaco-tree:focus .monaco-tree-row.focused {outline: solid 1px ${activeBorderColor}; outline-offset: -1px; }`); } const inactiveBorderColor = theme.getColor(listInactiveSelectionBackground); if (inactiveBorderColor) { - collector.addRule(`.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .monaco-tree .monaco-tree-row.focused {outline: solid 1px ${inactiveBorderColor}; outline-offset: -1px; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .monaco-tree .monaco-tree-row.focused {outline: solid 1px ${inactiveBorderColor}; outline-offset: -1px; }`); } })); @@ -466,7 +472,10 @@ export class SettingsEditor2 extends BaseEditor { } private toggleSearchMode(): void { - DOM.toggleClass(this.rootElement, 'search-mode', !!this.searchResultModel); + DOM.removeClass(this.rootElement, 'search-mode'); + if (this.configurationService.getValue('workbench.settings.settingsSearchTocBehavior') === 'hide') { + DOM.toggleClass(this.rootElement, 'search-mode', !!this.searchResultModel); + } } private onConfigUpdate(): TPromise { @@ -480,7 +489,13 @@ export class SettingsEditor2 extends BaseEditor { } else { this.settingsTreeModel = this.instantiationService.createInstance(SettingsTreeModel, this.viewState, resolvedSettingsRoot); this.settingsTree.setInput(this.settingsTreeModel.root); - this.tocTree.setInput(this.settingsTreeModel.root); + + this.tocTreeModel.settingsTreeRoot = this.settingsTreeModel.root as SettingsTreeGroupElement; + if (this.tocTree.getInput()) { + this.tocTree.refresh(); + } else { + this.tocTree.setInput(this.tocTreeModel); + } } return this.refreshTreeAndMaintainFocus(); @@ -538,6 +553,9 @@ export class SettingsEditor2 extends BaseEditor { } this.searchResultModel = null; + this.tocTreeModel.currentSearchModel = null; + this.viewState.filterToCategory = null; + this.tocTree.refresh(); this.toggleSearchMode(); this.settingsTree.setInput(this.settingsTreeModel.root); @@ -613,11 +631,13 @@ export class SettingsEditor2 extends BaseEditor { const [result] = results; if (!this.searchResultModel) { this.searchResultModel = new SearchResultModel(); + this.tocTreeModel.currentSearchModel = this.searchResultModel; this.toggleSearchMode(); this.settingsTree.setInput(this.searchResultModel); } this.searchResultModel.setResult(type, result); + this.tocTreeModel.update(); resolve(this.refreshTreeAndMaintainFocus()); }); }, () => { diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index 5cd8fd536e460..0802bb38bb540 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -41,7 +41,7 @@ export const modifiedItemForeground = registerColor('settings.modifiedItemForegr registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const modifiedItemForegroundColor = theme.getColor(modifiedItemForeground); if (modifiedItemForegroundColor) { - collector.addRule(`.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item.is-configured .setting-item-is-configured-label { color: ${modifiedItemForegroundColor}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.is-configured .setting-item-is-configured-label { color: ${modifiedItemForegroundColor}; }`); } }); @@ -359,6 +359,7 @@ function trimCategoryForGroup(category: string, groupId: string): string { export interface ISettingsEditorViewState { settingsTarget: SettingsTarget; showConfiguredOnly?: boolean; + filterToCategory?: SettingsTreeGroupElement; } interface IDisposableTemplate { @@ -674,7 +675,13 @@ export class SettingsTreeFilter implements IFilter { ) { } isVisible(tree: ITree, element: SettingsTreeElement): boolean { - if (this.viewState.showConfiguredOnly && element instanceof SettingsTreeSettingElement) { + if (this.viewState.filterToCategory && element instanceof SettingsTreeSettingElement) { + if (!this.settingContainedInGroup(element.setting, this.viewState.filterToCategory)) { + return false; + } + } + + if (element instanceof SettingsTreeSettingElement && this.viewState.showConfiguredOnly) { return element.isConfigured; } @@ -685,6 +692,18 @@ export class SettingsTreeFilter implements IFilter { return true; } + private settingContainedInGroup(setting: ISetting, group: SettingsTreeGroupElement): boolean { + return group.children.some(child => { + if (child instanceof SettingsTreeGroupElement) { + return this.settingContainedInGroup(setting, child); + } else if (child instanceof SettingsTreeSettingElement) { + return child.setting.key === setting.key; + } else { + return false; + } + }); + } + private groupHasConfiguredSetting(element: SettingsTreeGroupElement): boolean { for (let child of element.children) { if (child instanceof SettingsTreeSettingElement) { diff --git a/src/vs/workbench/parts/preferences/browser/tocTree.ts b/src/vs/workbench/parts/preferences/browser/tocTree.ts index b2b92fe6e7a03..e7e175e1661a2 100644 --- a/src/vs/workbench/parts/preferences/browser/tocTree.ts +++ b/src/vs/workbench/parts/preferences/browser/tocTree.ts @@ -6,25 +6,100 @@ import * as DOM from 'vs/base/browser/dom'; import { TPromise } from 'vs/base/common/winjs.base'; import { IDataSource, IRenderer, ITree } from 'vs/base/parts/tree/browser/tree'; -import { SettingsTreeElement, SettingsTreeGroupElement } from 'vs/workbench/parts/preferences/browser/settingsTree'; +import { SearchResultModel, SettingsTreeElement, SettingsTreeGroupElement, SettingsTreeSettingElement } from 'vs/workbench/parts/preferences/browser/settingsTree'; +import { ISetting } from 'vs/workbench/services/preferences/common/preferences'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const $ = DOM.$; +export class TOCTreeModel { + + private _currentSearchModel: SearchResultModel; + private _settingsTreeRoot: SettingsTreeGroupElement; + + public set settingsTreeRoot(value: SettingsTreeGroupElement) { + this._settingsTreeRoot = value; + this.update(); + } + + public set currentSearchModel(model: SearchResultModel) { + this._currentSearchModel = model; + this.update(); + } + + public get children(): SettingsTreeElement[] { + return this._settingsTreeRoot.children; + } + + public update(): void { + this.updateGroupCount(this._settingsTreeRoot); + } + + private updateGroupCount(group: SettingsTreeGroupElement): void { + (group).count = this._currentSearchModel ? + this.getSearchResultChildrenCount(group) : + undefined; + + group.children.forEach(child => { + if (child instanceof SettingsTreeGroupElement) { + this.updateGroupCount(child); + } + }); + } + + private getSearchResultChildrenCount(group: SettingsTreeGroupElement): number { + return this._currentSearchModel.getFlatSettings().filter(s => { + return this.groupContainsSetting(group, s); + }).length; + } + + private groupContainsSetting(group: SettingsTreeGroupElement, setting: ISetting): boolean { + return group.children.some(child => { + if (child instanceof SettingsTreeSettingElement) { + return child.setting.key === setting.key; + } else if (child instanceof SettingsTreeGroupElement) { + return this.groupContainsSetting(child, setting); + } else { + return false; + } + }); + } +} + +export type TOCTreeElement = SettingsTreeGroupElement | TOCTreeModel; + export class TOCDataSource implements IDataSource { + constructor( + @IConfigurationService private configService: IConfigurationService + ) { + } + getId(tree: ITree, element: SettingsTreeGroupElement): string { return element.id; } - hasChildren(tree: ITree, element: SettingsTreeElement): boolean { - return element instanceof SettingsTreeGroupElement && element.children && element.children.every(child => child instanceof SettingsTreeGroupElement); + hasChildren(tree: ITree, element: TOCTreeElement): boolean { + return element instanceof TOCTreeModel || + (element instanceof SettingsTreeGroupElement && element.children && element.children.every(child => child instanceof SettingsTreeGroupElement)); + } + + getChildren(tree: ITree, element: TOCTreeElement): TPromise { + return TPromise.as(this._getChildren(element)); } - getChildren(tree: ITree, element: SettingsTreeGroupElement): TPromise { - return TPromise.as(element.children); + private _getChildren(element: TOCTreeElement): SettingsTreeElement[] { + if (this.configService.getValue('workbench.settings.settingsSearchTocBehavior') === 'filter') { + const children = element.children as SettingsTreeElement[]; // ???? + return children.filter(group => { + return (group).count !== 0; + }); + } + + return element.children; } - getParent(tree: ITree, element: SettingsTreeElement): TPromise { - return TPromise.wrap(element.parent); + getParent(tree: ITree, element: TOCTreeElement): TPromise { + return TPromise.wrap(element instanceof SettingsTreeGroupElement && element.parent); } shouldAutoexpand() { @@ -54,7 +129,12 @@ export class TOCRenderer implements IRenderer { } renderElement(tree: ITree, element: SettingsTreeGroupElement, templateId: string, template: ITOCEntryTemplate): void { - template.element.textContent = element.label; + const label = (element).count ? + `${element.label} (${(element).count})` : + element.label; + + DOM.toggleClass(template.element, 'no-results', (element).count === 0); + template.element.textContent = label; } disposeTemplate(tree: ITree, templateId: string, templateData: any): void { diff --git a/src/vs/workbench/parts/search/browser/searchWidget.ts b/src/vs/workbench/parts/search/browser/searchWidget.ts index becbaf33d0967..8af51ede3862e 100644 --- a/src/vs/workbench/parts/search/browser/searchWidget.ts +++ b/src/vs/workbench/parts/search/browser/searchWidget.ts @@ -241,6 +241,7 @@ export class SearchWidget extends Widget { buttonHoverBackground: null }; this.toggleReplaceButton = this._register(new Button(parent, opts)); + this.toggleReplaceButton.element.setAttribute('aria-expanded', 'false'); this.toggleReplaceButton.icon = 'toggle-replace-button collapse'; // TODO@joh need to dispose this listener eventually this.toggleReplaceButton.onDidClick(() => this.onToggleReplaceButton()); @@ -330,6 +331,7 @@ export class SearchWidget extends Widget { dom.toggleClass(this.replaceContainer, 'disabled'); dom.toggleClass(this.toggleReplaceButton.element, 'collapse'); dom.toggleClass(this.toggleReplaceButton.element, 'expand'); + this.toggleReplaceButton.element.setAttribute('aria-expanded', this.isReplaceShown() ? 'true' : 'false'); this.updateReplaceActiveState(); this._onReplaceToggled.fire(); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts index 2d887bcd678ee..4d4f6645ad04d 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts @@ -137,14 +137,14 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { // Get the character dimensions from xterm if it's available if (xterm) { - if (xterm.charMeasure && xterm.charMeasure.width && xterm.charMeasure.height) { + if (xterm._core.charMeasure && xterm._core.charMeasure.width && xterm._core.charMeasure.height) { return { fontFamily, fontSize, letterSpacing, lineHeight, - charHeight: xterm.charMeasure.height, - charWidth: xterm.charMeasure.width + charHeight: xterm._core.charMeasure.height, + charWidth: xterm._core.charMeasure.width }; } } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 7f7ebf0366786..6203b5c5c9771 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -232,7 +232,7 @@ export class TerminalInstance implements ITerminalInstance { // it gets removed and then added back to the DOM (resetting scrollTop to 0). // Upstream issue: https://github.com/sourcelair/xterm.js/issues/291 if (this._xterm) { - this._xterm.emit('scroll', this._xterm.buffer.ydisp); + this._xterm.emit('scroll', this._xterm._core.buffer.ydisp); } } @@ -460,7 +460,7 @@ export class TerminalInstance implements ITerminalInstance { private _measureRenderTime(): void { const frameTimes: number[] = []; - const textRenderLayer = (this._xterm).renderer._renderLayers[0]; + const textRenderLayer = this._xterm._core.renderer._renderLayers[0]; const originalOnGridChanged = textRenderLayer.onGridChanged; const evaluateCanvasRenderer = () => { @@ -575,7 +575,7 @@ export class TerminalInstance implements ITerminalInstance { this._xtermElement = null; } if (this._xterm) { - const buffer = (this._xterm.buffer); + const buffer = (this._xterm._core.buffer); this._sendLineData(buffer, buffer.ybase + buffer.y); this._xterm.dispose(); this._xterm = null; @@ -648,7 +648,7 @@ export class TerminalInstance implements ITerminalInstance { // necessary if the number of rows in the terminal has decreased while it was in the // background since scrollTop changes take no effect but the terminal's position does // change since the number of visible rows decreases. - this._xterm.emit('scroll', this._xterm.buffer.ydisp); + this._xterm.emit('scroll', this._xterm._core.buffer.ydisp); if (this._container && this._container.parentElement) { // Force a layout when the instance becomes invisible. This is particularly important // for ensuring that terminals that are created in the background by an extension will @@ -848,7 +848,7 @@ export class TerminalInstance implements ITerminalInstance { } private _onLineFeed(): void { - const buffer = (this._xterm.buffer); + const buffer = (this._xterm._core.buffer); const newLine = buffer.lines.get(buffer.ybase + buffer.y); if (!newLine.isWrapped) { this._sendLineData(buffer, buffer.ybase + buffer.y - 1); @@ -974,7 +974,7 @@ export class TerminalInstance implements ITerminalInstance { // on Winodws/Linux would fire an event saying that the terminal was not visible. // This should only force a refresh if one is needed. if (this._xterm.getOption('rendererType') === 'canvas') { - (this._xterm).renderer.onIntersectionChange({ intersectionRatio: 1 }); + this._xterm._core.renderer.onIntersectionChange({ intersectionRatio: 1 }); } } } diff --git a/src/vs/workbench/parts/terminal/node/terminalCommandTracker.ts b/src/vs/workbench/parts/terminal/node/terminalCommandTracker.ts index bdde9f6e0b9cb..6e8f0335b8b36 100644 --- a/src/vs/workbench/parts/terminal/node/terminalCommandTracker.ts +++ b/src/vs/workbench/parts/terminal/node/terminalCommandTracker.ts @@ -49,7 +49,7 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa } private _onEnter(): void { - if (this._xterm.buffer.x >= MINIMUM_PROMPT_LENGTH) { + if (this._xterm._core.buffer.x >= MINIMUM_PROMPT_LENGTH) { this._xterm.addMarker(0); } } @@ -176,7 +176,7 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa private _getLine(marker: IMarker | Boundary): number { // Use the _second last_ row as the last row is likely the prompt if (marker === Boundary.Bottom) { - return this._xterm.buffer.ybase + this._xterm.rows - 1; + return this._xterm._core.buffer.ybase + this._xterm.rows - 1; } if (marker === Boundary.Top) { @@ -236,10 +236,10 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa if (this._currentMarker === Boundary.Bottom) { return 0; } else if (this._currentMarker === Boundary.Top) { - return 0 - (this._xterm.buffer.ybase + this._xterm.buffer.y); + return 0 - (this._xterm._core.buffer.ybase + this._xterm._core.buffer.y); } else { let offset = this._getLine(this._currentMarker); - offset -= this._xterm.buffer.ybase + this._xterm.buffer.y; + offset -= this._xterm._core.buffer.ybase + this._xterm._core.buffer.y; return offset; } } diff --git a/src/vs/workbench/parts/terminal/test/node/terminalCommandTracker.test.ts b/src/vs/workbench/parts/terminal/test/node/terminalCommandTracker.test.ts index 003f76b383bf5..495ffc45da234 100644 --- a/src/vs/workbench/parts/terminal/test/node/terminalCommandTracker.test.ts +++ b/src/vs/workbench/parts/terminal/test/node/terminalCommandTracker.test.ts @@ -4,19 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Terminal } from 'vscode-xterm'; +import { Terminal, TerminalCore } from 'vscode-xterm'; import { TerminalCommandTracker } from 'vs/workbench/parts/terminal/node/terminalCommandTracker'; import { isWindows } from 'vs/base/common/platform'; -interface TestTerminal extends Terminal { +interface TestTerminalCore extends TerminalCore { writeBuffer: string[]; _innerWrite(): void; } +interface TestTerminal extends Terminal { + _core: TestTerminalCore; +} + function syncWrite(term: TestTerminal, data: string): void { // Terminal.write is asynchronous - term.writeBuffer.push(data); - term._innerWrite(); + term._core.writeBuffer.push(data); + term._core._innerWrite(); } const ROWS = 10; @@ -62,24 +66,24 @@ suite('Workbench - TerminalCommandTracker', () => { for (let i = 0; i < 20; i++) { syncWrite(xterm, `\r\n`); } - assert.equal(xterm.buffer.ybase, 20); - assert.equal(xterm.buffer.ydisp, 20); + assert.equal(xterm._core.buffer.ybase, 20); + assert.equal(xterm._core.buffer.ydisp, 20); // Scroll to marker commandTracker.scrollToPreviousCommand(); - assert.equal(xterm.buffer.ydisp, 9); + assert.equal(xterm._core.buffer.ydisp, 9); // Scroll to top boundary commandTracker.scrollToPreviousCommand(); - assert.equal(xterm.buffer.ydisp, 0); + assert.equal(xterm._core.buffer.ydisp, 0); // Scroll to marker commandTracker.scrollToNextCommand(); - assert.equal(xterm.buffer.ydisp, 9); + assert.equal(xterm._core.buffer.ydisp, 9); // Scroll to bottom boundary commandTracker.scrollToNextCommand(); - assert.equal(xterm.buffer.ydisp, 20); + assert.equal(xterm._core.buffer.ydisp, 20); }); test('should select to the next and previous commands', () => { (window).matchMedia = () => { @@ -98,8 +102,8 @@ suite('Workbench - TerminalCommandTracker', () => { assert.equal(xterm.markers[1].line, 11); syncWrite(xterm, '\n\r3'); - assert.equal(xterm.buffer.ybase, 3); - assert.equal(xterm.buffer.ydisp, 3); + assert.equal(xterm._core.buffer.ybase, 3); + assert.equal(xterm._core.buffer.ydisp, 3); assert.equal(xterm.getSelection(), ''); commandTracker.selectToPreviousCommand(); @@ -128,8 +132,8 @@ suite('Workbench - TerminalCommandTracker', () => { assert.equal(xterm.markers[1].line, 11); syncWrite(xterm, '\n\r3'); - assert.equal(xterm.buffer.ybase, 3); - assert.equal(xterm.buffer.ydisp, 3); + assert.equal(xterm._core.buffer.ybase, 3); + assert.equal(xterm._core.buffer.ydisp, 3); assert.equal(xterm.getSelection(), ''); commandTracker.selectToPreviousLine(); diff --git a/src/vs/workbench/services/backup/common/backup.ts b/src/vs/workbench/services/backup/common/backup.ts index 86d5326107eee..be76ff6c4286a 100644 --- a/src/vs/workbench/services/backup/common/backup.ts +++ b/src/vs/workbench/services/backup/common/backup.ts @@ -22,11 +22,6 @@ export const BACKUP_FILE_UPDATE_OPTIONS: IUpdateContentOptions = { encoding: 'ut export interface IBackupFileService { _serviceBrand: any; - /** - * If backups are enabled. - */ - backupEnabled: boolean; - /** * Finds out if there are any backups stored. */ diff --git a/src/vs/workbench/services/backup/node/backupFileService.ts b/src/vs/workbench/services/backup/node/backupFileService.ts index fdeea689752f0..8b2c585dc4962 100644 --- a/src/vs/workbench/services/backup/node/backupFileService.ts +++ b/src/vs/workbench/services/backup/node/backupFileService.ts @@ -15,7 +15,8 @@ import { IFileService, ITextSnapshot } from 'vs/platform/files/common/files'; import { TPromise } from 'vs/base/common/winjs.base'; import { readToMatchingString } from 'vs/base/node/stream'; import { ITextBufferFactory } from 'vs/editor/common/model'; -import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; +import { createTextBufferFactoryFromStream, createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; +import { keys } from 'vs/base/common/map'; export interface IBackupFilesModel { resolve(backupRoot: string): TPromise; @@ -135,17 +136,9 @@ export class BackupFileService implements IBackupFileService { this.ready = this.init(); } - public get backupEnabled(): boolean { - return !!this.backupWorkspacePath; // Hot exit requires a backup path - } - private init(): TPromise { const model = new BackupFilesModel(); - if (!this.backupEnabled) { - return TPromise.as(model); - } - return model.resolve(this.backupWorkspacePath); } @@ -157,12 +150,9 @@ export class BackupFileService implements IBackupFileService { public loadBackupResource(resource: Uri): TPromise { return this.ready.then(model => { - const backupResource = this.toBackupResource(resource); - if (!backupResource) { - return void 0; - } // Return directly if we have a known backup with that resource + const backupResource = this.toBackupResource(resource); if (model.has(backupResource)) { return backupResource; } @@ -178,10 +168,6 @@ export class BackupFileService implements IBackupFileService { return this.ready.then(model => { const backupResource = this.toBackupResource(resource); - if (!backupResource) { - return void 0; - } - if (model.has(backupResource, versionId)) { return void 0; // return early if backup version id matches requested one } @@ -198,9 +184,6 @@ export class BackupFileService implements IBackupFileService { public discardResourceBackup(resource: Uri): TPromise { return this.ready.then(model => { const backupResource = this.toBackupResource(resource); - if (!backupResource) { - return void 0; - } return this.ioOperationQueues.queueFor(backupResource).queue(() => { return pfs.del(backupResource.fsPath).then(() => model.remove(backupResource)); @@ -212,10 +195,6 @@ export class BackupFileService implements IBackupFileService { this.isShuttingDown = true; return this.ready.then(model => { - if (!this.backupEnabled) { - return void 0; - } - return pfs.del(this.backupWorkspacePath).then(() => model.clear()); }); } @@ -226,8 +205,7 @@ export class BackupFileService implements IBackupFileService { model.get().forEach(fileBackup => { readPromises.push( - readToMatchingString(fileBackup.fsPath, BackupFileService.META_MARKER, 2000, 10000) - .then(Uri.parse) + readToMatchingString(fileBackup.fsPath, BackupFileService.META_MARKER, 2000, 10000).then(Uri.parse) ); }); @@ -259,10 +237,6 @@ export class BackupFileService implements IBackupFileService { } public toBackupResource(resource: Uri): Uri { - if (!this.backupEnabled) { - return null; - } - return Uri.file(path.join(this.backupWorkspacePath, resource.scheme, this.hashPath(resource))); } @@ -270,3 +244,63 @@ export class BackupFileService implements IBackupFileService { return crypto.createHash('md5').update(resource.fsPath).digest('hex'); } } + +export class InMemoryBackupFileService implements IBackupFileService { + + public _serviceBrand: any; + + private backups: Map = new Map(); + + hasBackups(): TPromise { + return TPromise.as(this.backups.size > 0); + } + + loadBackupResource(resource: Uri): TPromise { + const backupResource = this.toBackupResource(resource); + if (this.backups.has(backupResource.toString())) { + return TPromise.as(backupResource); + } + + return TPromise.as(void 0); + } + + backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): TPromise { + const backupResource = this.toBackupResource(resource); + this.backups.set(backupResource.toString(), content); + + return TPromise.as(void 0); + } + + resolveBackupContent(backupResource: Uri): TPromise { + const snapshot = this.backups.get(backupResource.toString()); + if (snapshot) { + return TPromise.as(createTextBufferFactoryFromSnapshot(snapshot)); + } + + return TPromise.as(void 0); + } + + getWorkspaceFileBackups(): TPromise { + return TPromise.as(keys(this.backups).map(key => Uri.parse(key))); + } + + discardResourceBackup(resource: Uri): TPromise { + this.backups.delete(this.toBackupResource(resource).toString()); + + return TPromise.as(void 0); + } + + discardAllWorkspaceBackups(): TPromise { + this.backups.clear(); + + return TPromise.as(void 0); + } + + toBackupResource(resource: Uri): Uri { + return Uri.file(path.join(resource.scheme, this.hashPath(resource))); + } + + private hashPath(resource: Uri): string { + return crypto.createHash('md5').update(resource.fsPath).digest('hex'); + } +} \ No newline at end of file diff --git a/src/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts index 3e6906377e776..f3f62fdbf024b 100644 --- a/src/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts @@ -5,8 +5,9 @@ 'use strict'; +import { mergeSort } from 'vs/base/common/arrays'; import { getPathLabel } from 'vs/base/common/labels'; -import { IDisposable, IReference, dispose } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, IReference } from 'vs/base/common/lifecycle'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -15,36 +16,30 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { EndOfLineSequence, IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; -import { ResourceFileEdit, ResourceTextEdit, WorkspaceEdit, isResourceFileEdit, isResourceTextEdit } from 'vs/editor/common/modes'; +import { isResourceFileEdit, isResourceTextEdit, ResourceFileEdit, ResourceTextEdit, WorkspaceEdit } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; -import { FileChangeType, IFileService } from 'vs/platform/files/common/files'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IProgress, IProgressRunner, emptyProgressRunner } from 'vs/platform/progress/common/progress'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { emptyProgressRunner, IProgress, IProgressRunner } from 'vs/platform/progress/common/progress'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { ILogService } from 'vs/platform/log/common/log'; abstract class Recording { static start(fileService: IFileService): Recording { let _changes = new Set(); - let stop: IDisposable; - - stop = fileService.onFileChanges(event => { - for (const change of event.changes) { - if (change.type === FileChangeType.UPDATED) { - _changes.add(change.resource.toString()); - } - } + let subscription = fileService.onAfterOperation(e => { + _changes.add(e.resource.toString()); }); return { - stop() { return dispose(stop); }, + stop() { return subscription.dispose(); }, hasChanged(resource) { return _changes.has(resource.toString()); } }; } @@ -97,15 +92,7 @@ class EditTask implements IDisposable { apply(): void { if (this._edits.length > 0) { - - this._edits = this._edits.map((value, index) => ({ value, index })).sort((a, b) => { - let ret = Range.compareRangesUsingStarts(a.value.range, b.value.range); - if (ret === 0) { - ret = a.index - b.index; - } - return ret; - }).map(element => element.value); - + this._edits = mergeSort(this._edits, (a, b) => Range.compareRangesUsingStarts(a.range, b.range)); this._initialSelections = this._getInitialSelections(); this._model.pushStackElement(); this._model.pushEditOperations(this._initialSelections, this._edits, (edits) => this._getEndCursorSelections(edits)); @@ -269,6 +256,7 @@ export class BulkEdit { constructor( editor: ICodeEditor, progress: IProgressRunner, + @ILogService private readonly _logService: ILogService, @ITextModelService private readonly _textModelService: ITextModelService, @IFileService private readonly _fileService: IFileService, @ITextFileService private readonly _textFileService: ITextFileService, @@ -343,21 +331,26 @@ export class BulkEdit { } private async _performFileEdits(edits: ResourceFileEdit[], progress: IProgress) { + this._logService.debug('_performFileEdits', JSON.stringify(edits)); for (const edit of edits) { - progress.report(undefined); + let overwrite = edit.options && edit.options.overwrite; if (edit.newUri && edit.oldUri) { - await this._textFileService.move(edit.oldUri, edit.newUri, false); + await this._textFileService.move(edit.oldUri, edit.newUri, overwrite); } else if (!edit.newUri && edit.oldUri) { await this._textFileService.delete(edit.oldUri, true); } else if (edit.newUri && !edit.oldUri) { - await this._fileService.createFile(edit.newUri, undefined, { overwrite: false }); + let ignoreIfExists = edit.options && edit.options.ignoreIfExists; + if (!ignoreIfExists || !await this._fileService.existsFile(edit.newUri)) { + await this._fileService.createFile(edit.newUri, undefined, { overwrite }); + } } } } private async _performTextEdits(edits: ResourceTextEdit[], progress: IProgress): TPromise { + this._logService.debug('_performTextEdits', JSON.stringify(edits)); const recording = Recording.start(this._fileService); const model = new BulkEditModel(this._textModelService, this._editor, edits, progress); @@ -424,14 +417,16 @@ export class BulkEditService implements IBulkEditService { } } - const bulkEdit = new BulkEdit(options.editor, options.progress, this._textModelService, this._fileService, this._textFileService, this._environmentService, this._contextService); + const bulkEdit = new BulkEdit(options.editor, options.progress, this._logService, this._textModelService, this._fileService, this._textFileService, this._environmentService, this._contextService); bulkEdit.add(edits); return bulkEdit.perform().then(selection => { return { selection, ariaSummary: bulkEdit.ariaMessage() }; }, err => { + // console.log('apply FAILED'); + // console.log(err); this._logService.error(err); - return TPromise.wrapError(err); + throw err; }); } } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 61547023c096a..c5943fb7778c8 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -361,7 +361,7 @@ export class ExtensionHostProcessWorker { } private _createExtHostInitData(): TPromise { - return TPromise.join([this._telemetryService.getTelemetryInfo(), this._extensions]).then(([telemetryInfo, extensionDescriptions]) => { + return TPromise.join([this._telemetryService.getTelemetryInfo(), this._extensions]).then(([telemetryInfo, extensionDescriptions]) => { const configurationData: IConfigurationInitData = { ...this._configurationService.getConfigurationData(), configurationScopes: {} }; const r: IInitData = { parentPid: process.pid, diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index f9a6fafae8161..4c8fae279e7a9 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -42,6 +42,7 @@ import * as strings from 'vs/base/common/strings'; import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; +import { Schemas } from 'vs/base/common/network'; let _SystemExtensionsRoot: string = null; function getSystemExtensionsRoot(): string { @@ -486,119 +487,131 @@ export class ExtensionService extends Disposable implements IExtensionService { private _scanAndHandleExtensions(): void { - this._getRuntimeExtensions().then(allExtensions => { - this._registry = new ExtensionDescriptionRegistry(allExtensions); + this._scanExtensions() + .then(allExtensions => this._getRuntimeExtensions(allExtensions)) + .then(allExtensions => { + this._registry = new ExtensionDescriptionRegistry(allExtensions); - let availableExtensions = this._registry.getAllExtensionDescriptions(); - let extensionPoints = ExtensionsRegistry.getExtensionPoints(); + let availableExtensions = this._registry.getAllExtensionDescriptions(); + let extensionPoints = ExtensionsRegistry.getExtensionPoints(); - let messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg); + let messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg); - for (let i = 0, len = extensionPoints.length; i < len; i++) { - ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler); - } + for (let i = 0, len = extensionPoints.length; i < len; i++) { + ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler); + } - mark('extensionHostReady'); - this._installedExtensionsReady.open(); - this._onDidRegisterExtensions.fire(void 0); - this._onDidChangeExtensionsStatus.fire(availableExtensions.map(e => e.id)); - }); + mark('extensionHostReady'); + this._installedExtensionsReady.open(); + this._onDidRegisterExtensions.fire(void 0); + this._onDidChangeExtensionsStatus.fire(availableExtensions.map(e => e.id)); + }); } - private _getRuntimeExtensions(): TPromise { + private _scanExtensions(): TPromise { const log = new Logger((severity, source, message) => { this._logOrShowMessage(severity, this._isDev ? messageWithSource(source, message) : message); }); return ExtensionService._scanInstalledExtensions(this._windowService, this._notificationService, this._environmentService, log) .then(({ system, user, development }) => { - return this._extensionEnablementService.getDisabledExtensions() - .then(disabledExtensions => { - let result: { [extensionId: string]: IExtensionDescription; } = {}; - let extensionsToDisable: IExtensionIdentifier[] = []; - let userMigratedSystemExtensions: IExtensionIdentifier[] = [{ id: BetterMergeId }]; - - system.forEach((systemExtension) => { - if (disabledExtensions.every(disabled => !areSameExtensions(disabled, systemExtension))) { - result[systemExtension.id] = systemExtension; - } - }); + let result: { [extensionId: string]: IExtensionDescription; } = {}; + system.forEach((systemExtension) => { + result[systemExtension.id] = systemExtension; + }); + user.forEach((userExtension) => { + if (result.hasOwnProperty(userExtension.id)) { + log.warn(userExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[userExtension.id].extensionLocation.fsPath, userExtension.extensionLocation.fsPath)); + } + result[userExtension.id] = userExtension; + }); + development.forEach(developedExtension => { + log.info('', nls.localize('extensionUnderDevelopment', "Loading development extension at {0}", developedExtension.extensionLocation.fsPath)); + if (result.hasOwnProperty(developedExtension.id)) { + log.warn(developedExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[developedExtension.id].extensionLocation.fsPath, developedExtension.extensionLocation.fsPath)); + } + result[developedExtension.id] = developedExtension; + }); + return Object.keys(result).map(name => result[name]); + }); + } - user.forEach((userExtension) => { - if (result.hasOwnProperty(userExtension.id)) { - log.warn(userExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[userExtension.id].extensionLocation.fsPath, userExtension.extensionLocation.fsPath)); - } - if (disabledExtensions.every(disabled => !areSameExtensions(disabled, userExtension))) { - // Check if the extension is changed to system extension - let userMigratedSystemExtension = userMigratedSystemExtensions.filter(userMigratedSystemExtension => areSameExtensions(userMigratedSystemExtension, { id: userExtension.id }))[0]; - if (userMigratedSystemExtension) { - extensionsToDisable.push(userMigratedSystemExtension); - } else { - result[userExtension.id] = userExtension; - } - } - }); + private _getRuntimeExtensions(allExtensions: IExtensionDescription[]): TPromise { + return this._extensionEnablementService.getDisabledExtensions() + .then(disabledExtensions => { - development.forEach(developedExtension => { - log.info('', nls.localize('extensionUnderDevelopment', "Loading development extension at {0}", developedExtension.extensionLocation.fsPath)); - if (result.hasOwnProperty(developedExtension.id)) { - log.warn(developedExtension.extensionLocation.fsPath, nls.localize('overwritingExtension', "Overwriting extension {0} with {1}.", result[developedExtension.id].extensionLocation.fsPath, developedExtension.extensionLocation.fsPath)); - } - // Do not disable extensions under development - result[developedExtension.id] = developedExtension; - }); + const result: { [extensionId: string]: IExtensionDescription; } = {}; + const extensionsToDisable: IExtensionIdentifier[] = []; + const userMigratedSystemExtensions: IExtensionIdentifier[] = [{ id: BetterMergeId }]; - const runtimeExtensions = Object.keys(result).map(name => result[name]); + const enableProposedApiForAll = !this._environmentService.isBuilt || (!!this._environmentService.extensionDevelopmentPath && product.nameLong.indexOf('Insiders') >= 0); + const enableProposedApiFor: string | string[] = this._environmentService.args['enable-proposed-api'] || []; - this._telemetryService.publicLog('extensionsScanned', { - totalCount: runtimeExtensions.length, - disabledCount: disabledExtensions.length - }); + for (const extension of allExtensions) { + const isExtensionUnderDevelopment = this._environmentService.isExtensionDevelopment && extension.extensionLocation.scheme === Schemas.file && extension.extensionLocation.fsPath.indexOf(this._environmentService.extensionDevelopmentPath) === 0; + // Do not disable extensions under development + if (!isExtensionUnderDevelopment) { + if (disabledExtensions.some(disabled => areSameExtensions(disabled, extension))) { + continue; + } + } - if (extensionsToDisable.length) { - return this.extensionManagementService.getInstalled(LocalExtensionType.User) - .then(installed => { - const toDisable = installed.filter(i => extensionsToDisable.some(e => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i) }, e))); - return TPromise.join(toDisable.map(e => this._extensionEnablementService.setEnablement(e, EnablementState.Disabled))); - }) - .then(() => { - this._storageService.store(BetterMergeDisabledNowKey, true); - return runtimeExtensions; - }); - } else { - return runtimeExtensions; + if (!extension.isBuiltin) { + // Check if the extension is changed to system extension + const userMigratedSystemExtension = userMigratedSystemExtensions.filter(userMigratedSystemExtension => areSameExtensions(userMigratedSystemExtension, { id: extension.id }))[0]; + if (userMigratedSystemExtension) { + extensionsToDisable.push(userMigratedSystemExtension); + continue; } - }); - }).then(extensions => this._updateEnableProposedApi(extensions)); - } + } + result[extension.id] = this._updateEnableProposedApi(extension, enableProposedApiForAll, enableProposedApiFor); + } + const runtimeExtensions = Object.keys(result).map(name => result[name]); - private _updateEnableProposedApi(extensions: IExtensionDescription[]): IExtensionDescription[] { - const enableProposedApiForAll = !this._environmentService.isBuilt || (!!this._environmentService.extensionDevelopmentPath && product.nameLong.indexOf('Insiders') >= 0); - const enableProposedApiFor = this._environmentService.args['enable-proposed-api'] || []; - for (const extension of extensions) { - if (!isFalsyOrEmpty(product.extensionAllowedProposedApi) - && product.extensionAllowedProposedApi.indexOf(extension.id) >= 0 - ) { - // fast lane -> proposed api is available to all extensions - // that are listed in product.json-files - extension.enableProposedApi = true; - - } else if (extension.enableProposedApi && !extension.isBuiltin) { - if ( - !enableProposedApiForAll && - enableProposedApiFor.indexOf(extension.id) < 0 - ) { - extension.enableProposedApi = false; - console.error(`Extension '${extension.id} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`); + this._telemetryService.publicLog('extensionsScanned', { + totalCount: runtimeExtensions.length, + disabledCount: disabledExtensions.length + }); + if (extensionsToDisable.length) { + return this.extensionManagementService.getInstalled(LocalExtensionType.User) + .then(installed => { + const toDisable = installed.filter(i => extensionsToDisable.some(e => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i) }, e))); + return TPromise.join(toDisable.map(e => this._extensionEnablementService.setEnablement(e, EnablementState.Disabled))); + }) + .then(() => { + this._storageService.store(BetterMergeDisabledNowKey, true); + return runtimeExtensions; + }); } else { - // proposed api is available when developing or when an extension was explicitly - // spelled out via a command line argument - console.warn(`Extension '${extension.id}' uses PROPOSED API which is subject to change and removal without notice.`); + return runtimeExtensions; } + }); + } + + private _updateEnableProposedApi(extension: IExtensionDescription, enableProposedApiForAll: boolean, enableProposedApiFor: string | string[]): IExtensionDescription { + if (!isFalsyOrEmpty(product.extensionAllowedProposedApi) + && product.extensionAllowedProposedApi.indexOf(extension.id) >= 0 + ) { + // fast lane -> proposed api is available to all extensions + // that are listed in product.json-files + extension.enableProposedApi = true; + + } else if (extension.enableProposedApi && !extension.isBuiltin) { + if ( + !enableProposedApiForAll && + enableProposedApiFor.indexOf(extension.id) < 0 + ) { + extension.enableProposedApi = false; + console.error(`Extension '${extension.id} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`); + + } else { + // proposed api is available when developing or when an extension was explicitly + // spelled out via a command line argument + console.warn(`Extension '${extension.id}' uses PROPOSED API which is subject to change and removal without notice.`); } } - return extensions; + return extension; } private _handleExtensionPointMessage(msg: IMessage) { diff --git a/src/vs/workbench/services/extensions/node/rpcProtocol.ts b/src/vs/workbench/services/extensions/node/rpcProtocol.ts index 6c38a0bbe90c6..6830dfa03c8d8 100644 --- a/src/vs/workbench/services/extensions/node/rpcProtocol.ts +++ b/src/vs/workbench/services/extensions/node/rpcProtocol.ts @@ -41,7 +41,7 @@ function _transformOutgoingURIs(obj: any, transformer: IURITransformer, depth: n return null; } -function transformOutgoingURIs(obj: any, transformer: IURITransformer): any { +export function transformOutgoingURIs(obj: any, transformer: IURITransformer): any { const result = _transformOutgoingURIs(obj, transformer, 0); if (result === null) { // no change diff --git a/src/vs/workbench/services/files/electron-browser/fileService.ts b/src/vs/workbench/services/files/electron-browser/fileService.ts index d8501eb66b0ae..75a2c671e5249 100644 --- a/src/vs/workbench/services/files/electron-browser/fileService.ts +++ b/src/vs/workbench/services/files/electron-browser/fileService.ts @@ -273,6 +273,7 @@ export class FileService implements IFileService { mtime: streamContent.mtime, etag: streamContent.etag, encoding: streamContent.encoding, + isReadonly: streamContent.isReadonly, value: '' }; @@ -302,6 +303,7 @@ export class FileService implements IFileService { mtime: void 0, etag: void 0, encoding: void 0, + isReadonly: false, value: void 0 }; @@ -929,16 +931,15 @@ export class FileService implements IFileService { private doMoveItemToTrash(resource: uri): TPromise { const absolutePath = resource.fsPath; - return asWinJSImport(import('electron')).then(electron => { - const result = electron.shell.moveItemToTrash(absolutePath); - if (!result) { - return TPromise.wrapError(new Error(isWindows ? nls.localize('binFailed', "Failed to move '{0}' to the recycle bin", paths.basename(absolutePath)) : nls.localize('trashFailed', "Failed to move '{0}' to the trash", paths.basename(absolutePath)))); - } + const shell = (require('electron') as Electron.RendererInterface).shell; // workaround for being able to run tests out of VSCode debugger + const result = shell.moveItemToTrash(absolutePath); + if (!result) { + return TPromise.wrapError(new Error(isWindows ? nls.localize('binFailed', "Failed to move '{0}' to the recycle bin", paths.basename(absolutePath)) : nls.localize('trashFailed', "Failed to move '{0}' to the trash", paths.basename(absolutePath)))); + } - this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE)); + this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE)); - return void 0; - }); + return TPromise.as(void 0); } private doDelete(resource: uri): TPromise { @@ -1129,6 +1130,7 @@ export class StatResolver { resource: this.resource, isDirectory: this.isDirectory, isSymbolicLink: this.isSymbolicLink, + isReadonly: false, name: this.name, etag: this.etag, size: this.size, @@ -1213,6 +1215,7 @@ export class StatResolver { resource: fileResource, isDirectory: fileStat.isDirectory(), isSymbolicLink, + isReadonly: false, name: file, mtime: fileStat.mtime.getTime(), etag: etag(fileStat), diff --git a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts index 0dca336c1a788..610f6c09692e3 100644 --- a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts +++ b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts @@ -45,6 +45,7 @@ function toIFileStat(provider: IFileSystemProvider, tuple: [URI, IStat], recurse name: posix.basename(resource.path), isDirectory: (stat.type & FileType.Directory) !== 0, isSymbolicLink: (stat.type & FileType.SymbolicLink) !== 0, + isReadonly: !!(provider.capabilities & FileSystemProviderCapabilities.Readonly), mtime: stat.mtime, size: stat.size, etag: stat.mtime.toString(29) + stat.size.toString(31), @@ -411,6 +412,7 @@ export class RemoteFileService extends FileService { name: fileStat.name, etag: fileStat.etag, mtime: fileStat.mtime, + isReadonly: fileStat.isReadonly }; }); }); @@ -498,7 +500,8 @@ export class RemoteFileService extends FileService { etag: content.etag, mtime: content.mtime, name: content.name, - resource: content.resource + resource: content.resource, + isReadonly: content.isReadonly }; content.value.on('data', chunk => result.value += chunk); content.value.on('error', reject); diff --git a/src/vs/workbench/services/jsonschemas/common/jsonValidationExtensionPoint.ts b/src/vs/workbench/services/jsonschemas/common/jsonValidationExtensionPoint.ts index 38e859052baa0..d5ab87d54f749 100644 --- a/src/vs/workbench/services/jsonschemas/common/jsonValidationExtensionPoint.ts +++ b/src/vs/workbench/services/jsonschemas/common/jsonValidationExtensionPoint.ts @@ -6,9 +6,8 @@ import * as nls from 'vs/nls'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import URI from 'vs/base/common/uri'; import * as strings from 'vs/base/common/strings'; -import * as paths from 'vs/base/common/paths'; +import * as resources from 'vs/base/common/resources'; interface IJSONValidationExtensionPoint { fileMatch: string; @@ -60,8 +59,10 @@ export class JSONValidationExtensionPoint { } if (strings.startsWith(uri, './')) { try { - //TODO@extensionLocation - uri = URI.file(paths.normalize(paths.join(extensionLocation.fsPath, uri))).toString(); + const colorThemeLocation = resources.joinPath(extensionLocation, uri); + if (colorThemeLocation.path.indexOf(extensionLocation.path) !== 0) { + collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.url` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", configurationExtPoint.name, location, extensionLocation.path)); + } } catch (e) { collector.error(nls.localize('invalid.url.fileschema', "'configuration.jsonValidation.url' is an invalid relative URL: {0}", e.message)); } diff --git a/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts b/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts index b45a12e0ab0bf..b081a9e7e6dbb 100644 --- a/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts +++ b/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts @@ -14,7 +14,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ITokenizationSupport, TokenizationRegistry, IState, LanguageId, TokenMetadata } from 'vs/editor/common/modes'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { StackElement, IGrammar, Registry, IEmbeddedLanguagesMap as IEmbeddedLanguagesMap2, ITokenTypeMap, StandardTokenType, parseRawGrammar } from 'vscode-textmate'; +import { StackElement, IGrammar, Registry, IEmbeddedLanguagesMap as IEmbeddedLanguagesMap2, ITokenTypeMap, StandardTokenType } from 'vscode-textmate'; import { IWorkbenchThemeService, ITokenColorizationRule } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITextMateService } from 'vs/workbench/services/textMate/electron-browser/textMateService'; import { grammarsExtPoint, IEmbeddedLanguagesMap, ITMSyntaxExtensionPoint, TokenTypesContribution } from 'vs/workbench/services/textMate/electron-browser/TMGrammars'; @@ -207,7 +207,7 @@ export class TextMateService implements ITextMateService { private _getOrCreateGrammarRegistry(): TPromise<[Registry, StackElement]> { if (!this._grammarRegistry) { - this._grammarRegistry = TPromise.wrap(import('vscode-textmate')).then(({ Registry, INITIAL }) => { + this._grammarRegistry = TPromise.wrap(import('vscode-textmate')).then(({ Registry, INITIAL, parseRawGrammar }) => { const grammarRegistry = new Registry({ loadGrammar: (scopeName: string) => { const location = this._scopeRegistry.getGrammarLocation(scopeName); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index a625dbdba080f..5d4c949604676 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -310,7 +310,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil mtime: Date.now(), etag: void 0, value: createTextBufferFactory(''), /* will be filled later from backup */ - encoding: this.fileService.encoding.getWriteEncoding(this.resource, this.preferredEncoding) + encoding: this.fileService.encoding.getWriteEncoding(this.resource, this.preferredEncoding), + isReadonly: false }; return this.loadWithContent(content, backup); @@ -406,7 +407,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil etag: content.etag, isDirectory: false, isSymbolicLink: false, - children: void 0 + children: void 0, + isReadonly: content.isReadonly }; this.updateLastResolvedDiskStat(resolvedStat); @@ -698,7 +700,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // be disposed if we are dirty, but if we are not dirty, save() and dispose() can still be triggered // one after the other without waiting for the save() to complete. If we are disposed(), we risk // saving contents to disk that are stale (see https://github.com/Microsoft/vscode/issues/50942). - // To fix this issue, we will not store the contents to disk when we got disposed. + // To fix this issue, we will not store the contents to disk when we got disposed. if (this.disposed) { return void 0; } @@ -983,6 +985,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return !types.isUndefinedOrNull(this.lastResolvedDiskStat); } + public isReadonly(): boolean { + return this.lastResolvedDiskStat && this.lastResolvedDiskStat.isReadonly; + } + /** * Returns true if the dispose() method of this model has been called. */ diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index 3e1b6a3f3a36e..0e4d700b9d0e5 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -14,7 +14,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import * as platform from 'vs/base/common/platform'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext } from 'vs/workbench/services/textfile/common/textfiles'; +import { IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, IWillMoveEvent } from 'vs/workbench/services/textfile/common/textfiles'; import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor'; import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -48,6 +48,9 @@ export abstract class TextFileService implements ITextFileService { public _serviceBrand: any; + private readonly _onWillMove = new Emitter(); + readonly onWillMove: Event = this._onWillMove.event; + private toUnbind: IDisposable[]; private _models: TextFileEditorModelManager; @@ -710,60 +713,75 @@ export abstract class TextFileService implements ITextFileService { public move(source: URI, target: URI, overwrite?: boolean): TPromise { - // Handle target models if existing (if target URI is a folder, this can be multiple) - let handleTargetModelPromise: TPromise = TPromise.as(void 0); - const dirtyTargetModels = this.getDirtyFileModels().filter(model => isEqualOrParent(model.getResource(), target, !platform.isLinux /* ignorecase */)); - if (dirtyTargetModels.length) { - handleTargetModelPromise = this.revertAll(dirtyTargetModels.map(targetModel => targetModel.getResource()), { soft: true }); - } + const waitForPromises: TPromise[] = []; + this._onWillMove.fire({ + oldResource: source, + newResource: target, + waitUntil(p: TPromise) { + waitForPromises.push(TPromise.wrap(p).then(undefined, errors.onUnexpectedError)); + } + }); - return handleTargetModelPromise.then(() => { + // prevent async waitUntil-calls + Object.freeze(waitForPromises); - // Handle dirty source models if existing (if source URI is a folder, this can be multiple) - let handleDirtySourceModels: TPromise; - const dirtySourceModels = this.getDirtyFileModels().filter(model => isEqualOrParent(model.getResource(), source, !platform.isLinux /* ignorecase */)); - const dirtyTargetModels: URI[] = []; - if (dirtySourceModels.length) { - handleDirtySourceModels = TPromise.join(dirtySourceModels.map(sourceModel => { - const sourceModelResource = sourceModel.getResource(); - let targetModelResource: URI; + return TPromise.join(waitForPromises).then(() => { - // If the source is the actual model, just use target as new resource - if (isEqual(sourceModelResource, source, !platform.isLinux /* ignorecase */)) { - targetModelResource = target; - } + // Handle target models if existing (if target URI is a folder, this can be multiple) + let handleTargetModelPromise: TPromise = TPromise.as(void 0); + const dirtyTargetModels = this.getDirtyFileModels().filter(model => isEqualOrParent(model.getResource(), target, !platform.isLinux /* ignorecase */)); + if (dirtyTargetModels.length) { + handleTargetModelPromise = this.revertAll(dirtyTargetModels.map(targetModel => targetModel.getResource()), { soft: true }); + } - // Otherwise a parent folder of the source is being moved, so we need - // to compute the target resource based on that - else { - targetModelResource = sourceModelResource.with({ path: paths.join(target.path, sourceModelResource.path.substr(source.path.length + 1)) }); - } + return handleTargetModelPromise.then(() => { - // Remember as dirty target model to load after the operation - dirtyTargetModels.push(targetModelResource); + // Handle dirty source models if existing (if source URI is a folder, this can be multiple) + let handleDirtySourceModels: TPromise; + const dirtySourceModels = this.getDirtyFileModels().filter(model => isEqualOrParent(model.getResource(), source, !platform.isLinux /* ignorecase */)); + const dirtyTargetModels: URI[] = []; + if (dirtySourceModels.length) { + handleDirtySourceModels = TPromise.join(dirtySourceModels.map(sourceModel => { + const sourceModelResource = sourceModel.getResource(); + let targetModelResource: URI; - // Backup dirty source model to the target resource it will become later - return this.backupFileService.backupResource(targetModelResource, sourceModel.createSnapshot(), sourceModel.getVersionId()); - })); - } else { - handleDirtySourceModels = TPromise.as(void 0); - } + // If the source is the actual model, just use target as new resource + if (isEqual(sourceModelResource, source, !platform.isLinux /* ignorecase */)) { + targetModelResource = target; + } + + // Otherwise a parent folder of the source is being moved, so we need + // to compute the target resource based on that + else { + targetModelResource = sourceModelResource.with({ path: paths.join(target.path, sourceModelResource.path.substr(source.path.length + 1)) }); + } - return handleDirtySourceModels.then(() => { + // Remember as dirty target model to load after the operation + dirtyTargetModels.push(targetModelResource); - // Soft revert the dirty source files if any - return this.revertAll(dirtySourceModels.map(dirtySourceModel => dirtySourceModel.getResource()), { soft: true }).then(() => { + // Backup dirty source model to the target resource it will become later + return this.backupFileService.backupResource(targetModelResource, sourceModel.createSnapshot(), sourceModel.getVersionId()); + })); + } else { + handleDirtySourceModels = TPromise.as(void 0); + } + + return handleDirtySourceModels.then(() => { - // Rename to target - return this.fileService.moveFile(source, target, overwrite).then(() => { + // Soft revert the dirty source files if any + return this.revertAll(dirtySourceModels.map(dirtySourceModel => dirtySourceModel.getResource()), { soft: true }).then(() => { - // Load models that were dirty before - return TPromise.join(dirtyTargetModels.map(dirtyTargetModel => this.models.loadOrCreate(dirtyTargetModel))).then(() => void 0); - }, error => { + // Rename to target + return this.fileService.moveFile(source, target, overwrite).then(() => { - // In case of an error, discard any dirty target backups that were made - return TPromise.join(dirtyTargetModels.map(dirtyTargetModel => this.backupFileService.discardResourceBackup(dirtyTargetModel))) - .then(() => TPromise.wrapError(error)); + // Load models that were dirty before + return TPromise.join(dirtyTargetModels.map(dirtyTargetModel => this.models.loadOrCreate(dirtyTargetModel))).then(() => void 0); + }, error => { + + // In case of an error, discard any dirty target backups that were made + return TPromise.join(dirtyTargetModels.map(dirtyTargetModel => this.backupFileService.discardResourceBackup(dirtyTargetModel))) + .then(() => TPromise.wrapError(error)); + }); }); }); }); diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index c5c6f3164dd82..724acf9bda7f4 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -232,15 +232,26 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport isResolved(): boolean; + isReadonly(): boolean; + isDisposed(): boolean; } + +export interface IWillMoveEvent { + oldResource: URI; + newResource: URI; + waitUntil(p: TPromise): void; +} + export interface ITextFileService extends IDisposable { _serviceBrand: any; readonly onAutoSaveConfigurationChange: Event; readonly onFilesAssociationChange: Event; + onWillMove: Event; + readonly isHotExitEnabled: boolean; /** diff --git a/src/vs/workbench/services/textfile/electron-browser/textFileService.ts b/src/vs/workbench/services/textfile/electron-browser/textFileService.ts index fcb157c88074e..830c9d7404223 100644 --- a/src/vs/workbench/services/textfile/electron-browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/textFileService.ts @@ -63,6 +63,7 @@ export class TextFileService extends AbstractTextFileService { mtime: streamContent.mtime, etag: streamContent.etag, encoding: streamContent.encoding, + isReadonly: streamContent.isReadonly, value: res }; return r; diff --git a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts index 5e8f0fb6ca9a8..b9c500f1c5ac4 100644 --- a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts @@ -202,8 +202,9 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { // TODO@Ben TODO@Sandeep the following requires ugly casts and should probably have a service interface // Reinitialize backup service - const backupFileService = this.backupFileService as BackupFileService; - backupFileService.initialize(result.backupPath); + if (this.backupFileService instanceof BackupFileService) { + this.backupFileService.initialize(result.backupPath); + } // Reinitialize configuration service const workspaceImpl = this.contextService as WorkspaceService; diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index 201206f9c0dd2..f5479d67f9eda 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -841,8 +841,10 @@ suite('ExtHostLanguageFeatures', function () { return rpcProtocol.sync().then(() => { return rename(model, new EditorPosition(1, 1), 'newName').then(value => { - assert.equal(value.edits.length, 1); // least relevant renamer - assert.equal((value.edits)[0].edits.length, 2); // least relevant renamer + // least relevant rename provider + assert.equal(value.edits.length, 2); + assert.equal((value.edits[0]).edits.length, 1); + assert.equal((value.edits[1]).edits.length, 1); }); }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts index 316a1e3a3ccdf..d14a9a0469ec2 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts @@ -368,60 +368,61 @@ suite('ExtHostTypes', function () { ]); edit.set(b, undefined); - assert.ok(edit.has(b)); - assert.equal(edit.size, 2); + assert.ok(!edit.has(b)); + assert.equal(edit.size, 1); edit.set(b, [types.TextEdit.insert(new types.Position(0, 0), 'ffff')]); assert.equal(edit.get(b).length, 1); + }); + + test('WorkspaceEdit - keep order of text and file changes', function () { + + const edit = new types.WorkspaceEdit(); + edit.replace(URI.parse('foo:a'), new types.Range(1, 1, 1, 1), 'foo'); + edit.renameFile(URI.parse('foo:a'), URI.parse('foo:b')); + edit.replace(URI.parse('foo:a'), new types.Range(2, 1, 2, 1), 'bar'); + edit.replace(URI.parse('foo:b'), new types.Range(3, 1, 3, 1), 'bazz'); + + const all = edit._allEntries(); + assert.equal(all.length, 4); + function isFileChange(thing: [URI, types.TextEdit[]] | [URI, URI, { overwrite?: boolean }]): thing is [URI, URI, { overwrite?: boolean }] { + const [f, s] = thing; + return URI.isUri(f) && URI.isUri(s); + } + + function isTextChange(thing: [URI, types.TextEdit[]] | [URI, URI, { overwrite?: boolean }]): thing is [URI, types.TextEdit[]] { + const [f, s] = thing; + return URI.isUri(f) && Array.isArray(s); + } + + const [first, second, third, fourth] = all; + assert.equal(first[0].toString(), 'foo:a'); + assert.ok(!isFileChange(first)); + assert.ok(isTextChange(first) && first[1].length === 1); + + assert.equal(second[0].toString(), 'foo:a'); + assert.ok(isFileChange(second)); + + assert.equal(third[0].toString(), 'foo:a'); + assert.ok(isTextChange(third) && third[1].length === 1); + + assert.equal(fourth[0].toString(), 'foo:b'); + assert.ok(!isFileChange(fourth)); + assert.ok(isTextChange(fourth) && fourth[1].length === 1); }); - // test('WorkspaceEdit should fail when editing deleted resource', () => { - // const resource = URI.parse('file:///a.ts'); - - // const edit = new types.WorkspaceEdit(); - // edit.deleteResource(resource); - // try { - // edit.insert(resource, new types.Position(0, 0), ''); - // assert.fail(false, 'Should disallow edit of deleted resource'); - // } catch { - // // expected - // } - // }); - - // test('WorkspaceEdit - keep order of text and file changes', function () { - - // const edit = new types.WorkspaceEdit(); - // edit.replace(URI.parse('foo:a'), new types.Range(1, 1, 1, 1), 'foo'); - // edit.renameResource(URI.parse('foo:a'), URI.parse('foo:b')); - // edit.replace(URI.parse('foo:a'), new types.Range(2, 1, 2, 1), 'bar'); - // edit.replace(URI.parse('foo:b'), new types.Range(3, 1, 3, 1), 'bazz'); - - // const all = edit.allEntries(); - // assert.equal(all.length, 3); - - // function isFileChange(thing: [URI, types.TextEdit[]] | [URI, URI]): thing is [URI, URI] { - // const [f, s] = thing; - // return URI.isUri(f) && URI.isUri(s); - // } - - // function isTextChange(thing: [URI, types.TextEdit[]] | [URI, URI]): thing is [URI, types.TextEdit[]] { - // const [f, s] = thing; - // return URI.isUri(f) && Array.isArray(s); - // } - - // const [first, second, third] = all; - // assert.equal(first[0].toString(), 'foo:a'); - // assert.ok(!isFileChange(first)); - // assert.ok(isTextChange(first) && first[1].length === 2); - - // assert.equal(second[0].toString(), 'foo:a'); - // assert.ok(isFileChange(second)); - - // assert.equal(third[0].toString(), 'foo:b'); - // assert.ok(!isFileChange(third)); - // assert.ok(isTextChange(third) && third[1].length === 1); - // }); + test('WorkspaceEdit - two edits for one resource', function () { + let edit = new types.WorkspaceEdit(); + let uri = URI.parse('foo:bar'); + edit.insert(uri, new types.Position(0, 0), 'Hello'); + edit.insert(uri, new types.Position(0, 0), 'Foo'); + + assert.equal(edit._allEntries().length, 2); + let [first, second] = edit._allEntries(); + assert.equal((first as [URI, types.TextEdit[]])[1][0].newText, 'Hello'); + assert.equal((second as [URI, types.TextEdit[]])[1][0].newText, 'Foo'); + }); test('DocumentLink', function () { assert.throws(() => new types.DocumentLink(null, null)); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts index 0150026110ff9..9e79075d377e7 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts @@ -75,7 +75,7 @@ suite('MainThreadEditors', () => { const workbenchEditorService = new TestEditorService(); const editorGroupService = new TestEditorGroupsService(); - const bulkEditService = new BulkEditService(NullLogService as any, modelService, new TestEditorService(), null, fileService, textFileService, TestEnvironmentService, new TestContextService()); + const bulkEditService = new BulkEditService(new NullLogService(), modelService, new TestEditorService(), null, fileService, textFileService, TestEnvironmentService, new TestContextService()); const rpcProtocol = new TestRPCProtocol(); rpcProtocol.set(ExtHostContext.ExtHostDocuments, new class extends mock() { @@ -135,9 +135,9 @@ suite('MainThreadEditors', () => { test(`applyWorkspaceEdit with only resource edit`, () => { return editors.$tryApplyWorkspaceEdit({ edits: [ - { oldUri: resource, newUri: resource }, - { oldUri: undefined, newUri: resource }, - { oldUri: resource, newUri: undefined } + { oldUri: resource, newUri: resource, options: undefined }, + { oldUri: undefined, newUri: resource, options: undefined }, + { oldUri: resource, newUri: undefined, options: undefined } ] }).then((result) => { assert.equal(result, true); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 461dcf4daf815..7348c180e156d 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -871,8 +871,6 @@ export class TestFileService implements IFileService { export class TestBackupFileService implements IBackupFileService { public _serviceBrand: any; - public backupEnabled: boolean; - public hasBackups(): TPromise { return TPromise.as(false); } diff --git a/yarn.lock b/yarn.lock index c564a7c1519ea..1a8735b5551fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6253,9 +6253,9 @@ vscode-textmate@^4.0.0-next.2: fast-plist "^0.1.2" oniguruma "^7.0.0" -vscode-xterm@3.5.0-beta12: - version "3.5.0-beta12" - resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.5.0-beta12.tgz#853b9bd42ac7bee2ef3deac08d9199e4b7d5dcdb" +vscode-xterm@3.5.0-beta15: + version "3.5.0-beta15" + resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.5.0-beta15.tgz#3098121b7f46048254e9e20905fea6cf792f6bd1" vso-node-api@^6.1.2-preview: version "6.1.2-preview"