diff --git a/.eslintignore b/.eslintignore index dda0884b38..f186c7ecd7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,7 +3,6 @@ **/vs/css.build.js **/vs/css.js **/vs/loader.js -**/promise-polyfill/** **/insane/** **/marked/** **/test/**/*.js diff --git a/.eslintrc.json b/.eslintrc.json index 13c995f300..30d84ee44b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -35,7 +35,8 @@ "external", "status", "origin", - "orientation" + "orientation", + "context" ], // non-complete list of globals that are easy to access unintentionally "no-var": "warn", "jsdoc/no-types": "warn", diff --git a/.vscode/launch.json b/.vscode/launch.json index e22fd1be1b..7b8ecd2604 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -92,6 +92,7 @@ ], "webRoot": "${workspaceFolder}", // Settings for js-debug: + "userDataDir": false, "pauseForSourceMap": false, "outFiles": [ "${workspaceFolder}/out/**/*.js" diff --git a/.vscode/searches/es6.code-search b/.vscode/searches/es6.code-search new file mode 100644 index 0000000000..09a841dc69 --- /dev/null +++ b/.vscode/searches/es6.code-search @@ -0,0 +1,74 @@ +# Query: @deprecated ES6 +# Flags: CaseSensitive WordMatch +# ContextLines: 2 + +11 results - 3 files + +src/vs/base/common/arrays.ts: + 401 + 402 /** + 403: * @deprecated ES6: use `Array.findIndex` + 404 */ + 405 export function firstIndex(array: ReadonlyArray, fn: (item: T) => boolean): number { + + 417 + 418 /** + 419: * @deprecated ES6: use `Array.find` + 420 */ + 421 export function first(array: ReadonlyArray, fn: (item: T) => boolean, notFoundValue: T): T; + + 474 + 475 /** + 476: * @deprecated ES6: use `Array.fill` + 477 */ + 478 export function fill(num: number, value: T, arr: T[] = []): T[] { + + 571 + 572 /** + 573: * @deprecated ES6: use `Array.find` + 574 */ + 575 export function find(arr: ArrayLike, predicate: (value: T, index: number, arr: ArrayLike) => any): T | undefined { + +src/vs/base/common/map.ts: + 9 + 10 /** + 11: * @deprecated ES6: use `[...SetOrMap.values()]` + 12 */ + 13 export function values(set: Set): V[]; + + 20 + 21 /** + 22: * @deprecated ES6: use `[...map.keys()]` + 23 */ + 24 export function keys(map: Map): K[] { + + 58 + 59 /** + 60: * @deprecated ES6: use `...Map.entries()` + 61 */ + 62 export function mapToSerializable(map: Map): [string, string][] { + + 71 + 72 /** + 73: * @deprecated ES6: use `new Map([[key1, value1],[key2, value2]])` + 74 */ + 75 export function serializableToMap(serializable: [string, string][]): Map { + +src/vs/base/common/strings.ts: + 16 + 17 /** + 18: * @deprecated ES6: use `String.padStart` + 19 */ + 20 export function pad(n: number, l: number, char: string = '0'): string { + + 147 + 148 /** + 149: * @deprecated ES6: use `String.startsWith` + 150 */ + 151 export function startsWith(haystack: string, needle: string): boolean { + + 168 + 169 /** + 170: * @deprecated ES6: use `String.endsWith` + 171 */ + 172 export function endsWith(haystack: string, needle: string): boolean { diff --git a/.vscode/settings.json b/.vscode/settings.json index d56f75c41d..2a9a8093ce 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -69,5 +69,8 @@ "msjsdiag.debugger-for-chrome": "workspace" }, "gulp.autoDetect": "off", - "files.insertFinalNewline": true + "files.insertFinalNewline": true, + "[typescript]": { + "editor.defaultFormatter": "vscode.typescript-language-features" + } } diff --git a/build/actions/AutoLabel/dist/index.js b/build/actions/AutoLabel/dist/index.js index b403895c6e..dbfbbf7867 100644 --- a/build/actions/AutoLabel/dist/index.js +++ b/build/actions/AutoLabel/dist/index.js @@ -16758,7 +16758,7 @@ exports.Deprecation = Deprecation; * isobject * * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. + * Released under the Source EULA. */ function isObject(val) { @@ -16769,7 +16769,7 @@ function isObject(val) { * is-plain-object * * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. + * Released under the Source EULA. */ function isObjectObject(o) { @@ -31507,7 +31507,7 @@ exports.restEndpointMethods = restEndpointMethods; * write * * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. + * Released under the Source EULA. */ diff --git a/build/actions/AutoMerge/dist/index.js b/build/actions/AutoMerge/dist/index.js index a1edc9c9b4..1d4ab2818d 100644 --- a/build/actions/AutoMerge/dist/index.js +++ b/build/actions/AutoMerge/dist/index.js @@ -16813,7 +16813,7 @@ exports.Deprecation = Deprecation; * isobject * * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. + * Released under the Source EULA. */ function isObject(val) { @@ -16824,7 +16824,7 @@ function isObject(val) { * is-plain-object * * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. + * Released under the Source EULA. */ function isObjectObject(o) { @@ -31562,7 +31562,7 @@ exports.restEndpointMethods = restEndpointMethods; * write * * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. + * Released under the Source EULA. */ diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index cbfcbfc50e..7e4142b566 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -135,6 +135,23 @@ steps: displayName: Run integration tests (Browser) condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) +- script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-darwin + APP_NAME="`ls $APP_ROOT | head -n 1`" + yarn smoketest --build "$APP_ROOT/$APP_NAME" + continueOnError: true + displayName: Run smoke tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + +- script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin" \ + yarn smoketest --web --headless + continueOnError: true + displayName: Run smoke tests (Browser) + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: | set -e security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 91a067a13d..3ca8dbb731 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -72,13 +72,8 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => { apiusages, extrausages ], - libs: [ - `lib.es5.d.ts`, - `lib.dom.d.ts`, - `lib.webworker.importscripts.d.ts` - ], shakeLevel: 2, // 0-Files, 1-InnerFile, 2-ClassMembers - importIgnorePattern: /(^vs\/css!)|(promise-polyfill\/polyfill)/, + importIgnorePattern: /(^vs\/css!)/, destRoot: path.join(root, 'out-editor-src'), redirects: [] }); @@ -131,6 +126,7 @@ const createESMSourcesAndResourcesTask = task.define('extract-editor-esm', () => }); const compileEditorESMTask = task.define('compile-editor-esm', () => { + const KEEP_PREV_ANALYSIS = false; console.log(`Launching the TS compiler at ${path.join(__dirname, '../out-editor-esm')}...`); let result; if (process.platform === 'win32') { @@ -149,41 +145,45 @@ const compileEditorESMTask = task.define('compile-editor-esm', () => { if (result.status !== 0) { console.log(`The TS Compilation failed, preparing analysis folder...`); const destPath = path.join(__dirname, '../../vscode-monaco-editor-esm-analysis'); - return util.rimraf(destPath)().then(() => { - fs.mkdirSync(destPath); - - // initialize a new repository - cp.spawnSync(`git`, [`init`], { - cwd: destPath - }); - + const keepPrevAnalysis = (KEEP_PREV_ANALYSIS && fs.existsSync(destPath)); + const cleanDestPath = (keepPrevAnalysis ? Promise.resolve() : util.rimraf(destPath)()); + return cleanDestPath.then(() => { // build a list of files to copy const files = util.rreddir(path.join(__dirname, '../out-editor-esm')); - // copy files from src - for (const file of files) { - const srcFilePath = path.join(__dirname, '../src', file); - const dstFilePath = path.join(destPath, file); - if (fs.existsSync(srcFilePath)) { - util.ensureDir(path.dirname(dstFilePath)); - const contents = fs.readFileSync(srcFilePath).toString().replace(/\r\n|\r|\n/g, '\n'); - fs.writeFileSync(dstFilePath, contents); + if (!keepPrevAnalysis) { + fs.mkdirSync(destPath); + + // initialize a new repository + cp.spawnSync(`git`, [`init`], { + cwd: destPath + }); + + // copy files from src + for (const file of files) { + const srcFilePath = path.join(__dirname, '../src', file); + const dstFilePath = path.join(destPath, file); + if (fs.existsSync(srcFilePath)) { + util.ensureDir(path.dirname(dstFilePath)); + const contents = fs.readFileSync(srcFilePath).toString().replace(/\r\n|\r|\n/g, '\n'); + fs.writeFileSync(dstFilePath, contents); + } } + + // create an initial commit to diff against + cp.spawnSync(`git`, [`add`, `.`], { + cwd: destPath + }); + + // create the commit + cp.spawnSync(`git`, [`commit`, `-m`, `"original sources"`, `--no-gpg-sign`], { + cwd: destPath + }); } - // create an initial commit to diff against - cp.spawnSync(`git`, [`add`, `.`], { - cwd: destPath - }); - - // create the commit - cp.spawnSync(`git`, [`commit`, `-m`, `"original sources"`, `--no-gpg-sign`], { - cwd: destPath - }); - - // copy files from esm + // copy files from tree shaken src for (const file of files) { - const srcFilePath = path.join(__dirname, '../out-editor-esm', file); + const srcFilePath = path.join(__dirname, '../out-editor-src', file); const dstFilePath = path.join(destPath, file); if (fs.existsSync(srcFilePath)) { util.ensureDir(path.dirname(dstFilePath)); @@ -334,6 +334,13 @@ const finalEditorResourcesTask = task.define('final-editor-resources', () => { ); }); +gulp.task('extract-editor-src', + task.series( + util.rimraf('out-editor-src'), + extractEditorSrcTask + ) +); + gulp.task('editor-distro', task.series( task.parallel( diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index b3f04acfab..95bcc0c57a 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -128,7 +128,6 @@ const copyrightFilter = [ '!**/*.disabled', '!**/*.code-workspace', '!**/*.js.map', - '!**/promise-polyfill/polyfill.js', '!build/**/*.init', '!resources/linux/snap/snapcraft.yaml', '!resources/linux/snap/electron-launch', diff --git a/build/lib/asar.js b/build/lib/asar.js index f0cee5d4ee..9ede8d48de 100644 --- a/build/lib/asar.js +++ b/build/lib/asar.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.createAsar = void 0; const path = require("path"); const es = require("event-stream"); const pickle = require('chromium-pickle-js'); diff --git a/build/lib/bundle.js b/build/lib/bundle.js index 14d65cd720..ac89e43f5d 100644 --- a/build/lib/bundle.js +++ b/build/lib/bundle.js @@ -4,6 +4,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.bundle = void 0; const fs = require("fs"); const path = require("path"); const vm = require("vm"); diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 903de37a70..7a36b15a2e 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.watchTask = exports.compileTask = void 0; const es = require("event-stream"); const fs = require("fs"); const gulp = require("gulp"); diff --git a/build/lib/electron.js b/build/lib/electron.js index 58dc53a64d..e9778b7a9c 100644 --- a/build/lib/electron.js +++ b/build/lib/electron.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.config = exports.getElectronVersion = void 0; const fs = require("fs"); const path = require("path"); const vfs = require("vinyl-fs"); diff --git a/build/lib/eslint/utils.js b/build/lib/eslint/utils.js index 2a3d952897..c9af82acc4 100644 --- a/build/lib/eslint/utils.js +++ b/build/lib/eslint/utils.js @@ -4,6 +4,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.createImportRuleListener = void 0; function createImportRuleListener(validateImport) { function _checkImport(node) { if (node && node.type === 'Literal' && typeof node.value === 'string') { diff --git a/build/lib/extensions.js b/build/lib/extensions.js index 455007e1b4..b33ce32de8 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -4,6 +4,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.packageRebuildExtensionsStream = exports.cleanRebuildExtensions = exports.packageExternalExtensionsStream = exports.packageMarketplaceExtensionsStream = exports.packageLocalExtensionsStream = exports.fromMarketplace = void 0; const es = require("event-stream"); const fs = require("fs"); const glob = require("glob"); diff --git a/build/lib/git.js b/build/lib/git.js index 212d21f790..b3624674fa 100644 --- a/build/lib/git.js +++ b/build/lib/git.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.getVersion = void 0; const path = require("path"); const fs = require("fs"); /** diff --git a/build/lib/i18n.js b/build/lib/i18n.js index 4ee4e9c525..d214a27fc8 100644 --- a/build/lib/i18n.js +++ b/build/lib/i18n.js @@ -4,6 +4,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.prepareIslFiles = exports.prepareI18nPackFiles = exports.pullI18nPackFiles = exports.prepareI18nFiles = exports.pullSetupXlfFiles = exports.pullCoreAndExtensionsXlfFiles = exports.findObsoleteResources = exports.pushXlfFiles = exports.createXlfFilesForIsl = exports.createXlfFilesForExtensions = exports.createXlfFilesForCoreBundle = exports.getResource = exports.processNlsFiles = exports.Limiter = exports.XLF = exports.Line = exports.externalExtensionsWithTranslations = exports.extraLanguages = exports.defaultLanguages = void 0; const path = require("path"); const fs = require("fs"); const event_stream_1 = require("event-stream"); @@ -100,155 +101,161 @@ class TextModel { return this._lines; } } -class XLF { - constructor(project) { - this.project = project; - this.buffer = []; - this.files = Object.create(null); - this.numberOfMessages = 0; - } - toString() { - this.appendHeader(); - for (let file in this.files) { - this.appendNewLine(``, 2); - for (let item of this.files[file]) { - this.addStringItem(item); - } - this.appendNewLine('', 2); +let XLF = /** @class */ (() => { + class XLF { + constructor(project) { + this.project = project; + this.buffer = []; + this.files = Object.create(null); + this.numberOfMessages = 0; } - this.appendFooter(); - return this.buffer.join('\r\n'); - } - addFile(original, keys, messages) { - if (keys.length === 0) { - console.log('No keys in ' + original); - return; - } - if (keys.length !== messages.length) { - throw new Error(`Unmatching keys(${keys.length}) and messages(${messages.length}).`); - } - this.numberOfMessages += keys.length; - this.files[original] = []; - let existingKeys = new Set(); - for (let i = 0; i < keys.length; i++) { - let key = keys[i]; - let realKey; - let comment; - if (Is.string(key)) { - realKey = key; - comment = undefined; - } - else if (LocalizeInfo.is(key)) { - realKey = key.key; - if (key.comment && key.comment.length > 0) { - comment = key.comment.map(comment => encodeEntities(comment)).join('\r\n'); + toString() { + this.appendHeader(); + for (let file in this.files) { + this.appendNewLine(``, 2); + for (let item of this.files[file]) { + this.addStringItem(file, item); } + this.appendNewLine('', 2); } - if (!realKey || existingKeys.has(realKey)) { - continue; + this.appendFooter(); + return this.buffer.join('\r\n'); + } + addFile(original, keys, messages) { + if (keys.length === 0) { + console.log('No keys in ' + original); + return; } - existingKeys.add(realKey); - let message = encodeEntities(messages[i]); - this.files[original].push({ id: realKey, message: message, comment: comment }); + if (keys.length !== messages.length) { + throw new Error(`Unmatching keys(${keys.length}) and messages(${messages.length}).`); + } + this.numberOfMessages += keys.length; + this.files[original] = []; + let existingKeys = new Set(); + for (let i = 0; i < keys.length; i++) { + let key = keys[i]; + let realKey; + let comment; + if (Is.string(key)) { + realKey = key; + comment = undefined; + } + else if (LocalizeInfo.is(key)) { + realKey = key.key; + if (key.comment && key.comment.length > 0) { + comment = key.comment.map(comment => encodeEntities(comment)).join('\r\n'); + } + } + if (!realKey || existingKeys.has(realKey)) { + continue; + } + existingKeys.add(realKey); + let message = encodeEntities(messages[i]); + this.files[original].push({ id: realKey, message: message, comment: comment }); + } + } + addStringItem(file, item) { + if (!item.id || item.message === undefined || item.message === null) { + throw new Error(`No item ID or value specified: ${JSON.stringify(item)}. File: ${file}`); + } + if (item.message.length === 0) { + log(`Item with id ${item.id} in file ${file} has an empty message.`); + } + this.appendNewLine(``, 4); + this.appendNewLine(`${item.message}`, 6); + if (item.comment) { + this.appendNewLine(`${item.comment}`, 6); + } + this.appendNewLine('', 4); + } + appendHeader() { + this.appendNewLine('', 0); + this.appendNewLine('', 0); + } + appendFooter() { + this.appendNewLine('', 0); + } + appendNewLine(content, indent) { + let line = new Line(indent); + line.append(content); + this.buffer.push(line.toString()); } } - addStringItem(item) { - if (!item.id || !item.message) { - throw new Error(`No item ID or value specified: ${JSON.stringify(item)}`); - } - this.appendNewLine(``, 4); - this.appendNewLine(`${item.message}`, 6); - if (item.comment) { - this.appendNewLine(`${item.comment}`, 6); - } - this.appendNewLine('', 4); - } - appendHeader() { - this.appendNewLine('', 0); - this.appendNewLine('', 0); - } - appendFooter() { - this.appendNewLine('', 0); - } - appendNewLine(content, indent) { - let line = new Line(indent); - line.append(content); - this.buffer.push(line.toString()); - } -} + XLF.parsePseudo = function (xlfString) { + return new Promise((resolve) => { + let parser = new xml2js.Parser(); + let files = []; + parser.parseString(xlfString, function (_err, result) { + const fileNodes = result['xliff']['file']; + fileNodes.forEach(file => { + const originalFilePath = file.$.original; + const messages = {}; + const transUnits = file.body[0]['trans-unit']; + if (transUnits) { + transUnits.forEach((unit) => { + const key = unit.$.id; + const val = pseudify(unit.source[0]['_'].toString()); + if (key && val) { + messages[key] = decodeEntities(val); + } + }); + files.push({ messages: messages, originalFilePath: originalFilePath, language: 'ps' }); + } + }); + resolve(files); + }); + }); + }; + XLF.parse = function (xlfString) { + return new Promise((resolve, reject) => { + let parser = new xml2js.Parser(); + let files = []; + parser.parseString(xlfString, function (err, result) { + if (err) { + reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`)); + } + const fileNodes = result['xliff']['file']; + if (!fileNodes) { + reject(new Error(`XLF parsing error: XLIFF file does not contain "xliff" or "file" node(s) required for parsing.`)); + } + fileNodes.forEach((file) => { + const originalFilePath = file.$.original; + if (!originalFilePath) { + reject(new Error(`XLF parsing error: XLIFF file node does not contain original attribute to determine the original location of the resource file.`)); + } + let language = file.$['target-language']; + if (!language) { + reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`)); + } + const messages = {}; + const transUnits = file.body[0]['trans-unit']; + if (transUnits) { + transUnits.forEach((unit) => { + const key = unit.$.id; + if (!unit.target) { + return; // No translation available + } + let val = unit.target[0]; + if (typeof val !== 'string') { + val = val._; + } + if (key && val) { + messages[key] = decodeEntities(val); + } + else { + reject(new Error(`XLF parsing error: XLIFF file ${originalFilePath} does not contain full localization data. ID or target translation for one of the trans-unit nodes is not present.`)); + } + }); + files.push({ messages: messages, originalFilePath: originalFilePath, language: language.toLowerCase() }); + } + }); + resolve(files); + }); + }); + }; + return XLF; +})(); exports.XLF = XLF; -XLF.parsePseudo = function (xlfString) { - return new Promise((resolve) => { - let parser = new xml2js.Parser(); - let files = []; - parser.parseString(xlfString, function (_err, result) { - const fileNodes = result['xliff']['file']; - fileNodes.forEach(file => { - const originalFilePath = file.$.original; - const messages = {}; - const transUnits = file.body[0]['trans-unit']; - if (transUnits) { - transUnits.forEach((unit) => { - const key = unit.$.id; - const val = pseudify(unit.source[0]['_'].toString()); - if (key && val) { - messages[key] = decodeEntities(val); - } - }); - files.push({ messages: messages, originalFilePath: originalFilePath, language: 'ps' }); - } - }); - resolve(files); - }); - }); -}; -XLF.parse = function (xlfString) { - return new Promise((resolve, reject) => { - let parser = new xml2js.Parser(); - let files = []; - parser.parseString(xlfString, function (err, result) { - if (err) { - reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`)); - } - const fileNodes = result['xliff']['file']; - if (!fileNodes) { - reject(new Error(`XLF parsing error: XLIFF file does not contain "xliff" or "file" node(s) required for parsing.`)); - } - fileNodes.forEach((file) => { - const originalFilePath = file.$.original; - if (!originalFilePath) { - reject(new Error(`XLF parsing error: XLIFF file node does not contain original attribute to determine the original location of the resource file.`)); - } - let language = file.$['target-language']; - if (!language) { - reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`)); - } - const messages = {}; - const transUnits = file.body[0]['trans-unit']; - if (transUnits) { - transUnits.forEach((unit) => { - const key = unit.$.id; - if (!unit.target) { - return; // No translation available - } - let val = unit.target[0]; - if (typeof val !== 'string') { - val = val._; - } - if (key && val) { - messages[key] = decodeEntities(val); - } - else { - reject(new Error(`XLF parsing error: XLIFF file ${originalFilePath} does not contain full localization data. ID or target translation for one of the trans-unit nodes is not present.`)); - } - }); - files.push({ messages: messages, originalFilePath: originalFilePath, language: language.toLowerCase() }); - } - }); - resolve(files); - }); - }); -}; class Limiter { constructor(maxDegreeOfParalellism) { this.maxDegreeOfParalellism = maxDegreeOfParalellism; diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index ae71fd3bf6..3494753b30 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -122,6 +122,10 @@ "name": "vs/workbench/contrib/preferences", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/quickaccess", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/quickopen", "project": "vscode-workbench" @@ -262,6 +266,10 @@ "name": "vs/workbench/services/files", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/log", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/integrity", "project": "vscode-workbench" diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index 60997611ea..856719574d 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -201,7 +201,7 @@ export class XLF { for (let file in this.files) { this.appendNewLine(``, 2); for (let item of this.files[file]) { - this.addStringItem(item); + this.addStringItem(file, item); } this.appendNewLine('', 2); } @@ -243,9 +243,12 @@ export class XLF { } } - private addStringItem(item: Item): void { - if (!item.id || !item.message) { - throw new Error(`No item ID or value specified: ${JSON.stringify(item)}`); + private addStringItem(file: string, item: Item): void { + if (!item.id || item.message === undefined || item.message === null) { + throw new Error(`No item ID or value specified: ${JSON.stringify(item)}. File: ${file}`); + } + if (item.message.length === 0) { + log(`Item with id ${item.id} in file ${file} has an empty message.`); } this.appendNewLine(``, 4); diff --git a/build/lib/optimize.js b/build/lib/optimize.js index 6acb1597fb..11a768f361 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.minifyTask = exports.optimizeTask = exports.loaderConfig = void 0; const es = require("event-stream"); const fs = require("fs"); const gulp = require("gulp"); diff --git a/build/lib/reporter.js b/build/lib/reporter.js index 72793a3edb..fe9ec7fbe6 100644 --- a/build/lib/reporter.js +++ b/build/lib/reporter.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.createReporter = void 0; const es = require("event-stream"); const _ = require("underscore"); const fancyLog = require("fancy-log"); diff --git a/build/lib/rollup.js b/build/lib/rollup.js index 5ddba1551d..680bae992f 100644 --- a/build/lib/rollup.js +++ b/build/lib/rollup.js @@ -4,6 +4,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.rollupAngular = exports.rollupAngularSlickgrid = void 0; const fs = require("fs"); const rollup = require("rollup"); const path = require("path"); diff --git a/build/lib/standalone.js b/build/lib/standalone.js index 3f5a44ae09..a0a85817d4 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -4,6 +4,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.createESMSourcesAndResources2 = exports.extractEditor = void 0; const ts = require("typescript"); const fs = require("fs"); const path = require("path"); diff --git a/build/lib/stats.js b/build/lib/stats.js index 121a3acf7a..cf60b69125 100644 --- a/build/lib/stats.js +++ b/build/lib/stats.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.submitAllStats = exports.createStatsStream = void 0; const es = require("event-stream"); const fancyLog = require("fancy-log"); const ansiColors = require("ansi-colors"); diff --git a/build/lib/task.js b/build/lib/task.js index cf1dafbd34..f3a3fd8123 100644 --- a/build/lib/task.js +++ b/build/lib/task.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.define = exports.parallel = exports.series = void 0; const fancyLog = require("fancy-log"); const ansiColors = require("ansi-colors"); function _isPromise(p) { diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index ffcffea8a8..7d11b990e6 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.shake = exports.toStringShakeLevel = exports.ShakeLevel = void 0; const fs = require("fs"); const path = require("path"); const ts = require("typescript"); @@ -75,11 +76,7 @@ function createTypeScriptLanguageService(options) { FILES[typing] = fs.readFileSync(filePath).toString(); }); // Resolve libs - const RESOLVED_LIBS = {}; - options.libs.forEach((filename) => { - const filepath = path.join(TYPESCRIPT_LIB_FOLDER, filename); - RESOLVED_LIBS[`defaultLib:${filename}`] = fs.readFileSync(filepath).toString(); - }); + const RESOLVED_LIBS = processLibFiles(options); const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, options.sourcesRoot).options; const host = new TypeScriptLanguageServiceHost(RESOLVED_LIBS, FILES, compilerOptions); return ts.createLanguageService(host); @@ -137,6 +134,29 @@ function discoverAndReadFiles(options) { } return FILES; } +/** + * Read lib files and follow lib references + */ +function processLibFiles(options) { + const stack = [...options.compilerOptions.lib]; + const result = {}; + while (stack.length > 0) { + const filename = `lib.${stack.shift().toLowerCase()}.d.ts`; + const key = `defaultLib:${filename}`; + if (!result[key]) { + // add this file + const filepath = path.join(TYPESCRIPT_LIB_FOLDER, filename); + const sourceText = fs.readFileSync(filepath).toString(); + result[key] = sourceText; + // precess dependencies and "recurse" + const info = ts.preProcessFile(sourceText); + for (let ref of info.libReferenceDirectives) { + stack.push(ref.fileName); + } + } + } + return result; +} /** * A TypeScript language service host */ @@ -234,6 +254,7 @@ function markNodes(languageService, options) { } const black_queue = []; const gray_queue = []; + const export_import_queue = []; const sourceFilesLoaded = {}; function enqueueTopLevelModuleStatements(sourceFile) { sourceFile.forEachChild((node) => { @@ -245,10 +266,16 @@ function markNodes(languageService, options) { return; } if (ts.isExportDeclaration(node)) { - if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) { + if (!node.exportClause && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) { + // export * from "foo"; setColor(node, 2 /* Black */); enqueueImport(node, node.moduleSpecifier.text); } + if (node.exportClause && ts.isNamedExports(node.exportClause)) { + for (const exportSpecifier of node.exportClause.elements) { + export_import_queue.push(exportSpecifier); + } + } return; } if (ts.isExpressionStatement(node) @@ -402,6 +429,7 @@ function markNodes(languageService, options) { || ts.isConstructSignatureDeclaration(member) || ts.isIndexSignatureDeclaration(member) || ts.isCallSignatureDeclaration(member) + || memberName === '[Symbol.iterator]' || memberName === 'toJSON' || memberName === 'toString' || memberName === 'dispose' // TODO: keeping all `dispose` methods @@ -426,6 +454,22 @@ function markNodes(languageService, options) { }; node.forEachChild(loop); } + while (export_import_queue.length > 0) { + const node = export_import_queue.shift(); + if (nodeOrParentIsBlack(node)) { + continue; + } + const symbol = node.symbol; + if (!symbol) { + continue; + } + const aliased = checker.getAliasedSymbol(symbol); + if (aliased.declarations && aliased.declarations.length > 0) { + if (nodeOrParentIsBlack(aliased.declarations[0]) || nodeOrChildIsBlack(aliased.declarations[0])) { + setColor(node, 2 /* Black */); + } + } + } } function nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol) { for (let i = 0, len = symbol.declarations.length; i < len; i++) { @@ -517,6 +561,21 @@ function generateResult(languageService, shakeLevel) { } } } + if (ts.isExportDeclaration(node)) { + if (node.exportClause && node.moduleSpecifier && ts.isNamedExports(node.exportClause)) { + let survivingExports = []; + for (const exportSpecifier of node.exportClause.elements) { + if (getColor(exportSpecifier) === 2 /* Black */) { + survivingExports.push(exportSpecifier.getFullText(sourceFile)); + } + } + const leadingTriviaWidth = node.getLeadingTriviaWidth(); + const leadingTrivia = sourceFile.text.substr(node.pos, leadingTriviaWidth); + if (survivingExports.length > 0) { + return write(`${leadingTrivia}export {${survivingExports.join(',')} } from${node.moduleSpecifier.getFullText(sourceFile)};`); + } + } + } if (shakeLevel === 2 /* ClassMembers */ && (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) && nodeOrChildIsBlack(node)) { let toWrite = node.getFullText(); for (let i = node.members.length - 1; i >= 0; i--) { @@ -567,7 +626,7 @@ function getRealNodeSymbol(checker, node) { // (2) when the aliased symbol is originating from an import. // function shouldSkipAlias(node, declaration) { - if (node.kind !== ts.SyntaxKind.Identifier) { + if (!ts.isShorthandPropertyAssignment(node) && node.kind !== ts.SyntaxKind.Identifier) { return false; } if (node.parent === declaration) { @@ -589,7 +648,9 @@ function getRealNodeSymbol(checker, node) { } } const { parent } = node; - let symbol = checker.getSymbolAtLocation(node); + let symbol = (ts.isShorthandPropertyAssignment(node) + ? checker.getShorthandAssignmentValueSymbol(node) + : checker.getSymbolAtLocation(node)); let importNode = null; // If this is an alias, and the request came at the declaration location // get the aliased symbol instead. This allows for goto def on an import e.g. diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index e187507c87..706053fe6e 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -18,7 +18,7 @@ export const enum ShakeLevel { } export function toStringShakeLevel(shakeLevel: ShakeLevel): string { - switch(shakeLevel) { + switch (shakeLevel) { case ShakeLevel.Files: return 'Files (0)'; case ShakeLevel.InnerFile: @@ -42,11 +42,6 @@ export interface ITreeShakingOptions { * Inline usages. */ inlineEntryPoints: string[]; - /** - * TypeScript libs. - * e.g. `lib.d.ts`, `lib.es2015.collection.d.ts` - */ - libs: string[]; /** * Other .d.ts files */ @@ -130,11 +125,7 @@ function createTypeScriptLanguageService(options: ITreeShakingOptions): ts.Langu }); // Resolve libs - const RESOLVED_LIBS: ILibMap = {}; - options.libs.forEach((filename) => { - const filepath = path.join(TYPESCRIPT_LIB_FOLDER, filename); - RESOLVED_LIBS[`defaultLib:${filename}`] = fs.readFileSync(filepath).toString(); - }); + const RESOLVED_LIBS = processLibFiles(options); const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, options.sourcesRoot).options; @@ -205,6 +196,34 @@ function discoverAndReadFiles(options: ITreeShakingOptions): IFileMap { return FILES; } +/** + * Read lib files and follow lib references + */ +function processLibFiles(options: ITreeShakingOptions): ILibMap { + + const stack: string[] = [...options.compilerOptions.lib]; + const result: ILibMap = {}; + + while (stack.length > 0) { + const filename = `lib.${stack.shift()!.toLowerCase()}.d.ts`; + const key = `defaultLib:${filename}`; + if (!result[key]) { + // add this file + const filepath = path.join(TYPESCRIPT_LIB_FOLDER, filename); + const sourceText = fs.readFileSync(filepath).toString(); + result[key] = sourceText; + + // precess dependencies and "recurse" + const info = ts.preProcessFile(sourceText); + for (let ref of info.libReferenceDirectives) { + stack.push(ref.fileName); + } + } + } + + return result; +} + interface ILibMap { [libName: string]: string; } interface IFileMap { [fileName: string]: string; } @@ -317,6 +336,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt const black_queue: ts.Node[] = []; const gray_queue: ts.Node[] = []; + const export_import_queue: ts.Node[] = []; const sourceFilesLoaded: { [fileName: string]: boolean } = {}; function enqueueTopLevelModuleStatements(sourceFile: ts.SourceFile): void { @@ -332,10 +352,16 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt } if (ts.isExportDeclaration(node)) { - if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) { + if (!node.exportClause && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) { + // export * from "foo"; setColor(node, NodeColor.Black); enqueueImport(node, node.moduleSpecifier.text); } + if (node.exportClause && ts.isNamedExports(node.exportClause)) { + for (const exportSpecifier of node.exportClause.elements) { + export_import_queue.push(exportSpecifier); + } + } return; } @@ -475,7 +501,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt } if (black_queue.length === 0) { - for (let i = 0; i< gray_queue.length; i++) { + for (let i = 0; i < gray_queue.length; i++) { const node = gray_queue[i]; const nodeParent = node.parent; if ((ts.isClassDeclaration(nodeParent) || ts.isInterfaceDeclaration(nodeParent)) && nodeOrChildIsBlack(nodeParent)) { @@ -521,6 +547,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt || ts.isConstructSignatureDeclaration(member) || ts.isIndexSignatureDeclaration(member) || ts.isCallSignatureDeclaration(member) + || memberName === '[Symbol.iterator]' || memberName === 'toJSON' || memberName === 'toString' || memberName === 'dispose'// TODO: keeping all `dispose` methods @@ -545,6 +572,23 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt }; node.forEachChild(loop); } + + while (export_import_queue.length > 0) { + const node = export_import_queue.shift()!; + if (nodeOrParentIsBlack(node)) { + continue; + } + const symbol: ts.Symbol | undefined = (node).symbol; + if (!symbol) { + continue; + } + const aliased = checker.getAliasedSymbol(symbol); + if (aliased.declarations && aliased.declarations.length > 0) { + if (nodeOrParentIsBlack(aliased.declarations[0]) || nodeOrChildIsBlack(aliased.declarations[0])) { + setColor(node, NodeColor.Black); + } + } + } } function nodeIsInItsOwnDeclaration(nodeSourceFile: ts.SourceFile, node: ts.Node, symbol: ts.Symbol): boolean { @@ -646,6 +690,22 @@ function generateResult(languageService: ts.LanguageService, shakeLevel: ShakeLe } } + if (ts.isExportDeclaration(node)) { + if (node.exportClause && node.moduleSpecifier && ts.isNamedExports(node.exportClause)) { + let survivingExports: string[] = []; + for (const exportSpecifier of node.exportClause.elements) { + if (getColor(exportSpecifier) === NodeColor.Black) { + survivingExports.push(exportSpecifier.getFullText(sourceFile)); + } + } + const leadingTriviaWidth = node.getLeadingTriviaWidth(); + const leadingTrivia = sourceFile.text.substr(node.pos, leadingTriviaWidth); + if (survivingExports.length > 0) { + return write(`${leadingTrivia}export {${survivingExports.join(',')} } from${node.moduleSpecifier.getFullText(sourceFile)};`); + } + } + } + if (shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) && nodeOrChildIsBlack(node)) { let toWrite = node.getFullText(); for (let i = node.members.length - 1; i >= 0; i--) { @@ -708,7 +768,7 @@ function getRealNodeSymbol(checker: ts.TypeChecker, node: ts.Node): [ts.Symbol | // (2) when the aliased symbol is originating from an import. // function shouldSkipAlias(node: ts.Node, declaration: ts.Node): boolean { - if (node.kind !== ts.SyntaxKind.Identifier) { + if (!ts.isShorthandPropertyAssignment(node) && node.kind !== ts.SyntaxKind.Identifier) { return false; } if (node.parent === declaration) { @@ -733,7 +793,12 @@ function getRealNodeSymbol(checker: ts.TypeChecker, node: ts.Node): [ts.Symbol | const { parent } = node; - let symbol = checker.getSymbolAtLocation(node); + let symbol = ( + ts.isShorthandPropertyAssignment(node) + ? checker.getShorthandAssignmentValueSymbol(node) + : checker.getSymbolAtLocation(node) + ); + let importNode: ts.Declaration | null = null; // If this is an alias, and the request came at the declaration location // get the aliased symbol instead. This allows for goto def on an import e.g. diff --git a/build/lib/util.js b/build/lib/util.js index 79cbe51ec6..bff8653056 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); +exports.streamToPromise = exports.versionStringToNumber = exports.filter = exports.rebase = exports.getVersion = exports.ensureDir = exports.rreddir = exports.rimraf = exports.stripSourceMappingURL = exports.loadSourcemaps = exports.cleanNodeModules = exports.skipDirectories = exports.toFileUri = exports.setExecutableBit = exports.fixWin32DirectoryPermissions = exports.incremental = void 0; const es = require("event-stream"); const debounce = require("debounce"); const _filter = require("gulp-filter"); diff --git a/build/monaco/ThirdPartyNotices.txt b/build/monaco/ThirdPartyNotices.txt index 1de70ddaab..8b488daf19 100644 --- a/build/monaco/ThirdPartyNotices.txt +++ b/build/monaco/ThirdPartyNotices.txt @@ -33,32 +33,6 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. END OF nodejs path library NOTICES AND INFORMATION -%% promise-polyfill version 8.1.0 (https://github.com/taylorhakes/promise-polyfill) -========================================= -Copyright (c) 2014 Taylor Hakes -Copyright (c) 2014 Forbes Lindesay - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -========================================= -END OF winjs NOTICES AND INFORMATION - - %% string_scorer version 0.1.20 (https://github.com/joshaven/string_score) diff --git a/build/monaco/api.js b/build/monaco/api.js index 8f10ce9e90..2fcd5b1438 100644 --- a/build/monaco/api.js +++ b/build/monaco/api.js @@ -4,6 +4,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.execute = exports.run3 = exports.DeclarationResolver = exports.FSProvider = exports.RECIPE_PATH = void 0; const fs = require("fs"); const ts = require("typescript"); const path = require("path"); diff --git a/build/package.json b/build/package.json index a42683371b..fab74fc915 100644 --- a/build/package.json +++ b/build/package.json @@ -50,7 +50,7 @@ "rollup-plugin-node-resolve": "^5.2.0", "service-downloader": "0.2.1", "terser": "4.3.8", - "typescript": "3.8.2", + "typescript": "3.9.0-dev.20200304", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" diff --git a/build/yarn.lock b/build/yarn.lock index df9665959a..4b7d1e55dd 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -3676,10 +3676,10 @@ typed-rest-client@^0.9.0: tunnel "0.0.4" underscore "1.8.3" -typescript@3.8.2: - version "3.8.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.2.tgz#91d6868aaead7da74f493c553aeff76c0c0b1d5a" - integrity sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ== +typescript@3.9.0-dev.20200304: + version "3.9.0-dev.20200304" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200304.tgz#3cc35357eff29dc5604b4fa56d6597e13daf86ed" + integrity sha512-eUip/GgJmjp4qtHiJDxVhE5SDDiPzBUg7KBAFUgb7HgL/tv10JAHej7fnS1i+7xrq1eDtbkJyPaYOVnhL9db7Q== typescript@^3.0.1: version "3.5.3" diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index fe4a7b44d2..75d77925b0 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -35,9 +35,13 @@ "url": "vscode://schemas/language-configuration" }, { - "fileMatch": "*icon-theme.json", + "fileMatch": ["*icon-theme.json", "!*product-icon-theme.json"], "url": "vscode://schemas/icon-theme" }, + { + "fileMatch": "*product-icon-theme.json", + "url": "vscode://schemas/product-icon-theme" + }, { "fileMatch": "*color-theme.json", "url": "vscode://schemas/color-theme" diff --git a/extensions/git/package.json b/extensions/git/package.json index 5246020c6c..e299d60c11 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -40,10 +40,7 @@ "command": "git.init", "title": "%command.init%", "category": "Git", - "icon": { - "light": "resources/icons/light/git.svg", - "dark": "resources/icons/dark/git.svg" - } + "icon": "$(add)" }, { "command": "git.openRepository", @@ -59,37 +56,25 @@ "command": "git.refresh", "title": "%command.refresh%", "category": "Git", - "icon": { - "light": "resources/icons/light/refresh.svg", - "dark": "resources/icons/dark/refresh.svg" - } + "icon": "$(refresh)" }, { "command": "git.openChange", "title": "%command.openChange%", "category": "Git", - "icon": { - "light": "resources/icons/light/open-change.svg", - "dark": "resources/icons/dark/open-change.svg" - } + "icon": "$(compare-changes)" }, { "command": "git.openFile", "title": "%command.openFile%", "category": "Git", - "icon": { - "light": "resources/icons/light/open-file.svg", - "dark": "resources/icons/dark/open-file.svg" - } + "icon": "$(go-to-file)" }, { "command": "git.openFile2", "title": "%command.openFile%", "category": "Git", - "icon": { - "light": "resources/icons/light/open-file.svg", - "dark": "resources/icons/dark/open-file.svg" - } + "icon": "$(go-to-file)" }, { "command": "git.openHEADFile", @@ -100,37 +85,25 @@ "command": "git.stage", "title": "%command.stage%", "category": "Git", - "icon": { - "light": "resources/icons/light/stage.svg", - "dark": "resources/icons/dark/stage.svg" - } + "icon": "$(add)" }, { "command": "git.stageAll", "title": "%command.stageAll%", "category": "Git", - "icon": { - "light": "resources/icons/light/stage.svg", - "dark": "resources/icons/dark/stage.svg" - } + "icon": "$(add)" }, { "command": "git.stageAllTracked", "title": "%command.stageAllTracked%", "category": "Git", - "icon": { - "light": "resources/icons/light/stage.svg", - "dark": "resources/icons/dark/stage.svg" - } + "icon": "$(add)" }, { "command": "git.stageAllUntracked", "title": "%command.stageAllUntracked%", "category": "Git", - "icon": { - "light": "resources/icons/light/stage.svg", - "dark": "resources/icons/dark/stage.svg" - } + "icon": "$(add)" }, { "command": "git.stageSelectedRanges", @@ -146,37 +119,25 @@ "command": "git.stageChange", "title": "%command.stageChange%", "category": "Git", - "icon": { - "light": "resources/icons/light/stage.svg", - "dark": "resources/icons/dark/stage.svg" - } + "icon": "$(add)" }, { "command": "git.revertChange", "title": "%command.revertChange%", "category": "Git", - "icon": { - "light": "resources/icons/light/clean.svg", - "dark": "resources/icons/dark/clean.svg" - } + "icon": "$(discard)" }, { "command": "git.unstage", "title": "%command.unstage%", "category": "Git", - "icon": { - "light": "resources/icons/light/unstage.svg", - "dark": "resources/icons/dark/unstage.svg" - } + "icon": "$(remove)" }, { "command": "git.unstageAll", "title": "%command.unstageAll%", "category": "Git", - "icon": { - "light": "resources/icons/light/unstage.svg", - "dark": "resources/icons/dark/unstage.svg" - } + "icon": "$(remove)" }, { "command": "git.unstageSelectedRanges", @@ -187,46 +148,31 @@ "command": "git.clean", "title": "%command.clean%", "category": "Git", - "icon": { - "light": "resources/icons/light/clean.svg", - "dark": "resources/icons/dark/clean.svg" - } + "icon": "$(discard)" }, { "command": "git.cleanAll", "title": "%command.cleanAll%", "category": "Git", - "icon": { - "light": "resources/icons/light/clean.svg", - "dark": "resources/icons/dark/clean.svg" - } + "icon": "$(discard)" }, { "command": "git.cleanAllTracked", "title": "%command.cleanAllTracked%", "category": "Git", - "icon": { - "light": "resources/icons/light/clean.svg", - "dark": "resources/icons/dark/clean.svg" - } + "icon": "$(discard)" }, { "command": "git.cleanAllUntracked", "title": "%command.cleanAllUntracked%", "category": "Git", - "icon": { - "light": "resources/icons/light/clean.svg", - "dark": "resources/icons/dark/clean.svg" - } + "icon": "$(discard)" }, { "command": "git.commit", "title": "%command.commit%", "category": "Git", - "icon": { - "light": "resources/icons/light/check.svg", - "dark": "resources/icons/dark/check.svg" - } + "icon": "$(check)" }, { "command": "git.commitStaged", diff --git a/extensions/git/resources/icons/dark/check.svg b/extensions/git/resources/icons/dark/check.svg deleted file mode 100644 index 2d16f39007..0000000000 --- a/extensions/git/resources/icons/dark/check.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/dark/clean.svg b/extensions/git/resources/icons/dark/clean.svg deleted file mode 100644 index de85d6ba67..0000000000 --- a/extensions/git/resources/icons/dark/clean.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/dark/git.svg b/extensions/git/resources/icons/dark/git.svg deleted file mode 100644 index 4d9389336b..0000000000 --- a/extensions/git/resources/icons/dark/git.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/dark/open-change.svg b/extensions/git/resources/icons/dark/open-change.svg deleted file mode 100644 index 41ebc85a8c..0000000000 --- a/extensions/git/resources/icons/dark/open-change.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/dark/open-file.svg b/extensions/git/resources/icons/dark/open-file.svg deleted file mode 100644 index ed302ae139..0000000000 --- a/extensions/git/resources/icons/dark/open-file.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/dark/refresh.svg b/extensions/git/resources/icons/dark/refresh.svg deleted file mode 100644 index e1f05aadee..0000000000 --- a/extensions/git/resources/icons/dark/refresh.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/extensions/git/resources/icons/dark/stage.svg b/extensions/git/resources/icons/dark/stage.svg deleted file mode 100644 index 4d9389336b..0000000000 --- a/extensions/git/resources/icons/dark/stage.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/dark/unstage.svg b/extensions/git/resources/icons/dark/unstage.svg deleted file mode 100644 index 4c5a9c1e3a..0000000000 --- a/extensions/git/resources/icons/dark/unstage.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/light/check.svg b/extensions/git/resources/icons/light/check.svg deleted file mode 100644 index a9f8aa131b..0000000000 --- a/extensions/git/resources/icons/light/check.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/light/clean.svg b/extensions/git/resources/icons/light/clean.svg deleted file mode 100644 index b70626957d..0000000000 --- a/extensions/git/resources/icons/light/clean.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/light/git.svg b/extensions/git/resources/icons/light/git.svg deleted file mode 100644 index 01a9de7d5a..0000000000 --- a/extensions/git/resources/icons/light/git.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/light/open-change.svg b/extensions/git/resources/icons/light/open-change.svg deleted file mode 100644 index 772c3c198f..0000000000 --- a/extensions/git/resources/icons/light/open-change.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/light/open-file.svg b/extensions/git/resources/icons/light/open-file.svg deleted file mode 100644 index 392a840c5e..0000000000 --- a/extensions/git/resources/icons/light/open-file.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/light/refresh.svg b/extensions/git/resources/icons/light/refresh.svg deleted file mode 100644 index 9b1d910840..0000000000 --- a/extensions/git/resources/icons/light/refresh.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/extensions/git/resources/icons/light/stage.svg b/extensions/git/resources/icons/light/stage.svg deleted file mode 100644 index 01a9de7d5a..0000000000 --- a/extensions/git/resources/icons/light/stage.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/resources/icons/light/unstage.svg b/extensions/git/resources/icons/light/unstage.svg deleted file mode 100644 index d12a8ee313..0000000000 --- a/extensions/git/resources/icons/light/unstage.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index 0fd723f3c7..31fd356bd0 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -80,7 +80,7 @@ export class GitTimelineProvider implements TimelineProvider { constructor(private readonly _model: Model) { this._disposable = Disposable.from( _model.onDidOpenRepository(this.onRepositoriesChanged, this), - workspace.registerTimelineProvider('*', this), + workspace.registerTimelineProvider(['file', 'git', 'gitlens-git'], this), ); } @@ -114,9 +114,9 @@ export class GitTimelineProvider implements TimelineProvider { // TODO[ECA]: Ensure that the uri is a file -- if not we could get the history of the repo? let limit: number | undefined; - if (typeof options.limit === 'string') { + if (options.limit !== undefined && typeof options.limit !== 'number') { try { - const result = await this._model.git.exec(repo.root, ['rev-list', '--count', `${options.limit}..`, '--', uri.fsPath]); + const result = await this._model.git.exec(repo.root, ['rev-list', '--count', `${options.limit.cursor}..`, '--', uri.fsPath]); if (!result.exitCode) { // Ask for 1 more than so we can determine if there are more commits limit = Number(result.stdout) + 1; @@ -182,7 +182,7 @@ export class GitTimelineProvider implements TimelineProvider { const item = new GitTimelineItem('~', 'HEAD', localize('git.timeline.stagedChanges', 'Staged Changes'), date.getTime(), 'index', 'git:file:index'); // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = you; + item.description = ''; item.detail = localize('git.timeline.detail', '{0} \u2014 {1}\n{2}\n\n{3}', you, localize('git.index', 'Index'), dateFormatter.format('MMMM Do, YYYY h:mma'), Resource.getStatusText(index.type)); item.command = { title: 'Open Comparison', @@ -201,7 +201,7 @@ export class GitTimelineProvider implements TimelineProvider { const item = new GitTimelineItem('', index ? '~' : 'HEAD', localize('git.timeline.uncommitedChanges', 'Uncommited Changes'), date.getTime(), 'working', 'git:file:working'); // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = you; + item.description = ''; item.detail = localize('git.timeline.detail', '{0} \u2014 {1}\n{2}\n\n{3}', you, localize('git.workingTree', 'Working Tree'), dateFormatter.format('MMMM Do, YYYY h:mma'), Resource.getStatusText(working.type)); item.command = { title: 'Open Comparison', diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index 94450bb6e1..66b7fa005c 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -40,8 +40,13 @@ export interface ISchemaAssociations { [pattern: string]: string[]; } +export interface ISchemaAssociation { + fileMatch: string[]; + uri: string; +} + namespace SchemaAssociationNotification { - export const type: NotificationType = new NotificationType('json/schemaAssociations'); + export const type: NotificationType = new NotificationType('json/schemaAssociations'); } namespace ResultLimitReachedNotification { @@ -264,10 +269,10 @@ export function activate(context: ExtensionContext) { toDispose.push(commands.registerCommand('_json.retryResolveSchema', handleRetryResolveSchemaCommand)); - client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociation(context)); + client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context)); extensions.onDidChange(_ => { - client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociation(context)); + client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context)); }); // manually register / deregister format provider based on the `html.format.enable` setting avoiding issues with late registration. See #71652. @@ -324,8 +329,8 @@ export function deactivate(): Promise { return telemetryReporter ? telemetryReporter.dispose() : Promise.resolve(null); } -function getSchemaAssociation(_context: ExtensionContext): ISchemaAssociations { - const associations: ISchemaAssociations = {}; +function getSchemaAssociations(_context: ExtensionContext): ISchemaAssociation[] { + const associations: ISchemaAssociation[] = []; extensions.all.forEach(extension => { const packageJSON = extension.packageJSON; if (packageJSON && packageJSON.contributes && packageJSON.contributes.jsonValidation) { @@ -333,23 +338,21 @@ function getSchemaAssociation(_context: ExtensionContext): ISchemaAssociations { if (Array.isArray(jsonValidation)) { jsonValidation.forEach(jv => { let { fileMatch, url } = jv; - if (fileMatch && url) { - if (url[0] === '.' && url[1] === '/') { - url = Uri.file(path.join(extension.extensionPath, url)).toString(); - } - if (fileMatch[0] === '%') { - fileMatch = fileMatch.replace(/%APP_SETTINGS_HOME%/, '/User'); - fileMatch = fileMatch.replace(/%MACHINE_SETTINGS_HOME%/, '/Machine'); - fileMatch = fileMatch.replace(/%APP_WORKSPACES_HOME%/, '/Workspaces'); - } else if (fileMatch.charAt(0) !== '/' && !fileMatch.match(/\w+:\/\//)) { - fileMatch = '/' + fileMatch; - } - let association = associations[fileMatch]; - if (!association) { - association = []; - associations[fileMatch] = association; - } - association.push(url); + if (typeof fileMatch === 'string') { + fileMatch = [fileMatch]; + } + if (Array.isArray(fileMatch) && url) { + fileMatch = fileMatch.map(fm => { + if (fm[0] === '%') { + fm = fm.replace(/%APP_SETTINGS_HOME%/, '/User'); + fm = fm.replace(/%MACHINE_SETTINGS_HOME%/, '/Machine'); + fm = fm.replace(/%APP_WORKSPACES_HOME%/, '/Workspaces'); + } else if (!fm.match(/^(\w+:\/\/|\/|!)/)) { + fm = '/' + fm; + } + return fm; + }); + associations.push({ fileMatch, uri: url }); } }); } diff --git a/extensions/json-language-features/package.nls.json b/extensions/json-language-features/package.nls.json index 6b692bdbb2..35cf05e2d0 100644 --- a/extensions/json-language-features/package.nls.json +++ b/extensions/json-language-features/package.nls.json @@ -3,7 +3,7 @@ "description": "Provides rich language support for JSON files.", "json.schemas.desc": "Associate schemas to JSON files in the current project", "json.schemas.url.desc": "A URL to a schema or a relative path to a schema in the current directory", - "json.schemas.fileMatch.desc": "An array of file patterns to match against when resolving JSON files to schemas.", + "json.schemas.fileMatch.desc": "An array of file patterns to match against when resolving JSON files to schemas. `*` can be used as a wildcard. Exclusion patterns can also be defined and start with '!'. A file matches when there at least one matching pattern and the last matching pattern is not an exclusion pattern.", "json.schemas.fileMatch.item.desc": "A file pattern that can contain '*' to match against when resolving JSON files to schemas.", "json.schemas.schema.desc": "The schema definition for the given URL. The schema only needs to be provided to avoid accesses to the schema URL.", "json.format.enable.desc": "Enable/disable default JSON formatter", diff --git a/extensions/json-language-features/server/README.md b/extensions/json-language-features/server/README.md index 43cc04837d..18e397c533 100644 --- a/extensions/json-language-features/server/README.md +++ b/extensions/json-language-features/server/README.md @@ -62,7 +62,7 @@ The server supports the following settings: - `format` - `enable`: Whether the server should register the formatting support. This option is only applicable if the client supports *dynamicRegistration* for *rangeFormatting* and `initializationOptions.provideFormatter` is not defined. - `schema`: Configures association of file names to schema URL or schemas and/or associations of schema URL to schema content. - - `fileMatch`: an array of file names or paths (separated by `/`). `*` can be used as a wildcard. + - `fileMatch`: an array of file names or paths (separated by `/`). `*` can be used as a wildcard. Exclusion patterns can also be defined and start with '!'. A file matches when there at least one matching pattern and the last matching pattern is not an exclusion pattern. - `url`: The URL of the schema, optional when also a schema is provided. - `schema`: The schema content. - `resultLimit`: The max number foldig ranges and otline symbols to be computed (for performance reasons) diff --git a/extensions/json-language-features/server/src/jsonServerMain.ts b/extensions/json-language-features/server/src/jsonServerMain.ts index 15fc2da0e5..f4c36b2bd9 100644 --- a/extensions/json-language-features/server/src/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/jsonServerMain.ts @@ -23,8 +23,13 @@ interface ISchemaAssociations { [pattern: string]: string[]; } +interface ISchemaAssociation { + fileMatch: string[]; + uri: string; +} + namespace SchemaAssociationNotification { - export const type: NotificationType = new NotificationType('json/schemaAssociations'); + export const type: NotificationType = new NotificationType('json/schemaAssociations'); } namespace VSCodeContentRequest { @@ -230,7 +235,7 @@ namespace LimitExceededWarnings { } let jsonConfigurationSettings: JSONSchemaSettings[] | undefined = undefined; -let schemaAssociations: ISchemaAssociations | undefined = undefined; +let schemaAssociations: ISchemaAssociations | ISchemaAssociation[] | undefined = undefined; let formatterRegistration: Thenable | null = null; // The settings have changed. Is send on server activation as well. @@ -291,12 +296,16 @@ function updateConfiguration() { schemas: new Array() }; if (schemaAssociations) { - for (const pattern in schemaAssociations) { - const association = schemaAssociations[pattern]; - if (Array.isArray(association)) { - association.forEach(uri => { - languageSettings.schemas.push({ uri, fileMatch: [pattern] }); - }); + if (Array.isArray(schemaAssociations)) { + Array.prototype.push.apply(languageSettings.schemas, schemaAssociations); + } else { + for (const pattern in schemaAssociations) { + const association = schemaAssociations[pattern]; + if (Array.isArray(association)) { + association.forEach(uri => { + languageSettings.schemas.push({ uri, fileMatch: [pattern] }); + }); + } } } } diff --git a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index 1babbfd546..af6042b559 100644 --- a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json +++ b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/8fbbc11a6bb917f287bbe21d0573454020599547", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/7cf9aa7bb76c55428063383610edc0a631230d58", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -2267,7 +2267,7 @@ "name": "meta.other.valid-ampersand.markdown" }, "bold": { - "begin": "(?x) (\\*\\*(?=\\w)|(?]*+> # HTML tags\n | (?`+)([^`]|(?!(?(?!`))`)*+\\k\n # Raw\n | \\\\[\\\\`*_{}\\[\\]()#.!+\\->]?+ # Escapes\n | \\[\n (\n (? # Named group\n [^\\[\\]\\\\] # Match most chars\n | \\\\. # Escaped chars\n | \\[ \\g*+ \\] # Nested brackets\n )*+\n \\]\n (\n ( # Reference Link\n [ ]? # Optional space\n \\[[^\\]]*+\\] # Ref name\n )\n | ( # Inline Link\n \\( # Opening paren\n [ \\t]*+ # Optional whitespace\n ? # URL\n [ \\t]*+ # Optional whitespace\n ( # Optional Title\n (?['\"])\n (.*?)\n \\k<title>\n )?\n \\)\n )\n )\n )\n | (?!(?<=\\S)\\1). # Everything besides\n # style closer\n )++\n (?<=\\S)(?=__\\b|\\*\\*)\\1 # Close\n)\n", + "begin": "(?x) (?<open>(\\*\\*(?=\\w)|(?<!\\w)\\*\\*|(?<!\\w)\\b__))(?=\\S) (?=\n (\n <[^>]*+> # HTML tags\n | (?<raw>`+)([^`]|(?!(?<!`)\\k<raw>(?!`))`)*+\\k<raw>\n # Raw\n | \\\\[\\\\`*_{}\\[\\]()#.!+\\->]?+ # Escapes\n | \\[\n (\n (?<square> # Named group\n [^\\[\\]\\\\] # Match most chars\n | \\\\. # Escaped chars\n | \\[ \\g<square>*+ \\] # Nested brackets\n )*+\n \\]\n (\n ( # Reference Link\n [ ]? # Optional space\n \\[[^\\]]*+\\] # Ref name\n )\n | ( # Inline Link\n \\( # Opening paren\n [ \\t]*+ # Optional whitespace\n <?(.*?)>? # URL\n [ \\t]*+ # Optional whitespace\n ( # Optional Title\n (?<title>['\"])\n (.*?)\n \\k<title>\n )?\n \\)\n )\n )\n )\n | (?!(?<=\\S)\\k<open>). # Everything besides\n # style closer\n )++\n (?<=\\S)(?=__\\b|\\*\\*)\\k<open> # Close\n)\n", "captures": { "1": { "name": "punctuation.definition.bold.markdown" @@ -2412,7 +2412,7 @@ "name": "meta.image.reference.markdown" }, "italic": { - "begin": "(?x) (\\*(?=\\w)|(?<!\\w)\\*|(?<!\\w)\\b_)(?=\\S) # Open\n (?=\n (\n <[^>]*+> # HTML tags\n | (?<raw>`+)([^`]|(?!(?<!`)\\k<raw>(?!`))`)*+\\k<raw>\n # Raw\n | \\\\[\\\\`*_{}\\[\\]()#.!+\\->]?+ # Escapes\n | \\[\n (\n (?<square> # Named group\n [^\\[\\]\\\\] # Match most chars\n | \\\\. # Escaped chars\n | \\[ \\g<square>*+ \\] # Nested brackets\n )*+\n \\]\n (\n ( # Reference Link\n [ ]? # Optional space\n \\[[^\\]]*+\\] # Ref name\n )\n | ( # Inline Link\n \\( # Opening paren\n [ \\t]*+ # Optional whtiespace\n <?(.*?)>? # URL\n [ \\t]*+ # Optional whtiespace\n ( # Optional Title\n (?<title>['\"])\n (.*?)\n \\k<title>\n )?\n \\)\n )\n )\n )\n | \\1\\1 # Must be bold closer\n | (?!(?<=\\S)\\1). # Everything besides\n # style closer\n )++\n (?<=\\S)(?=_\\b|\\*)\\1 # Close\n )\n", + "begin": "(?x) (?<open>(\\*(?=\\w)|(?<!\\w)\\*|(?<!\\w)\\b_))(?=\\S) # Open\n (?=\n (\n <[^>]*+> # HTML tags\n | (?<raw>`+)([^`]|(?!(?<!`)\\k<raw>(?!`))`)*+\\k<raw>\n # Raw\n | \\\\[\\\\`*_{}\\[\\]()#.!+\\->]?+ # Escapes\n | \\[\n (\n (?<square> # Named group\n [^\\[\\]\\\\] # Match most chars\n | \\\\. # Escaped chars\n | \\[ \\g<square>*+ \\] # Nested brackets\n )*+\n \\]\n (\n ( # Reference Link\n [ ]? # Optional space\n \\[[^\\]]*+\\] # Ref name\n )\n | ( # Inline Link\n \\( # Opening paren\n [ \\t]*+ # Optional whtiespace\n <?(.*?)>? # URL\n [ \\t]*+ # Optional whtiespace\n ( # Optional Title\n (?<title>['\"])\n (.*?)\n \\k<title>\n )?\n \\)\n )\n )\n )\n | \\k<open>\\k<open> # Must be bold closer\n | (?!(?<=\\S)\\k<open>). # Everything besides\n # style closer\n )++\n (?<=\\S)(?=_\\b|\\*)\\k<open> # Close\n )\n", "captures": { "1": { "name": "punctuation.definition.italic.markdown" diff --git a/extensions/markdown-language-features/src/features/foldingProvider.ts b/extensions/markdown-language-features/src/features/foldingProvider.ts index ba3e074717..6aac83f54e 100644 --- a/extensions/markdown-language-features/src/features/foldingProvider.ts +++ b/extensions/markdown-language-features/src/features/foldingProvider.ts @@ -77,6 +77,9 @@ export default class MarkdownFoldingProvider implements vscode.FoldingRangeProvi return token.map[1] > token.map[0]; case 'html_block': + if (isRegionMarker(token)) { + return false; + } return token.map[1] > token.map[0] + 1; default: @@ -92,7 +95,7 @@ export default class MarkdownFoldingProvider implements vscode.FoldingRangeProvi if (document.lineAt(end).isEmptyOrWhitespace && end >= start + 1) { end = end - 1; } - return new vscode.FoldingRange(start, end); + return new vscode.FoldingRange(start, end, listItem.type === 'html_block' && listItem.content.startsWith('<!--') ? vscode.FoldingRangeKind.Comment : undefined); }); } } diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index aaf8083c3f..ce626ce7de 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -457,7 +457,10 @@ export class DynamicMarkdownPreview extends Disposable { const folder = vscode.workspace.getWorkspaceFolder(base); if (folder) { - baseRoots.push(folder.uri); + const workspaceRoots = vscode.workspace.workspaceFolders?.map(folder => folder.uri); + if (workspaceRoots) { + baseRoots.push(...workspaceRoots); + } } else if (!base.scheme || base.scheme === 'file') { baseRoots.push(vscode.Uri.file(path.dirname(base.fsPath))); } diff --git a/extensions/markdown-language-features/src/features/previewManager.ts b/extensions/markdown-language-features/src/features/previewManager.ts index e64c7e9e0f..880a679c1e 100644 --- a/extensions/markdown-language-features/src/features/previewManager.ts +++ b/extensions/markdown-language-features/src/features/previewManager.ts @@ -52,7 +52,7 @@ class PreviewStore extends Disposable { } } -export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer, vscode.CustomEditorProvider { +export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer, vscode.CustomTextEditorProvider { private static readonly markdownPreviewActiveContextKey = 'markdownPreviewFocus'; private readonly _topmostLineMonitor = new TopmostLineMonitor(); @@ -152,8 +152,8 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview return {}; } - public async resolveCustomEditor( - document: vscode.CustomDocument, + public async resolveCustomTextEditor( + document: vscode.TextDocument, webview: vscode.WebviewPanel ): Promise<void> { const preview = DynamicMarkdownPreview.revive( diff --git a/extensions/markdown-language-features/src/test/foldingProvider.test.ts b/extensions/markdown-language-features/src/test/foldingProvider.test.ts index 5d6edd1c0d..76f67d4ba4 100644 --- a/extensions/markdown-language-features/src/test/foldingProvider.test.ts +++ b/extensions/markdown-language-features/src/test/foldingProvider.test.ts @@ -175,6 +175,18 @@ a`); assert.strictEqual(firstFold.start, 1); assert.strictEqual(firstFold.end, 3); }); + + test('Should fold html block comments', async () => { + const folds = await getFoldsForDocument(`x +<!-- +fa +-->`); + assert.strictEqual(folds.length, 1); + const firstFold = folds[0]; + assert.strictEqual(firstFold.start, 1); + assert.strictEqual(firstFold.end, 3); + assert.strictEqual(firstFold.kind, vscode.FoldingRangeKind.Comment); + }); }); diff --git a/extensions/package.json b/extensions/package.json index b8e304b234..7b4e8171c1 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.8.2" + "typescript": "3.8.3" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/python/cgmanifest.json b/extensions/python/cgmanifest.json index 3ee82895a8..37a21b2de5 100644 --- a/extensions/python/cgmanifest.json +++ b/extensions/python/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "MagicStack/MagicPython", "repositoryUrl": "https://github.com/MagicStack/MagicPython", - "commitHash": "c0f8d514bbe6e9d3899f2b002bcd6971aef5e34b" + "commitHash": "c9b3409deb69acec31bbf7913830e93a046b30cc" } }, "license": "MIT", diff --git a/extensions/python/syntaxes/MagicPython.tmLanguage.json b/extensions/python/syntaxes/MagicPython.tmLanguage.json index f59618f75f..bf5277fdc1 100644 --- a/extensions/python/syntaxes/MagicPython.tmLanguage.json +++ b/extensions/python/syntaxes/MagicPython.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/MagicStack/MagicPython/commit/c0f8d514bbe6e9d3899f2b002bcd6971aef5e34b", + "version": "https://github.com/MagicStack/MagicPython/commit/c9b3409deb69acec31bbf7913830e93a046b30cc", "name": "MagicPython", "scopeName": "source.python", "patterns": [ diff --git a/extensions/python/syntaxes/MagicRegExp.tmLanguage.json b/extensions/python/syntaxes/MagicRegExp.tmLanguage.json index c7e67436a0..fc11fa5aff 100644 --- a/extensions/python/syntaxes/MagicRegExp.tmLanguage.json +++ b/extensions/python/syntaxes/MagicRegExp.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/MagicStack/MagicPython/commit/361a4964a559481330764a447e7bab88d4f1b01b", + "version": "https://github.com/MagicStack/MagicPython/commit/c9b3409deb69acec31bbf7913830e93a046b30cc", "name": "MagicRegExp", "scopeName": "source.regexp.python", "patterns": [ diff --git a/extensions/search-result/syntaxes/generateTMLanguage.js b/extensions/search-result/syntaxes/generateTMLanguage.js index 7a9eb9204f..23e0615a71 100644 --- a/extensions/search-result/syntaxes/generateTMLanguage.js +++ b/extensions/search-result/syntaxes/generateTMLanguage.js @@ -55,6 +55,7 @@ const mappings = [ ['vb', 'source.asp.vb.net'], ['xml', 'text.xml'], ['yaml', 'source.yaml'], + ['yml', 'source.yaml'], ]; const scopes = { @@ -113,8 +114,8 @@ mappings.forEach(([ext, scope, regexp]) => patterns: [ { name: [scopes.resultBlock.result.meta, scopes.resultBlock.result.metaMultiLine].join(' '), - begin: '^ ((\\d+) )', - while: '^ (?:((\\d+)(:))|((\\d+) ))', + begin: '^ (?:\\s*)((\\d+) )', + while: '^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))', beginCaptures: { '0': { name: scopes.resultBlock.result.prefix.meta }, '1': { name: scopes.resultBlock.result.prefix.metaContext }, @@ -132,7 +133,7 @@ mappings.forEach(([ext, scope, regexp]) => patterns: [{ include: scope }] }, { - begin: '^ ((\\d+)(:))', + begin: '^ (?:\\s*)((\\d+)(:))', while: '(?=not)possible', name: [scopes.resultBlock.result.meta, scopes.resultBlock.result.metaSingleLine].join(' '), beginCaptures: { @@ -214,7 +215,7 @@ const plainText = [ } }, { - match: '^ (?:((\\d+)(:))|((\\d+)( ))(.*))', + match: '^ (?:\\s*)(?:((\\d+)(:))|((\\d+)( ))(.*))', name: [scopes.resultBlock.meta, scopes.resultBlock.result.meta].join(' '), captures: { '1': { name: [scopes.resultBlock.result.prefix.meta, scopes.resultBlock.result.prefix.metaMatch].join(' ') }, diff --git a/extensions/search-result/syntaxes/searchResult.tmLanguage.json b/extensions/search-result/syntaxes/searchResult.tmLanguage.json index 8edb4a7f89..a8a5557c3e 100644 --- a/extensions/search-result/syntaxes/searchResult.tmLanguage.json +++ b/extensions/search-result/syntaxes/searchResult.tmLanguage.json @@ -240,6 +240,9 @@ { "include": "#yaml" }, + { + "include": "#yml" + }, { "match": "^(?!\\s)(.*?)([^\\\\\\/\\n]*)(:)$", "name": "meta.resultBlock.search string meta.path.search", @@ -256,7 +259,7 @@ } }, { - "match": "^ (?:((\\d+)(:))|((\\d+)( ))(.*))", + "match": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+)( ))(.*))", "name": "meta.resultBlock.search meta.resultLine.search", "captures": { "1": { @@ -299,8 +302,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -339,7 +342,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -385,8 +388,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -425,7 +428,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -471,8 +474,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -511,7 +514,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -557,8 +560,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -597,7 +600,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -643,8 +646,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -683,7 +686,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -729,8 +732,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -769,7 +772,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -815,8 +818,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -855,7 +858,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -901,8 +904,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -941,7 +944,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -987,8 +990,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1027,7 +1030,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1073,8 +1076,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1113,7 +1116,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1159,8 +1162,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1199,7 +1202,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1245,8 +1248,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1285,7 +1288,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1331,8 +1334,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1371,7 +1374,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1417,8 +1420,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1457,7 +1460,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1503,8 +1506,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1543,7 +1546,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1589,8 +1592,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1629,7 +1632,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1675,8 +1678,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1715,7 +1718,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1761,8 +1764,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1801,7 +1804,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1847,8 +1850,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1887,7 +1890,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -1933,8 +1936,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1973,7 +1976,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2019,8 +2022,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2059,7 +2062,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2105,8 +2108,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2145,7 +2148,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2191,8 +2194,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2231,7 +2234,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2277,8 +2280,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2317,7 +2320,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2363,8 +2366,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2403,7 +2406,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2449,8 +2452,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2489,7 +2492,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2535,8 +2538,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2575,7 +2578,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2621,8 +2624,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2661,7 +2664,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2707,8 +2710,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2747,7 +2750,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2793,8 +2796,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2833,7 +2836,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2879,8 +2882,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2919,7 +2922,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -2965,8 +2968,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3005,7 +3008,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3051,8 +3054,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3091,7 +3094,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3137,8 +3140,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3177,7 +3180,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3223,8 +3226,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3263,7 +3266,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3309,8 +3312,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3349,7 +3352,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3395,8 +3398,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3435,7 +3438,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3481,8 +3484,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3521,7 +3524,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3567,8 +3570,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3607,7 +3610,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3653,8 +3656,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3693,7 +3696,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3739,8 +3742,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3779,7 +3782,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3825,8 +3828,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3865,7 +3868,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3911,8 +3914,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3951,7 +3954,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -3997,8 +4000,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4037,7 +4040,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4083,8 +4086,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4123,7 +4126,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4169,8 +4172,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4209,7 +4212,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4255,8 +4258,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4295,7 +4298,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4341,8 +4344,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4381,7 +4384,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4427,8 +4430,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4467,7 +4470,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4513,8 +4516,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4553,7 +4556,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4599,8 +4602,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4639,7 +4642,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4685,8 +4688,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4725,7 +4728,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4771,8 +4774,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4811,7 +4814,7 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { @@ -4857,8 +4860,8 @@ "patterns": [ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", - "begin": "^ ((\\d+) )", - "while": "^ (?:((\\d+)(:))|((\\d+) ))", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4897,7 +4900,93 @@ ] }, { - "begin": "^ ((\\d+)(:))", + "begin": "^ (?:\\s*)((\\d+)(:))", + "while": "(?=not)possible", + "name": "meta.resultLine.search meta.resultLine.singleLine.search", + "beginCaptures": { + "0": { + "name": "constant.numeric.integer meta.resultLinePrefix.search" + }, + "1": { + "name": "meta.resultLinePrefix.matchLinePrefix.search" + }, + "2": { + "name": "meta.resultLinePrefix.lineNumber.search" + }, + "3": { + "name": "punctuation.separator" + } + }, + "patterns": [ + { + "include": "source.yaml" + } + ] + } + ] + }, + "yml": { + "name": "meta.resultBlock.search", + "begin": "^(?!\\s)(.*?)([^\\\\\\/\\n]*\\.yml)(:)$", + "end": "^(?!\\s)", + "beginCaptures": { + "0": { + "name": "string meta.path.search" + }, + "1": { + "name": "meta.path.dirname.search" + }, + "2": { + "name": "meta.path.basename.search" + }, + "3": { + "name": "punctuation.separator" + } + }, + "patterns": [ + { + "name": "meta.resultLine.search meta.resultLine.multiLine.search", + "begin": "^ (?:\\s*)((\\d+) )", + "while": "^ (?:\\s*)(?:((\\d+)(:))|((\\d+) ))", + "beginCaptures": { + "0": { + "name": "constant.numeric.integer meta.resultLinePrefix.search" + }, + "1": { + "name": "meta.resultLinePrefix.contextLinePrefix.search" + }, + "2": { + "name": "meta.resultLinePrefix.lineNumber.search" + } + }, + "whileCaptures": { + "0": { + "name": "constant.numeric.integer meta.resultLinePrefix.search" + }, + "1": { + "name": "meta.resultLinePrefix.matchLinePrefix.search" + }, + "2": { + "name": "meta.resultLinePrefix.lineNumber.search" + }, + "3": { + "name": "punctuation.separator" + }, + "4": { + "name": "meta.resultLinePrefix.contextLinePrefix.search" + }, + "5": { + "name": "meta.resultLinePrefix.lineNumber.search" + } + }, + "patterns": [ + { + "include": "source.yaml" + } + ] + }, + { + "begin": "^ (?:\\s*)((\\d+)(:))", "while": "(?=not)possible", "name": "meta.resultLine.search meta.resultLine.singleLine.search", "beginCaptures": { diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json index d99e050d30..0f3815b2b6 100644 --- a/extensions/vscode-colorize-tests/package.json +++ b/extensions/vscode-colorize-tests/package.json @@ -55,6 +55,14 @@ "fontStyle": "bold" } } + ], + "productIconThemes": [ + { + "id": "Test Product Icons", + "label": "The Test Product Icon Theme", + "path": "./producticons/test-product-icon-theme.json", + "_watch": true + } ] } } diff --git a/extensions/vscode-colorize-tests/producticons/ElegantIcons.woff b/extensions/vscode-colorize-tests/producticons/ElegantIcons.woff new file mode 100644 index 0000000000..393305253e Binary files /dev/null and b/extensions/vscode-colorize-tests/producticons/ElegantIcons.woff differ diff --git a/extensions/vscode-colorize-tests/producticons/index.html b/extensions/vscode-colorize-tests/producticons/index.html new file mode 100644 index 0000000000..0d34ddedb5 --- /dev/null +++ b/extensions/vscode-colorize-tests/producticons/index.html @@ -0,0 +1,3049 @@ +<!doctype html> +<html> + +<head> + <title>Your Font/Glyphs + + + + + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+

Class Names

+
+ + +  arrow_up + + + +  arrow_down + + + +  arrow_left + + + +  arrow_right + + + +  arrow_left-up + + + +  arrow_right-up + + + +  arrow_right-down + + + +  arrow_left-down + + + +  arrow-up-down + + + +  arrow_up-down_alt + + + +  arrow_left-right_alt + + + +  arrow_left-right + + + +  arrow_expand_alt2 + + + +  arrow_expand_alt + + + +  arrow_condense + + + +  arrow_expand + + + +  arrow_move + + + +  arrow_carrot-up + + + +  arrow_carrot-down + + + +  arrow_carrot-left + + + +  arrow_carrot-right + + + +  arrow_carrot-2up + + + +  arrow_carrot-2down + + + +  arrow_carrot-2left + + + +  arrow_carrot-2right + + + +  arrow_carrot-up_alt2 + + + +  arrow_carrot-down_alt2 + + + +  arrow_carrot-left_alt2 + + + +  arrow_carrot-right_alt2 + + + +  arrow_carrot-2up_alt2 + + + +  arrow_carrot-2down_alt2 + + + +  arrow_carrot-2left_alt2 + + + +  arrow_carrot-2right_alt2 + + + +  arrow_triangle-up + + + +  arrow_triangle-down + + + +  arrow_triangle-left + + + +  arrow_triangle-right + + + +  arrow_triangle-up_alt2 + + + +  arrow_triangle-down_alt2 + + + +  arrow_triangle-left_alt2 + + + +  arrow_triangle-right_alt2 + + + +  arrow_back + + + +  icon_minus-06 + + + +  icon_plus + + + +  icon_close + + + +  icon_check + + + +  icon_minus_alt2 + + + +  icon_plus_alt2 + + + +  icon_close_alt2 + + + +  icon_check_alt2 + + + +  icon_zoom-out_alt + + + +  icon_zoom-in_alt + + + +  icon_search + + + +  icon_box-empty + + + +  icon_box-selected + + + +  icon_minus-box + + + +  icon_plus-box + + + +  icon_box-checked + + + +  icon_circle-empty + + + +  icon_circle-slelected + + + +  icon_stop_alt2 + + + +  icon_stop + + + +  icon_pause_alt2 + + + +  icon_pause + + + +  icon_menu + + + +  icon_menu-square_alt2 + + + +  icon_menu-circle_alt2 + + + +  icon_ul + + + +  icon_ol + + + +  icon_adjust-horiz + + + +  icon_adjust-vert + + + +  icon_document_alt + + + +  icon_documents_alt + + + +  icon_pencil + + + +  icon_pencil-edit_alt + + + +  icon_pencil-edit + + + +  icon_folder-alt + + + +  icon_folder-open_alt + + + +  icon_folder-add_alt + + + +  icon_info_alt + + + +  icon_error-oct_alt + + + +  icon_error-circle_alt + + + +  icon_error-triangle_alt + + + +  icon_question_alt2 + + + +  icon_question + + + +  icon_comment_alt + + + +  icon_chat_alt + + + +  icon_vol-mute_alt + + + +  icon_volume-low_alt + + + +  icon_volume-high_alt + + + +  icon_quotations + + + +  icon_quotations_alt2 + + + +  icon_clock_alt + + + +  icon_lock_alt + + + +  icon_lock-open_alt + + + +  icon_key_alt + + + +  icon_cloud_alt + + + +  icon_cloud-upload_alt + + + +  icon_cloud-download_alt + + + +  icon_image + + + +  icon_images + + + +  icon_lightbulb_alt + + + +  icon_gift_alt + + + +  icon_house_alt + + + +  icon_genius + + + +  icon_mobile + + + +  icon_tablet + + + +  icon_laptop + + + +  icon_desktop + + + +  icon_camera_alt + + + +  icon_mail_alt + + + +  icon_cone_alt + + + +  icon_ribbon_alt + + + +  icon_bag_alt + + + +  icon_creditcard + + + +  icon_cart_alt + + + +  icon_paperclip + + + +  icon_tag_alt + + + +  icon_tags_alt + + + +  icon_trash_alt + + + +  icon_cursor_alt + + + +  icon_mic_alt + + + +  icon_compass_alt + + + +  icon_pin_alt + + + +  icon_pushpin_alt + + + +  icon_map_alt + + + +  icon_drawer_alt + + + +  icon_toolbox_alt + + + +  icon_book_alt + + + +  icon_calendar + + + +  icon_film + + + +  icon_table + + + +  icon_contacts_alt + + + +  icon_headphones + + + +  icon_lifesaver + + + +  icon_piechart + + + +  icon_refresh + + + +  icon_link_alt + + + +  icon_link + + + +  icon_loading + + + +  icon_blocked + + + +  icon_archive_alt + + + +  icon_heart_alt + + +
+ + + +  icon_printer + + + +  icon_calulator + + + +  icon_building + + + +  icon_floppy + + + +  icon_drive + + + +  icon_search-2 + + + +  icon_id + + + +  icon_id-2 + + + +  icon_puzzle + + + +  icon_like + + + +  icon_dislike + + + +  icon_mug + + + +  icon_currency + + + +  icon_wallet + + + +  icon_pens + + + +  icon_easel + + + +  icon_flowchart + + + +  icon_datareport + + + +  icon_briefcase + + + +  icon_shield + + + +  icon_percent + + + +  icon_globe + + + +  icon_globe-2 + + + +  icon_target + + + +  icon_hourglass + + + +  icon_balance + + +
+ + + +  icon_star_alt + + + +  icon_star-half_alt + + + +  icon_star + + + +  icon_star-half + + + +  icon_tools + + + +  icon_tool + + + +  icon_cog + + + +  icon_cogs + + + +  arrow_up_alt + + + +  arrow_down_alt + + + +  arrow_left_alt + + + +  arrow_right_alt + + + +  arrow_left-up_alt + + + +  arrow_right-up_alt + + + +  arrow_right-down_alt + + + +  arrow_left-down_alt + + + +  arrow_condense_alt + + + +  arrow_expand_alt3 + + + +  arrow_carrot_up_alt + + + +  arrow_carrot-down_alt + + + +  arrow_carrot-left_alt + + + +  arrow_carrot-right_alt + + + +  arrow_carrot-2up_alt + + + +  arrow_carrot-2dwnn_alt + + + +  arrow_carrot-2left_alt + + + +  arrow_carrot-2right_alt + + + +  arrow_triangle-up_alt + + + +  arrow_triangle-down_alt + + + +  arrow_triangle-left_alt + + + +  arrow_triangle-right_alt + + + +  icon_minus_alt + + + +  icon_plus_alt + + + +  icon_close_alt + + + +  icon_check_alt + + + +  icon_zoom-out + + + +  icon_zoom-in + + + +  icon_stop_alt + + + +  icon_menu-square_alt + + + +  icon_menu-circle_alt + + + +  icon_document + + + +  icon_documents + + + +  icon_pencil_alt + + + +  icon_folder + + + +  icon_folder-open + + + +  icon_folder-add + + + +  icon_folder_upload + + + +  icon_folder_download + + + +  icon_info + + + +  icon_error-circle + + + +  icon_error-oct + + + +  icon_error-triangle + + + +  icon_question_alt + + + +  icon_comment + + + +  icon_chat + + + +  icon_vol-mute + + + +  icon_volume-low + + + +  icon_volume-high + + + +  icon_quotations_alt + + + +  icon_clock + + + +  icon_lock + + + +  icon_lock-open + + + +  icon_key + + + +  icon_cloud + + + +  icon_cloud-upload + + + +  icon_cloud-download + + + +  icon_lightbulb + + + +  icon_gift + + + +  icon_house + + + +  icon_camera + + + +  icon_mail + + + +  icon_cone + + + +  icon_ribbon + + + +  icon_bag + + + +  icon_cart + + + +  icon_tag + + + +  icon_tags + + + +  icon_trash + + + +  icon_cursor + + + +  icon_mic + + + +  icon_compass + + + +  icon_pin + + + +  icon_pushpin + + + +  icon_map + + + +  icon_drawer + + + +  icon_toolbox + + + +  icon_book + + + +  icon_contacts + + + +  icon_archive + + + +  icon_heart + + + +  icon_profile + + + +  icon_group + + + +  icon_grid-2x2 + + + +  icon_grid-3x3 + + + +  icon_music + + + +  icon_pause_alt + + + +  icon_phone + + + +  icon_upload + + + +  icon_download + + + +  icon_rook + + +
+ + + +  icon_printer-alt + + + +  icon_calculator_alt + + + +  icon_building_alt + + + +  icon_floppy_alt + + + +  icon_drive_alt + + + +  icon_search_alt + + + +  icon_id_alt + + + +  icon_id-2_alt + + + +  icon_puzzle_alt + + + +  icon_like_alt + + + +  icon_dislike_alt + + + +  icon_mug_alt + + + +  icon_currency_alt + + + +  icon_wallet_alt + + + +  icon_pens_alt + + + +  icon_easel_alt + + + +  icon_flowchart_alt + + + +  icon_datareport_alt + + + +  icon_briefcase_alt + + + +  icon_shield_alt + + + +  icon_percent_alt + + + +  icon_globe_alt + + + +  icon_clipboard + + +
+ + + +  social_facebook + + + +  social_twitter + + + +  social_pinterest + + + +  social_googleplus + + + +  social_tumblr + + + +  social_tumbleupon + + + +  social_wordpress + + + +  social_instagram + + + +  social_dribbble + + + +  social_vimeo + + + +  social_linkedin + + + +  social_rss + + + +  social_deviantart + + + +  social_share + + + +  social_myspace + + + +  social_skype + + + +  social_youtube + + + +  social_picassa + + + +  social_googledrive + + + +  social_flickr + + + +  social_blogger + + + +  social_spotify + + + +  social_delicious + + + +  social_facebook_circle + + + +  social_twitter_circle + + + +  social_pinterest_circle + + + +  social_googleplus_circle + + + +  social_tumblr_circle + + + +  social_stumbleupon_circle + + + +  social_wordpress_circle + + + +  social_instagram_circle + + + +  social_dribbble_circle + + + +  social_vimeo_circle + + + +  social_linkedin_circle + + + +  social_rss_circle + + + +  social_deviantart_circle + + + +  social_share_circle + + + +  social_myspace_circle + + + +  social_skype_circle + + + +  social_youtube_circle + + + +  social_picassa_circle + + + +  social_googledrive_alt2 + + + +  social_flickr_circle + + + +  social_blogger_circle + + + +  social_spotify_circle + + + +  social_delicious_circle + + + +  social_facebook_square + + + +  social_twitter_square + + + +  social_pinterest_square + + + +  social_googleplus_square + + + +  social_tumblr_square + + + +  social_stumbleupon_square + + + +  social_wordpress_square + + + +  social_instagram_square + + + +  social_dribbble_square + + + +  social_vimeo_square + + + +  social_linkedin_square + + + +  social_rss_square + + + +  social_deviantart_square + + + +  social_share_square + + + +  social_myspace_square + + + +  social_skype_square + + + +  social_youtube_square + + + +  social_picassa_square + + + +  social_googledrive_square + + + +  social_flickr_square + + + +  social_blogger_square + + + +  social_spotify_square + + + +  social_delicious_square + +
+ +
+ + + + diff --git a/src/vs/editor/common/standalone/promise-polyfill/polyfill.license.txt b/extensions/vscode-colorize-tests/producticons/mit_license.txt similarity index 92% rename from src/vs/editor/common/standalone/promise-polyfill/polyfill.license.txt rename to extensions/vscode-colorize-tests/producticons/mit_license.txt index 6f7c012316..effefee5f0 100644 --- a/src/vs/editor/common/standalone/promise-polyfill/polyfill.license.txt +++ b/extensions/vscode-colorize-tests/producticons/mit_license.txt @@ -1,5 +1,6 @@ -Copyright (c) 2014 Taylor Hakes -Copyright (c) 2014 Forbes Lindesay +The MIT License (MIT) + +Copyright (c) <2013> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -17,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +THE SOFTWARE. \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/producticons/test-product-icon-theme.json b/extensions/vscode-colorize-tests/producticons/test-product-icon-theme.json new file mode 100644 index 0000000000..dc076aef5f --- /dev/null +++ b/extensions/vscode-colorize-tests/producticons/test-product-icon-theme.json @@ -0,0 +1,43 @@ +{ + // ElegantIcons from https://www.elegantthemes.com/icons/elegant_font.zip + "fonts": [ + { + "id": "elegant", + "src": [ + { + "path": "./ElegantIcons.woff", + "format": "woff" + } + ], + "weight": "normal", + "style": "normal", + } + ], + "iconDefinitions": { + "chevron-down": { + "fontCharacter": "\\43", + }, + "chevron-right": { + "fontCharacter": "\\45" + }, + "error": { + "fontCharacter": "\\e062" + }, + "warning": { + "fontCharacter": "\\e063" + }, + "settings-gear": { + "fontCharacter": "\\e030" + }, + "files": { + "fontCharacter": "\\e056" + }, + "extensions": { + "fontCharacter": "\\e015" + }, + "debug-alt-2": { + "fontCharacter": "\\e072" + } + + } +} diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 43a70c058c..83bd84cdd9 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@3.8.2: - version "3.8.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.2.tgz#91d6868aaead7da74f493c553aeff76c0c0b1d5a" - integrity sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ== +typescript@3.8.3: + version "3.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" + integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== diff --git a/package.json b/package.json index 85447e1fe5..80e09773b9 100644 --- a/package.json +++ b/package.json @@ -179,7 +179,7 @@ "style-loader": "^1.0.0", "ts-loader": "^4.4.2", "typemoq": "^0.3.2", - "typescript": "3.8.2", + "typescript": "3.9.0-dev.20200304", "typescript-formatter": "7.1.0", "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", diff --git a/src/bootstrap-fork.js b/src/bootstrap-fork.js index 531e1be448..422168128a 100644 --- a/src/bootstrap-fork.js +++ b/src/bootstrap-fork.js @@ -8,6 +8,9 @@ const bootstrap = require('./bootstrap'); +// Remove global paths from the node module lookup +bootstrap.removeGlobalNodeModuleLookupPaths(); + // Enable ASAR in our forked processes bootstrap.enableASARSupport(); diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index 697e369f0b..bcd6c4a5c9 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -31,7 +31,7 @@ exports.load = function (modulePaths, resultCallback, options) { const args = parseURLQueryArgs(); /** - * // configuration: IWindowConfiguration + * // configuration: INativeWindowConfiguration * @type {{ * zoomLevel?: number, * extensionDevelopmentPath?: string[], diff --git a/src/bootstrap.js b/src/bootstrap.js index 58e1408811..11f110d08f 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -53,6 +53,29 @@ exports.injectNodeModuleLookupPath = function (injectPath) { }; //#endregion +//#region Remove global paths from the node lookup paths + +exports.removeGlobalNodeModuleLookupPaths = function() { + // @ts-ignore + const Module = require('module'); + // @ts-ignore + const globalPaths = Module.globalPaths; + + // @ts-ignore + const originalResolveLookupPaths = Module._resolveLookupPaths; + + // @ts-ignore + Module._resolveLookupPaths = function (moduleName, parent) { + const paths = originalResolveLookupPaths(moduleName, parent); + let commonSuffixLength = 0; + while (commonSuffixLength < paths.length && paths[paths.length - 1 - commonSuffixLength] === globalPaths[globalPaths.length - 1 - commonSuffixLength]) { + commonSuffixLength++; + } + return paths.slice(0, paths.length - commonSuffixLength); + }; +}; +//#endregion + //#region Add support for using node_modules.asar /** * @param {string=} nodeModulesPath diff --git a/src/main.js b/src/main.js index 292b3979b5..e572053219 100644 --- a/src/main.js +++ b/src/main.js @@ -182,7 +182,7 @@ function configureCommandlineSwitchesSync(cliArgs) { app.commandLine.appendSwitch('js-flags', jsFlags); } - // TODO@Ben TODO@Deepak Electron 7 workaround for https://github.com/microsoft/vscode/issues/88873 + // TODO@Deepak Electron 7 workaround for https://github.com/microsoft/vscode/issues/88873 app.commandLine.appendSwitch('disable-features', 'LayoutNG'); return argvConfig; diff --git a/src/sql/base/browser/ui/dropdownList/dropdownList.ts b/src/sql/base/browser/ui/dropdownList/dropdownList.ts index 8ccc0a1de5..101cb8a914 100644 --- a/src/sql/base/browser/ui/dropdownList/dropdownList.ts +++ b/src/sql/base/browser/ui/dropdownList/dropdownList.ts @@ -76,7 +76,7 @@ export class DropdownList extends Dropdown { } })); - this._register(this._list.onSelectionChange(() => { + this._register(this._list.onDidChangeSelection(() => { // focus on the dropdown label then hide the dropdown list this.element.focus(); this.hide(); diff --git a/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts b/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts index d40bbaeb54..7085c05633 100644 --- a/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts +++ b/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts @@ -468,7 +468,7 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements const model = this._untitledEditorService.create({ untitledResource: uri, mode: 'notebook', initialValue: options.initialContent }); fileInput = this._instantiationService.createInstance(UntitledTextEditorInput, model); } else { - fileInput = this._editorService.createInput({ forceFile: true, resource: uri, mode: 'notebook' }) as FileEditorInput; + fileInput = this._editorService.createEditorInput({ forceFile: true, resource: uri, mode: 'notebook' }) as FileEditorInput; } } let input: NotebookInput; diff --git a/src/sql/workbench/api/browser/mainThreadQueryEditor.ts b/src/sql/workbench/api/browser/mainThreadQueryEditor.ts index af0340a5be..8a6ef7e1fb 100644 --- a/src/sql/workbench/api/browser/mainThreadQueryEditor.ts +++ b/src/sql/workbench/api/browser/mainThreadQueryEditor.ts @@ -38,7 +38,7 @@ export class MainThreadQueryEditor extends Disposable implements MainThreadQuery public $connect(fileUri: string, connectionId: string): Thenable { return new Promise((resolve, reject) => { - let editors = this._editorService.visibleControls.filter(resource => { + let editors = this._editorService.visibleEditorPanes.filter(resource => { return !!resource && resource.input.resource.toString() === fileUri; }); let editor = editors && editors.length > 0 ? editors[0] : undefined; @@ -75,7 +75,7 @@ export class MainThreadQueryEditor extends Disposable implements MainThreadQuery public $connectWithProfile(fileUri: string, connection: azdata.connection.ConnectionProfile): Thenable { return new Promise(async (resolve, reject) => { - let editors = this._editorService.visibleControls.filter(resource => { + let editors = this._editorService.visibleEditorPanes.filter(resource => { return !!resource && resource.input.resource.toString() === fileUri; }); let editor = editors && editors.length > 0 ? editors[0] : undefined; @@ -97,7 +97,7 @@ export class MainThreadQueryEditor extends Disposable implements MainThreadQuery } public $runQuery(fileUri: string, runCurrentQuery: boolean = true): void { - let filteredEditors = this._editorService.visibleControls.filter(editor => editor.input.resource.toString() === fileUri); + let filteredEditors = this._editorService.visibleEditorPanes.filter(editor => editor.input.resource.toString() === fileUri); if (filteredEditors && filteredEditors.length > 0) { let editor = filteredEditors[0]; if (editor instanceof QueryEditor) { @@ -118,7 +118,7 @@ export class MainThreadQueryEditor extends Disposable implements MainThreadQuery } public $createQueryTab(fileUri: string, title: string, componentId: string): void { - let editors = this._editorService.visibleControls.filter(resource => { + let editors = this._editorService.visibleEditorPanes.filter(resource => { return !!resource && resource.input.resource.toString() === fileUri; }); diff --git a/src/sql/workbench/browser/modal/modal.ts b/src/sql/workbench/browser/modal/modal.ts index 5eba61c79a..52b56a6fff 100644 --- a/src/sql/workbench/browser/modal/modal.ts +++ b/src/sql/workbench/browser/modal/modal.ts @@ -20,13 +20,13 @@ import { localize } from 'vs/nls'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { find, firstIndex } from 'vs/base/common/arrays'; import { IThemable } from 'vs/base/common/styler'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export enum MessageLevel { Error = 0, @@ -151,7 +151,7 @@ export abstract class Modal extends Disposable implements IThemable { private _title: string, private _name: string, private readonly _telemetryService: IAdsTelemetryService, - protected readonly layoutService: IWorkbenchLayoutService, + protected readonly layoutService: ILayoutService, protected readonly _clipboardService: IClipboardService, protected readonly _themeService: IThemeService, protected readonly logService: ILogService, @@ -181,7 +181,7 @@ export abstract class Modal extends Disposable implements IThemable { } this._bodyContainer = DOM.$(`.${builderClass}`, { role: 'dialog', 'aria-label': this._title }); - const top = this.layoutService.getTitleBarOffset(); + const top = this.layoutService.offset?.top ?? 0; this._bodyContainer.style.top = `${top}px`; this._modalDialog = DOM.append(this._bodyContainer, DOM.$('.modal-dialog')); this._modalContent = DOM.append(this._modalDialog, DOM.$('.modal-content')); diff --git a/src/sql/workbench/browser/modal/optionsDialog.ts b/src/sql/workbench/browser/modal/optionsDialog.ts index c8623a8bb7..f753e0bc9f 100644 --- a/src/sql/workbench/browser/modal/optionsDialog.ts +++ b/src/sql/workbench/browser/modal/optionsDialog.ts @@ -27,9 +27,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { append, $ } from 'vs/base/browser/dom'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -38,6 +37,7 @@ import { IViewDescriptorService } from 'vs/workbench/common/views'; import { ServiceOptionType } from 'sql/platform/connection/common/interfaces'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export class CategoryView extends ViewPane { @@ -98,7 +98,7 @@ export class OptionsDialog extends Modal { title: string, name: string, options: IOptionsDialogOptions, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IThemeService themeService: IThemeService, @IContextViewService private _contextViewService: IContextViewService, @IInstantiationService private _instantiationService: IInstantiationService, @@ -123,8 +123,8 @@ export class OptionsDialog extends Modal { // Theme styler attachButtonStyler(okButton, this._themeService); attachButtonStyler(closeButton, this._themeService); - this._register(this._themeService.onThemeChange(e => this.updateTheme(e))); - this.updateTheme(this._themeService.getTheme()); + this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e))); + this.updateTheme(this._themeService.getColorTheme()); } protected renderBody(container: HTMLElement) { @@ -142,7 +142,7 @@ export class OptionsDialog extends Modal { } // Update theming that is specific to options dialog flyout body - private updateTheme(theme: ITheme): void { + private updateTheme(theme: IColorTheme): void { let borderColor = theme.getColor(contrastBorder); let border = borderColor ? borderColor.toString() : null; if (this._dividerBuilder) { @@ -239,7 +239,7 @@ export class OptionsDialog extends Modal { this.fillInOptions(bodyContainer, serviceOptions); let viewSize = this._optionCategoryPadding + serviceOptions.length * this._optionRowSize; - let categoryView = this._instantiationService.createInstance(CategoryView, bodyContainer, viewSize, { title: category, ariaHeaderLabel: category, id: category }); + let categoryView = this._instantiationService.createInstance(CategoryView, bodyContainer, viewSize, { title: category, id: category }); this.splitview.addView(categoryView, viewSize); categoryView.render(); attachPanelStyler(categoryView, this._themeService); diff --git a/src/sql/workbench/browser/modelComponents/card.component.ts b/src/sql/workbench/browser/modelComponents/card.component.ts index 8c3b3155f4..c95952f26a 100644 --- a/src/sql/workbench/browser/modelComponents/card.component.ts +++ b/src/sql/workbench/browser/modelComponents/card.component.ts @@ -11,13 +11,14 @@ import { import * as azdata from 'azdata'; import * as colors from 'vs/platform/theme/common/colorRegistry'; -import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ComponentWithIconBase } from 'sql/workbench/browser/modelComponents/componentWithIconBase'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import * as DOM from 'vs/base/browser/dom'; import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/platform/dashboard/browser/interfaces'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; export interface ActionDescriptor { label: string; diff --git a/src/sql/workbench/browser/modelComponents/editor.component.ts b/src/sql/workbench/browser/modelComponents/editor.component.ts index f6010527f9..5822085c74 100644 --- a/src/sql/workbench/browser/modelComponents/editor.component.ts +++ b/src/sql/workbench/browser/modelComponents/editor.component.ts @@ -68,7 +68,7 @@ export default class EditorComponent extends ComponentBase implements IComponent this._editor.create(this._el.nativeElement); this._editor.setVisible(true); let uri = this.createUri(); - this._editorInput = this.editorService.createInput({ forceUntitled: true, resource: uri, mode: 'plaintext' }) as UntitledTextEditorInput; + this._editorInput = this.editorService.createEditorInput({ forceUntitled: true, resource: uri, mode: 'plaintext' }) as UntitledTextEditorInput; await this._editor.setInput(this._editorInput, undefined); const model = await this._editorInput.resolve(); this._editorModel = model.textEditorModel; diff --git a/src/sql/workbench/browser/modelComponents/modelComponentWrapper.component.ts b/src/sql/workbench/browser/modelComponents/modelComponentWrapper.component.ts index d534d0b5e3..dea541c011 100644 --- a/src/sql/workbench/browser/modelComponents/modelComponentWrapper.component.ts +++ b/src/sql/workbench/browser/modelComponents/modelComponentWrapper.component.ts @@ -19,7 +19,7 @@ import { memoize } from 'vs/base/common/decorators'; import { generateUuid } from 'vs/base/common/uuid'; import { Event } from 'vs/base/common/event'; import { LayoutRequestParams } from 'sql/workbench/services/dialog/browser/dialogContainer.component'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { ILogService } from 'vs/platform/log/common/log'; import { IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams'; import { IComponentDescriptor, IModelStore, IComponent } from 'sql/platform/dashboard/browser/interfaces'; @@ -75,11 +75,11 @@ export class ModelComponentWrapper extends AngularDisposable implements OnInit { } ngOnInit() { - this._register(this.themeService.onThemeChange(event => this.updateTheme(event))); + this._register(this.themeService.onDidColorThemeChange(event => this.updateTheme(event))); } ngAfterViewInit() { - this.updateTheme(this.themeService.getTheme()); + this.updateTheme(this.themeService.getColorTheme()); if (this.componentHost) { this.loadComponent(); } @@ -148,7 +148,7 @@ export class ModelComponentWrapper extends AngularDisposable implements OnInit { el.style.position = 'relative'; } - private updateTheme(theme: ITheme): void { + private updateTheme(theme: IColorTheme): void { // TODO handle theming appropriately let el = this._ref.nativeElement; let backgroundColor = theme.getColor(colors.editorBackground, true); diff --git a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts index d7937ea81b..124aea6b13 100644 --- a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts +++ b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts @@ -126,7 +126,7 @@ export class QueryTextEditor extends BaseTextEditor { return editorWidget.getScrollHeight(); } - public setHeightToScrollHeight(configChanged?: boolean, isEditorCollapsed?: boolean, ) { + public setHeightToScrollHeight(configChanged?: boolean, isEditorCollapsed?: boolean,) { let editorWidget = this.getControl() as ICodeEditor; let layoutInfo = editorWidget.getLayoutInfo(); if (!this._scrollbarHeight) { diff --git a/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts b/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts index b9ba8d3bb2..2f33759c44 100644 --- a/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts +++ b/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts @@ -138,7 +138,7 @@ export class TreeComponentRenderer extends Disposable implements IRenderer { * Render a element, given an object bag returned by the template */ public renderElement(tree: ITree, element: ITreeComponentItem, templateId: string, templateData: TreeDataTemplate): void { - const icon = this.themeService.getTheme().type === LIGHT ? element.icon : element.iconDark; + const icon = this.themeService.getColorTheme().type === LIGHT ? element.icon : element.iconDark; const iconUri = icon ? URI.revive(icon) : null; templateData.icon.style.backgroundImage = iconUri ? `url('${iconUri.toString(true)}')` : ''; templateData.icon.style.backgroundRepeat = 'no-repeat'; diff --git a/src/sql/workbench/browser/parts/editor/editorStatusModeSelect.ts b/src/sql/workbench/browser/parts/editor/editorStatusModeSelect.ts index 46f36a2ff8..f973a7e450 100644 --- a/src/sql/workbench/browser/parts/editor/editorStatusModeSelect.ts +++ b/src/sql/workbench/browser/parts/editor/editorStatusModeSelect.ts @@ -20,8 +20,8 @@ const languageAssociationRegistry = Registry.as(La */ export async function setMode(accessor: ServicesAccessor, modeSupport: IModeSupport, activeEditor: IEditorInput, language: string): Promise { const editorService = accessor.get(IEditorService); - const activeWidget = getCodeEditor(editorService.activeTextEditorWidget); - const activeControl = editorService.activeControl; + const activeWidget = getCodeEditor(editorService.activeTextEditorControl); + const activeControl = editorService.activeEditorPane; const textModel = activeWidget.getModel(); const oldLanguage = textModel.getLanguageIdentifier().language; if (language !== oldLanguage) { diff --git a/src/sql/workbench/browser/parts/views/customView.ts b/src/sql/workbench/browser/parts/views/customView.ts index 2cd09acce8..88f6d16e6b 100644 --- a/src/sql/workbench/browser/parts/views/customView.ts +++ b/src/sql/workbench/browser/parts/views/customView.ts @@ -27,7 +27,7 @@ import { URI } from 'vs/base/common/uri'; import { dirname, basename } from 'vs/base/common/resources'; import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService'; import { FileKind } from 'vs/platform/files/common/files'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { localize } from 'vs/nls'; import { timeout } from 'vs/base/common/async'; import { editorFindMatchHighlight, editorFindMatchHighlightBorder, textLinkForeground, textCodeBlockBackground, focusBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -52,7 +52,7 @@ import { firstIndex } from 'vs/base/common/arrays'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -export class CustomTreeViewPanel extends ViewPane { +export class CustomTreeViewPane extends ViewPane { private treeView: ITreeView; @@ -69,13 +69,14 @@ export class CustomTreeViewPanel extends ViewPane { @IThemeService protected themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super({ ...(options as IViewPaneOptions) }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id)); this.treeView = treeView as ITreeView; this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this)); this._register(this.treeView.onDidChangeTitle((newTitle) => this.updateTitle(newTitle))); this._register(toDisposable(() => this.treeView.setVisibility(false))); this._register(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility())); + this._register(this.treeView.onDidChangeWelcomeState(() => this._onDidChangeViewWelcomeState.fire())); this.updateTreeVisibility(); } @@ -90,18 +91,14 @@ export class CustomTreeViewPanel extends ViewPane { } } + shouldShowWelcome(): boolean { + return (this.treeView.dataProvider === undefined) && (this.treeView.message === undefined); + } + layoutBody(height: number, width: number): void { this.treeView.layout(height, width); } - getActions(): IAction[] { - return [...this.treeView.getPrimaryActions()]; - } - - getSecondaryActions(): IAction[] { - return [...this.treeView.getSecondaryActions()]; - } - getActionViewItem(action: IAction): IActionViewItem | undefined { return action instanceof MenuItemAction ? new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService) : undefined; } @@ -209,6 +206,9 @@ export class CustomTreeView extends Disposable implements ITreeView { private readonly _onDidChangeActions: Emitter = this._register(new Emitter()); readonly onDidChangeActions: Event = this._onDidChangeActions.event; + private readonly _onDidChangeWelcomeState: Emitter = this._register(new Emitter()); + readonly onDidChangeWelcomeState: Event = this._onDidChangeWelcomeState.event; + private readonly _onDidChangeTitle: Emitter = this._register(new Emitter()); readonly onDidChangeTitle: Event = this._onDidChangeTitle.event; @@ -232,7 +232,7 @@ export class CustomTreeView extends Disposable implements ITreeView { this.menus = this._register(instantiationService.createInstance(TitleMenus, this.id)); this._register(this.menus.onDidChangeTitle(() => this._onDidChangeActions.fire())); this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); - this._register(this.themeService.onThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); + this._register(this.themeService.onDidColorThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('explorer.decorations')) { this.doRefresh([this.root]).catch(onUnexpectedError); /** soft refresh **/ @@ -277,6 +277,8 @@ export class CustomTreeView extends Disposable implements ITreeView { this._dataProvider = null; this.updateMessage(); } + + this._onDidChangeWelcomeState.fire(); } private _message: string | undefined; @@ -287,6 +289,7 @@ export class CustomTreeView extends Disposable implements ITreeView { set message(message: string | undefined) { this._message = message; this.updateMessage(); + this._onDidChangeWelcomeState.fire(); } get title(): string { @@ -448,7 +451,7 @@ export class CustomTreeView extends Disposable implements ITreeView { })); this.tree.setInput(this.root).then(() => this.updateContentAreas()); - const customTreeNavigator = new TreeResourceNavigator(this.tree); + const customTreeNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false }); this._register(customTreeNavigator); this._register(customTreeNavigator.onDidOpenResource(e => { if (!e.browserEvent) { @@ -809,7 +812,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer { + public revert(group: GroupIdentifier, options?: IRevertOptions): Promise { return this._text.revert(group, options); } diff --git a/src/sql/workbench/contrib/backup/browser/backup.component.ts b/src/sql/workbench/contrib/backup/browser/backup.component.ts index daeee97af2..5c18807874 100644 --- a/src/sql/workbench/contrib/backup/browser/backup.component.ts +++ b/src/sql/workbench/contrib/backup/browser/backup.component.ts @@ -30,10 +30,10 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { ITheme } from 'vs/platform/theme/common/themeService'; import { AngularDisposable } from 'sql/base/browser/lifecycle'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { fileFiltersSet } from 'sql/workbench/services/restore/common/constants'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; export const BACKUP_SELECTOR: string = 'backup-component'; @@ -349,7 +349,7 @@ export class BackupComponent extends AngularDisposable { this.mediaDescriptionBox.disable(); this.registerListeners(); - this.updateTheme(this.themeService.getTheme()); + this.updateTheme(this.themeService.getColorTheme()); } ngAfterViewInit() { @@ -552,7 +552,7 @@ export class BackupComponent extends AngularDisposable { } // Update theming that is specific to backup dialog - private updateTheme(theme: ITheme): void { + private updateTheme(theme: IColorTheme): void { // set modal footer style let footerHtmlElement: HTMLElement = this.modalFooterElement.nativeElement; const backgroundColor = theme.getColor(SIDE_BAR_BACKGROUND); diff --git a/src/sql/workbench/contrib/backup/browser/backupDialog.ts b/src/sql/workbench/contrib/backup/browser/backupDialog.ts index d27e334146..cac6f4faed 100644 --- a/src/sql/workbench/contrib/backup/browser/backupDialog.ts +++ b/src/sql/workbench/contrib/backup/browser/backupDialog.ts @@ -16,10 +16,10 @@ import { bootstrapAngular } from 'sql/workbench/services/bootstrap/browser/boots import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { append, $ } from 'vs/base/browser/dom'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export class BackupDialog extends Modal { private _body: HTMLElement; @@ -28,7 +28,7 @@ export class BackupDialog extends Modal { constructor( @IThemeService themeService: IThemeService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IAdsTelemetryService telemetryService: IAdsTelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService private _instantiationService: IInstantiationService, diff --git a/src/sql/workbench/contrib/charts/browser/graphInsight.ts b/src/sql/workbench/contrib/charts/browser/graphInsight.ts index a2cf42eb2a..4212bf78ac 100644 --- a/src/sql/workbench/contrib/charts/browser/graphInsight.ts +++ b/src/sql/workbench/contrib/charts/browser/graphInsight.ts @@ -9,7 +9,7 @@ import { mixin } from 'sql/base/common/objects'; import { localize } from 'vs/nls'; import * as colors from 'vs/platform/theme/common/colorRegistry'; import { editorLineNumbers } from 'vs/editor/common/view/editorColorRegistry'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { IInsight, IPointDataSet, customMixin } from './interfaces'; import { IInsightOptions, DataDirection, ChartType, LegendPosition, DataType } from 'sql/workbench/contrib/charts/common/interfaces'; @@ -53,14 +53,14 @@ export class Graph implements IInsight { public static readonly types = [ChartType.Bar, ChartType.Doughnut, ChartType.HorizontalBar, ChartType.Line, ChartType.Pie, ChartType.Scatter, ChartType.TimeSeries]; public readonly types = Graph.types; - private _theme: ITheme; + private _theme: IColorTheme; constructor( container: HTMLElement, options: IInsightOptions = defaultOptions, @IThemeService themeService: IThemeService ) { - this._theme = themeService.getTheme(); - themeService.onThemeChange(e => { + this._theme = themeService.getColorTheme(); + themeService.onDidColorThemeChange(e => { this._theme = e; this.data = this._data; }); diff --git a/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts b/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts index 8895f5b690..2f08d47657 100644 --- a/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts @@ -26,7 +26,7 @@ import { WebviewWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/w import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service'; -import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as colors from 'vs/platform/theme/common/colorRegistry'; import * as themeColors from 'vs/workbench/common/theme'; import { Action, IAction } from 'vs/base/common/actions'; @@ -37,6 +37,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { values } from 'vs/base/common/collections'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; const componentMap: { [x: string]: Type } = { 'properties-widget': PropertiesWidgetComponent, diff --git a/src/sql/workbench/contrib/dashboard/browser/contents/webviewContent.component.ts b/src/sql/workbench/contrib/dashboard/browser/contents/webviewContent.component.ts index 1a72c3a715..640136a2a9 100644 --- a/src/sql/workbench/contrib/dashboard/browser/contents/webviewContent.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/contents/webviewContent.component.ts @@ -98,7 +98,7 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo this._onMessageDisposable.dispose(); } - this._webview = this.webviewService.createWebview(this.id, + this._webview = this.webviewService.createWebviewElement(this.id, {}, { allowScripts: true diff --git a/src/sql/workbench/contrib/dashboard/browser/core/dashboardPanelStyles.ts b/src/sql/workbench/contrib/dashboard/browser/core/dashboardPanelStyles.ts index 77a375ba56..ecfa8c2cbb 100644 --- a/src/sql/workbench/contrib/dashboard/browser/core/dashboardPanelStyles.ts +++ b/src/sql/workbench/contrib/dashboard/browser/core/dashboardPanelStyles.ts @@ -5,14 +5,14 @@ import 'vs/css!./dashboardPanel'; -import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_ACTIVE_BORDER, TAB_INACTIVE_BACKGROUND, TAB_INACTIVE_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BACKGROUND, TAB_BORDER, EDITOR_GROUP_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Title Active const tabActiveBackground = theme.getColor(TAB_ACTIVE_BACKGROUND); @@ -112,4 +112,4 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { } `); } -}); \ No newline at end of file +}); diff --git a/src/sql/workbench/contrib/dashboard/browser/dashboard.component.ts b/src/sql/workbench/contrib/dashboard/browser/dashboard.component.ts index 84684df61e..a10fc25212 100644 --- a/src/sql/workbench/contrib/dashboard/browser/dashboard.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/dashboard.component.ts @@ -15,10 +15,11 @@ import { RefreshWidgetAction, EditDashboardAction } from 'sql/workbench/contrib/ import { DashboardPage } from 'sql/workbench/contrib/dashboard/browser/core/dashboardPage.component'; import { AngularDisposable } from 'sql/base/browser/lifecycle'; -import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IDisposable } from 'vs/base/common/lifecycle'; import * as themeColors from 'vs/workbench/common/theme'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { onUnexpectedError } from 'vs/base/common/errors'; export const DASHBOARD_SELECTOR: string = 'dashboard-component'; diff --git a/src/sql/workbench/contrib/dashboard/browser/widgets/insights/views/charts/chartInsight.component.ts b/src/sql/workbench/contrib/dashboard/browser/widgets/insights/views/charts/chartInsight.component.ts index a79cfdc96b..081afbc951 100644 --- a/src/sql/workbench/contrib/dashboard/browser/widgets/insights/views/charts/chartInsight.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/widgets/insights/views/charts/chartInsight.component.ts @@ -15,7 +15,7 @@ import * as colors from 'vs/platform/theme/common/colorRegistry'; import * as types from 'vs/base/common/types'; import { Disposable } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { IPointDataSet } from 'sql/workbench/contrib/charts/browser/interfaces'; import { IInsightsView, IInsightData } from 'sql/platform/dashboard/browser/insightRegistry'; import { ChartType, LegendPosition } from 'sql/workbench/contrib/charts/common/interfaces'; @@ -61,8 +61,8 @@ export abstract class ChartInsight extends Disposable implements IInsightsView { } init() { - this._register(this.themeService.onThemeChange(e => this.updateTheme(e))); - this.updateTheme(this.themeService.getTheme()); + this._register(this.themeService.onDidColorThemeChange(e => this.updateTheme(e))); + this.updateTheme(this.themeService.getColorTheme()); // Note: must use a boolean to not render the canvas until all properties such as the labels and chart type are set. // This is because chart.js doesn't auto-update anything other than dataset when re-rendering so defaults are used // hence it's easier to not render until ready @@ -96,7 +96,7 @@ export abstract class ChartInsight extends Disposable implements IInsightsView { return this._options; } - protected updateTheme(e: ITheme): void { + protected updateTheme(e: IColorTheme): void { const foregroundColor = e.getColor(colors.editorForeground); const foreground = foregroundColor ? foregroundColor.toString() : null; const backgroundColor = e.getColor(colors.editorBackground); diff --git a/src/sql/workbench/contrib/dashboard/browser/widgets/insights/views/charts/types/barChart.component.ts b/src/sql/workbench/contrib/dashboard/browser/widgets/insights/views/charts/types/barChart.component.ts index 13c873c464..4afe2d50e7 100644 --- a/src/sql/workbench/contrib/dashboard/browser/widgets/insights/views/charts/types/barChart.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/widgets/insights/views/charts/types/barChart.component.ts @@ -10,7 +10,7 @@ import { IChartConfig } from 'sql/workbench/contrib/dashboard/browser/widgets/in import * as colors from 'vs/platform/theme/common/colorRegistry'; import { editorLineNumbers } from 'vs/editor/common/view/editorColorRegistry'; import { ChangeDetectorRef, Inject, forwardRef } from '@angular/core'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { customMixin } from 'sql/workbench/contrib/charts/browser/interfaces'; import { ChartType } from 'sql/workbench/contrib/charts/common/interfaces'; @@ -127,7 +127,7 @@ export default class BarChart extends ChartInsight { super.setConfig(config); } - protected updateTheme(e: ITheme): void { + protected updateTheme(e: IColorTheme): void { super.updateTheme(e); const foregroundColor = e.getColor(colors.editorForeground); const foreground = foregroundColor ? foregroundColor.toString() : null; diff --git a/src/sql/workbench/contrib/dashboard/browser/widgets/tasks/tasksWidget.component.ts b/src/sql/workbench/contrib/dashboard/browser/widgets/tasks/tasksWidget.component.ts index 2d4fddb1bc..27c546dd19 100644 --- a/src/sql/workbench/contrib/dashboard/browser/widgets/tasks/tasksWidget.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/widgets/tasks/tasksWidget.component.ts @@ -17,7 +17,7 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; /* VS imports */ import * as themeColors from 'vs/workbench/common/theme'; import * as colors from 'vs/platform/theme/common/colorRegistry'; -import { registerThemingParticipant, ICssStyleCollector, ITheme } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, ICssStyleCollector, IColorTheme } from 'vs/platform/theme/common/themeService'; import * as types from 'vs/base/common/types'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; @@ -149,7 +149,7 @@ export class TasksWidget extends DashboardWidget implements IDashboardWidget, On return tile; } - private registerThemeing(theme: ITheme, collector: ICssStyleCollector) { + private registerThemeing(theme: IColorTheme, collector: ICssStyleCollector) { const contrastBorder = theme.getColor(colors.contrastBorder); const sideBarColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND); if (contrastBorder) { diff --git a/src/sql/workbench/contrib/dashboard/browser/widgets/webview/webviewWidget.component.ts b/src/sql/workbench/contrib/dashboard/browser/widgets/webview/webviewWidget.component.ts index ed36bcfa52..013eb9b324 100644 --- a/src/sql/workbench/contrib/dashboard/browser/widgets/webview/webviewWidget.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/widgets/webview/webviewWidget.component.ts @@ -97,7 +97,7 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget, this._onMessageDisposable.dispose(); } - this._webview = this.webviewService.createWebview(this.id, + this._webview = this.webviewService.createWebviewElement(this.id, {}, { allowScripts: true, diff --git a/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts b/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts index 7eb21f0112..ecba62230a 100644 --- a/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts +++ b/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts @@ -50,7 +50,7 @@ export class ConnectionViewletPanel extends ViewPane { @ILogService private readonly logService: ILogService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, opener, themeService, telemetryService); + super({ ...(options as IViewPaneOptions) }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, opener, themeService, telemetryService); this._addServerAction = this.instantiationService.createInstance(AddServerAction, AddServerAction.ID, AddServerAction.LABEL); diff --git a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerExtensionPoint.ts b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerExtensionPoint.ts index bd3108f609..48742f0313 100644 --- a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerExtensionPoint.ts +++ b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerExtensionPoint.ts @@ -14,7 +14,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce } from 'vs/base/common/arrays'; -import { CustomTreeViewPanel, CustomTreeView } from 'sql/workbench/browser/parts/views/customView'; +import { CustomTreeViewPane, CustomTreeView } from 'sql/workbench/browser/parts/views/customView'; import { VIEWLET_ID } from 'sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -106,7 +106,7 @@ export class DataExplorerContainerExtensionHandler implements IWorkbenchContribu const viewDescriptor = { id: item.id, name: item.name, - ctorDescriptor: new SyncDescriptor(CustomTreeViewPanel), + ctorDescriptor: new SyncDescriptor(CustomTreeViewPane), when: ContextKeyExpr.deserialize(item.when), canToggleVisibility: true, collapsed: this.showCollapsed(container), diff --git a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts index 226905718f..edd05ce414 100644 --- a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts +++ b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts @@ -7,7 +7,7 @@ import * as strings from 'vs/base/common/strings'; import * as DOM from 'vs/base/browser/dom'; import * as nls from 'vs/nls'; -import { EditorOptions, EditorInput, IEditorControl, IEditor } from 'vs/workbench/common/editor'; +import { EditorOptions, EditorInput, IEditorControl, IEditorPane } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -522,7 +522,7 @@ export class EditDataEditor extends BaseEditor { this._createEditor(newInput.sql, this._sqlEditorContainer) ]); }; - onEditorsCreated = (result: IEditor[]) => { + onEditorsCreated = (result: IEditorPane[]) => { return Promise.all([ this._onResultsEditorCreated(result[0], newInput.results, options), this._onSqlEditorCreated(result[1], newInput.sql, options) @@ -583,7 +583,7 @@ export class EditDataEditor extends BaseEditor { this._createEditor(input.results, this._resultsEditorContainer) .then(async result => { - await this._onResultsEditorCreated(result, input.results, this.options); + await this._onResultsEditorCreated(result, input.results, this._options); this.resultsEditorVisibility = true; this.hideQueryResultsView = false; this._doLayout(true); @@ -630,7 +630,7 @@ export class EditDataEditor extends BaseEditor { let visible = currentEditorIsVisible; if (!currentEditorIsVisible) { // Current editor is closing but still tracked as visible. Check if any other editor is visible - const candidates = [...this._editorService.visibleControls].filter(e => { + const candidates = [...this._editorService.visibleEditorPanes].filter(e => { if (e && e.getId) { return e.getId() === EditDataEditor.ID; } diff --git a/src/sql/workbench/contrib/editData/browser/gridCommands.ts b/src/sql/workbench/contrib/editData/browser/gridCommands.ts index 3b8f071b43..0e62e74592 100644 --- a/src/sql/workbench/contrib/editData/browser/gridCommands.ts +++ b/src/sql/workbench/contrib/editData/browser/gridCommands.ts @@ -13,7 +13,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic function runActionOnActiveResultsEditor(accessor: ServicesAccessor, eventName: string): void { let editorService = accessor.get(IEditorService); - const candidates = [editorService.activeControl, ...editorService.visibleControls].filter(e => { + const candidates = [editorService.activeEditorPane, ...editorService.visibleEditorPanes].filter(e => { if (e) { let id = e.getId(); if (id === QueryEditor.ID || id === EditDataEditor.ID) { diff --git a/src/sql/workbench/contrib/editorReplacement/test/browser/editorReplacerContribution.test.ts b/src/sql/workbench/contrib/editorReplacement/test/browser/editorReplacerContribution.test.ts index ed509652d6..318af3c555 100644 --- a/src/sql/workbench/contrib/editorReplacement/test/browser/editorReplacerContribution.test.ts +++ b/src/sql/workbench/contrib/editorReplacement/test/browser/editorReplacerContribution.test.ts @@ -197,6 +197,7 @@ class MockEditorService extends TestEditorService { class TestModeService implements IModeService { _serviceBrand: undefined; onDidCreateMode: Event; + onLanguagesMaybeChanged: Event; isRegisteredMode(mimetypeOrModeId: string): boolean { throw new Error('Method not implemented.'); diff --git a/src/sql/workbench/contrib/jobManagement/browser/jobsView.component.ts b/src/sql/workbench/contrib/jobManagement/browser/jobsView.component.ts index 43b00b8936..c5de79e7b0 100644 --- a/src/sql/workbench/contrib/jobManagement/browser/jobsView.component.ts +++ b/src/sql/workbench/contrib/jobManagement/browser/jobsView.component.ts @@ -27,12 +27,13 @@ import { IAction } from 'vs/base/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService'; import { escape } from 'sql/base/common/strings'; -import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { tableBackground, cellBackground, cellBorderColor } from 'sql/platform/theme/common/colors'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { attachButtonStyler } from 'sql/platform/theme/common/styler'; import { find, fill } from 'vs/base/common/arrays'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { onUnexpectedError } from 'vs/base/common/errors'; export const JOBSVIEW_SELECTOR: string = 'jobsview-component'; diff --git a/src/sql/workbench/contrib/jobManagement/browser/notebooksView.component.ts b/src/sql/workbench/contrib/jobManagement/browser/notebooksView.component.ts index 30d2dbf7e0..4e531b47fb 100644 --- a/src/sql/workbench/contrib/jobManagement/browser/notebooksView.component.ts +++ b/src/sql/workbench/contrib/jobManagement/browser/notebooksView.component.ts @@ -27,7 +27,7 @@ import { IAction } from 'vs/base/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService'; import { escape } from 'sql/base/common/strings'; -import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { tableBackground, cellBackground, cellBorderColor } from 'sql/platform/theme/common/colors'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; @@ -35,6 +35,7 @@ import { attachButtonStyler } from 'sql/platform/theme/common/styler'; import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; import { find, fill } from 'vs/base/common/arrays'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; export const NOTEBOOKSVIEW_SELECTOR: string = 'notebooksview-component'; diff --git a/src/sql/workbench/contrib/modelView/browser/webview.component.ts b/src/sql/workbench/contrib/modelView/browser/webview.component.ts index aa58385845..d678400501 100644 --- a/src/sql/workbench/contrib/modelView/browser/webview.component.ts +++ b/src/sql/workbench/contrib/modelView/browser/webview.component.ts @@ -67,7 +67,7 @@ export default class WebViewComponent extends ComponentBase implements IComponen } private _createWebview(): void { - this._webview = this.webviewService.createWebview(this.id, + this._webview = this.webviewService.createWebviewElement(this.id, {}, { allowScripts: true diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts index 686986daa3..503fb5e2ad 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts @@ -13,7 +13,7 @@ import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; import { RunCellAction, CellContext } from 'sql/workbench/contrib/notebook/browser/cellViews/codeActions'; import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel'; -import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as themeColors from 'vs/workbench/common/theme'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITextModel } from 'vs/editor/common/model'; @@ -36,6 +36,7 @@ import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { SimpleProgressIndicator } from 'sql/workbench/services/progress/browser/simpleProgressIndicator'; import { notebookConstants } from 'sql/workbench/services/notebook/browser/interfaces'; import { tryMatchCellMagic } from 'sql/workbench/services/notebook/browser/utils'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; export const CODE_SELECTOR: string = 'code-component'; const MARKDOWN_CLASS = 'markdown'; diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/output.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/output.component.ts index e470db738e..6a67b6a0ca 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/output.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/output.component.ts @@ -10,7 +10,7 @@ import { Event } from 'vs/base/common/event'; import { nb } from 'azdata'; import { ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; import * as outputProcessor from 'sql/workbench/contrib/notebook/browser/models/outputProcessor'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import * as DOM from 'vs/base/browser/dom'; import { ComponentHostDirective } from 'sql/base/browser/componentHost.directive'; import { Extensions, IMimeComponent, IMimeComponentRegistry } from 'sql/workbench/contrib/notebook/browser/outputs/mimeRegistry'; @@ -53,7 +53,7 @@ export class OutputComponent extends CellView implements OnInit, AfterViewInit { } ngOnInit() { - this._register(this._themeService.onThemeChange(event => this.updateTheme(event))); + this._register(this._themeService.onDidColorThemeChange(event => this.updateTheme(event))); this.loadComponent(); this.layout(); this._initialized = true; @@ -62,7 +62,7 @@ export class OutputComponent extends CellView implements OnInit, AfterViewInit { } ngAfterViewInit() { - this.updateTheme(this._themeService.getTheme()); + this.updateTheme(this._themeService.getColorTheme()); } ngOnChanges(changes: { [propKey: string]: SimpleChange }) { @@ -128,7 +128,7 @@ export class OutputComponent extends CellView implements OnInit, AfterViewInit { public hasError(): boolean { return !types.isUndefinedOrNull(this.errorText); } - private updateTheme(theme: ITheme): void { + private updateTheme(theme: IColorTheme): void { let el = this._ref.nativeElement; let backgroundColor = theme.getColor(colors.editorBackground, true); let foregroundColor = theme.getColor(themeColors.SIDE_BAR_FOREGROUND, true); diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/outputArea.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/outputArea.component.ts index e535071f23..4da99c5f0c 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/outputArea.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/outputArea.component.ts @@ -8,8 +8,9 @@ import { OnInit, Component, Input, Inject, ElementRef, ViewChild, forwardRef, Ch import { AngularDisposable } from 'sql/base/browser/lifecycle'; import { ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; import * as themeColors from 'vs/workbench/common/theme'; -import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { URI } from 'vs/base/common/uri'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; export const OUTPUT_AREA_SELECTOR: string = 'output-area-component'; diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.ts index 4a9a3fae94..e4474ec47b 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.ts @@ -9,7 +9,7 @@ import 'vs/css!./media/highlight'; import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnChanges, SimpleChange, HostListener, ViewChildren, QueryList } from '@angular/core'; import { localize } from 'vs/nls'; -import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as themeColors from 'vs/workbench/common/theme'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter } from 'vs/base/common/event'; @@ -27,6 +27,7 @@ import { CellToggleMoreActions } from 'sql/workbench/contrib/notebook/browser/ce import { CodeComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/code.component'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; import { NotebookRange } from 'sql/workbench/services/notebook/browser/notebookService'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; export const TEXT_SELECTOR: string = 'text-cell-component'; const USER_SELECT_CLASS = 'actionselect'; diff --git a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts index f7f1a21433..67a7355331 100644 --- a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts +++ b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts @@ -231,7 +231,7 @@ export abstract class NotebookInput extends EditorInput { return this._textInput; } - public revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + public revert(group: GroupIdentifier, options?: IRevertOptions): Promise { return this._textInput.revert(group, options); } diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.component.ts b/src/sql/workbench/contrib/notebook/browser/notebook.component.ts index 09e7ac2285..15b67828c5 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebook.component.ts @@ -6,7 +6,7 @@ import { nb } from 'azdata'; import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnDestroy, ViewChildren, QueryList } from '@angular/core'; -import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as themeColors from 'vs/workbench/common/theme'; import { INotificationService, INotification } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; @@ -54,6 +54,7 @@ import { find, firstIndex } from 'vs/base/common/arrays'; import { CodeCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/codeCell.component'; import { TextCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/textCell.component'; import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -260,7 +261,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe }); this.notificationService.error(errorWithAction); - let editors = this.editorService.visibleControls; + let editors = this.editorService.visibleEditorPanes; for (let editor of editors) { if (editor && editor.input.resource === this._notebookParams.input.notebookUri) { await editor.group.closeEditor(this._notebookParams.input as NotebookInput, { preserveFocus: true }); // sketchy diff --git a/src/sql/workbench/contrib/notebook/browser/notebookStyles.ts b/src/sql/workbench/contrib/notebook/browser/notebookStyles.ts index 681d037b8a..598868a2f6 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebookStyles.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebookStyles.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./notebook'; -import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { SIDE_BAR_BACKGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, EDITOR_GROUP_HEADER_TABS_BACKGROUND } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, buttonBackground, textLinkForeground, textLinkActiveForeground, textPreformatForeground, textBlockQuoteBackground, textBlockQuoteBorder, buttonForeground, editorBackground, lighten } from 'vs/platform/theme/common/colorRegistry'; import { editorLineHighlight, editorLineHighlightBorder } from 'vs/editor/common/view/editorColorRegistry'; @@ -15,7 +15,7 @@ import { getZoomLevel } from 'vs/base/browser/browser'; import * as types from 'vs/base/common/types'; export function registerNotebookThemes(overrideEditorThemeSetting: boolean, configurationService: IConfigurationService): IDisposable { - return registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + return registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { let lightBoxShadow = '0px 4px 6px 0px rgba(0, 0, 0, 0.14)'; let darkBoxShadow = '0px 4px 6px 0px rgba(0, 0, 0, 1)'; diff --git a/src/sql/workbench/contrib/notebook/find/notebookFindModel.ts b/src/sql/workbench/contrib/notebook/find/notebookFindModel.ts index b7ae4e849c..9bef6634d1 100644 --- a/src/sql/workbench/contrib/notebook/find/notebookFindModel.ts +++ b/src/sql/workbench/contrib/notebook/find/notebookFindModel.ts @@ -660,7 +660,7 @@ export class NotebookIntervalNode { abstract class SettingsCommand extends Command { protected getNotebookEditor(accessor: ServicesAccessor): NotebookEditor { - const activeEditor = accessor.get(IEditorService).activeControl; + const activeEditor = accessor.get(IEditorService).activeEditorPane; if (activeEditor instanceof NotebookEditor) { return activeEditor; } diff --git a/src/sql/workbench/contrib/notebook/find/notebookFindWidget.ts b/src/sql/workbench/contrib/notebook/find/notebookFindWidget.ts index 2845b752b5..65c69d2e24 100644 --- a/src/sql/workbench/contrib/notebook/find/notebookFindWidget.ts +++ b/src/sql/workbench/contrib/notebook/find/notebookFindWidget.ts @@ -21,7 +21,7 @@ import { IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference import { FIND_IDS, CONTEXT_FIND_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel'; import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import * as colors from 'vs/platform/theme/common/colorRegistry'; import { IEditorAction } from 'vs/editor/common/editorCommon'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -159,8 +159,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._notebookController.addOverlayWidget(this); - this._applyTheme(themeService.getTheme()); - this._register(themeService.onThemeChange(this._applyTheme.bind(this))); + this._applyTheme(themeService.getColorTheme()); + this._register(themeService.onDidColorThemeChange(this._applyTheme.bind(this))); this.onkeyup(this._domNode, e => { if (e.equals(KeyCode.Escape)) { @@ -299,7 +299,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } } - private _applyTheme(theme: ITheme) { + private _applyTheme(theme: IColorTheme) { let inputStyles: IFindInputStyles = { inputActiveOptionBorder: theme.getColor(colors.inputActiveOptionBorder), inputBackground: theme.getColor(colors.inputBackground), diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookEditorModel.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookEditorModel.test.ts index a6646bd145..dfbcda52b6 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookEditorModel.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookEditorModel.test.ts @@ -28,7 +28,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { TestEnvironmentService, TestLifecycleService, TestStorageService, TestTextFileService, workbenchInstantiationService, TestTextResourcePropertiesService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEnvironmentService, TestLifecycleService, TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Range } from 'vs/editor/common/core/range'; import { nb } from 'azdata'; import { Emitter } from 'vs/base/common/event'; @@ -38,6 +38,7 @@ import { startsWith } from 'vs/base/common/strings'; import { assign } from 'vs/base/common/objects'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { TestStorageService, TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices'; class ServiceAccessor { diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts index 1a1741a1fa..fabca4ccd7 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts @@ -25,7 +25,7 @@ import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/t import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ClientSession } from 'sql/workbench/services/notebook/browser/models/clientSession'; -import { TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; import { NotebookEditorContentManager } from 'sql/workbench/contrib/notebook/browser/models/notebookInput'; import { NotebookRange } from 'sql/workbench/services/notebook/browser/notebookService'; import { NotebookMarkdownRenderer } from 'sql/workbench/contrib/notebook/browser/outputs/notebookMarkdown'; diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts index b062414774..137537e9f2 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts @@ -22,7 +22,7 @@ import { Memento } from 'vs/workbench/common/memento'; import { Emitter } from 'vs/base/common/event'; import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; -import { TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; diff --git a/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts b/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts index 4bc3c5c18e..85c7fc5bf1 100644 --- a/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts +++ b/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts @@ -33,6 +33,7 @@ import { $ } from 'vs/base/browser/dom'; import { OEManageConnectionAction } from 'sql/workbench/contrib/dashboard/browser/dashboardActions'; import { IViewsService, IView } from 'vs/workbench/common/views'; import { ConsoleLogService } from 'vs/platform/log/common/log'; +import { IProgressIndicator } from 'vs/platform/progress/common/progress'; suite('SQL Connection Tree Action tests', () => { let errorMessageService: TypeMoq.Mock; @@ -109,6 +110,9 @@ suite('SQL Connection Tree Action tests', () => { }); const viewsService = new class implements IViewsService { + getProgressIndicator(id: string): IProgressIndicator { + throw new Error('Method not implemented.'); + } getActiveViewWithId(id: string): T | null { throw new Error('Method not implemented.'); } diff --git a/src/sql/workbench/contrib/profiler/browser/profilerActions.contribution.ts b/src/sql/workbench/contrib/profiler/browser/profilerActions.contribution.ts index b723607680..6a97a5d5bc 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerActions.contribution.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerActions.contribution.ts @@ -83,7 +83,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ let profilerService: IProfilerService = accessor.get(IProfilerService); let editorService: IEditorService = accessor.get(IEditorService); - let activeEditor = editorService.activeControl; + let activeEditor = editorService.activeEditorPane; if (activeEditor instanceof ProfilerEditor) { let profilerInput = activeEditor.input; if (profilerInput.state.isRunning) { diff --git a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts index 6332af1276..dd62a57759 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts @@ -297,13 +297,13 @@ export class ProfilerEditor extends BaseEditor { profilerTableContainer.style.height = '100%'; profilerTableContainer.style.overflow = 'hidden'; profilerTableContainer.style.position = 'relative'; - let theme = this.themeService.getTheme(); + let theme = this.themeService.getColorTheme(); if (theme.type === DARK) { DOM.addClass(profilerTableContainer, VS_DARK_THEME); } else if (theme.type === HIGH_CONTRAST) { DOM.addClass(profilerTableContainer, VS_HC_THEME); } - this.themeService.onThemeChange(e => { + this.themeService.onDidColorThemeChange(e => { DOM.removeClasses(profilerTableContainer, VS_DARK_THEME, VS_HC_THEME); if (e.type === DARK) { DOM.addClass(profilerTableContainer, VS_DARK_THEME); @@ -623,7 +623,7 @@ export class ProfilerEditor extends BaseEditor { abstract class SettingsCommand extends Command { protected getProfilerEditor(accessor: ServicesAccessor): ProfilerEditor { - const activeEditor = accessor.get(IEditorService).activeControl; + const activeEditor = accessor.get(IEditorService).activeEditorPane; if (activeEditor instanceof ProfilerEditor) { return activeEditor; } diff --git a/src/sql/workbench/contrib/profiler/browser/profilerFindWidget.ts b/src/sql/workbench/contrib/profiler/browser/profilerFindWidget.ts index b0305db79d..354ab7bb0d 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerFindWidget.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerFindWidget.ts @@ -21,7 +21,7 @@ import { IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference import { FIND_IDS, CONTEXT_FIND_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel'; import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import * as colors from 'vs/platform/theme/common/colorRegistry'; import { IEditorAction } from 'vs/editor/common/editorCommon'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -156,8 +156,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._tableController.addOverlayWidget(this); - this._applyTheme(themeService.getTheme()); - this._register(themeService.onThemeChange(this._applyTheme.bind(this))); + this._applyTheme(themeService.getColorTheme()); + this._register(themeService.onDidColorThemeChange(this._applyTheme.bind(this))); } // ----- IOverlayWidget API @@ -288,7 +288,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } } - private _applyTheme(theme: ITheme) { + private _applyTheme(theme: IColorTheme) { let inputStyles: IFindInputStyles = { inputActiveOptionBorder: theme.getColor(colors.inputActiveOptionBorder), inputBackground: theme.getColor(colors.inputBackground), diff --git a/src/sql/workbench/contrib/query/browser/actions.ts b/src/sql/workbench/contrib/query/browser/actions.ts index bc29052b11..a01f5dd90b 100644 --- a/src/sql/workbench/contrib/query/browser/actions.ts +++ b/src/sql/workbench/contrib/query/browser/actions.ts @@ -221,7 +221,7 @@ export class ChartDataAction extends Action { // show the visualizer extension recommendation notification this.extensionTipsService.promptRecommendedExtensionsByScenario(Constants.visualizerExtensions); - const activeEditor = this.editorService.activeControl as QueryEditor; + const activeEditor = this.editorService.activeEditorPane as QueryEditor; activeEditor.chart({ batchId: context.batchId, resultId: context.resultId }); return Promise.resolve(true); } diff --git a/src/sql/workbench/contrib/query/browser/flavorStatus.ts b/src/sql/workbench/contrib/query/browser/flavorStatus.ts index 41cf434009..7d81ab0c33 100644 --- a/src/sql/workbench/contrib/query/browser/flavorStatus.ts +++ b/src/sql/workbench/contrib/query/browser/flavorStatus.ts @@ -99,7 +99,7 @@ export class SqlFlavorStatusbarItem extends Disposable implements IWorkbenchCont let uri = event.editor.resource.toString(); if (uri && uri in this._sqlStatusEditors) { // If active editor is being closed, hide the query status. - let activeEditor = this.editorService.activeControl; + let activeEditor = this.editorService.activeEditorPane; if (activeEditor) { let currentUri = activeEditor.input.resource.toString(); if (uri === currentUri) { @@ -112,7 +112,7 @@ export class SqlFlavorStatusbarItem extends Disposable implements IWorkbenchCont } private _onEditorsChanged(): void { - let activeEditor = this.editorService.activeControl; + let activeEditor = this.editorService.activeEditorPane; if (activeEditor) { let uri = activeEditor.input.resource.toString(); @@ -143,7 +143,7 @@ export class SqlFlavorStatusbarItem extends Disposable implements IWorkbenchCont // Show/hide query status for active editor private _showStatus(uri: string): void { - let activeEditor = this.editorService.activeControl; + let activeEditor = this.editorService.activeEditorPane; if (activeEditor) { let currentUri = activeEditor.input.resource.toString(); if (uri === currentUri) { @@ -185,7 +185,7 @@ export class ChangeFlavorAction extends Action { } public run(): Promise { - let activeEditor = this._editorService.activeControl; + let activeEditor = this._editorService.activeEditorPane; let currentUri = activeEditor?.input.resource.toString(); if (this._connectionManagementService.isConnected(currentUri)) { let currentProvider = this._connectionManagementService.getProviderIdFromUri(currentUri); @@ -206,7 +206,7 @@ export class ChangeFlavorAction extends Action { return this._quickInputService.pick(providerOptions, { placeHolder: nls.localize('pickSqlProvider', "Select SQL Language Provider") }).then(provider => { if (provider) { - let activeEditor = this._editorService.activeControl.getControl(); + let activeEditor = this._editorService.activeEditorPane.getControl(); const editorWidget = getCodeEditor(activeEditor); if (editorWidget) { if (currentUri) { diff --git a/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts b/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts index a3423e990b..fc33bbe53d 100644 --- a/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts +++ b/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts @@ -33,7 +33,7 @@ function isConnected(editor: QueryEditor, connectionManagementService: IConnecti } function runActionOnActiveQueryEditor(editorService: IEditorService, action: (QueryEditor) => void): void { - const candidates = [editorService.activeControl, ...editorService.visibleControls].filter(e => e instanceof QueryEditor); + const candidates = [editorService.activeEditorPane, ...editorService.visibleEditorPanes].filter(e => e instanceof QueryEditor); if (candidates.length > 0) { action(candidates[0]); } @@ -73,7 +73,7 @@ export class FocusOnCurrentQueryKeyboardAction extends Action { } public run(): Promise { - const editor = this._editorService.activeControl; + const editor = this._editorService.activeEditorPane; if (editor instanceof QueryEditor) { editor.focus(); } @@ -99,7 +99,7 @@ export class RunQueryKeyboardAction extends Action { } public run(): Promise { - const editor = this._editorService.activeControl; + const editor = this._editorService.activeEditorPane; if (editor instanceof QueryEditor || editor instanceof EditDataEditor) { editor.runQuery(); } @@ -124,7 +124,7 @@ export class RunCurrentQueryKeyboardAction extends Action { } public run(): Promise { - const editor = this._editorService.activeControl; + const editor = this._editorService.activeEditorPane; if (editor instanceof QueryEditor) { editor.runCurrentQuery(); } @@ -146,7 +146,7 @@ export class RunCurrentQueryWithActualPlanKeyboardAction extends Action { } public run(): Promise { - const editor = this._editorService.activeControl; + const editor = this._editorService.activeEditorPane; if (editor instanceof QueryEditor) { editor.runCurrentQueryWithActualPlan(); } @@ -172,7 +172,7 @@ export class CancelQueryKeyboardAction extends Action { } public run(): Promise { - const editor = this._editorService.activeControl; + const editor = this._editorService.activeEditorPane; if (editor instanceof QueryEditor || editor instanceof EditDataEditor) { editor.cancelQuery(); } @@ -224,7 +224,7 @@ export class ToggleQueryResultsKeyboardAction extends Action { } public run(): Promise { - const editor = this._editorService.activeControl; + const editor = this._editorService.activeEditorPane; if (editor instanceof QueryEditor) { editor.toggleResultsEditorVisibility(); } @@ -404,7 +404,7 @@ export class ParseSyntaxAction extends Action { } public run(): Promise { - const editor = this.editorService.activeControl; + const editor = this.editorService.activeEditorPane; if (editor instanceof QueryEditor) { if (!editor.isSelectionEmpty()) { if (this.isConnected(editor)) { diff --git a/src/sql/workbench/contrib/query/browser/messagePanel.ts b/src/sql/workbench/contrib/query/browser/messagePanel.ts index 886351212b..cda346b22b 100644 --- a/src/sql/workbench/contrib/query/browser/messagePanel.ts +++ b/src/sql/workbench/contrib/query/browser/messagePanel.ts @@ -16,7 +16,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; import { attachListStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { OpenMode, ClickBehavior, ICancelableEvent, IControllerOptions } from 'vs/base/parts/tree/browser/treeDefaults'; import { WorkbenchTreeController } from 'vs/platform/list/browser/listService'; @@ -102,8 +102,8 @@ export class MessagePanel extends Disposable { this.container.style.width = '100%'; this.container.style.height = '100%'; this._register(attachListStyler(this.tree, this.themeService)); - this._register(this.themeService.onThemeChange(this.applyStyles, this)); - this.applyStyles(this.themeService.getTheme()); + this._register(this.themeService.onDidColorThemeChange(this.applyStyles, this)); + this.applyStyles(this.themeService.getColorTheme()); this.controller.onKeyDown = (tree, event) => { if (event.ctrlKey && event.code === 'KeyC') { let context: IMessagesActionContext = { @@ -210,7 +210,7 @@ export class MessagePanel extends Disposable { } } - private applyStyles(theme: ITheme): void { + private applyStyles(theme: IColorTheme): void { const errorColor = theme.getColor(resultsErrorColor); const content: string[] = []; if (errorColor) { @@ -387,7 +387,7 @@ export class MessageController extends WorkbenchTreeController { if (element.selection) { let selection: ISelectionData = element.selection; // this is a batch statement - let editor = this.workbenchEditorService.activeControl as QueryEditor; + let editor = this.workbenchEditorService.activeEditorPane as QueryEditor; const codeEditor = editor.getControl(); codeEditor.focus(); codeEditor.setSelection({ endColumn: selection.endColumn + 1, endLineNumber: selection.endLine + 1, startColumn: selection.startColumn + 1, startLineNumber: selection.startLine + 1 }); diff --git a/src/sql/workbench/contrib/query/browser/queryEditor.ts b/src/sql/workbench/contrib/query/browser/queryEditor.ts index a95eb41750..08fdcafd63 100644 --- a/src/sql/workbench/contrib/query/browser/queryEditor.ts +++ b/src/sql/workbench/contrib/query/browser/queryEditor.ts @@ -394,7 +394,7 @@ export class QueryEditor extends BaseEditor { let visible = currentEditorIsVisible; if (!currentEditorIsVisible) { // Current editor is closing but still tracked as visible. Check if any other editor is visible - const candidates = [...this.editorService.visibleControls].filter(e => { + const candidates = [...this.editorService.visibleEditorPanes].filter(e => { if (e && e.getId) { return e.getId() === QueryEditor.ID; } diff --git a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts index bde5583f75..64e199a4c5 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts @@ -23,7 +23,7 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import * as TypeMoq from 'typemoq'; import * as assert from 'assert'; -import { TestStorageService, TestFileService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestFileService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { UntitledQueryEditorInput } from 'sql/workbench/common/editor/query/untitledQueryEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; @@ -33,6 +33,7 @@ import { TestConnectionManagementService } from 'sql/platform/connection/test/co import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; suite('SQL QueryAction Tests', () => { diff --git a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts index a396d0fa61..3e10a94728 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts @@ -17,7 +17,7 @@ import * as TypeMoq from 'typemoq'; import * as assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { TestStorageService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { UntitledQueryEditorInput } from 'sql/workbench/common/editor/query/untitledQueryEditorInput'; @@ -27,6 +27,7 @@ import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/commo import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; suite('SQL QueryEditor Tests', () => { let instantiationService: TypeMoq.Mock; diff --git a/src/sql/workbench/contrib/queryPlan/browser/queryPlan.component.ts b/src/sql/workbench/contrib/queryPlan/browser/queryPlan.component.ts index ac6efff6f7..5000c1d90b 100644 --- a/src/sql/workbench/contrib/queryPlan/browser/queryPlan.component.ts +++ b/src/sql/workbench/contrib/queryPlan/browser/queryPlan.component.ts @@ -11,7 +11,7 @@ import * as QP from 'html-query-plan'; import { IQueryPlanParams, IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { registerThemingParticipant, ICssStyleCollector, ITheme } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, ICssStyleCollector, IColorTheme } from 'vs/platform/theme/common/themeService'; import * as colors from 'vs/platform/theme/common/colorRegistry'; export const QUERYPLAN_SELECTOR: string = 'queryplan-component'; @@ -53,7 +53,7 @@ export class QueryPlanComponent implements OnDestroy, OnInit { } } - private _updateTheme(theme: ITheme, collector: ICssStyleCollector) { + private _updateTheme(theme: IColorTheme, collector: ICssStyleCollector) { let backgroundColor = theme.getColor(colors.editorBackground); let foregroundColor = theme.getColor(colors.editorForeground); diff --git a/src/sql/workbench/contrib/webview/browser/webViewDialog.ts b/src/sql/workbench/contrib/webview/browser/webViewDialog.ts index db528f9c18..50e1e2aeaa 100644 --- a/src/sql/workbench/contrib/webview/browser/webViewDialog.ts +++ b/src/sql/workbench/contrib/webview/browser/webViewDialog.ts @@ -14,7 +14,6 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { localize } from 'vs/nls'; import { toDisposable } from 'vs/base/common/lifecycle'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import * as DOM from 'vs/base/browser/dom'; import { ILogService } from 'vs/platform/log/common/log'; import { IWebviewService, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; @@ -22,6 +21,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export class WebViewDialog extends Modal { @@ -44,7 +44,7 @@ export class WebViewDialog extends Modal { constructor( @IThemeService themeService: IThemeService, @IClipboardService clipboardService: IClipboardService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IAdsTelemetryService telemetryService: IAdsTelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @ILogService logService: ILogService, @@ -91,7 +91,7 @@ export class WebViewDialog extends Modal { protected renderBody(container: HTMLElement) { this._body = DOM.append(container, DOM.$('div.webview-dialog')); - this._webview = this.webviewService.createWebview(this.id, + this._webview = this.webviewService.createWebviewElement(this.id, {}, { allowScripts: true diff --git a/src/sql/workbench/services/accountManagement/browser/accountDialog.ts b/src/sql/workbench/services/accountManagement/browser/accountDialog.ts index e19f956d3b..3ceeebeae5 100644 --- a/src/sql/workbench/services/accountManagement/browser/accountDialog.ts +++ b/src/sql/workbench/services/accountManagement/browser/accountDialog.ts @@ -32,7 +32,6 @@ import { AccountProviderAddedEventParams, UpdateAccountListEventParams } from 's import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -40,6 +39,7 @@ import { attachModalDialogStyler, attachPanelStyler } from 'sql/workbench/common import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; class AccountPanel extends ViewPane { public index: number; @@ -123,7 +123,7 @@ export class AccountDialog extends Modal { public get onCloseEvent(): Event { return this._onCloseEmitter.event; } constructor( - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IThemeService themeService: IThemeService, @IInstantiationService private _instantiationService: IInstantiationService, @IContextMenuService private _contextMenuService: IContextMenuService, @@ -298,8 +298,7 @@ export class AccountDialog extends Modal { let providerView = new AccountPanel( { id: newProvider.addedProvider.id, - title: newProvider.addedProvider.displayName, - ariaHeaderLabel: newProvider.addedProvider.displayName + title: newProvider.addedProvider.displayName }, this._keybindingService, this._contextMenuService, diff --git a/src/sql/workbench/services/accountManagement/browser/accountPickerImpl.ts b/src/sql/workbench/services/accountManagement/browser/accountPickerImpl.ts index 9caad05fb2..04670dc1b4 100644 --- a/src/sql/workbench/services/accountManagement/browser/accountPickerImpl.ts +++ b/src/sql/workbench/services/accountManagement/browser/accountPickerImpl.ts @@ -15,7 +15,7 @@ import { attachListStyler } from 'vs/platform/theme/common/styler'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import * as azdata from 'azdata'; import { DropdownList } from 'sql/base/browser/ui/dropdownList/dropdownList'; @@ -107,7 +107,7 @@ export class AccountPicker extends Disposable { this._dropdown = this._register(new DropdownList(this._rootElement, option, this._listContainer, this._accountList, addAccountAction)); this._register(attachDropdownStyler(this._dropdown, this._themeService)); - this._register(this._accountList.onSelectionChange((e: IListEvent) => { + this._register(this._accountList.onDidChangeSelection((e: IListEvent) => { if (e.elements.length === 1) { this._dropdown.renderLabel(); this.onAccountSelectionChange(e.elements[0]); @@ -128,8 +128,8 @@ export class AccountPicker extends Disposable { DOM.hide(this._refreshContainer); } - this._register(this._themeService.onThemeChange(e => this.updateTheme(e))); - this.updateTheme(this._themeService.getTheme()); + this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e))); + this.updateTheme(this._themeService.getColorTheme()); // Load the initial contents of the view model this.viewModel.initialize() @@ -226,7 +226,7 @@ export class AccountPicker extends Disposable { /** * Update theming that is specific to account picker */ - private updateTheme(theme: ITheme): void { + private updateTheme(theme: IColorTheme): void { const linkColor = theme.getColor(buttonBackground); const link = linkColor ? linkColor.toString() : null; this._refreshContainer.style.color = link; diff --git a/src/sql/workbench/services/accountManagement/browser/autoOAuthDialog.ts b/src/sql/workbench/services/accountManagement/browser/autoOAuthDialog.ts index 5c408757d3..b153d4b653 100644 --- a/src/sql/workbench/services/accountManagement/browser/autoOAuthDialog.ts +++ b/src/sql/workbench/services/accountManagement/browser/autoOAuthDialog.ts @@ -21,10 +21,10 @@ import { attachButtonStyler } from 'sql/platform/theme/common/styler'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export class AutoOAuthDialog extends Modal { private _copyAndOpenButton: Button; @@ -45,7 +45,7 @@ export class AutoOAuthDialog extends Modal { public get onCloseEvent(): Event { return this._onCloseEvent.event; } constructor( - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IThemeService themeService: IThemeService, @IContextViewService private _contextViewService: IContextViewService, @IAdsTelemetryService telemetryService: IAdsTelemetryService, diff --git a/src/sql/workbench/services/accountManagement/test/browser/accountManagementService.test.ts b/src/sql/workbench/services/accountManagement/test/browser/accountManagementService.test.ts index 026e01dcc0..55852fa2c0 100644 --- a/src/sql/workbench/services/accountManagement/test/browser/accountManagementService.test.ts +++ b/src/sql/workbench/services/accountManagement/test/browser/accountManagementService.test.ts @@ -13,7 +13,7 @@ import { AccountAdditionResult, AccountProviderAddedEventParams, UpdateAccountLi import { IAccountStore } from 'sql/platform/accounts/common/interfaces'; import { AccountProviderStub } from 'sql/platform/accounts/test/common/testAccountManagementService'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; -import { TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; import { EventVerifierSingle } from 'sql/base/test/common/event'; // SUITE CONSTANTS ///////////////////////////////////////////////////////// diff --git a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts index 0a05894a7d..c066289506 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts @@ -33,13 +33,13 @@ import * as DOM from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { entries } from 'sql/base/common/collections'; import { attachTabbedPanelStyler, attachModalDialogStyler } from 'sql/workbench/common/styler'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export interface OnShowUIResponse { selectedProviderDisplayName: string; @@ -94,7 +94,7 @@ export class ConnectionDialogWidget extends Modal { @IInstantiationService private _instantiationService: IInstantiationService, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IThemeService themeService: IThemeService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IAdsTelemetryService telemetryService: IAdsTelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService private _contextMenuService: IContextMenuService, @@ -220,8 +220,8 @@ export class ConnectionDialogWidget extends Modal { this._connectionUIContainer = DOM.$('.connection-provider-info', { id: 'connectionProviderInfo' }); this._body.append(this._connectionUIContainer); - this._register(this._themeService.onThemeChange(e => this.updateTheme(e))); - this.updateTheme(this._themeService.getTheme()); + this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e))); + this.updateTheme(this._themeService.getColorTheme()); } /** @@ -240,7 +240,7 @@ export class ConnectionDialogWidget extends Modal { } // Update theming that is specific to connection flyout body - private updateTheme(theme: ITheme): void { + private updateTheme(theme: IColorTheme): void { const borderColor = theme.getColor(contrastBorder); const border = borderColor ? borderColor.toString() : null; const backgroundColor = theme.getColor(SIDE_BAR_BACKGROUND); diff --git a/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts b/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts index ab40278d52..a6fabb9b6b 100644 --- a/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts +++ b/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts @@ -28,12 +28,13 @@ import * as TypeMoq from 'typemoq'; import { IConnectionProfileGroup, ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { TestAccountManagementService } from 'sql/platform/accounts/test/common/testAccountManagementService'; -import { TestStorageService, TestEnvironmentService, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEnvironmentService, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { NullLogService } from 'vs/platform/log/common/log'; import { assign } from 'vs/base/common/objects'; import { NullAdsTelemetryService } from 'sql/platform/telemetry/common/adsTelemetryService'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; suite('SQL ConnectionManagementService tests', () => { diff --git a/src/sql/workbench/services/dashboard/browser/newDashboardTabDialogImpl.ts b/src/sql/workbench/services/dashboard/browser/newDashboardTabDialogImpl.ts index d3ebc5b2d8..179cfc13fd 100644 --- a/src/sql/workbench/services/dashboard/browser/newDashboardTabDialogImpl.ts +++ b/src/sql/workbench/services/dashboard/browser/newDashboardTabDialogImpl.ts @@ -24,10 +24,10 @@ import { NewDashboardTabViewModel, IDashboardUITab } from 'sql/workbench/service import { IDashboardTab } from 'sql/workbench/services/dashboard/browser/common/interfaces'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; class ExtensionListDelegate implements IListVirtualDelegate { @@ -108,7 +108,7 @@ export class NewDashboardTabDialog extends Modal { public get onCancel(): Event { return this._onCancel.event; } constructor( - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IThemeService themeService: IThemeService, @IAdsTelemetryService telemetryService: IAdsTelemetryService, @IContextKeyService contextKeyService: IContextKeyService, diff --git a/src/sql/workbench/services/dialog/browser/dialogModal.ts b/src/sql/workbench/services/dialog/browser/dialogModal.ts index ed204df8d0..10870586e4 100644 --- a/src/sql/workbench/services/dialog/browser/dialogModal.ts +++ b/src/sql/workbench/services/dialog/browser/dialogModal.ts @@ -18,13 +18,13 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DialogMessage } from 'sql/workbench/api/common/sqlExtHostTypes'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { append, $ } from 'vs/base/browser/dom'; import { ILogService } from 'vs/platform/log/common/log'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { onUnexpectedError } from 'vs/base/common/errors'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export class DialogModal extends Modal { private _dialogPane: DialogPane; @@ -38,7 +38,7 @@ export class DialogModal extends Modal { private _dialog: Dialog, name: string, options: IModalOptions, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IWorkbenchThemeService themeService: IWorkbenchThemeService, @IAdsTelemetryService telemetryService: IAdsTelemetryService, @IContextKeyService contextKeyService: IContextKeyService, diff --git a/src/sql/workbench/services/dialog/browser/wizardModal.ts b/src/sql/workbench/services/dialog/browser/wizardModal.ts index 93734d3a34..9de13d26f7 100644 --- a/src/sql/workbench/services/dialog/browser/wizardModal.ts +++ b/src/sql/workbench/services/dialog/browser/wizardModal.ts @@ -21,11 +21,11 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { append, $ } from 'vs/base/browser/dom'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { onUnexpectedError } from 'vs/base/common/errors'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export class WizardModal extends Modal { private _dialogPanes = new Map(); @@ -47,7 +47,7 @@ export class WizardModal extends Modal { private _wizard: Wizard, name: string, options: IModalOptions, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IThemeService themeService: IThemeService, @IAdsTelemetryService telemetryService: IAdsTelemetryService, @IContextKeyService contextKeyService: IContextKeyService, diff --git a/src/sql/workbench/services/dialog/browser/wizardNavigation.component.ts b/src/sql/workbench/services/dialog/browser/wizardNavigation.component.ts index 27014fd947..64553065ef 100644 --- a/src/sql/workbench/services/dialog/browser/wizardNavigation.component.ts +++ b/src/sql/workbench/services/dialog/browser/wizardNavigation.component.ts @@ -47,7 +47,7 @@ export class WizardNavigation implements AfterViewInit { } ngAfterViewInit() { - this._themeService.onThemeChange(() => this.style()); + this._themeService.onDidColorThemeChange(() => this.style()); this.style(); this._params.wizard.onPageChanged(() => this._changeRef.detectChanges()); } @@ -75,7 +75,7 @@ export class WizardNavigation implements AfterViewInit { } private style(): void { - let theme = this._themeService.getTheme(); + let theme = this._themeService.getColorTheme(); let navigationBackgroundColor = theme.getColor(SIDE_BAR_BACKGROUND); if (theme.type === 'light') { navigationBackgroundColor = navigationBackgroundColor.lighten(0.03); diff --git a/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts b/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts index 1d244e1db4..4d404b9d1e 100644 --- a/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts +++ b/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts @@ -19,11 +19,11 @@ import { localize } from 'vs/nls'; import { IAction } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { onUnexpectedError } from 'vs/base/common/errors'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; const maxActions = 1; @@ -46,7 +46,7 @@ export class ErrorMessageDialog extends Modal { constructor( @IThemeService themeService: IThemeService, @IClipboardService clipboardService: IClipboardService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IAdsTelemetryService telemetryService: IAdsTelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @ILogService logService: ILogService, diff --git a/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts b/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts index 99c6f12ad0..84ea2c6f94 100644 --- a/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts +++ b/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts @@ -28,11 +28,11 @@ import * as strings from 'vs/base/common/strings'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { onUnexpectedError } from 'vs/base/common/errors'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export class FileBrowserDialog extends Modal { private _viewModel: FileBrowserViewModel; @@ -50,7 +50,7 @@ export class FileBrowserDialog extends Modal { private _isFolderSelected: boolean; constructor(title: string, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IThemeService themeService: IThemeService, @IInstantiationService private _instantiationService: IInstantiationService, @IContextViewService private _contextViewService: IContextViewService, @@ -235,7 +235,7 @@ export class FileBrowserDialog extends Modal { this._register(attachButtonStyler(this._okButton, this._themeService)); this._register(attachButtonStyler(this._cancelButton, this._themeService)); - this._register(this._themeService.onThemeChange(e => this.updateTheme())); + this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme())); } // Update theming that is specific to file browser diff --git a/src/sql/workbench/services/insights/browser/insightsDialogView.ts b/src/sql/workbench/services/insights/browser/insightsDialogView.ts index 5bd76afcd5..5afbf4253e 100644 --- a/src/sql/workbench/services/insights/browser/insightsDialogView.ts +++ b/src/sql/workbench/services/insights/browser/insightsDialogView.ts @@ -36,7 +36,6 @@ import { SplitView, Orientation, Sizing } from 'vs/base/browser/ui/splitview/spl import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IInsightsConfigDetails } from 'sql/platform/dashboard/browser/insightRegistry'; import { TaskRegistry } from 'sql/workbench/services/tasks/browser/tasksRegistry'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; @@ -47,6 +46,7 @@ import { attachPanelStyler, attachModalDialogStyler } from 'sql/workbench/common import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; const labelDisplay = nls.localize("insights.item", "Item"); const valueDisplay = nls.localize("insights.value", "Value"); @@ -171,7 +171,7 @@ export class InsightsDialogView extends Modal { private _model: IInsightsDialogModel, @IThemeService themeService: IThemeService, @IClipboardService clipboardService: IClipboardService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IAdsTelemetryService telemetryService: IAdsTelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @ILogService logService: ILogService, @@ -222,12 +222,12 @@ export class InsightsDialogView extends Modal { this._topTableData = new TableDataView(); this._bottomTableData = new TableDataView(); - let topTableView = this._instantiationService.createInstance(InsightTableView, this._topColumns, this._topTableData, { forceFitColumns: true }, { id: 'insights.top', title: itemsHeaderTitle, ariaHeaderLabel: itemsHeaderTitle }) as InsightTableView; + let topTableView = this._instantiationService.createInstance(InsightTableView, this._topColumns, this._topTableData, { forceFitColumns: true }, { id: 'insights.top', title: itemsHeaderTitle }) as InsightTableView; topTableView.render(); attachPanelStyler(topTableView, this._themeService); this._topTable = topTableView.table; this._topTable.setSelectionModel(new RowSelectionModel()); - let bottomTableView = this._instantiationService.createInstance(InsightTableView, this._bottomColumns, this._bottomTableData, { forceFitColumns: true }, { id: 'insights.bottom', title: itemsDetailHeaderTitle, ariaHeaderLabel: itemsDetailHeaderTitle }) as InsightTableView; + let bottomTableView = this._instantiationService.createInstance(InsightTableView, this._bottomColumns, this._bottomTableData, { forceFitColumns: true }, { id: 'insights.bottom', title: itemsDetailHeaderTitle }) as InsightTableView; bottomTableView.render(); attachPanelStyler(bottomTableView, this._themeService); this._bottomTable = bottomTableView.table; diff --git a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts index 34b59985ad..39989a321e 100644 --- a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts +++ b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts @@ -12,7 +12,8 @@ import * as path from 'vs/base/common/path'; import { Workspace, toWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; -import { TestContextService, TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { IExtensionHostDebugParams, IDebugParams, ParsedArgs } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -53,7 +54,9 @@ class TestEnvironmentService implements IWorkbenchEnvironmentService { get configuration(): IWindowConfiguration { return { - userEnv: this.userEnv + userEnv: this.userEnv, + sessionId: 'id', + _: [] } as IWindowConfiguration; } diff --git a/src/sql/workbench/services/profiler/browser/profilerColumnEditorDialog.ts b/src/sql/workbench/services/profiler/browser/profilerColumnEditorDialog.ts index 4dcf6d6e22..1a644ec934 100644 --- a/src/sql/workbench/services/profiler/browser/profilerColumnEditorDialog.ts +++ b/src/sql/workbench/services/profiler/browser/profilerColumnEditorDialog.ts @@ -23,10 +23,10 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; class EventItem { @@ -311,7 +311,7 @@ export class ProfilerColumnEditorDialog extends Modal { private _treeContainer: HTMLElement; constructor( - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IThemeService themeService: IThemeService, @IAdsTelemetryService telemetryService: IAdsTelemetryService, @IContextKeyService contextKeyService: IContextKeyService, diff --git a/src/sql/workbench/services/profiler/browser/profilerFilterDialog.ts b/src/sql/workbench/services/profiler/browser/profilerFilterDialog.ts index be72fccb2b..8f58bc474f 100644 --- a/src/sql/workbench/services/profiler/browser/profilerFilterDialog.ts +++ b/src/sql/workbench/services/profiler/browser/profilerFilterDialog.ts @@ -23,12 +23,12 @@ import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ProfilerFilter, ProfilerFilterClause, ProfilerFilterClauseOperator, IProfilerService } from 'sql/workbench/services/profiler/browser/interfaces'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { find, firstIndex } from 'vs/base/common/arrays'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { onUnexpectedError } from 'vs/base/common/errors'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; const ClearText: string = localize('profilerFilterDialog.clear', "Clear all"); @@ -76,7 +76,7 @@ export class ProfilerFilterDialog extends Modal { constructor( @IThemeService themeService: IThemeService, @IClipboardService clipboardService: IClipboardService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IAdsTelemetryService telemetryService: IAdsTelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @ILogService logService: ILogService, diff --git a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts index c769062535..51d7b31222 100644 --- a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts +++ b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts @@ -47,7 +47,7 @@ export class QueryEditorService implements IQueryEditorService { let docUri: URI = URI.from({ scheme: Schemas.untitled, path: filePath }); // Create a sql document pane with accoutrements - const fileInput = this._editorService.createInput({ forceUntitled: true, resource: docUri, mode: 'sql' }) as UntitledTextEditorInput; + const fileInput = this._editorService.createEditorInput({ forceUntitled: true, resource: docUri, mode: 'sql' }) as UntitledTextEditorInput; let untitledEditorModel = await fileInput.resolve() as UntitledTextEditorModel; if (sqlContent) { untitledEditorModel.textEditorModel.setValue(sqlContent); @@ -82,7 +82,7 @@ export class QueryEditorService implements IQueryEditorService { let docUri: URI = URI.from({ scheme: Schemas.untitled, path: filePath }); // Create a sql document pane with accoutrements - const fileInput = this._editorService.createInput({ forceUntitled: true, resource: docUri, mode: 'sql' }) as UntitledTextEditorInput; + const fileInput = this._editorService.createEditorInput({ forceUntitled: true, resource: docUri, mode: 'sql' }) as UntitledTextEditorInput; const m = await fileInput.resolve() as UntitledTextEditorModel; //when associatedResource editor is created it is dirty, this must be set to false to be able to detect changes to the editor. m.setDirty(false); diff --git a/src/sql/workbench/services/resourceProvider/browser/firewallRuleDialog.ts b/src/sql/workbench/services/resourceProvider/browser/firewallRuleDialog.ts index 4f44c52830..8eb0130db7 100644 --- a/src/sql/workbench/services/resourceProvider/browser/firewallRuleDialog.ts +++ b/src/sql/workbench/services/resourceProvider/browser/firewallRuleDialog.ts @@ -15,7 +15,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { URI } from 'vs/base/common/uri'; import * as azdata from 'azdata'; @@ -27,11 +27,11 @@ import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox'; import { IAccountPickerService } from 'sql/workbench/services/accountManagement/browser/accountPicker'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; // TODO: Make the help link 1) extensible (01/08/2018, https://github.com/Microsoft/azuredatastudio/issues/450) // in case that other non-Azure sign in is to be used @@ -66,7 +66,7 @@ export class FirewallRuleDialog extends Modal { constructor( @IAccountPickerService private _accountPickerService: IAccountPickerService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IThemeService themeService: IThemeService, @IInstantiationService private _instantiationService: IInstantiationService, @IContextViewService private _contextViewService: IContextViewService, @@ -184,8 +184,8 @@ export class FirewallRuleDialog extends Modal { ariaLabel: LocalizedStrings.TO }); - this._register(this._themeService.onThemeChange(e => this.updateTheme(e))); - this.updateTheme(this._themeService.getTheme()); + this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e))); + this.updateTheme(this._themeService.getColorTheme()); this._register(DOM.addDisposableListener(this._IPAddressElement, DOM.EventType.CLICK, () => { this.onFirewallRuleOptionSelected(true); @@ -219,7 +219,7 @@ export class FirewallRuleDialog extends Modal { } // Update theming that is specific to firewall rule flyout body - private updateTheme(theme: ITheme): void { + private updateTheme(theme: IColorTheme): void { const linkColor = theme.getColor(buttonBackground); const link = linkColor ? linkColor.toString() : null; if (this._helpLink) { diff --git a/src/sql/workbench/services/restore/browser/restoreDialog.ts b/src/sql/workbench/services/restore/browser/restoreDialog.ts index 65b73ae0a3..ac9249dafb 100644 --- a/src/sql/workbench/services/restore/browser/restoreDialog.ts +++ b/src/sql/workbench/services/restore/browser/restoreDialog.ts @@ -40,11 +40,11 @@ import { ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; import { IFileBrowserDialogController } from 'sql/workbench/services/fileBrowser/common/fileBrowserDialogController'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { attachModalDialogStyler, attachTabbedPanelStyler } from 'sql/workbench/common/styler'; import { fileFiltersSet } from 'sql/workbench/services/restore/common/constants'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; interface FileListElement { logicalFileName: string; @@ -131,7 +131,7 @@ export class RestoreDialog extends Modal { constructor( optionsMetadata: azdata.ServiceOption[], - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IThemeService themeService: IThemeService, @IContextViewService private _contextViewService: IContextViewService, @IAdsTelemetryService telemetryService: IAdsTelemetryService, diff --git a/src/sql/workbench/services/serverGroup/browser/serverGroupDialog.ts b/src/sql/workbench/services/serverGroup/browser/serverGroupDialog.ts index 4b72063a40..2952604f26 100644 --- a/src/sql/workbench/services/serverGroup/browser/serverGroupDialog.ts +++ b/src/sql/workbench/services/serverGroup/browser/serverGroupDialog.ts @@ -25,12 +25,12 @@ import { attachButtonStyler } from 'sql/platform/theme/common/styler'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { Color } from 'vs/base/common/color'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; import { assertIsDefined, isUndefinedOrNull } from 'vs/base/common/types'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; interface IRenderedServerGroupDialog { groupNameInputBox: InputBox; @@ -64,7 +64,7 @@ export class ServerGroupDialog extends Modal { private _closeButton?: Button; constructor( - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ILayoutService layoutService: ILayoutService, @IThemeService themeService: IThemeService, @IContextViewService private _contextViewService: IContextViewService, @IAdsTelemetryService telemetryService: IAdsTelemetryService, diff --git a/src/tsconfig.base.json b/src/tsconfig.base.json index 07c36f8d13..67d9cfaae6 100644 --- a/src/tsconfig.base.json +++ b/src/tsconfig.base.json @@ -21,6 +21,14 @@ "sql/*": [ "./sql/*" ] - } + }, + "lib": [ + "ES2015", + "ES2017.String", + "ES2018.Promise", + "DOM", + "DOM.Iterable", + "WebWorker.ImportScripts" + ] } } diff --git a/src/tsconfig.json b/src/tsconfig.json index 20ea16c2df..d08c09c3f8 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -4,13 +4,8 @@ "removeComments": false, "preserveConstEnums": true, "sourceMap": false, - "outDir": "../out", + "outDir": "../out/vs", "target": "es2017", - "lib": [ - "dom", - "es5", - "es2015.iterable" - ], "types": [ "keytar", "mocha", @@ -23,8 +18,5 @@ "./typings", "./vs", "./sql" - ], - "exclude": [ - "./typings/es6-promise.d.ts" ] } diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index a6430a44cc..1e169f96b3 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -8,17 +8,14 @@ "moduleResolution": "classic", "removeComments": false, "preserveConstEnums": true, - "target": "es5", + "target": "es6", "sourceMap": false, "declaration": true }, "include": [ "typings/require.d.ts", "typings/thenable.d.ts", - "typings/es6-promise.d.ts", - "typings/lib.es2018.promise.d.ts", "typings/lib.array-ext.d.ts", - "typings/lib.ie11_safe_es6.d.ts", "vs/css.d.ts", "vs/monaco.d.ts", "vs/nls.d.ts", diff --git a/src/typings/es2015-proxy.d.ts b/src/typings/es2015-proxy.d.ts deleted file mode 100644 index 91dd511792..0000000000 --- a/src/typings/es2015-proxy.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// from TypeScript: lib.es2015.proxy.d.ts - -interface ProxyHandler { - getPrototypeOf?(target: T): object | null; - setPrototypeOf?(target: T, v: any): boolean; - isExtensible?(target: T): boolean; - preventExtensions?(target: T): boolean; - getOwnPropertyDescriptor?(target: T, p: PropertyKey): PropertyDescriptor | undefined; - has?(target: T, p: PropertyKey): boolean; - get?(target: T, p: PropertyKey, receiver: any): any; - set?(target: T, p: PropertyKey, value: any, receiver: any): boolean; - deleteProperty?(target: T, p: PropertyKey): boolean; - defineProperty?(target: T, p: PropertyKey, attributes: PropertyDescriptor): boolean; - enumerate?(target: T): PropertyKey[]; - ownKeys?(target: T): PropertyKey[]; - apply?(target: T, thisArg: any, argArray?: any): any; - construct?(target: T, argArray: any, newTarget?: any): object; -} - -interface ProxyConstructor { - revocable(target: T, handler: ProxyHandler): { proxy: T; revoke: () => void; }; - new (target: T, handler: ProxyHandler): T; -} -declare var Proxy: ProxyConstructor; diff --git a/src/typings/es6-promise.d.ts b/src/typings/es6-promise.d.ts deleted file mode 100644 index 2d3271e284..0000000000 --- a/src/typings/es6-promise.d.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Type definitions for es6-promise -// Project: https://github.com/jakearchibald/ES6-Promise -// Definitions by: François de Campredon , vvakame -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -interface Thenable { - then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => U | Thenable): Thenable; - then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => void): Thenable; -} - -declare class Promise implements Thenable { - /** - * If you call resolve in the body of the callback passed to the constructor, - * your promise is fulfilled with result object passed to resolve. - * If you call reject your promise is rejected with the object passed to reject. - * For consistency and debugging (eg stack traces), obj should be an instanceof Error. - * Any errors thrown in the constructor callback will be implicitly passed to reject(). - */ - constructor(callback: (resolve: (value?: T | Thenable) => void, reject: (error?: any) => void) => void); - - /** - * onFulfilled is called when/if "promise" resolves. onRejected is called when/if "promise" rejects. - * Both are optional, if either/both are omitted the next onFulfilled/onRejected in the chain is called. - * Both callbacks have a single parameter , the fulfillment value or rejection reason. - * "then" returns a new promise equivalent to the value you return from onFulfilled/onRejected after being passed through Promise.resolve. - * If an error is thrown in the callback, the returned promise rejects with that error. - * - * @param onFulfilled called when/if "promise" resolves - * @param onRejected called when/if "promise" rejects - */ - then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => U | Thenable): Promise; - then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => void): Promise; - - /** - * Sugar for promise.then(undefined, onRejected) - * - * @param onRejected called when/if "promise" rejects - */ - catch(onRejected?: (error: any) => U | Thenable): Promise; -} - -declare namespace Promise { - /** - * Make a new promise from the thenable. - * A thenable is promise-like in as far as it has a "then" method. - */ - function resolve(value: T | Thenable): Promise; - - /** - * - */ - function resolve(): Promise; - - /** - * Make a promise that rejects to obj. For consistency and debugging (eg stack traces), obj should be an instanceof Error - */ - function reject(error: any): Promise; - function reject(error: T): Promise; - - /** - * Make a promise that fulfills when every item in the array fulfills, and rejects if (and when) any item rejects. - * the array passed to all can be a mixture of promise-like objects and other objects. - * The fulfillment value is an array (in order) of fulfillment values. The rejection value is the first rejection value. - */ - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable, T5 | Thenable, T6 | Thenable, T7 | Thenable, T8 | Thenable, T9 | Thenable, T10 | Thenable]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>; - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable, T5 | Thenable, T6 | Thenable, T7 | Thenable, T8 | Thenable, T9 | Thenable]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>; - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable, T5 | Thenable, T6 | Thenable, T7 | Thenable, T8 | Thenable]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8]>; - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable, T5 | Thenable, T6 | Thenable, T7 | Thenable]): Promise<[T1, T2, T3, T4, T5, T6, T7]>; - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable, T5 | Thenable, T6 | Thenable]): Promise<[T1, T2, T3, T4, T5, T6]>; - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable, T5 | Thenable]): Promise<[T1, T2, T3, T4, T5]>; - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable, T4 | Thenable]): Promise<[T1, T2, T3, T4]>; - function all(values: [T1 | Thenable, T2 | Thenable, T3 | Thenable]): Promise<[T1, T2, T3]>; - function all(values: [T1 | Thenable, T2 | Thenable]): Promise<[T1, T2]>; - function all(values: (T | Thenable)[]): Promise; - - /** - * Make a Promise that fulfills when any item fulfills, and rejects if any item rejects. - */ - function race(promises: (T | Thenable)[]): Promise; -} - -declare module 'es6-promise' { - var foo: typeof Promise; // Temp variable to reference Promise in local context - namespace rsvp { - export var Promise: typeof foo; - export function polyfill(): void; - } - export = rsvp; -} diff --git a/src/typings/lib.es2018.promise.d.ts b/src/typings/lib.es2018.promise.d.ts deleted file mode 100644 index 9f7b2d38cb..0000000000 --- a/src/typings/lib.es2018.promise.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at http://www.apache.org/licenses/LICENSE-2.0 - -THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED -WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -MERCHANTABLITY OR NON-INFRINGEMENT. - -See the Apache Version 2.0 License for specific language governing permissions -and limitations under the License. -***************************************************************************** */ - -/** - * Represents the completion of an asynchronous operation - */ -interface Promise { - /** - * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The - * resolved value cannot be modified from the callback. - * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). - * @returns A Promise for the completion of the callback. - */ - finally(onfinally?: (() => void) | undefined | null): Promise; -} diff --git a/src/typings/lib.ie11_safe_es6.d.ts b/src/typings/lib.ie11_safe_es6.d.ts deleted file mode 100644 index 548703cfc0..0000000000 --- a/src/typings/lib.ie11_safe_es6.d.ts +++ /dev/null @@ -1,821 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// Defined a subset of ES6 built ins that run in IE11 -// CHECK WITH http://kangax.github.io/compat-table/es6/#ie11 - -interface Map { - clear(): void; - delete(key: K): boolean; - forEach(callbackfn: (value: V, index: K, map: Map) => void, thisArg?: any): void; - get(key: K): V | undefined; - has(key: K): boolean; - set(key: K, value: V): Map; - readonly size: number; - - // not supported on IE11: - // entries(): IterableIterator<[K, V]>; - // keys(): IterableIterator; - // values(): IterableIterator; - // [Symbol.iterator]():IterableIterator<[K,V]>; - // [Symbol.toStringTag]: string; -} - -interface MapConstructor { - new (): Map; - readonly prototype: Map; - - // not supported on IE11: - // new (iterable: Iterable<[K, V]>): Map; -} -declare var Map: MapConstructor; - - -interface Set { - add(value: T): Set; - clear(): void; - delete(value: T): boolean; - forEach(callbackfn: (value: T, index: T, set: Set) => void, thisArg?: any): void; - has(value: T): boolean; - readonly size: number; - - // not supported on IE11: - // entries(): IterableIterator<[T, T]>; - // keys(): IterableIterator; - // values(): IterableIterator; - // [Symbol.iterator]():IterableIterator; - // [Symbol.toStringTag]: string; -} - -interface SetConstructor { - new (): Set; - readonly prototype: Set; - - // not supported on IE11: - // new (iterable: Iterable): Set; -} -declare var Set: SetConstructor; - - -interface WeakMap { - delete(key: K): boolean; - get(key: K): V | undefined; - has(key: K): boolean; - // IE11 doesn't return this - // set(key: K, value?: V): this; - set(key: K, value?: V): undefined; -} - -interface WeakMapConstructor { - new(): WeakMap; - new (): WeakMap; - // new (entries?: [K, V][]): WeakMap; - readonly prototype: WeakMap; -} -declare var WeakMap: WeakMapConstructor; - - -// /** -// * Represents a raw buffer of binary data, which is used to store data for the -// * different typed arrays. ArrayBuffers cannot be read from or written to directly, -// * but can be passed to a typed array or DataView Object to interpret the raw -// * buffer as needed. -// */ -// interface ArrayBuffer { -// /** -// * Read-only. The length of the ArrayBuffer (in bytes). -// */ -// readonly byteLength: number; - -// /** -// * Returns a section of an ArrayBuffer. -// */ -// slice(begin: number, end?: number): ArrayBuffer; -// } - -// interface ArrayBufferConstructor { -// readonly prototype: ArrayBuffer; -// new (byteLength: number): ArrayBuffer; -// isView(arg: any): arg is ArrayBufferView; -// } -// declare const ArrayBuffer: ArrayBufferConstructor; - -// interface ArrayBufferView { -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// byteOffset: number; -// } - -// interface DataView { -// readonly buffer: ArrayBuffer; -// readonly byteLength: number; -// readonly byteOffset: number; -// /** -// * Gets the Float32 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getFloat32(byteOffset: number, littleEndian?: boolean): number; - -// /** -// * Gets the Float64 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getFloat64(byteOffset: number, littleEndian?: boolean): number; - -// /** -// * Gets the Int8 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getInt8(byteOffset: number): number; - -// /** -// * Gets the Int16 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getInt16(byteOffset: number, littleEndian?: boolean): number; -// /** -// * Gets the Int32 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getInt32(byteOffset: number, littleEndian?: boolean): number; - -// /** -// * Gets the Uint8 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getUint8(byteOffset: number): number; - -// /** -// * Gets the Uint16 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getUint16(byteOffset: number, littleEndian?: boolean): number; - -// /** -// * Gets the Uint32 value at the specified byte offset from the start of the view. There is -// * no alignment constraint; multi-byte values may be fetched from any offset. -// * @param byteOffset The place in the buffer at which the value should be retrieved. -// */ -// getUint32(byteOffset: number, littleEndian?: boolean): number; - -// /** -// * Stores an Float32 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// * @param littleEndian If false or undefined, a big-endian value should be written, -// * otherwise a little-endian value should be written. -// */ -// setFloat32(byteOffset: number, value: number, littleEndian?: boolean): void; - -// /** -// * Stores an Float64 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// * @param littleEndian If false or undefined, a big-endian value should be written, -// * otherwise a little-endian value should be written. -// */ -// setFloat64(byteOffset: number, value: number, littleEndian?: boolean): void; - -// /** -// * Stores an Int8 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// */ -// setInt8(byteOffset: number, value: number): void; - -// /** -// * Stores an Int16 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// * @param littleEndian If false or undefined, a big-endian value should be written, -// * otherwise a little-endian value should be written. -// */ -// setInt16(byteOffset: number, value: number, littleEndian?: boolean): void; - -// /** -// * Stores an Int32 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// * @param littleEndian If false or undefined, a big-endian value should be written, -// * otherwise a little-endian value should be written. -// */ -// setInt32(byteOffset: number, value: number, littleEndian?: boolean): void; - -// /** -// * Stores an Uint8 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// */ -// setUint8(byteOffset: number, value: number): void; - -// /** -// * Stores an Uint16 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// * @param littleEndian If false or undefined, a big-endian value should be written, -// * otherwise a little-endian value should be written. -// */ -// setUint16(byteOffset: number, value: number, littleEndian?: boolean): void; - -// /** -// * Stores an Uint32 value at the specified byte offset from the start of the view. -// * @param byteOffset The place in the buffer at which the value should be set. -// * @param value The value to set. -// * @param littleEndian If false or undefined, a big-endian value should be written, -// * otherwise a little-endian value should be written. -// */ -// setUint32(byteOffset: number, value: number, littleEndian?: boolean): void; -// } - -// interface DataViewConstructor { -// new (buffer: ArrayBuffer, byteOffset?: number, byteLength?: number): DataView; -// } -// declare const DataView: DataViewConstructor; - - -// /** -// * A typed array of 8-bit integer values. The contents are initialized to 0. If the requested -// * number of bytes could not be allocated an exception is raised. -// */ -// interface Int8Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } -// interface Int8ArrayConstructor { -// readonly prototype: Int8Array; -// new (length: number): Int8Array; -// new (array: ArrayLike): Int8Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Int8Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// } -// declare const Int8Array: Int8ArrayConstructor; - -// /** -// * A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the -// * requested number of bytes could not be allocated an exception is raised. -// */ -// interface Uint8Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } - -// interface Uint8ArrayConstructor { -// readonly prototype: Uint8Array; -// new (length: number): Uint8Array; -// new (array: ArrayLike): Uint8Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint8Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// } -// declare const Uint8Array: Uint8ArrayConstructor; - - -// /** -// * A typed array of 16-bit signed integer values. The contents are initialized to 0. If the -// * requested number of bytes could not be allocated an exception is raised. -// */ -// interface Int16Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } - -// interface Int16ArrayConstructor { -// readonly prototype: Int16Array; -// new (length: number): Int16Array; -// new (array: ArrayLike): Int16Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Int16Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// } -// declare const Int16Array: Int16ArrayConstructor; - -// /** -// * A typed array of 16-bit unsigned integer values. The contents are initialized to 0. If the -// * requested number of bytes could not be allocated an exception is raised. -// */ -// interface Uint16Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } - -// interface Uint16ArrayConstructor { -// readonly prototype: Uint16Array; -// new (length: number): Uint16Array; -// new (array: ArrayLike): Uint16Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint16Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// } -// declare const Uint16Array: Uint16ArrayConstructor; -// /** -// * A typed array of 32-bit signed integer values. The contents are initialized to 0. If the -// * requested number of bytes could not be allocated an exception is raised. -// */ -// interface Int32Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } - -// interface Int32ArrayConstructor { -// readonly prototype: Int32Array; -// new (length: number): Int32Array; -// new (array: ArrayLike): Int32Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Int32Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; -// } - -// declare const Int32Array: Int32ArrayConstructor; - -// /** -// * A typed array of 32-bit unsigned integer values. The contents are initialized to 0. If the -// * requested number of bytes could not be allocated an exception is raised. -// */ -// interface Uint32Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } - -// interface Uint32ArrayConstructor { -// readonly prototype: Uint32Array; -// new (length: number): Uint32Array; -// new (array: ArrayLike): Uint32Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint32Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; -// } - -// declare const Uint32Array: Uint32ArrayConstructor; - -// /** -// * A typed array of 32-bit float values. The contents are initialized to 0. If the requested number -// * of bytes could not be allocated an exception is raised. -// */ -// interface Float32Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } - -// interface Float32ArrayConstructor { -// readonly prototype: Float32Array; -// new (length: number): Float32Array; -// new (array: ArrayLike): Float32Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Float32Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// } -// declare const Float32Array: Float32ArrayConstructor; - -// /** -// * A typed array of 64-bit float values. The contents are initialized to 0. If the requested -// * number of bytes could not be allocated an exception is raised. -// */ -// interface Float64Array { -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; - -// /** -// * The ArrayBuffer instance referenced by the array. -// */ -// readonly buffer: ArrayBuffer; - -// /** -// * The length in bytes of the array. -// */ -// readonly byteLength: number; - -// /** -// * The offset in bytes of the array. -// */ -// readonly byteOffset: number; - -// /** -// * The length of the array. -// */ -// readonly length: number; - -// /** -// * Sets a value or an array of values. -// * @param index The index of the location to set. -// * @param value The value to set. -// */ -// set(index: number, value: number): void; - -// /** -// * Sets a value or an array of values. -// * @param array A typed or untyped array of values to set. -// * @param offset The index in the current array at which the values are to be written. -// */ -// set(array: ArrayLike, offset?: number): void; - -// /** -// * Converts a number to a string by using the current locale. -// */ -// toLocaleString(): string; - -// /** -// * Returns a string representation of an array. -// */ -// toString(): string; - -// [index: number]: number; -// } - -// interface Float64ArrayConstructor { -// readonly prototype: Float64Array; -// new (length: number): Float64Array; -// new (array: ArrayLike): Float64Array; -// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Float64Array; - -// /** -// * The size in bytes of each element in the array. -// */ -// readonly BYTES_PER_ELEMENT: number; -// } - -// declare const Float64Array: Float64ArrayConstructor; diff --git a/src/typings/lib.webworker.importscripts.d.ts b/src/typings/lib.webworker.importscripts.d.ts deleted file mode 100644 index e84f717c9a..0000000000 --- a/src/typings/lib.webworker.importscripts.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at http://www.apache.org/licenses/LICENSE-2.0 - -THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED -WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -MERCHANTABLITY OR NON-INFRINGEMENT. - -See the Apache Version 2.0 License for specific language governing permissions -and limitations under the License. -***************************************************************************** */ - - - - -///////////////////////////// -/// WorkerGlobalScope APIs -///////////////////////////// -// These are only available in a Web Worker -declare function importScripts(...urls: string[]): void; diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 57da73ec85..4968e1652d 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -110,10 +110,7 @@ export const onDidChangeFullscreen = WindowManager.INSTANCE.onDidChangeFullscree const userAgent = navigator.userAgent; -export const isIE = (userAgent.indexOf('Trident') >= 0); export const isEdge = (userAgent.indexOf('Edge/') >= 0); -export const isEdgeOrIE = isIE || isEdge; - export const isOpera = (userAgent.indexOf('Opera') >= 0); export const isFirefox = (userAgent.indexOf('Firefox') >= 0); export const isWebKit = (userAgent.indexOf('AppleWebKit') >= 0); diff --git a/src/vs/base/browser/canIUse.ts b/src/vs/base/browser/canIUse.ts index c44476cd5b..a1729e784b 100644 --- a/src/vs/base/browser/canIUse.ts +++ b/src/vs/base/browser/canIUse.ts @@ -27,10 +27,6 @@ export const BrowserFeatures = { || !!(navigator && navigator.clipboard && navigator.clipboard.readText) ), richText: (() => { - if (browser.isIE) { - return false; - } - if (browser.isEdge) { let index = navigator.userAgent.indexOf('Edge/'); let version = parseInt(navigator.userAgent.substring(index + 5, navigator.userAgent.indexOf('.', index)), 10); diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index e749f1e1bd..b96ab3f508 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -8,7 +8,6 @@ import { domEvent } from 'vs/base/browser/event'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { TimeoutTimer } from 'vs/base/common/async'; -import { CharCode } from 'vs/base/common/charCode'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -49,117 +48,7 @@ interface IDomClassList { toggleClass(node: HTMLElement | SVGElement, className: string, shouldHaveIt?: boolean): void; } -const _manualClassList = new class implements IDomClassList { - - private _lastStart: number = -1; - private _lastEnd: number = -1; - - private _findClassName(node: HTMLElement, className: string): void { - - let classes = node.className; - if (!classes) { - this._lastStart = -1; - return; - } - - className = className.trim(); - - let classesLen = classes.length, - classLen = className.length; - - if (classLen === 0) { - this._lastStart = -1; - return; - } - - if (classesLen < classLen) { - this._lastStart = -1; - return; - } - - if (classes === className) { - this._lastStart = 0; - this._lastEnd = classesLen; - return; - } - - let idx = -1, - idxEnd: number; - - while ((idx = classes.indexOf(className, idx + 1)) >= 0) { - - idxEnd = idx + classLen; - - // a class that is followed by another class - if ((idx === 0 || classes.charCodeAt(idx - 1) === CharCode.Space) && classes.charCodeAt(idxEnd) === CharCode.Space) { - this._lastStart = idx; - this._lastEnd = idxEnd + 1; - return; - } - - // last class - if (idx > 0 && classes.charCodeAt(idx - 1) === CharCode.Space && idxEnd === classesLen) { - this._lastStart = idx - 1; - this._lastEnd = idxEnd; - return; - } - - // equal - duplicate of cmp above - if (idx === 0 && idxEnd === classesLen) { - this._lastStart = 0; - this._lastEnd = idxEnd; - return; - } - } - - this._lastStart = -1; - } - - hasClass(node: HTMLElement, className: string): boolean { - this._findClassName(node, className); - return this._lastStart !== -1; - } - - addClasses(node: HTMLElement, ...classNames: string[]): void { - classNames.forEach(nameValue => nameValue.split(' ').forEach(name => this.addClass(node, name))); - } - - addClass(node: HTMLElement, className: string): void { - if (!node.className) { // doesn't have it for sure - node.className = className; - } else { - this._findClassName(node, className); // see if it's already there - if (this._lastStart === -1) { - node.className = node.className + ' ' + className; - } - } - } - - removeClass(node: HTMLElement, className: string): void { - this._findClassName(node, className); - if (this._lastStart === -1) { - return; // Prevent styles invalidation if not necessary - } else { - node.className = node.className.substring(0, this._lastStart) + node.className.substring(this._lastEnd); - } - } - - removeClasses(node: HTMLElement, ...classNames: string[]): void { - classNames.forEach(nameValue => nameValue.split(' ').forEach(name => this.removeClass(node, name))); - } - - toggleClass(node: HTMLElement, className: string, shouldHaveIt?: boolean): void { - this._findClassName(node, className); - if (this._lastStart !== -1 && (shouldHaveIt === undefined || !shouldHaveIt)) { - this.removeClass(node, className); - } - if (this._lastStart === -1 && (shouldHaveIt === undefined || shouldHaveIt)) { - this.addClass(node, className); - } - } -}; - -const _nativeClassList = new class implements IDomClassList { +const _classList: IDomClassList = new class implements IDomClassList { hasClass(node: HTMLElement, className: string): boolean { return Boolean(className) && node.classList && node.classList.contains(className); } @@ -191,9 +80,6 @@ const _nativeClassList = new class implements IDomClassList { } }; -// In IE11 there is only partial support for `classList` which makes us keep our -// custom implementation. Otherwise use the native implementation, see: http://caniuse.com/#search=classlist -const _classList: IDomClassList = browser.isIE ? _manualClassList : _nativeClassList; export const hasClass: (node: HTMLElement | SVGElement, className: string) => boolean = _classList.hasClass.bind(_classList); export const addClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.addClass.bind(_classList); export const addClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.addClasses.bind(_classList); @@ -606,7 +492,12 @@ class SizeUtils { // ---------------------------------------------------------------------------------------- // Position & Dimension -export class Dimension { +export interface IDimension { + readonly width: number; + readonly height: number; +} + +export class Dimension implements IDimension { constructor( public readonly width: number, diff --git a/src/vs/base/browser/fastDomNode.ts b/src/vs/base/browser/fastDomNode.ts index 021c363526..cc62090bfd 100644 --- a/src/vs/base/browser/fastDomNode.ts +++ b/src/vs/base/browser/fastDomNode.ts @@ -244,11 +244,11 @@ export class FastDomNode { this.domNode.removeAttribute(name); } - public appendChild(child: FastDomNode): void { + public appendChild(child: FastDomNode): void { this.domNode.appendChild(child.domNode); } - public removeChild(child: FastDomNode): void { + public removeChild(child: FastDomNode): void { this.domNode.removeChild(child.domNode); } } diff --git a/src/vs/base/browser/globalMouseMoveMonitor.ts b/src/vs/base/browser/globalMouseMoveMonitor.ts index 014a4ec78e..5265d2fb1b 100644 --- a/src/vs/base/browser/globalMouseMoveMonitor.ts +++ b/src/vs/base/browser/globalMouseMoveMonitor.ts @@ -5,7 +5,6 @@ import * as dom from 'vs/base/browser/dom'; import * as platform from 'vs/base/common/platform'; -import * as browser from 'vs/base/browser/browser'; import { IframeUtils } from 'vs/base/browser/iframe'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -103,7 +102,7 @@ export class GlobalMouseMoveMonitor implements I for (const element of listenTo) { this._hooks.add(dom.addDisposableThrottledListener(element, mouseMove, (data: R) => { - if (!browser.isIE && data.buttons !== initialButtons) { + if (data.buttons !== initialButtons) { // Buttons state has changed in the meantime this.stopMonitoring(true); return; diff --git a/src/vs/base/browser/iframe.ts b/src/vs/base/browser/iframe.ts index 7d0f5647bb..b6a3387697 100644 --- a/src/vs/base/browser/iframe.ts +++ b/src/vs/base/browser/iframe.ts @@ -98,7 +98,7 @@ export class IframeUtils { /** * Returns the position of `childWindow` relative to `ancestorWindow` */ - public static getPositionOfChildWindowRelativeToAncestorWindow(childWindow: Window, ancestorWindow: any) { + public static getPositionOfChildWindowRelativeToAncestorWindow(childWindow: Window, ancestorWindow: Window | null) { if (!ancestorWindow || childWindow === ancestorWindow) { return { diff --git a/src/vs/base/browser/keyboardEvent.ts b/src/vs/base/browser/keyboardEvent.ts index 6a9177865c..1bb5f6f0df 100644 --- a/src/vs/base/browser/keyboardEvent.ts +++ b/src/vs/base/browser/keyboardEvent.ts @@ -145,9 +145,7 @@ let INVERSE_KEY_CODE_MAP: KeyCode[] = new Array(KeyCode.MAX_VALUE); */ define(229, KeyCode.KEY_IN_COMPOSITION); - if (browser.isIE) { - define(91, KeyCode.Meta); - } else if (browser.isFirefox) { + if (browser.isFirefox) { define(59, KeyCode.US_SEMICOLON); define(107, KeyCode.US_EQUAL); define(109, KeyCode.US_MINUS); diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 7945c3a7c7..d9060f4354 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -58,12 +58,16 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende return href; // no tranformation performed } if (isDomUri) { - uri = DOM.asDomUri(uri); + // this URI will end up as "src"-attribute of a dom node + // and because of that special rewriting needs to be done + // so that the URI uses a protocol that's understood by + // browsers (like http or https) + return DOM.asDomUri(uri).toString(true); } if (uri.query) { uri = uri.with({ query: _uriMassage(uri.query) }); } - return uri.toString(true); + return uri.toString(); }; // signal to code-block render that the diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts index a8536d73d4..ee3e7436da 100644 --- a/src/vs/base/browser/touch.ts +++ b/src/vs/base/browser/touch.ts @@ -131,7 +131,7 @@ export class Gesture extends Disposable { @memoize private static isTouchDevice(): boolean { - return 'ontouchstart' in window as any || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0; + return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0; } public dispose(): void { @@ -247,7 +247,7 @@ export class Gesture extends Disposable { } private newGestureEvent(type: string, initialTarget?: EventTarget): GestureEvent { - let event = (document.createEvent('CustomEvent')); + let event = document.createEvent('CustomEvent') as unknown as GestureEvent; event.initEvent(type, false, true); event.initialTarget = initialTarget; event.tapCount = 0; diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index d93c4f6f06..358fdca90e 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -45,6 +45,10 @@ display: inline-block; } +.monaco-action-bar .action-item .codicon { + vertical-align: middle; +} + .monaco-action-bar .action-label { font-size: 11px; margin-right: 4px; diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 8dc4c5ed2f..0ad28cccc5 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -104,7 +104,7 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { return this._action.enabled; } - setActionContext(newContext: any): void { + setActionContext(newContext: unknown): void { this._context = newContext; } @@ -248,7 +248,7 @@ export class ActionViewItem extends BaseActionViewItem { private cssClass?: string; - constructor(context: any, action: IAction, options: IActionViewItemOptions = {}) { + constructor(context: unknown, action: IAction, options: IActionViewItemOptions = {}) { super(context, action, options); this.options = options; @@ -423,7 +423,7 @@ export class ActionBar extends Disposable implements IActionRunner { options: IActionBarOptions; private _actionRunner: IActionRunner; - private _context: any; + private _context: unknown; // View Items viewItems: IActionViewItem[]; @@ -821,7 +821,7 @@ export class ActionBar extends Disposable implements IActionRunner { this._onDidCancel.fire(); } - run(action: IAction, context?: any): Promise { + run(action: IAction, context?: unknown): Promise { return this._actionRunner.run(action, context); } @@ -838,7 +838,7 @@ export class ActionBar extends Disposable implements IActionRunner { export class SelectActionViewItem extends BaseActionViewItem { protected selectBox: SelectBox; - constructor(ctx: any, action: IAction, options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, selectBoxOptions?: ISelectBoxOptions) { + constructor(ctx: unknown, action: IAction, options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, selectBoxOptions?: ISelectBoxOptions) { super(ctx, action); this.selectBox = new SelectBox(options, selected, contextViewProvider, undefined, selectBoxOptions); diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts b/src/vs/base/browser/ui/codiconLabel/codicon/codicon-modifications.css similarity index 51% rename from src/vs/workbench/browser/parts/quickinput/quickInputActions.ts rename to src/vs/base/browser/ui/codiconLabel/codicon/codicon-modifications.css index f59187737d..7245e86d58 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputActions.ts +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon-modifications.css @@ -3,8 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { QuickPickManyToggle, QuickPickBack } from 'vs/workbench/browser/parts/quickinput/quickInput'; -import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; - -KeybindingsRegistry.registerCommandAndKeybindingRule(QuickPickManyToggle); -KeybindingsRegistry.registerCommandAndKeybindingRule(QuickPickBack); +.codicon-wrench-subaction { + opacity: 0.5; +} diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index 2635caa6e1..13cfd2856c 100644 --- a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css @@ -5,7 +5,7 @@ @font-face { font-family: "codicon"; - src: url("./codicon.ttf?b5dd8f5aa953889dc1f4c9fa9b44d3dd") format("truetype"); + src: url("./codicon.ttf?df9e07bbeddc0cf98f4d7a7c92bef3d8") format("truetype"); } .codicon[class*='codicon-'] { @@ -360,6 +360,8 @@ .codicon-symbol-misc:before { content: "\eb63" } .codicon-symbol-operator:before { content: "\eb64" } .codicon-symbol-property:before { content: "\eb65" } +.codicon-wrench:before { content: "\eb65" } +.codicon-wrench-subaction:before { content: "\eb65" } .codicon-symbol-snippet:before { content: "\eb66" } .codicon-tasklist:before { content: "\eb67" } .codicon-telescope:before { content: "\eb68" } @@ -415,6 +417,5 @@ .codicon-group-by-ref-type:before { content: "\eb97" } .codicon-ungroup-by-ref-type:before { content: "\eb98" } .codicon-bell-dot:before { content: "\f101" } -.codicon-bell-progress:before { content: "\f102" } -.codicon-debug-alt-2:before { content: "\f103" } -.codicon-debug-alt:before { content: "\f104" } +.codicon-debug-alt-2:before { content: "\f102" } +.codicon-debug-alt:before { content: "\f103" } diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf index 845205f5b3..94b2533d5b 100644 Binary files a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf and b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf differ diff --git a/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts b/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts index 1b27d9aff3..fbf05404a2 100644 --- a/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts +++ b/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./codicon/codicon'; +import 'vs/css!./codicon/codicon-modifications'; import 'vs/css!./codicon/codicon-animations'; import { escape } from 'vs/base/common/strings'; import { renderCodicons } from 'vs/base/common/codicons'; diff --git a/src/vs/base/browser/ui/dialog/dialog.css b/src/vs/base/browser/ui/dialog/dialog.css index dc5d8a6302..8dcf3a7298 100644 --- a/src/vs/base/browser/ui/dialog/dialog.css +++ b/src/vs/base/browser/ui/dialog/dialog.css @@ -25,7 +25,7 @@ flex-direction: column-reverse; width: min-content; min-width: 500px; - max-width: 90%; + max-width: 90vw; min-height: 75px; padding: 10px; transform: translate3d(0px, 0px, 0px); diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index 278d9106da..0cd0bd8326 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -271,7 +271,7 @@ export class DropdownMenu extends BaseDropdown { } export class DropdownMenuActionViewItem extends BaseActionViewItem { - private menuActionsOrProvider: any; + private menuActionsOrProvider: ReadonlyArray | IActionProvider; private dropdownMenu: DropdownMenu | undefined; private contextMenuProvider: IContextMenuProvider; private actionViewItemProvider?: IActionViewItemProvider; @@ -317,7 +317,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { if (Array.isArray(this.menuActionsOrProvider)) { options.actions = this.menuActionsOrProvider; } else { - options.actionProvider = this.menuActionsOrProvider; + options.actionProvider = this.menuActionsOrProvider as IActionProvider; } this.dropdownMenu = this._register(new DropdownMenu(container, options)); @@ -341,7 +341,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { } } - setActionContext(newContext: any): void { + setActionContext(newContext: unknown): void { super.setActionContext(newContext); if (this.dropdownMenu) { diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 643cd87449..5711f17636 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -6,7 +6,6 @@ import 'vs/css!./inputBox'; import * as nls from 'vs/nls'; -import * as Bal from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer'; import { renderFormattedText, renderText } from 'vs/base/browser/formattedTextRenderer'; @@ -234,14 +233,6 @@ export class InputBox extends Widget { this.onblur(this.input, () => this.onBlur()); this.onfocus(this.input, () => this.onFocus()); - // Add placeholder shim for IE because IE decides to hide the placeholder on focus (we dont want that!) - if (this.placeholder && Bal.isIE) { - this.onclick(this.input, (e) => { - dom.EventHelper.stop(e, true); - this.input.focus(); - }); - } - this.ignoreGesture(this.input); setTimeout(() => this.updateMirror(), 0); @@ -281,6 +272,10 @@ export class InputBox extends Widget { } } + public getAriaLabel(): string { + return this.ariaLabel; + } + public get mirrorElement(): HTMLElement | undefined { return this.mirror; } diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index 4dd4ef8c52..d20bf85bdf 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -64,6 +64,9 @@ export interface IIdentityProvider { } export enum ListAriaRootRole { + /** default list structure role */ + LIST = 'list', + /** default tree structure role */ TREE = 'tree', diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index 716fc514f2..850b482571 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -114,16 +114,16 @@ export class PagedList implements IDisposable { return this.list.onDidDispose; } - get onFocusChange(): Event> { - return Event.map(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); + get onDidChangeFocus(): Event> { + return Event.map(this.list.onDidChangeFocus, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); } - get onOpen(): Event> { + get onDidOpen(): Event> { return Event.map(this.list.onDidOpen, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent })); } - get onSelectionChange(): Event> { - return Event.map(this.list.onSelectionChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); + get onDidChangeSelection(): Event> { + return Event.map(this.list.onDidChangeSelection, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); } get onPin(): Event> { @@ -191,8 +191,8 @@ export class PagedList implements IDisposable { return this.list.getFocus(); } - setSelection(indexes: number[]): void { - this.list.setSelection(indexes); + setSelection(indexes: number[], browserEvent?: UIEvent): void { + this.list.setSelection(indexes, browserEvent); } getSelection(): number[] { diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index d27149675e..5b6f00ff06 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -21,6 +21,7 @@ import { equals, distinct } from 'vs/base/common/arrays'; import { DataTransfers, StaticDND, IDragAndDropData } from 'vs/base/browser/dnd'; import { disposableTimeout, Delayer } from 'vs/base/common/async'; import { isFirefox } from 'vs/base/browser/browser'; +import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; interface IItem { readonly id: string; @@ -198,6 +199,7 @@ export class ListView implements ISpliceable, IDisposable { get onDidScroll(): Event { return this.scrollableElement.onScroll; } get onWillScroll(): Event { return this.scrollableElement.onWillScroll; } + get containerDomNode(): HTMLElement { return this.rowsContainer; } constructor( container: HTMLElement, @@ -273,6 +275,31 @@ export class ListView implements ISpliceable, IDisposable { this.layout(); } + triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { + this.scrollableElement.triggerScrollFromMouseWheelEvent(browserEvent); + } + + updateElementHeight(index: number, size: number): void { + if (this.items[index].size === size) { + return; + } + + const lastRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); + + const heightDiff = index < lastRenderRange.start ? size - this.items[index].size : 0; + this.rangeMap.splice(index, 1, [{ size: size }]); + this.items[index].size = size; + + this.render(lastRenderRange, this.lastRenderTop + heightDiff, this.lastRenderHeight, undefined, undefined, true); + + this.eventuallyUpdateScrollDimensions(); + + if (this.supportDynamicHeights) { + this._rerender(this.lastRenderTop, this.lastRenderHeight); + } + return; + } + splice(start: number, deleteCount: number, elements: T[] = []): T[] { if (this.splicing) { throw new Error('Can\'t run recursive splices.'); @@ -516,14 +543,21 @@ export class ListView implements ISpliceable, IDisposable { // Render - private render(renderTop: number, renderHeight: number, renderLeft: number, scrollWidth: number): void { - const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); + private render(previousRenderRange: IRange, renderTop: number, renderHeight: number, renderLeft: number | undefined, scrollWidth: number | undefined, updateItemsInDOM: boolean = false): void { const renderRange = this.getRenderRange(renderTop, renderHeight); const rangesToInsert = Range.relativeComplement(renderRange, previousRenderRange); const rangesToRemove = Range.relativeComplement(previousRenderRange, renderRange); const beforeElement = this.getNextToLastElement(rangesToInsert); + if (updateItemsInDOM) { + const rangesToUpdate = Range.intersect(previousRenderRange, renderRange); + + for (let i = rangesToUpdate.start; i < rangesToUpdate.end; i++) { + this.updateItemInDOM(this.items[i], i); + } + } + for (const range of rangesToInsert) { for (let i = range.start; i < range.end; i++) { this.insertItemInDOM(i, beforeElement); @@ -536,10 +570,13 @@ export class ListView implements ISpliceable, IDisposable { } } - this.rowsContainer.style.left = `-${renderLeft}px`; + if (renderLeft !== undefined) { + this.rowsContainer.style.left = `-${renderLeft}px`; + } + this.rowsContainer.style.top = `-${renderTop}px`; - if (this.horizontalScrolling) { + if (this.horizontalScrolling && scrollWidth !== undefined) { this.rowsContainer.style.width = `${Math.max(scrollWidth, this.renderWidth)}px`; } @@ -741,7 +778,8 @@ export class ListView implements ISpliceable, IDisposable { private onScroll(e: ScrollEvent): void { try { - this.render(e.scrollTop, e.height, e.scrollLeft, e.scrollWidth); + const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); + this.render(previousRenderRange, e.scrollTop, e.height, e.scrollLeft, e.scrollWidth); if (this.supportDynamicHeights) { this._rerender(e.scrollTop, e.height); @@ -1097,6 +1135,14 @@ export class ListView implements ISpliceable, IDisposable { } const size = item.size; + + if (!this.setRowHeight && item.row && item.row.domNode) { + let newSize = item.row.domNode.offsetHeight; + item.size = newSize; + item.lastDynamicHeightWidth = this.renderWidth; + return newSize - size; + } + const row = this.cache.alloc(item.templateId); row.domNode!.style.height = ''; diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 1b7a4af75e..2d4f8555cd 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -180,10 +180,10 @@ class Trait implements ISpliceable, IDisposable { } } -class FocusTrait extends Trait { +class SelectionTrait extends Trait { constructor() { - super('focused'); + super('selected'); } renderIndex(index: number, container: HTMLElement): void { @@ -192,7 +192,7 @@ class FocusTrait extends Trait { if (this.contains(index)) { container.setAttribute('aria-selected', 'true'); } else { - container.removeAttribute('aria-selected'); + container.setAttribute('aria-selected', 'false'); } } } @@ -901,7 +901,7 @@ const DefaultOptions = { onDragOver() { return false; }, drop() { } }, - ariaRootRole: ListAriaRootRole.TREE + ariaRootRole: ListAriaRootRole.LIST }; // TODO@Joao: move these utils into a SortedArray class @@ -1115,7 +1115,7 @@ export class List implements ISpliceable, IDisposable { private focus: Trait; private selection: Trait; private eventBufferer = new EventBufferer(); - private view: ListView; + protected view: ListView; private spliceable: ISpliceable; private styleController: IStyleController; private typeLabelController?: TypeLabelController; @@ -1123,11 +1123,11 @@ export class List implements ISpliceable, IDisposable { protected readonly disposables = new DisposableStore(); - @memoize get onFocusChange(): Event> { + @memoize get onDidChangeFocus(): Event> { return Event.map(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e)); } - @memoize get onSelectionChange(): Event> { + @memoize get onDidChangeSelection(): Event> { return Event.map(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e)); } @@ -1198,8 +1198,8 @@ export class List implements ISpliceable, IDisposable { renderers: IListRenderer[], private _options: IListOptions = DefaultOptions ) { - this.focus = new FocusTrait(); - this.selection = new Trait('selected'); + this.selection = new SelectionTrait(); + this.focus = new Trait('focused'); mixin(_options, defaultStyles, false); @@ -1225,7 +1225,7 @@ export class List implements ISpliceable, IDisposable { this.view = new ListView(container, virtualDelegate, renderers, viewOptions); if (typeof _options.ariaRole !== 'string') { - this.view.domNode.setAttribute('role', ListAriaRootRole.TREE); + this.view.domNode.setAttribute('role', ListAriaRootRole.LIST); } else { this.view.domNode.setAttribute('role', _options.ariaRole); } @@ -1266,12 +1266,15 @@ export class List implements ISpliceable, IDisposable { this.disposables.add(this.createMouseController(_options)); - this.onFocusChange(this._onFocusChange, this, this.disposables); - this.onSelectionChange(this._onSelectionChange, this, this.disposables); + this.onDidChangeFocus(this._onFocusChange, this, this.disposables); + this.onDidChangeSelection(this._onSelectionChange, this, this.disposables); if (_options.ariaLabel) { this.view.domNode.setAttribute('aria-label', localize('aria list', "{0}. Use the navigation keys to navigate.", _options.ariaLabel)); } + if (_options.multipleSelectionSupport) { + this.view.domNode.setAttribute('aria-multiselectable', 'true'); + } } protected createMouseController(options: IListOptions): MouseController { @@ -1310,6 +1313,10 @@ export class List implements ISpliceable, IDisposable { this.view.updateWidth(index); } + updateElementHeight(index: number, size: number): void { + this.view.updateElementHeight(index, size); + } + rerender(): void { this.view.rerender(); } diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index c7f1b794f8..db8120018b 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -6,7 +6,7 @@ import 'vs/css!./menu'; import * as nls from 'vs/nls'; import * as strings from 'vs/base/common/strings'; -import { IActionRunner, IAction, Action, IActionViewItem } from 'vs/base/common/actions'; +import { IActionRunner, IAction, Action } from 'vs/base/common/actions'; import { ActionBar, IActionViewItemProvider, ActionsOrientation, Separator, ActionViewItem, IActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes'; import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses, clearNode } from 'vs/base/browser/dom'; @@ -19,6 +19,7 @@ import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; import { Event } from 'vs/base/common/event'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { isLinux, isMacintosh } from 'vs/base/common/platform'; +import { stripCodicons } from 'vs/base/common/codicons'; export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/; export const MENU_ESCAPED_MNEMONIC_REGEX = /(&)?(&)([^\s&])/g; @@ -205,7 +206,7 @@ export class Menu extends ActionBar { container.appendChild(this.scrollableElement.getDomNode()); this.scrollableElement.scanDomNode(); - this.viewItems.filter(item => !(item instanceof MenuSeparatorActionViewItem)).forEach((item: IActionViewItem, index: number, array: any[]) => { + this.viewItems.filter(item => !(item instanceof MenuSeparatorActionViewItem)).forEach((item, index, array) => { (item as BaseMenuActionViewItem).updatePositionInSet(index + 1, array.length); }); } @@ -363,7 +364,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { private cssClass: string; protected menuStyle: IMenuStyles | undefined; - constructor(ctx: any, action: IAction, options: IMenuItemOptions = {}) { + constructor(ctx: unknown, action: IAction, options: IMenuItemOptions = {}) { options.isMenu = true; super(action, action, options); @@ -471,7 +472,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { if (this.options.label) { clearNode(this.label); - let label = this.getAction().label; + let label = stripCodicons(this.getAction().label); if (label) { const cleanLabel = cleanMnemonic(label); if (!this.options.enableMnemonics) { diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 3fcc01a64e..d79f69c41e 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/scrollbars'; -import { isEdgeOrIE } from 'vs/base/browser/browser'; +import { isEdge } from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { IMouseEvent, StandardWheelEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; @@ -303,6 +303,10 @@ export abstract class AbstractScrollableElement extends Widget { this._revealOnScroll = value; } + public triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { + this._onMouseWheel(new StandardWheelEvent(browserEvent)); + } + // -------------------- mouse wheel scrolling -------------------- private _setListeningToMouseWheel(shouldListen: boolean): void { @@ -322,7 +326,7 @@ export abstract class AbstractScrollableElement extends Widget { this._onMouseWheel(new StandardWheelEvent(browserEvent)); }; - this._mouseWheelToDispose.push(dom.addDisposableListener(this._listenOnDomNode, isEdgeOrIE ? 'mousewheel' : 'wheel', onMouseWheel, { passive: false })); + this._mouseWheelToDispose.push(dom.addDisposableListener(this._listenOnDomNode, isEdge ? 'mousewheel' : 'wheel', onMouseWheel, { passive: false })); } } diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 97843d2a36..4a18aa9cca 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -762,7 +762,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi .on(e => this.onMouseUp(e), this)); this._register(this.selectList.onMouseOver(e => typeof e.index !== 'undefined' && this.selectList.setFocus([e.index]))); - this._register(this.selectList.onFocusChange(e => this.onListFocus(e))); + this._register(this.selectList.onDidChangeFocus(e => this.onListFocus(e))); this._register(dom.addDisposableListener(this.selectDropDownContainer, dom.EventType.FOCUS_OUT, e => { if (!this._isVisible || dom.isAncestor(e.relatedTarget as HTMLElement, this.selectDropDownContainer)) { diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index 800329ad21..f72ed5d8a9 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -15,12 +15,15 @@ import { Color, RGBA } from 'vs/base/common/color'; import { SplitView, IView } from './splitview'; import { isFirefox } from 'vs/base/browser/browser'; import { DataTransfers } from 'vs/base/browser/dnd'; +import { Orientation } from 'vs/base/browser/ui/sash/sash'; +import { localize } from 'vs/nls'; export interface IPaneOptions { - ariaHeaderLabel?: string; minimumBodySize?: number; maximumBodySize?: number; expanded?: boolean; + orientation?: Orientation; + title: string; } export interface IPaneStyles { @@ -48,6 +51,8 @@ export abstract class Pane extends Disposable implements IView { private body!: HTMLElement; protected _expanded: boolean; + protected _orientation: Orientation; + protected _preventCollapse?: boolean; private expandedSize: number | undefined = undefined; private _headerVisible = true; @@ -114,12 +119,13 @@ export abstract class Pane extends Disposable implements IView { return headerSize + maximumBodySize; } - width: number = 0; + orthogonalSize: number = 0; - constructor(options: IPaneOptions = {}) { + constructor(options: IPaneOptions) { super(); this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded; - this.ariaHeaderLabel = options.ariaHeaderLabel || ''; + this._orientation = typeof options.orientation === 'undefined' ? Orientation.VERTICAL : Orientation.HORIZONTAL; + this.ariaHeaderLabel = localize('viewSection', "{0} Section", options.title); this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : 120; this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY; @@ -183,31 +189,37 @@ export abstract class Pane extends Disposable implements IView { this.updateHeader(); - const onHeaderKeyDown = Event.chain(domEvent(this.header, 'keydown')) - .map(e => new StandardKeyboardEvent(e)); - this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space) - .event(() => this.setExpanded(!this.isExpanded()), null)); + if (!this._preventCollapse) { + const onHeaderKeyDown = Event.chain(domEvent(this.header, 'keydown')) + .map(e => new StandardKeyboardEvent(e)); - this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.LeftArrow) - .event(() => this.setExpanded(false), null)); + this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space) + .event(() => this.setExpanded(!this.isExpanded()), null)); - this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.RightArrow) - .event(() => this.setExpanded(true), null)); + this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.LeftArrow) + .event(() => this.setExpanded(false), null)); - this._register(domEvent(this.header, 'click') - (() => this.setExpanded(!this.isExpanded()), null)); + this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.RightArrow) + .event(() => this.setExpanded(true), null)); + + this._register(domEvent(this.header, 'click') + (() => this.setExpanded(!this.isExpanded()), null)); + } this.body = append(this.element, $('.pane-body')); this.renderBody(this.body); } - layout(height: number): void { + layout(size: number): void { const headerSize = this.headerVisible ? Pane.HEADER_SIZE : 0; + const width = this._orientation === Orientation.VERTICAL ? this.orthogonalSize : size; + const height = this._orientation === Orientation.VERTICAL ? size - headerSize : this.orthogonalSize - headerSize; + if (this.isExpanded()) { - this.layoutBody(height - headerSize, this.width); - this.expandedSize = height; + this.layoutBody(height, width); + this.expandedSize = size; } } @@ -371,6 +383,7 @@ export class DefaultPaneDndController implements IPaneDndController { export interface IPaneViewOptions { dnd?: IPaneDndController; + orientation?: Orientation; } interface IPaneItem { @@ -384,8 +397,9 @@ export class PaneView extends Disposable { private dndContext: IDndContext = { draggable: null }; private el: HTMLElement; private paneItems: IPaneItem[] = []; - private width: number = 0; + private orthogonalSize: number = 0; private splitview: SplitView; + private orientation: Orientation; private animationTimer: number | undefined = undefined; private _onDidDrop = this._register(new Emitter<{ from: Pane, to: Pane }>()); @@ -397,8 +411,9 @@ export class PaneView extends Disposable { super(); this.dnd = options.dnd; + this.orientation = options.orientation ?? Orientation.VERTICAL; this.el = append(container, $('.monaco-pane-view')); - this.splitview = this._register(new SplitView(this.el)); + this.splitview = this._register(new SplitView(this.el, { orientation: this.orientation })); this.onDidSashChange = this.splitview.onDidSashChange; } @@ -408,7 +423,7 @@ export class PaneView extends Disposable { const paneItem = { pane: pane, disposable: disposables }; this.paneItems.splice(index, 0, paneItem); - pane.width = this.width; + pane.orthogonalSize = this.orthogonalSize; this.splitview.addView(pane, size, index); if (this.dnd) { @@ -465,13 +480,13 @@ export class PaneView extends Disposable { } layout(height: number, width: number): void { - this.width = width; + this.orthogonalSize = this.orientation === Orientation.VERTICAL ? width : height; for (const paneItem of this.paneItems) { - paneItem.pane.width = width; + paneItem.pane.orthogonalSize = this.orthogonalSize; } - this.splitview.layout(height); + this.splitview.layout(this.orientation === Orientation.HORIZONTAL ? width : height); } private setupAnimation(): void { diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 21a3b47728..05cbba844f 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -86,7 +86,7 @@ export class ToolBar extends Disposable { return this.actionBar.actionRunner; } - set context(context: any) { + set context(context: unknown) { this.actionBar.context = context; if (this.toggleMenuActionViewItem.value) { this.toggleMenuActionViewItem.value.setActionContext(context); @@ -166,10 +166,8 @@ class ToggleMenuAction extends Action { this.toggleDropdownMenu = toggleDropdownMenu; } - run(): Promise { + async run(): Promise { this.toggleDropdownMenu(); - - return Promise.resolve(true); } get menuActions(): ReadonlyArray { diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index d44347991c..f734a7089c 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/tree'; import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/listWidget'; -import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate, ListAriaRootRole } from 'vs/base/browser/ui/list/list'; import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode, addClasses, removeClasses } from 'vs/base/browser/dom'; import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -14,7 +14,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter, ITreeNavigator, ICollapseStateChangeEvent, ITreeDragAndDrop, TreeDragOverBubble, TreeVisibility, TreeFilterResult, ITreeModelSpliceEvent, TreeMouseEventTarget } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; import { IDragAndDropData, StaticDND, DragAndDropData } from 'vs/base/browser/dnd'; -import { range, equals, distinctES6, fromSet } from 'vs/base/common/arrays'; +import { range, equals, distinctES6 } from 'vs/base/common/arrays'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { domEvent } from 'vs/base/browser/event'; import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters'; @@ -197,7 +197,8 @@ function asListOptions(modelProvider: () => ITreeModel { return options.ariaProvider!.getRole!(node.element); } : () => 'treeitem' - } + }, + ariaRole: ListAriaRootRole.TREE }; } @@ -1320,7 +1321,7 @@ export abstract class AbstractTree implements IDisposable set.add(node); } - return fromSet(set); + return values(set); }).event; if (_options.keyboardSupport !== false) { diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index b57f50f9af..341a02d3b0 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -5,7 +5,7 @@ import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree'; import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; -import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction, ListAriaRootRole } from 'vs/base/browser/ui/list/list'; import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper, ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree'; import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; @@ -272,6 +272,7 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt return options.ariaProvider?.isChecked!(e.element as T); } : undefined }, + ariaRole: ListAriaRootRole.TREE, additionalScrollHeight: options.additionalScrollHeight }; } diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index ee7b34d63a..97cc8ae4ee 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -372,12 +372,6 @@ export function distinctES6(array: ReadonlyArray): T[] { }); } -export function fromSet(set: Set): T[] { - const result: T[] = []; - set.forEach(o => result.push(o)); - return result; -} - export function uniqueFilter(keyFn: (t: T) => string): (t: T) => boolean { const seen: { [key: string]: boolean; } = Object.create(null); @@ -405,6 +399,9 @@ export function lastIndex(array: ReadonlyArray, fn: (item: T) => boolean): return -1; } +/** + * @deprecated ES6: use `Array.findIndex` + */ export function firstIndex(array: ReadonlyArray, fn: (item: T) => boolean): number { for (let i = 0; i < array.length; i++) { const element = array[i]; @@ -417,6 +414,10 @@ export function firstIndex(array: ReadonlyArray, fn: (item: T) => boolean) return -1; } + +/** + * @deprecated ES6: use `Array.find` + */ export function first(array: ReadonlyArray, fn: (item: T) => boolean, notFoundValue: T): T; export function first(array: ReadonlyArray, fn: (item: T) => boolean): T | undefined; export function first(array: ReadonlyArray, fn: (item: T) => boolean, notFoundValue: T | undefined = undefined): T | undefined { @@ -471,6 +472,9 @@ export function range(arg: number, to?: number): number[] { return result; } +/** + * @deprecated ES6: use `Array.fill` + */ export function fill(num: number, value: T, arr: T[] = []): T[] { for (let i = 0; i < num; i++) { arr[i] = value; @@ -564,6 +568,10 @@ export function pushToEnd(arr: T[], value: T): void { } } + +/** + * @deprecated ES6: use `Array.find` + */ export function find(arr: ArrayLike, predicate: (value: T, index: number, arr: ArrayLike) => any): T | undefined { for (let i = 0; i < arr.length; i++) { const element = arr[i]; diff --git a/src/vs/base/common/assert.ts b/src/vs/base/common/assert.ts index b0fe9b5af8..e504c5b0a8 100644 --- a/src/vs/base/common/assert.ts +++ b/src/vs/base/common/assert.ts @@ -6,8 +6,8 @@ /** * Throws an error with the provided message if the provided value does not evaluate to a true Javascript value. */ -export function ok(value?: any, message?: string) { +export function ok(value?: unknown, message?: string) { if (!value) { - throw new Error(message ? 'Assertion failed (' + message + ')' : 'Assertion Failed'); + throw new Error(message ? `Assertion failed (${message})` : 'Assertion Failed'); } } diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 168ccdeca1..5b094ade7d 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -837,7 +837,7 @@ export class TaskSequentializer { this._pending?.cancel(); } - setPending(taskId: number, promise: Promise, onCancel?: () => void, ): Promise { + setPending(taskId: number, promise: Promise, onCancel?: () => void,): Promise { this._pending = { taskId: taskId, cancel: () => onCancel?.(), promise }; promise.then(() => this.donePending(taskId), () => this.donePending(taskId)); diff --git a/src/vs/base/common/cancellation.ts b/src/vs/base/common/cancellation.ts index 7570c124ee..a98bfc6a3e 100644 --- a/src/vs/base/common/cancellation.ts +++ b/src/vs/base/common/cancellation.ts @@ -31,7 +31,7 @@ const shortcutEvent: Event = Object.freeze(function (callback, context?): I export namespace CancellationToken { - export function isCancellationToken(thing: any): thing is CancellationToken { + export function isCancellationToken(thing: unknown): thing is CancellationToken { if (thing === CancellationToken.None || thing === CancellationToken.Cancelled) { return true; } diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index f5b4437d41..8d64acf3f5 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -27,3 +27,8 @@ export function renderCodicons(text: string): string { : ``; }); } + +const stripCodiconsRegex = /(\s)?(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)(\s)?/gi; +export function stripCodicons(text: string): string { + return text.replace(stripCodiconsRegex, (match, preWhitespace, escaped, postWhitespace) => escaped ? match : preWhitespace || postWhitespace || ''); +} diff --git a/src/vs/base/common/collections.ts b/src/vs/base/common/collections.ts index 9393302f3b..39522f4d5c 100644 --- a/src/vs/base/common/collections.ts +++ b/src/vs/base/common/collections.ts @@ -97,11 +97,6 @@ export function fromMap(original: Map): IStringDictionary { return result; } -export function mapValues(map: Map): V[] { - const result: V[] = []; - map.forEach(v => result.push(v)); - return result; -} export class SetMap { diff --git a/src/vs/base/common/errorsWithActions.ts b/src/vs/base/common/errorsWithActions.ts index 86a4d5c6ac..843ffeec01 100644 --- a/src/vs/base/common/errorsWithActions.ts +++ b/src/vs/base/common/errorsWithActions.ts @@ -13,7 +13,7 @@ export interface IErrorWithActions { actions?: ReadonlyArray; } -export function isErrorWithActions(obj: any): obj is IErrorWithActions { +export function isErrorWithActions(obj: unknown): obj is IErrorWithActions { return obj instanceof Error && Array.isArray((obj as IErrorWithActions).actions); } diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 32992e5523..0c97345d08 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -576,8 +576,8 @@ export class Emitter { this._deliveryQueue = new LinkedList(); } - for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) { - this._deliveryQueue.push([e.value, event]); + for (let listener of this._listeners) { + this._deliveryQueue.push([listener, event]); } while (this._deliveryQueue.size > 0) { @@ -671,8 +671,8 @@ export class AsyncEmitter extends Emitter { this._asyncDeliveryQueue = new LinkedList(); } - for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) { - this._asyncDeliveryQueue.push([e.value, data]); + for (const listener of this._listeners) { + this._asyncDeliveryQueue.push([listener, data]); } while (this._asyncDeliveryQueue.size > 0 && !token.isCancellationRequested) { diff --git a/src/vs/base/common/functional.ts b/src/vs/base/common/functional.ts index 0fb866b864..5e00e486a6 100644 --- a/src/vs/base/common/functional.ts +++ b/src/vs/base/common/functional.ts @@ -3,10 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export function once(this: any, fn: T): T { +export function once(this: unknown, fn: T): T { const _this = this; let didCall = false; - let result: any; + let result: unknown; return function () { if (didCall) { @@ -17,5 +17,5 @@ export function once(this: any, fn: T): T { result = fn.apply(_this, arguments); return result; - } as any as T; -} \ No newline at end of file + } as unknown as T; +} diff --git a/src/vs/base/parts/quickopen/common/quickOpenScorer.ts b/src/vs/base/common/fuzzyScorer.ts similarity index 97% rename from src/vs/base/parts/quickopen/common/quickOpenScorer.ts rename to src/vs/base/common/fuzzyScorer.ts index 99d743f0c8..da265d5e24 100644 --- a/src/vs/base/parts/quickopen/common/quickOpenScorer.ts +++ b/src/vs/base/common/fuzzyScorer.ts @@ -285,15 +285,15 @@ export interface IItemAccessor { /** * Just the label of the item to score on. */ - getItemLabel(item: T): string | null; + getItemLabel(item: T): string | undefined; /** - * The optional description of the item to score on. Can be null. + * The optional description of the item to score on. */ - getItemDescription(item: T): string | null; + getItemDescription(item: T): string | undefined; /** - * If the item is a file, the path of the file to score on. Can be null. + * If the item is a file, the path of the file to score on. */ getItemPath(file: T): string | undefined; } @@ -311,7 +311,7 @@ export interface IPreparedQuery { } /** - * Helper function to prepare a search value for scoring in quick open by removing unwanted characters. + * Helper function to prepare a search value for scoring by removing unwanted characters. */ export function prepareQuery(original: string): IPreparedQuery { if (!original) { @@ -364,6 +364,7 @@ function createMatches(offsets: undefined | number[]): IMatch[] { if (!offsets) { return ret; } + let last: IMatch | undefined; for (const pos of offsets) { if (last && last.end === pos) { @@ -373,10 +374,11 @@ function createMatches(offsets: undefined | number[]): IMatch[] { ret.push(last); } } + return ret; } -function doScoreItem(label: string, description: string | null, path: string | undefined, query: IPreparedQuery, fuzzy: boolean): IItemScore { +function doScoreItem(label: string, description: string | undefined, path: string | undefined, query: IPreparedQuery, fuzzy: boolean): IItemScore { // 1.) treat identity matches on full path highest if (path && (isLinux ? query.original === path : equalsIgnoreCase(query.original, path))) { @@ -589,7 +591,7 @@ function compareByMatchLength(matchesA?: IMatch[], matchesB?: IMatch[]): number return matchLengthA === matchLengthB ? 0 : matchLengthB < matchLengthA ? 1 : -1; } -export function fallbackCompare(itemA: T, itemB: T, query: IPreparedQuery, accessor: IItemAccessor): number { +function fallbackCompare(itemA: T, itemB: T, query: IPreparedQuery, accessor: IItemAccessor): number { // check for label + description length and prefer shorter const labelA = accessor.getItemLabel(itemA) || ''; diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts index 07675932e2..0a9c5eb89f 100644 --- a/src/vs/base/common/iterator.ts +++ b/src/vs/base/common/iterator.ts @@ -34,6 +34,28 @@ export interface NativeIterator { next(): NativeIteratorResult; } +export namespace Iterable { + + export function first(iterable: Iterable): T | undefined { + return iterable[Symbol.iterator]().next().value; + } + + export function some(iterable: Iterable, predicate: (t: T) => boolean): boolean { + for (const element of iterable) { + if (predicate(element)) { + return true; + } + } + return false; + } + + export function* map(iterable: Iterable, fn: (t: T) => R): IterableIterator { + for (const element of iterable) { + return yield fn(element); + } + } +} + export module Iterator { const _empty: Iterator = { next() { diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index 7f8b6ad2be..05e6ab819c 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { sep, posix, normalize, win32 } from 'vs/base/common/path'; +import { posix, normalize, win32, sep } from 'vs/base/common/path'; import { endsWith, startsWithIgnoreCase, rtrim, startsWith } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform'; @@ -160,7 +160,7 @@ export function untildify(path: string, userHome: string): string { const ellipsis = '\u2026'; const unc = '\\\\'; const home = '~'; -export function shorten(paths: string[]): string[] { +export function shorten(paths: string[], pathSeparator: string = sep): string[] { const shortenedPaths: string[] = new Array(paths.length); // for every path @@ -169,7 +169,7 @@ export function shorten(paths: string[]): string[] { let path = paths[pathIndex]; if (path === '') { - shortenedPaths[pathIndex] = `.${sep}`; + shortenedPaths[pathIndex] = `.${pathSeparator}`; continue; } @@ -185,20 +185,20 @@ export function shorten(paths: string[]): string[] { if (path.indexOf(unc) === 0) { prefix = path.substr(0, path.indexOf(unc) + unc.length); path = path.substr(path.indexOf(unc) + unc.length); - } else if (path.indexOf(sep) === 0) { - prefix = path.substr(0, path.indexOf(sep) + sep.length); - path = path.substr(path.indexOf(sep) + sep.length); + } else if (path.indexOf(pathSeparator) === 0) { + prefix = path.substr(0, path.indexOf(pathSeparator) + pathSeparator.length); + path = path.substr(path.indexOf(pathSeparator) + pathSeparator.length); } else if (path.indexOf(home) === 0) { prefix = path.substr(0, path.indexOf(home) + home.length); path = path.substr(path.indexOf(home) + home.length); } // pick the first shortest subpath found - const segments: string[] = path.split(sep); + const segments: string[] = path.split(pathSeparator); for (let subpathLength = 1; match && subpathLength <= segments.length; subpathLength++) { for (let start = segments.length - subpathLength; match && start >= 0; start--) { match = false; - let subpath = segments.slice(start, start + subpathLength).join(sep); + let subpath = segments.slice(start, start + subpathLength).join(pathSeparator); // that is unique to any other path for (let otherPathIndex = 0; !match && otherPathIndex < paths.length; otherPathIndex++) { @@ -209,7 +209,7 @@ export function shorten(paths: string[]): string[] { // Adding separator as prefix for subpath, such that 'endsWith(src, trgt)' considers subpath as directory name instead of plain string. // prefix is not added when either subpath is root directory or path[otherPathIndex] does not have multiple directories. - const subpathWithSep: string = (start > 0 && paths[otherPathIndex].indexOf(sep) > -1) ? sep + subpath : subpath; + const subpathWithSep: string = (start > 0 && paths[otherPathIndex].indexOf(pathSeparator) > -1) ? pathSeparator + subpath : subpath; const isOtherPathEnding: boolean = endsWith(paths[otherPathIndex], subpathWithSep); match = !isSubpathEnding || isOtherPathEnding; @@ -226,11 +226,11 @@ export function shorten(paths: string[]): string[] { // extend subpath to include disk drive prefix start = 0; subpathLength++; - subpath = segments[0] + sep + subpath; + subpath = segments[0] + pathSeparator + subpath; } if (start > 0) { - result = segments[0] + sep; + result = segments[0] + pathSeparator; } result = prefix + result; @@ -238,14 +238,14 @@ export function shorten(paths: string[]): string[] { // add ellipsis at the beginning if neeeded if (start > 0) { - result = result + ellipsis + sep; + result = result + ellipsis + pathSeparator; } result = result + subpath; // add ellipsis at the end if needed if (start + subpathLength < segments.length) { - result = result + sep + ellipsis; + result = result + pathSeparator + ellipsis; } shortenedPaths[pathIndex] = result; diff --git a/src/vs/base/common/lazy.ts b/src/vs/base/common/lazy.ts index 4ba28d5298..d11924d963 100644 --- a/src/vs/base/common/lazy.ts +++ b/src/vs/base/common/lazy.ts @@ -21,7 +21,7 @@ export class Lazy { private _didRun: boolean = false; private _value?: T; - private _error: any; + private _error: Error | undefined; constructor( private readonly executor: () => T, diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index d8ad2bb6f0..e63d1f2b75 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -49,8 +49,7 @@ export interface IDisposable { } export function isDisposable(thing: E): thing is E & IDisposable { - return typeof (thing).dispose === 'function' - && (thing).dispose.length === 0; + return typeof (thing).dispose === 'function' && (thing).dispose.length === 0; } export function dispose(disposable: T): T; @@ -124,7 +123,7 @@ export class DisposableStore implements IDisposable { if (!t) { return t; } - if ((t as any as DisposableStore) === this) { + if ((t as unknown as DisposableStore) === this) { throw new Error('Cannot register a disposable on itself!'); } @@ -158,7 +157,7 @@ export abstract class Disposable implements IDisposable { } protected _register(t: T): T { - if ((t as any as Disposable) === this) { + if ((t as unknown as Disposable) === this) { throw new Error('Cannot register a disposable on itself!'); } return this._store.add(t); diff --git a/src/vs/base/common/linkedList.ts b/src/vs/base/common/linkedList.ts index 1849266b11..57c5e9e2e7 100644 --- a/src/vs/base/common/linkedList.ts +++ b/src/vs/base/common/linkedList.ts @@ -3,8 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Iterator, IteratorResult, FIN } from 'vs/base/common/iterator'; - class Node { static readonly Undefined = new Node(undefined); @@ -126,24 +124,12 @@ export class LinkedList { this._size -= 1; } - iterator(): Iterator { - let element: { done: false; value: E; }; + *[Symbol.iterator](): Iterator { let node = this._first; - return { - next(): IteratorResult { - if (node === Node.Undefined) { - return FIN; - } - - if (!element) { - element = { done: false, value: node.element }; - } else { - element.value = node.element; - } - node = node.next; - return element; - } - }; + while (node !== Node.Undefined) { + yield node.element; + node = node.next; + } } toArray(): E[] { diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index ea33460391..18a08f0c41 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -5,9 +5,11 @@ import { URI } from 'vs/base/common/uri'; import { CharCode } from 'vs/base/common/charCode'; -import { Iterator, IteratorResult, FIN } from './iterator'; - +import { FIN } from './iterator'; +/** + * @deprecated ES6: use `[...SetOrMap.values()]` + */ export function values(set: Set): V[]; export function values(map: Map): V[]; export function values(forEachable: { forEach(callback: (value: V, ...more: any[]) => any): void }): V[] { @@ -16,6 +18,9 @@ export function values(forEachable: { forEach(callback: (value: V, ...more: a return result; } +/** + * @deprecated ES6: use `[...map.keys()]` + */ export function keys(map: Map): K[] { const result: K[] = []; map.forEach((_value, key) => result.push(key)); @@ -51,6 +56,9 @@ export function setToString(set: Set): string { return `Set(${set.size}) {${entries.join(', ')}}`; } +/** + * @deprecated ES6: use `...Map.entries()` + */ export function mapToSerializable(map: Map): [string, string][] { const serializable: [string, string][] = []; @@ -61,6 +69,9 @@ export function mapToSerializable(map: Map): [string, string][] return serializable; } +/** + * @deprecated ES6: use `new Map([[key1, value1],[key2, value2]])` + */ export function serializableToMap(serializable: [string, string][]): Map { const items = new Map(); diff --git a/src/vs/base/common/normalization.ts b/src/vs/base/common/normalization.ts index 281a273845..e627dac75b 100644 --- a/src/vs/base/common/normalization.ts +++ b/src/vs/base/common/normalization.ts @@ -11,7 +11,7 @@ import { LRUCache } from 'vs/base/common/map'; * * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize} */ -export const canNormalize = typeof (('').normalize) === 'function'; +export const canNormalize = typeof (String.prototype as any /* standalone editor compilation */).normalize === 'function'; const nfcCache = new LRUCache(10000); // bounded to 10000 elements export function normalizeNFC(str: string): string { @@ -46,3 +46,17 @@ function normalize(str: string, form: string, normalizedCache: LRUCache string = (function () { + if (!canNormalize) { + // no ES6 features... + return function (str: string) { return str; }; + } else { + // transform into NFD form and remove accents + // see: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463 + const regex = /[\u0300-\u036f]/g; + return function (str: string) { + return normalizeNFD(str).replace(regex, ''); + }; + } +})(); diff --git a/src/vs/base/common/path.ts b/src/vs/base/common/path.ts index 029422cedc..15a215f401 100644 --- a/src/vs/base/common/path.ts +++ b/src/vs/base/common/path.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // NOTE: VSCode's copy of nodejs path library to be usable in common (non-node) namespace -// Copied from: https://github.com/nodejs/node/tree/43dd49c9782848c25e5b03448c8a0f923f13c158 +// Copied from: https://github.com/nodejs/node/blob/v12.8.1/lib/path.js /** * Copyright Joyent, Inc. and other Node contributors. @@ -88,7 +88,7 @@ function normalizeString(path: string, allowAboveRoot: boolean, separator: strin let lastSegmentLength = 0; let lastSlash = -1; let dots = 0; - let code; + let code = 0; for (let i = 0; i <= path.length; ++i) { if (i < path.length) { code = path.charCodeAt(i); @@ -103,7 +103,7 @@ function normalizeString(path: string, allowAboveRoot: boolean, separator: strin if (isPathSeparator(code)) { if (lastSlash === i - 1 || dots === 1) { // NOOP - } else if (lastSlash !== i - 1 && dots === 2) { + } else if (dots === 2) { if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== CHAR_DOT || res.charCodeAt(res.length - 2) !== CHAR_DOT) { @@ -119,7 +119,7 @@ function normalizeString(path: string, allowAboveRoot: boolean, separator: strin lastSlash = i; dots = 0; continue; - } else if (res.length === 2 || res.length === 1) { + } else if (res.length !== 0) { res = ''; lastSegmentLength = 0; lastSlash = i; @@ -128,17 +128,12 @@ function normalizeString(path: string, allowAboveRoot: boolean, separator: strin } } if (allowAboveRoot) { - if (res.length > 0) { - res += `${separator}..`; - } - else { - res = '..'; - } + res += res.length > 0 ? `${separator}..` : '..'; lastSegmentLength = 2; } } else { if (res.length > 0) { - res += separator + path.slice(lastSlash + 1, i); + res += `${separator}${path.slice(lastSlash + 1, i)}`; } else { res = path.slice(lastSlash + 1, i); @@ -157,16 +152,16 @@ function normalizeString(path: string, allowAboveRoot: boolean, separator: strin } function _format(sep: string, pathObject: ParsedPath) { + if (pathObject === null || typeof pathObject !== 'object') { + throw new ErrorInvalidArgType('pathObject', 'Object', pathObject); + } const dir = pathObject.dir || pathObject.root; const base = pathObject.base || - ((pathObject.name || '') + (pathObject.ext || '')); + `${pathObject.name || ''}${pathObject.ext || ''}`; if (!dir) { return base; } - if (dir === pathObject.root) { - return dir + base; - } - return dir + sep + base; + return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`; } export interface ParsedPath { @@ -206,7 +201,13 @@ export const win32: IPath = { let path; if (i >= 0) { path = pathSegments[i]; - } else if (!resolvedDevice) { + validateString(path, 'path'); + + // Skip empty entries + if (path.length === 0) { + continue; + } + } else if (resolvedDevice.length === 0) { path = process.cwd(); } else { // Windows has the concept of drive-specific current working @@ -214,24 +215,17 @@ export const win32: IPath = { // absolute path, get cwd for that drive, or the process cwd if // the drive cwd is not available. We're sure the device is not // a UNC path at this points, because UNC paths are always absolute. - path = (process.env as any)['=' + resolvedDevice] || process.cwd(); + path = (process.env as any)[`=${resolvedDevice}`] || process.cwd(); // Verify that a cwd was found and that it actually points // to our drive. If not, default to the drive's root. if (path === undefined || - path.slice(0, 3).toLowerCase() !== - resolvedDevice.toLowerCase() + '\\') { - path = resolvedDevice + '\\'; + path.slice(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() && + path.charCodeAt(2) === CHAR_BACKWARD_SLASH) { + path = `${resolvedDevice}\\`; } } - validateString(path, 'path'); - - // Skip empty entries - if (path.length === 0) { - continue; - } - const len = path.length; let rootEnd = 0; let device = ''; @@ -239,98 +233,86 @@ export const win32: IPath = { const code = path.charCodeAt(0); // Try to match a root - if (len > 1) { + if (len === 1) { if (isPathSeparator(code)) { - // Possible UNC root - - // If we started with a separator, we know we at least have an - // absolute path of some kind (UNC or otherwise) + // `path` contains just a path separator + rootEnd = 1; isAbsolute = true; - - if (isPathSeparator(path.charCodeAt(1))) { - // Matched double path separator at beginning - let j = 2; - let last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } - } - if (j < len && j !== last) { - const firstPart = path.slice(last, j); - // Matched! - last = j; - // Match 1 or more path separators - for (; j < len; ++j) { - if (!isPathSeparator(path.charCodeAt(j))) { - break; - } - } - if (j < len && j !== last) { - // Matched! - last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } - } - if (j === len) { - // We matched a UNC root only - - device = '\\\\' + firstPart + '\\' + path.slice(last); - rootEnd = j; - } else if (j !== last) { - // We matched a UNC root with leftovers - - device = '\\\\' + firstPart + '\\' + path.slice(last, j); - rootEnd = j; - } - } - } - } else { - rootEnd = 1; - } - } else if (isWindowsDeviceRoot(code)) { - // Possible device root - - if (path.charCodeAt(1) === CHAR_COLON) { - device = path.slice(0, 2); - rootEnd = 2; - if (len > 2) { - if (isPathSeparator(path.charCodeAt(2))) { - // Treat separator following drive name as an absolute path - // indicator - isAbsolute = true; - rootEnd = 3; - } - } - } } } else if (isPathSeparator(code)) { - // `path` contains just a path separator - rootEnd = 1; + // Possible UNC root + + // If we started with a separator, we know we at least have an + // absolute path of some kind (UNC or otherwise) isAbsolute = true; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j === len || j !== last) { + // We matched a UNC root + device = `\\\\${firstPart}\\${path.slice(last, j)}`; + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code) && + path.charCodeAt(1) === CHAR_COLON) { + // Possible device root + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2 && isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } } - if (device.length > 0 && - resolvedDevice.length > 0 && - device.toLowerCase() !== resolvedDevice.toLowerCase()) { - // This path points to another device so it is not applicable - continue; + if (device.length > 0) { + if (resolvedDevice.length > 0) { + if (device.toLowerCase() !== resolvedDevice.toLowerCase()) { + // This path points to another device so it is not applicable + continue; + } + } else { + resolvedDevice = device; + } } - if (resolvedDevice.length === 0 && device.length > 0) { - resolvedDevice = device; - } - if (!resolvedAbsolute) { - resolvedTail = path.slice(rootEnd) + '\\' + resolvedTail; + if (resolvedAbsolute) { + if (resolvedDevice.length > 0) { + break; + } + } else { + resolvedTail = `${path.slice(rootEnd)}\\${resolvedTail}`; resolvedAbsolute = isAbsolute; - } - - if (resolvedDevice.length > 0 && resolvedAbsolute) { - break; + if (isAbsolute && resolvedDevice.length > 0) { + break; + } } } @@ -342,8 +324,9 @@ export const win32: IPath = { resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', isPathSeparator); - return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) || - '.'; + return resolvedAbsolute ? + `${resolvedDevice}\\${resolvedTail}` : + `${resolvedDevice}${resolvedTail}` || '.'; }, normalize(path: string): string { @@ -358,89 +341,72 @@ export const win32: IPath = { const code = path.charCodeAt(0); // Try to match a root - if (len > 1) { - if (isPathSeparator(code)) { - // Possible UNC root + if (len === 1) { + // `path` contains just a single char, exit early to avoid + // unnecessary work + return isPosixPathSeparator(code) ? '\\' : path; + } + if (isPathSeparator(code)) { + // Possible UNC root - // If we started with a separator, we know we at least have an absolute - // path of some kind (UNC or otherwise) - isAbsolute = true; + // If we started with a separator, we know we at least have an absolute + // path of some kind (UNC or otherwise) + isAbsolute = true; - if (isPathSeparator(path.charCodeAt(1))) { - // Matched double path separator at beginning - let j = 2; - let last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; } if (j < len && j !== last) { - const firstPart = path.slice(last, j); // Matched! last = j; - // Match 1 or more path separators - for (; j < len; ++j) { - if (!isPathSeparator(path.charCodeAt(j))) { - break; - } + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; } - if (j < len && j !== last) { - // Matched! - last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } - } - if (j === len) { - // We matched a UNC root only - // Return the normalized version of the UNC root since there - // is nothing left to process - - return '\\\\' + firstPart + '\\' + path.slice(last) + '\\'; - } else if (j !== last) { - // We matched a UNC root with leftovers - - device = '\\\\' + firstPart + '\\' + path.slice(last, j); - rootEnd = j; - } + if (j === len) { + // We matched a UNC root only + // Return the normalized version of the UNC root since there + // is nothing left to process + return `\\\\${firstPart}\\${path.slice(last)}\\`; } - } - } else { - rootEnd = 1; - } - } else if (isWindowsDeviceRoot(code)) { - // Possible device root - - if (path.charCodeAt(1) === CHAR_COLON) { - device = path.slice(0, 2); - rootEnd = 2; - if (len > 2) { - if (isPathSeparator(path.charCodeAt(2))) { - // Treat separator following drive name as an absolute path - // indicator - isAbsolute = true; - rootEnd = 3; + if (j !== last) { + // We matched a UNC root with leftovers + device = `\\\\${firstPart}\\${path.slice(last, j)}`; + rootEnd = j; } } } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { + // Possible device root + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2 && isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; } - } else if (isPathSeparator(code)) { - // `path` contains just a path separator, exit early to avoid unnecessary - // work - return '\\'; } - let tail; - if (rootEnd < len) { - tail = normalizeString(path.slice(rootEnd), !isAbsolute, '\\', - isPathSeparator); - } else { - tail = ''; - } + let tail = rootEnd < len ? + normalizeString(path.slice(rootEnd), !isAbsolute, '\\', isPathSeparator) : + ''; if (tail.length === 0 && !isAbsolute) { tail = '.'; } @@ -448,30 +414,9 @@ export const win32: IPath = { tail += '\\'; } if (device === undefined) { - if (isAbsolute) { - if (tail.length > 0) { - return '\\' + tail; - } - else { - return '\\'; - } - } else if (tail.length > 0) { - return tail; - } else { - return ''; - } - } else if (isAbsolute) { - if (tail.length > 0) { - return device + '\\' + tail; - } - else { - return device + '\\'; - } - } else if (tail.length > 0) { - return device + tail; - } else { - return device; + return isAbsolute ? `\\${tail}` : tail; } + return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`; }, isAbsolute(path: string): boolean { @@ -482,18 +427,12 @@ export const win32: IPath = { } const code = path.charCodeAt(0); - if (isPathSeparator(code)) { - return true; - } else if (isWindowsDeviceRoot(code)) { + return isPathSeparator(code) || // Possible device root - - if (len > 2 && path.charCodeAt(1) === CHAR_COLON) { - if (isPathSeparator(path.charCodeAt(2))) { - return true; - } - } - } - return false; + len > 2 && + isWindowsDeviceRoot(code) && + path.charCodeAt(1) === CHAR_COLON && + isPathSeparator(path.charCodeAt(2)); }, join(...paths: string[]): string { @@ -511,7 +450,7 @@ export const win32: IPath = { joined = firstPart = arg; } else { - joined += '\\' + arg; + joined += `\\${arg}`; } } } @@ -538,32 +477,28 @@ export const win32: IPath = { if (typeof firstPart === 'string' && isPathSeparator(firstPart.charCodeAt(0))) { ++slashCount; const firstLen = firstPart.length; - if (firstLen > 1) { - if (isPathSeparator(firstPart.charCodeAt(1))) { - ++slashCount; - if (firstLen > 2) { - if (isPathSeparator(firstPart.charCodeAt(2))) { - ++slashCount; - } - else { - // We matched a UNC path in the first part - needsReplace = false; - } + if (firstLen > 1 && isPathSeparator(firstPart.charCodeAt(1))) { + ++slashCount; + if (firstLen > 2) { + if (isPathSeparator(firstPart.charCodeAt(2))) { + ++slashCount; + } else { + // We matched a UNC path in the first part + needsReplace = false; } } } } if (needsReplace) { // Find any more consecutive slashes we need to replace - for (; slashCount < joined.length; ++slashCount) { - if (!isPathSeparator(joined.charCodeAt(slashCount))) { - break; - } + while (slashCount < joined.length && + isPathSeparator(joined.charCodeAt(slashCount))) { + slashCount++; } // Replace the slashes if needed if (slashCount >= 2) { - joined = '\\' + joined.slice(slashCount); + joined = `\\${joined.slice(slashCount)}`; } } @@ -599,111 +534,102 @@ export const win32: IPath = { // Trim any leading backslashes let fromStart = 0; - for (; fromStart < from.length; ++fromStart) { - if (from.charCodeAt(fromStart) !== CHAR_BACKWARD_SLASH) { - break; - } + while (fromStart < from.length && + from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) { + fromStart++; } // Trim trailing backslashes (applicable to UNC paths only) let fromEnd = from.length; - for (; fromEnd - 1 > fromStart; --fromEnd) { - if (from.charCodeAt(fromEnd - 1) !== CHAR_BACKWARD_SLASH) { - break; - } + while (fromEnd - 1 > fromStart && + from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) { + fromEnd--; } - const fromLen = (fromEnd - fromStart); + const fromLen = fromEnd - fromStart; // Trim any leading backslashes let toStart = 0; - for (; toStart < to.length; ++toStart) { - if (to.charCodeAt(toStart) !== CHAR_BACKWARD_SLASH) { - break; - } + while (toStart < to.length && + to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) { + toStart++; } // Trim trailing backslashes (applicable to UNC paths only) let toEnd = to.length; - for (; toEnd - 1 > toStart; --toEnd) { - if (to.charCodeAt(toEnd - 1) !== CHAR_BACKWARD_SLASH) { - break; - } + while (toEnd - 1 > toStart && + to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) { + toEnd--; } - const toLen = (toEnd - toStart); + const toLen = toEnd - toStart; // Compare paths to find the longest common path from root - const length = (fromLen < toLen ? fromLen : toLen); + const length = fromLen < toLen ? fromLen : toLen; let lastCommonSep = -1; let i = 0; - for (; i <= length; ++i) { - if (i === length) { - if (toLen > length) { - if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) { - // We get here if `from` is the exact base path for `to`. - // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz' - return toOrig.slice(toStart + i + 1); - } else if (i === 2) { - // We get here if `from` is the device root. - // For example: from='C:\\'; to='C:\\foo' - return toOrig.slice(toStart + i); - } - } - if (fromLen > length) { - if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) { - // We get here if `to` is the exact base path for `from`. - // For example: from='C:\\foo\\bar'; to='C:\\foo' - lastCommonSep = i; - } else if (i === 2) { - // We get here if `to` is the device root. - // For example: from='C:\\foo\\bar'; to='C:\\' - lastCommonSep = 3; - } - } - break; - } + for (; i < length; i++) { const fromCode = from.charCodeAt(fromStart + i); - const toCode = to.charCodeAt(toStart + i); - if (fromCode !== toCode) { + if (fromCode !== to.charCodeAt(toStart + i)) { break; - } - else if (fromCode === CHAR_BACKWARD_SLASH) { + } else if (fromCode === CHAR_BACKWARD_SLASH) { lastCommonSep = i; } } // We found a mismatch before the first common path separator was seen, so // return the original `to`. - if (i !== length && lastCommonSep === -1) { - return toOrig; + if (i !== length) { + if (lastCommonSep === -1) { + return toOrig; + } + } else { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz' + return toOrig.slice(toStart + i + 1); + } + if (i === 2) { + // We get here if `from` is the device root. + // For example: from='C:\\'; to='C:\\foo' + return toOrig.slice(toStart + i); + } + } + if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='C:\\foo\\bar'; to='C:\\foo' + lastCommonSep = i; + } else if (i === 2) { + // We get here if `to` is the device root. + // For example: from='C:\\foo\\bar'; to='C:\\' + lastCommonSep = 3; + } + } + if (lastCommonSep === -1) { + lastCommonSep = 0; + } } let out = ''; - if (lastCommonSep === -1) { - lastCommonSep = 0; - } // Generate the relative path based on the path difference between `to` and // `from` for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) { - if (out.length === 0) { - out += '..'; - } - else { - out += '\\..'; - } + out += out.length === 0 ? '..' : '\\..'; } } + toStart += lastCommonSep; + // Lastly, append the rest of the destination (`to`) path that comes after // the common path parts if (out.length > 0) { - return out + toOrig.slice(toStart + lastCommonSep, toEnd); + return `${out}${toOrig.slice(toStart, toEnd)}`; } - else { - toStart += lastCommonSep; - if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) { - ++toStart; - } - return toOrig.slice(toStart, toEnd); + + if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) { + ++toStart; } + + return toOrig.slice(toStart, toEnd); }, toNamespacedPath(path: string): string { @@ -718,26 +644,24 @@ export const win32: IPath = { const resolvedPath = win32.resolve(path); - if (resolvedPath.length >= 3) { - if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) { - // Possible UNC root + if (resolvedPath.length <= 2) { + return path; + } - if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) { - const code = resolvedPath.charCodeAt(2); - if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) { - // Matched non-long UNC root, convert the path to a long UNC path - return '\\\\?\\UNC\\' + resolvedPath.slice(2); - } - } - } else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0))) { - // Possible device root - - if (resolvedPath.charCodeAt(1) === CHAR_COLON && - resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH) { - // Matched device root, convert the path to a long UNC path - return '\\\\?\\' + resolvedPath; + if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) { + // Possible UNC root + if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) { + const code = resolvedPath.charCodeAt(2); + if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) { + // Matched non-long UNC root, convert the path to a long UNC path + return `\\\\?\\UNC\\${resolvedPath.slice(2)}`; } } + } else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) && + resolvedPath.charCodeAt(1) === CHAR_COLON && + resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH) { + // Matched device root, convert the path to a long UNC path + return `\\\\?\\${resolvedPath}`; } return path; @@ -750,78 +674,65 @@ export const win32: IPath = { return '.'; } let rootEnd = -1; - let end = -1; - let matchedSlash = true; let offset = 0; const code = path.charCodeAt(0); + if (len === 1) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work or a dot. + return isPathSeparator(code) ? path : '.'; + } + // Try to match a root - if (len > 1) { - if (isPathSeparator(code)) { - // Possible UNC root + if (isPathSeparator(code)) { + // Possible UNC root - rootEnd = offset = 1; + rootEnd = offset = 1; - if (isPathSeparator(path.charCodeAt(1))) { - // Matched double path separator at beginning - let j = 2; - let last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; } if (j < len && j !== last) { // Matched! last = j; - // Match 1 or more path separators - for (; j < len; ++j) { - if (!isPathSeparator(path.charCodeAt(j))) { - break; - } + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; } - if (j < len && j !== last) { - // Matched! - last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } - } - if (j === len) { - // We matched a UNC root only - return path; - } - if (j !== last) { - // We matched a UNC root with leftovers - - // Offset by 1 to include the separator after the UNC root to - // treat it as a "normal root" on top of a (UNC) root - rootEnd = offset = j + 1; - } + if (j === len) { + // We matched a UNC root only + return path; } - } - } - } else if (isWindowsDeviceRoot(code)) { - // Possible device root + if (j !== last) { + // We matched a UNC root with leftovers - if (path.charCodeAt(1) === CHAR_COLON) { - rootEnd = offset = 2; - if (len > 2) { - if (isPathSeparator(path.charCodeAt(2))) { - rootEnd = offset = 3; + // Offset by 1 to include the separator after the UNC root to + // treat it as a "normal root" on top of a (UNC) root + rootEnd = offset = j + 1; } } } } - } else if (isPathSeparator(code)) { - // `path` contains just a path separator, exit early to avoid - // unnecessary work - return path; + // Possible device root + } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { + rootEnd = len > 2 && isPathSeparator(path.charCodeAt(2)) ? 3 : 2; + offset = rootEnd; } + let end = -1; + let matchedSlash = true; for (let i = len - 1; i >= offset; --i) { if (isPathSeparator(path.charCodeAt(i))) { if (!matchedSlash) { @@ -838,9 +749,8 @@ export const win32: IPath = { if (rootEnd === -1) { return '.'; } - else { - end = rootEnd; - } + + end = rootEnd; } return path.slice(0, end); }, @@ -858,17 +768,14 @@ export const win32: IPath = { // Check for a drive letter prefix so as not to mistake the following // path separator as an extra separator at the end of the path that can be // disregarded - if (path.length >= 2) { - const drive = path.charCodeAt(0); - if (isWindowsDeviceRoot(drive)) { - if (path.charCodeAt(1) === CHAR_COLON) { - start = 2; - } - } + if (path.length >= 2 && + isWindowsDeviceRoot(path.charCodeAt(0)) && + path.charCodeAt(1) === CHAR_COLON) { + start = 2; } if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { - if (ext.length === path.length && ext === path) { + if (ext === path) { return ''; } let extIdx = ext.length - 1; @@ -909,33 +816,31 @@ export const win32: IPath = { if (start === end) { end = firstNonSlashEnd; - } - else if (end === -1) { + } else if (end === -1) { end = path.length; } return path.slice(start, end); - } else { - for (i = path.length - 1; i >= start; --i) { - if (isPathSeparator(path.charCodeAt(i))) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - start = i + 1; - break; - } - } else if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // path component - matchedSlash = false; - end = i + 1; - } - } - - if (end === -1) { - return ''; - } - return path.slice(start, end); } + for (i = path.length - 1; i >= start; --i) { + if (isPathSeparator(path.charCodeAt(i))) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) { + return ''; + } + return path.slice(start, end); }, extname(path: string): string { @@ -1004,14 +909,7 @@ export const win32: IPath = { return path.slice(startDot, end); }, - format(pathObject): string { - if (pathObject === null || typeof pathObject !== 'object') { - throw new ErrorInvalidArgType('pathObject', 'Object', pathObject); - } - - return _format('\\', pathObject); - }, - + format: _format.bind(null, '\\'), parse(path) { validateString(path, 'path'); @@ -1025,82 +923,72 @@ export const win32: IPath = { let rootEnd = 0; let code = path.charCodeAt(0); - // Try to match a root - if (len > 1) { + if (len === 1) { if (isPathSeparator(code)) { - // Possible UNC root + // `path` contains just a path separator, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + ret.base = ret.name = path; + return ret; + } + // Try to match a root + if (isPathSeparator(code)) { + // Possible UNC root - rootEnd = 1; - if (isPathSeparator(path.charCodeAt(1))) { - // Matched double path separator at beginning - let j = 2; - let last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } + rootEnd = 1; + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; } if (j < len && j !== last) { // Matched! last = j; - // Match 1 or more path separators - for (; j < len; ++j) { - if (!isPathSeparator(path.charCodeAt(j))) { - break; - } + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; } - if (j < len && j !== last) { - // Matched! - last = j; - // Match 1 or more non-path separators - for (; j < len; ++j) { - if (isPathSeparator(path.charCodeAt(j))) { - break; - } - } - if (j === len) { - // We matched a UNC root only - - rootEnd = j; - } else if (j !== last) { - // We matched a UNC root with leftovers - - rootEnd = j + 1; - } + if (j === len) { + // We matched a UNC root only + rootEnd = j; + } else if (j !== last) { + // We matched a UNC root with leftovers + rootEnd = j + 1; } } } - } else if (isWindowsDeviceRoot(code)) { - // Possible device root - - if (path.charCodeAt(1) === CHAR_COLON) { - rootEnd = 2; - if (len > 2) { - if (isPathSeparator(path.charCodeAt(2))) { - if (len === 3) { - // `path` contains just a drive root, exit early to avoid - // unnecessary work - ret.root = ret.dir = path; - return ret; - } - rootEnd = 3; - } - } else { - // `path` contains just a drive root, exit early to avoid - // unnecessary work - ret.root = ret.dir = path; - return ret; - } - } } - } else if (isPathSeparator(code)) { - // `path` contains just a path separator, exit early to avoid - // unnecessary work - ret.root = ret.dir = path; - return ret; + } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { + // Possible device root + if (len <= 2) { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + rootEnd = 2; + if (isPathSeparator(path.charCodeAt(2))) { + if (len === 3) { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + rootEnd = 3; + } } - if (rootEnd > 0) { ret.root = path.slice(0, rootEnd); } @@ -1137,8 +1025,7 @@ export const win32: IPath = { // If this is our first dot, mark it as the start of our extension if (startDot === -1) { startDot = i; - } - else if (preDotState !== 1) { + } else if (preDotState !== 1) { preDotState = 1; } } else if (startDot !== -1) { @@ -1148,21 +1035,20 @@ export const win32: IPath = { } } - if (startDot === -1 || - end === -1 || - // We saw a non-dot character immediately before the dot - preDotState === 0 || - // The (right-most) trimmed path component is exactly '..' - (preDotState === 1 && - startDot === end - 1 && - startDot === startPart + 1)) { - if (end !== -1) { + if (end !== -1) { + if (startDot === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { ret.base = ret.name = path.slice(startPart, end); + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + ret.ext = path.slice(startDot, end); } - } else { - ret.name = path.slice(startPart, startDot); - ret.base = path.slice(startPart, end); - ret.ext = path.slice(startDot, end); } // If the directory is the root, use the entire root as the `dir` including @@ -1170,8 +1056,7 @@ export const win32: IPath = { // trailing slash (`C:\abc\def` -> `C:\abc`). if (startPart > 0 && startPart !== rootEnd) { ret.dir = path.slice(0, startPart - 1); - } - else { + } else { ret.dir = ret.root; } @@ -1191,13 +1076,7 @@ export const posix: IPath = { let resolvedAbsolute = false; for (let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--) { - let path; - if (i >= 0) { - path = pathSegments[i]; - } - else { - path = process.cwd(); - } + const path = i >= 0 ? pathSegments[i] : process.cwd(); validateString(path, 'path'); @@ -1206,7 +1085,7 @@ export const posix: IPath = { continue; } - resolvedPath = path + '/' + resolvedPath; + resolvedPath = `${path}/${resolvedPath}`; resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; } @@ -1218,17 +1097,9 @@ export const posix: IPath = { isPosixPathSeparator); if (resolvedAbsolute) { - if (resolvedPath.length > 0) { - return '/' + resolvedPath; - } - else { - return '/'; - } - } else if (resolvedPath.length > 0) { - return resolvedPath; - } else { - return '.'; + return `/${resolvedPath}`; } + return resolvedPath.length > 0 ? resolvedPath : '.'; }, normalize(path: string): string { @@ -1245,17 +1116,17 @@ export const posix: IPath = { // Normalize the path path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator); - if (path.length === 0 && !isAbsolute) { - path = '.'; + if (path.length === 0) { + if (isAbsolute) { + return '/'; + } + return trailingSeparator ? './' : '.'; } - if (path.length > 0 && trailingSeparator) { + if (trailingSeparator) { path += '/'; } - if (isAbsolute) { - return '/' + path; - } - return path; + return isAbsolute ? `/${path}` : path; }, isAbsolute(path: string): boolean { @@ -1269,14 +1140,13 @@ export const posix: IPath = { } let joined; for (let i = 0; i < paths.length; ++i) { - const arg = arguments[i]; + const arg = paths[i]; validateString(arg, 'path'); if (arg.length > 0) { if (joined === undefined) { joined = arg; - } - else { - joined += '/' + arg; + } else { + joined += `/${arg}`; } } } @@ -1294,6 +1164,7 @@ export const posix: IPath = { return ''; } + // Trim leading forward slashes. from = posix.resolve(from); to = posix.resolve(to); @@ -1301,91 +1172,61 @@ export const posix: IPath = { return ''; } - // Trim any leading backslashes - let fromStart = 1; - for (; fromStart < from.length; ++fromStart) { - if (from.charCodeAt(fromStart) !== CHAR_FORWARD_SLASH) { - break; - } - } + const fromStart = 1; const fromEnd = from.length; - const fromLen = (fromEnd - fromStart); - - // Trim any leading backslashes - let toStart = 1; - for (; toStart < to.length; ++toStart) { - if (to.charCodeAt(toStart) !== CHAR_FORWARD_SLASH) { - break; - } - } - const toEnd = to.length; - const toLen = (toEnd - toStart); + const fromLen = fromEnd - fromStart; + const toStart = 1; + const toLen = to.length - toStart; // Compare paths to find the longest common path from root const length = (fromLen < toLen ? fromLen : toLen); let lastCommonSep = -1; let i = 0; - for (; i <= length; ++i) { - if (i === length) { - if (toLen > length) { - if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) { - // We get here if `from` is the exact base path for `to`. - // For example: from='/foo/bar'; to='/foo/bar/baz' - return to.slice(toStart + i + 1); - } else if (i === 0) { - // We get here if `from` is the root - // For example: from='/'; to='/foo' - return to.slice(toStart + i); - } - } else if (fromLen > length) { - if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) { - // We get here if `to` is the exact base path for `from`. - // For example: from='/foo/bar/baz'; to='/foo/bar' - lastCommonSep = i; - } else if (i === 0) { - // We get here if `to` is the root. - // For example: from='/foo'; to='/' - lastCommonSep = 0; - } - } - break; - } + for (; i < length; i++) { const fromCode = from.charCodeAt(fromStart + i); - const toCode = to.charCodeAt(toStart + i); - if (fromCode !== toCode) { + if (fromCode !== to.charCodeAt(toStart + i)) { break; - } - else if (fromCode === CHAR_FORWARD_SLASH) { + } else if (fromCode === CHAR_FORWARD_SLASH) { lastCommonSep = i; } } + if (i === length) { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='/foo/bar'; to='/foo/bar/baz' + return to.slice(toStart + i + 1); + } + if (i === 0) { + // We get here if `from` is the root + // For example: from='/'; to='/foo' + return to.slice(toStart + i); + } + } else if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='/foo/bar/baz'; to='/foo/bar' + lastCommonSep = i; + } else if (i === 0) { + // We get here if `to` is the root. + // For example: from='/foo/bar'; to='/' + lastCommonSep = 0; + } + } + } let out = ''; // Generate the relative path based on the path difference between `to` - // and `from` + // and `from`. for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) { - if (out.length === 0) { - out += '..'; - } - else { - out += '/..'; - } + out += out.length === 0 ? '..' : '/..'; } } // Lastly, append the rest of the destination (`to`) path that comes after - // the common path parts - if (out.length > 0) { - return out + to.slice(toStart + lastCommonSep); - } - else { - toStart += lastCommonSep; - if (to.charCodeAt(toStart) === CHAR_FORWARD_SLASH) { - ++toStart; - } - return to.slice(toStart); - } + // the common path parts. + return `${out}${to.slice(toStart + lastCommonSep)}`; }, toNamespacedPath(path: string): string { @@ -1434,7 +1275,7 @@ export const posix: IPath = { let i; if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { - if (ext.length === path.length && ext === path) { + if (ext === path) { return ''; } let extIdx = ext.length - 1; @@ -1475,33 +1316,31 @@ export const posix: IPath = { if (start === end) { end = firstNonSlashEnd; - } - else if (end === -1) { + } else if (end === -1) { end = path.length; } return path.slice(start, end); - } else { - for (i = path.length - 1; i >= 0; --i) { - if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - start = i + 1; - break; - } - } else if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // path component - matchedSlash = false; - end = i + 1; - } - } - - if (end === -1) { - return ''; - } - return path.slice(start, end); } + for (i = path.length - 1; i >= 0; --i) { + if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) { + return ''; + } + return path.slice(start, end); }, extname(path: string): string { @@ -1558,13 +1397,7 @@ export const posix: IPath = { return path.slice(startDot, end); }, - format(pathObject): string { - if (pathObject === null || typeof pathObject !== 'object') { - throw new ErrorInvalidArgType('pathObject', 'Object', pathObject); - } - - return _format('/', pathObject); - }, + format: _format.bind(null, '/'), parse(path: string): ParsedPath { validateString(path, 'path'); @@ -1613,8 +1446,7 @@ export const posix: IPath = { // If this is our first dot, mark it as the start of our extension if (startDot === -1) { startDot = i; - } - else if (preDotState !== 1) { + } else if (preDotState !== 1) { preDotState = 1; } } else if (startDot !== -1) { @@ -1624,37 +1456,26 @@ export const posix: IPath = { } } - if (startDot === -1 || - end === -1 || - // We saw a non-dot character immediately before the dot - preDotState === 0 || - // The (right-most) trimmed path component is exactly '..' - (preDotState === 1 && - startDot === end - 1 && - startDot === startPart + 1)) { - if (end !== -1) { - if (startPart === 0 && isAbsolute) { - ret.base = ret.name = path.slice(1, end); - } - else { - ret.base = ret.name = path.slice(startPart, end); - } - } - } else { - if (startPart === 0 && isAbsolute) { - ret.name = path.slice(1, startDot); - ret.base = path.slice(1, end); + if (end !== -1) { + const start = startPart === 0 && isAbsolute ? 1 : startPart; + if (startDot === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + ret.base = ret.name = path.slice(start, end); } else { - ret.name = path.slice(startPart, startDot); - ret.base = path.slice(startPart, end); + ret.name = path.slice(start, startDot); + ret.base = path.slice(start, end); + ret.ext = path.slice(startDot, end); } - ret.ext = path.slice(startDot, end); } if (startPart > 0) { ret.dir = path.slice(0, startPart - 1); - } - else if (isAbsolute) { + } else if (isAbsolute) { ret.dir = '/'; } diff --git a/src/vs/base/common/resourceTree.ts b/src/vs/base/common/resourceTree.ts index 10d13eff57..d7433949ed 100644 --- a/src/vs/base/common/resourceTree.ts +++ b/src/vs/base/common/resourceTree.ts @@ -8,8 +8,7 @@ import * as paths from 'vs/base/common/path'; import { Iterator } from 'vs/base/common/iterator'; import { relativePath, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { mapValues } from 'vs/base/common/collections'; -import { PathIterator } from 'vs/base/common/map'; +import { PathIterator, values } from 'vs/base/common/map'; export interface IResourceNode { readonly uri: URI; @@ -32,7 +31,7 @@ class Node implements IResourceNode { } get children(): Iterator> { - return Iterator.fromArray(mapValues(this._children)); + return Iterator.fromArray(values(this._children)); } @memoize diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts index 26f23dd95d..a756d4ef76 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -5,7 +5,7 @@ import * as extpath from 'vs/base/common/extpath'; import * as paths from 'vs/base/common/path'; -import { URI } from 'vs/base/common/uri'; +import { URI, originalFSPath as uriOriginalFSPath } from 'vs/base/common/uri'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import { isLinux, isWindows } from 'vs/base/common/platform'; @@ -13,6 +13,8 @@ import { CharCode } from 'vs/base/common/charCode'; import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob'; import { TernarySearchTree } from 'vs/base/common/map'; +export const originalFSPath = uriOriginalFSPath; + export function getComparisonKey(resource: URI): string { return hasToIgnoreCase(resource) ? resource.toString().toLowerCase() : resource.toString(); } @@ -107,15 +109,7 @@ export function dirname(resource: URI): URI { * @returns The resulting URI. */ export function joinPath(resource: URI, ...pathFragment: string[]): URI { - let joinedPath: string; - if (resource.scheme === Schemas.file) { - joinedPath = URI.file(paths.join(originalFSPath(resource), ...pathFragment)).path; - } else { - joinedPath = paths.posix.join(resource.path || '/', ...pathFragment); - } - return resource.with({ - path: joinedPath - }); + return URI.joinPaths(resource, ...pathFragment); } /** @@ -139,33 +133,6 @@ export function normalizePath(resource: URI): URI { }); } -/** - * Returns the fsPath of an URI where the drive letter is not normalized. - * See #56403. - */ -export function originalFSPath(uri: URI): string { - let value: string; - const uriPath = uri.path; - if (uri.authority && uriPath.length > 1 && uri.scheme === Schemas.file) { - // unc path: file://shares/c$/far/boo - value = `//${uri.authority}${uriPath}`; - } else if ( - isWindows - && uriPath.charCodeAt(0) === CharCode.Slash - && extpath.isWindowsDriveLetter(uriPath.charCodeAt(1)) - && uriPath.charCodeAt(2) === CharCode.Colon - ) { - value = uriPath.substr(1); - } else { - // other path - value = uriPath; - } - if (isWindows) { - value = value.replace(/\//g, '\\'); - } - return value; -} - /** * Returns true if the URI path is absolute. */ diff --git a/src/vs/base/common/stream.ts b/src/vs/base/common/stream.ts index 0b7884b56e..961dd7e6c0 100644 --- a/src/vs/base/common/stream.ts +++ b/src/vs/base/common/stream.ts @@ -95,8 +95,8 @@ export interface WriteableStream extends ReadableStream { end(result?: T | Error): void; } -export function isReadableStream(obj: any): obj is ReadableStream { - const candidate: ReadableStream = obj; +export function isReadableStream(obj: unknown): obj is ReadableStream { + const candidate = obj as ReadableStream; return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function'); } diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index d7de8ec9e4..7fd8352939 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -14,7 +14,7 @@ export function isFalsyOrWhitespace(str: string | undefined): boolean { } /** - * @returns the provided number with the given number of preceding zeros. + * @deprecated ES6: use `String.padStart` */ export function pad(n: number, l: number, char: string = '0'): string { const str = '' + n; @@ -145,7 +145,7 @@ export function stripWildcards(pattern: string): string { } /** - * Determines if haystack starts with needle. + * @deprecated ES6: use `String.startsWith` */ export function startsWith(haystack: string, needle: string): boolean { if (haystack.length < needle.length) { @@ -166,7 +166,7 @@ export function startsWith(haystack: string, needle: string): boolean { } /** - * Determines if haystack ends with needle. + * @deprecated ES6: use `String.endsWith` */ export function endsWith(haystack: string, needle: string): boolean { const diff = haystack.length - needle.length; @@ -240,7 +240,7 @@ export function regExpFlags(regexp: RegExp): string { return (regexp.global ? 'g' : '') + (regexp.ignoreCase ? 'i' : '') + (regexp.multiline ? 'm' : '') - + ((regexp as any).unicode ? 'u' : ''); + + ((regexp as any /* standalone editor compilation */).unicode ? 'u' : ''); } /** @@ -428,43 +428,6 @@ export function commonSuffixLength(a: string, b: string): number { return len; } -function substrEquals(a: string, aStart: number, aEnd: number, b: string, bStart: number, bEnd: number): boolean { - while (aStart < aEnd && bStart < bEnd) { - if (a[aStart] !== b[bStart]) { - return false; - } - aStart += 1; - bStart += 1; - } - return true; -} - -/** - * Return the overlap between the suffix of `a` and the prefix of `b`. - * For instance `overlap("foobar", "arr, I'm a pirate") === 2`. - */ -export function overlap(a: string, b: string): number { - const aEnd = a.length; - let bEnd = b.length; - let aStart = aEnd - bEnd; - - if (aStart === 0) { - return a === b ? aEnd : 0; - } else if (aStart < 0) { - bEnd += aStart; - aStart = 0; - } - - while (aStart < aEnd && bEnd > 0) { - if (substrEquals(a, aStart, aEnd, b, 0, bEnd)) { - return bEnd; - } - bEnd -= 1; - aStart += 1; - } - return 0; -} - // --- unicode // http://en.wikipedia.org/wiki/Surrogate_pair // Returns the code point starting at a specified index in a string @@ -852,21 +815,6 @@ export function removeAnsiEscapeCodes(str: string): string { return str; } -export const removeAccents: (str: string) => string = (function () { - if (typeof (String.prototype as any).normalize !== 'function') { - // ☹️ no ES6 features... - return function (str: string) { return str; }; - } else { - // transform into NFD form and remove accents - // see: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463 - const regex = /[\u0300-\u036f]/g; - return function (str: string) { - return (str as any).normalize('NFD').replace(regex, ''); - }; - } -})(); - - // -- UTF-8 BOM export const UTF8_BOM_CHARACTER = String.fromCharCode(CharCode.UTF8_BOM); diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index 8512ff542e..8e72cba783 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -5,6 +5,8 @@ import { isWindows } from 'vs/base/common/platform'; import { CharCode } from 'vs/base/common/charCode'; +import * as paths from 'vs/base/common/path'; +import * as extpath from 'vs/base/common/extpath'; const _schemePattern = /^\w[\w\d+.-]*$/; const _singleSlashStart = /^\//; @@ -333,6 +335,25 @@ export class URI implements UriComponents { ); } + /** + * Join a URI path with path fragments and normalizes the resulting path. + * + * @param resource The input URI. + * @param pathFragment The path fragment to add to the URI path. + * @returns The resulting URI. + */ + static joinPaths(resource: URI, ...pathFragment: string[]): URI { + let joinedPath: string; + if (resource.scheme === 'file') { + joinedPath = URI.file(paths.join(originalFSPath(resource), ...pathFragment)).path; + } else { + joinedPath = paths.posix.join(resource.path || '/', ...pathFragment); + } + return resource.with({ + path: joinedPath + }); + } + // ---- printing/externalize --------------------------- /** @@ -672,3 +693,29 @@ function percentDecode(str: string): string { } return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match)); } + + +// --- utils + +export function originalFSPath(uri: URI): string { + let value: string; + const uriPath = uri.path; + if (uri.authority && uriPath.length > 1 && uri.scheme === 'file') { + // unc path: file://shares/c$/far/boo + value = `//${uri.authority}${uriPath}`; + } else if ( + isWindows + && uriPath.charCodeAt(0) === CharCode.Slash + && extpath.isWindowsDriveLetter(uriPath.charCodeAt(1)) + && uriPath.charCodeAt(2) === CharCode.Colon + ) { + value = uriPath.substr(1); + } else { + // other path + value = uriPath; + } + if (isWindows) { + value = value.replace(/\//g, '\\'); + } + return value; +} diff --git a/src/vs/base/common/uuid.ts b/src/vs/base/common/uuid.ts index c90b892dd4..6fe6ed1a52 100644 --- a/src/vs/base/common/uuid.ts +++ b/src/vs/base/common/uuid.ts @@ -3,87 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/** - * Represents a UUID as defined by rfc4122. - */ -export interface UUID { - - /** - * @returns the canonical representation in sets of hexadecimal numbers separated by dashes. - */ - asHex(): string; -} - -class ValueUUID implements UUID { - - constructor(public _value: string) { - // empty - } - - public asHex(): string { - return this._value; - } -} - -class V4UUID extends ValueUUID { - - private static readonly _chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; - - private static readonly _timeHighBits = ['8', '9', 'a', 'b']; - - private static _oneOf(array: string[]): string { - return array[Math.floor(array.length * Math.random())]; - } - - private static _randomHex(): string { - return V4UUID._oneOf(V4UUID._chars); - } - - constructor() { - super([ - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - '-', - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - '-', - '4', - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - '-', - V4UUID._oneOf(V4UUID._timeHighBits), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - '-', - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - V4UUID._randomHex(), - ].join('')); - } -} - -export function v4(): UUID { - return new V4UUID(); -} const _UUIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; @@ -91,18 +10,52 @@ export function isUUID(value: string): boolean { return _UUIDPattern.test(value); } -/** - * Parses a UUID that is of the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. - * @param value A uuid string. - */ -export function parse(value: string): UUID { - if (!isUUID(value)) { - throw new Error('invalid uuid'); - } - - return new ValueUUID(value); +// prep-work +const _data = new Uint8Array(16); +const _hex: string[] = []; +for (let i = 0; i < 256; i++) { + _hex.push(i.toString(16).padStart(2, '0')); } +// todo@joh node nodejs use `crypto#randomBytes`, see: https://nodejs.org/docs/latest/api/crypto.html#crypto_crypto_randombytes_size_callback +// todo@joh use browser-crypto +const _fillRandomValues = function (bucket: Uint8Array): Uint8Array { + for (let i = 0; i < bucket.length; i++) { + bucket[i] = Math.floor(Math.random() * 256); + } + return bucket; +}; + export function generateUuid(): string { - return v4().asHex(); + // get data + _fillRandomValues(_data); + + // set version bits + _data[6] = (_data[6] & 0x0f) | 0x40; + _data[8] = (_data[8] & 0x3f) | 0x80; + + // print as string + let i = 0; + let result = ''; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + return result; } diff --git a/src/vs/base/node/crypto.ts b/src/vs/base/node/crypto.ts index 778799af9c..e5d54f1eaa 100644 --- a/src/vs/base/node/crypto.ts +++ b/src/vs/base/node/crypto.ts @@ -5,19 +5,17 @@ import * as fs from 'fs'; import * as crypto from 'crypto'; -import * as stream from 'stream'; import { once } from 'vs/base/common/functional'; export function checksum(path: string, sha1hash: string | undefined): Promise { const promise = new Promise((c, e) => { const input = fs.createReadStream(path); const hash = crypto.createHash('sha1'); - const hashStream = hash as any as stream.PassThrough; - input.pipe(hashStream); + input.pipe(hash); const done = once((err?: Error, result?: string) => { input.removeAllListeners(); - hashStream.removeAllListeners(); + hash.removeAllListeners(); if (err) { e(err); @@ -28,8 +26,8 @@ export function checksum(path: string, sha1hash: string | undefined): Promise done(undefined, data.toString('hex'))); + hash.once('error', done); + hash.once('data', (data: Buffer) => done(undefined, data.toString('hex'))); }); return promise.then(hash => { diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index faa1d10b34..a44df9e677 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -60,9 +60,9 @@ export interface IQuickInputStyles { export interface IQuickInputWidgetStyles { quickInputBackground?: Color; quickInputForeground?: Color; + quickInputTitleBackground?: Color; contrastBorder?: Color; widgetShadow?: Color; - titleColor: string | undefined; } const $ = dom.$; @@ -142,6 +142,7 @@ class QuickInput extends Disposable implements IQuickInput { private buttonsUpdated = false; private readonly onDidTriggerButtonEmitter = this._register(new Emitter()); private readonly onDidHideEmitter = this._register(new Emitter()); + private readonly onDisposeEmitter = this._register(new Emitter()); protected readonly visibleDisposables = this._register(new DisposableStore()); @@ -235,7 +236,7 @@ class QuickInput extends Disposable implements IQuickInput { this.update(); } - onDidTriggerButton = this.onDidTriggerButtonEmitter.event; + readonly onDidTriggerButton = this.onDidTriggerButtonEmitter.event; show(): void { if (this.visible) { @@ -266,7 +267,7 @@ class QuickInput extends Disposable implements IQuickInput { this.onDidHideEmitter.fire(); } - onDidHide = this.onDidHideEmitter.event; + readonly onDidHide = this.onDidHideEmitter.event; protected update() { if (!this.visible) { @@ -298,9 +299,8 @@ class QuickInput extends Disposable implements IQuickInput { 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}`, '', button.iconClass || getIconClass(button.iconPath), true, () => { + const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, async () => { this.onDidTriggerButtonEmitter.fire(button); - return Promise.resolve(null); }); action.tooltip = button.tooltip || ''; return action; @@ -308,9 +308,8 @@ class QuickInput extends Disposable implements IQuickInput { 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}`, '', button.iconClass || getIconClass(button.iconPath), true, () => { + const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, async () => { this.onDidTriggerButtonEmitter.fire(button); - return Promise.resolve(null); }); action.tooltip = button.tooltip || ''; return action; @@ -362,17 +361,22 @@ class QuickInput extends Disposable implements IQuickInput { } } + readonly onDispose = this.onDisposeEmitter.event; + public dispose(): void { this.hide(); + this.onDisposeEmitter.fire(); + super.dispose(); } } class QuickPick extends QuickInput implements IQuickPick { - private static readonly INPUT_BOX_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results."); + private static readonly DEFAULT_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results."); private _value = ''; + private _ariaLabel = QuickPick.DEFAULT_ARIA_LABEL; private _placeholder: string | undefined; private readonly onDidChangeValueEmitter = this._register(new Emitter()); private readonly onDidAcceptEmitter = this._register(new Emitter()); @@ -404,7 +408,6 @@ class QuickPick extends QuickInput implements IQuickPi quickNavigate: IQuickNavigateConfiguration | undefined; - get value() { return this._value; } @@ -414,6 +417,17 @@ class QuickPick extends QuickInput implements IQuickPi this.update(); } + filterValue = (value: string) => value; + + set ariaLabel(ariaLabel: string) { + this._ariaLabel = ariaLabel || QuickPick.DEFAULT_ARIA_LABEL; + this.update(); + } + + get ariaLabel() { + return this._ariaLabel; + } + get placeholder() { return this._placeholder; } @@ -599,7 +613,7 @@ class QuickPick extends QuickInput implements IQuickPi return; } this._value = value; - this.ui.list.filter(this.ui.inputBox.value); + this.ui.list.filter(this.filterValue(this.ui.inputBox.value)); this.trySelectFirst(); this.onDidChangeValueEmitter.fire(value); })); @@ -770,10 +784,17 @@ class QuickPick extends QuickInput implements IQuickPi if (this.ui.inputBox.placeholder !== (this.placeholder || '')) { this.ui.inputBox.placeholder = (this.placeholder || ''); } + if (this.ui.inputBox.ariaLabel !== this.ariaLabel) { + this.ui.inputBox.ariaLabel = this.ariaLabel; + } + this.ui.list.matchOnDescription = this.matchOnDescription; + this.ui.list.matchOnDetail = this.matchOnDetail; + this.ui.list.matchOnLabel = this.matchOnLabel; + this.ui.list.sortByLabel = this.sortByLabel; if (this.itemsUpdated) { this.itemsUpdated = false; this.ui.list.setElements(this.items); - this.ui.list.filter(this.ui.inputBox.value); + this.ui.list.filter(this.filterValue(this.ui.inputBox.value)); this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked(); this.ui.visibleCount.setCount(this.ui.list.getVisibleCount()); this.ui.count.setCount(this.ui.list.getCheckedCount()); @@ -815,12 +836,7 @@ class QuickPick extends QuickInput implements IQuickPi } this.ui.customButton.label = this.customLabel || ''; this.ui.customButton.element.title = this.customHover || ''; - this.ui.list.matchOnDescription = this.matchOnDescription; - this.ui.list.matchOnDetail = this.matchOnDetail; - this.ui.list.matchOnLabel = this.matchOnLabel; - this.ui.list.sortByLabel = this.sortByLabel; this.ui.setComboboxAccessibility(true); - this.ui.inputBox.setAttribute('aria-label', QuickPick.INPUT_BOX_ARIA_LABEL); } } @@ -948,7 +964,7 @@ export class QuickInputController extends Disposable { private idPrefix: string; private ui: QuickInputUI | undefined; - private dimension?: dom.Dimension; + private dimension?: dom.IDimension; private titleBarOffset?: number; private comboboxAccessibility = false; private enabled = true; @@ -1378,7 +1394,7 @@ export class QuickInputController extends Disposable { ui.list.sortByLabel = true; ui.ignoreFocusOut = false; this.setComboboxAccessibility(false); - ui.inputBox.removeAttribute('aria-label'); + ui.inputBox.ariaLabel = ''; const backKeybindingLabel = this.options.backKeybindingLabel(); backButton.tooltip = backKeybindingLabel ? localize('quickInput.backWithKeybinding', "Back ({0})", backKeybindingLabel) : localize('quickInput.back', "Back"); @@ -1472,22 +1488,19 @@ export class QuickInputController extends Disposable { } } - accept() { + async accept() { this.onDidAcceptEmitter.fire(); - return Promise.resolve(undefined); } - back() { + async back() { this.onDidTriggerButtonEmitter.fire(this.backButton); - return Promise.resolve(undefined); } - cancel() { + async cancel() { this.hide(); - return Promise.resolve(undefined); } - layout(dimension: dom.Dimension, titleBarOffset: number): void { + layout(dimension: dom.IDimension, titleBarOffset: number): void { this.dimension = dimension; this.titleBarOffset = titleBarOffset; this.updateLayout(); @@ -1515,13 +1528,13 @@ export class QuickInputController extends Disposable { private updateStyles() { if (this.ui) { const { - titleColor, + quickInputTitleBackground, quickInputBackground, quickInputForeground, contrastBorder, widgetShadow, } = this.styles.widget; - this.ui.titleBar.style.backgroundColor = titleColor || ''; + this.ui.titleBar.style.backgroundColor = quickInputTitleBackground ? quickInputTitleBackground.toString() : ''; this.ui.container.style.backgroundColor = quickInputBackground ? quickInputBackground.toString() : ''; this.ui.container.style.color = quickInputForeground ? quickInputForeground.toString() : ''; this.ui.container.style.border = contrastBorder ? `1px solid ${contrastBorder}` : ''; diff --git a/src/vs/base/parts/quickinput/browser/quickInputBox.ts b/src/vs/base/parts/quickinput/browser/quickInputBox.ts index d047c88520..983bd41ea4 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputBox.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputBox.ts @@ -66,6 +66,14 @@ export class QuickInputBox extends Disposable { this.inputBox.setPlaceHolder(placeholder); } + get ariaLabel() { + return this.inputBox.getAriaLabel(); + } + + set ariaLabel(ariaLabel: string) { + this.inputBox.setAriaLabel(ariaLabel); + } + get password() { return this.inputBox.inputElement.type === 'password'; } diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index 5bd32b7446..4f8fac5424 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -25,7 +25,7 @@ import { Action } from 'vs/base/common/actions'; import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput'; -import { IListOptions, List, IListStyles } from 'vs/base/browser/ui/list/listWidget'; +import { IListOptions, List, IListStyles, IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; const $ = dom.$; @@ -33,8 +33,12 @@ interface IListElement { readonly index: number; readonly item: IQuickPickItem; readonly saneLabel: string; + readonly saneAriaLabel: string; readonly saneDescription?: string; readonly saneDetail?: string; + readonly labelHighlights?: IMatch[]; + readonly descriptionHighlights?: IMatch[]; + readonly detailHighlights?: IMatch[]; readonly checked: boolean; readonly separator?: IQuickPickSeparator; readonly fireButtonTriggered: (event: IQuickPickItemButtonEvent) => void; @@ -44,6 +48,7 @@ class ListElement implements IListElement { index!: number; item!: IQuickPickItem; saneLabel!: string; + saneAriaLabel!: string; saneDescription?: string; saneDetail?: string; hidden = false; @@ -142,17 +147,12 @@ class ListElementRenderer implements IListRenderer s && parseCodicons(s).text) - .filter(s => !!s) - .join(', ')); - // Separator if (element.separator && element.separator.label) { data.separator.textContent = element.separator.label; @@ -244,12 +244,14 @@ export class QuickInputList { this.id = id; this.container = dom.append(this.parent, $('.quick-input-list')); const delegate = new ListElementDelegate(); + const accessibilityProvider = new QuickInputAccessibilityProvider(); this.list = options.createList('QuickInput', this.container, delegate, [new ListElementRenderer()], { identityProvider: { getId: element => element.saneLabel }, openController: { shouldOpen: () => false }, // Workaround #58124 setRowLineHeight: false, multipleSelectionSupport: false, horizontalScrolling: false, + accessibilityProvider } as IListOptions); this.list.getHTMLElement().id = id; this.disposables.push(this.list); @@ -295,12 +297,12 @@ export class QuickInputList { @memoize get onDidChangeFocus() { - return Event.map(this.list.onFocusChange, e => e.elements.map(e => e.item)); + return Event.map(this.list.onDidChangeFocus, e => e.elements.map(e => e.item)); } @memoize get onDidChangeSelection() { - return Event.map(this.list.onSelectionChange, e => e.elements.map(e => e.item)); + return Event.map(this.list.onDidChangeSelection, e => e.elements.map(e => e.item)); } getAllVisibleChecked() { @@ -364,12 +366,24 @@ export class QuickInputList { this.elements = inputElements.reduce((result, item, index) => { if (item.type !== 'separator') { const previous = index && inputElements[index - 1]; + const saneLabel = item.label && item.label.replace(/\r?\n/g, ' '); + const saneDescription = item.description && item.description.replace(/\r?\n/g, ' '); + const saneDetail = item.detail && item.detail.replace(/\r?\n/g, ' '); + const saneAriaLabel = item.ariaLabel || [saneLabel, saneDescription, saneDetail] + .map(s => s && parseCodicons(s).text) + .filter(s => !!s) + .join(', '); + result.push(new ListElement({ index, item, - saneLabel: item.label && item.label.replace(/\r?\n/g, ' '), - saneDescription: item.description && item.description.replace(/\r?\n/g, ' '), - saneDetail: item.detail && item.detail.replace(/\r?\n/g, ' '), + saneLabel, + saneAriaLabel, + saneDescription, + saneDetail, + labelHighlights: item.highlights?.label, + descriptionHighlights: item.highlights?.description, + detailHighlights: item.highlights?.detail, checked: false, separator: previous && previous.type === 'separator' ? previous : undefined, fireButtonTriggered @@ -472,6 +486,7 @@ export class QuickInputList { filter(query: string) { if (!(this.sortByLabel || this.matchOnLabel || this.matchOnDescription || this.matchOnDetail)) { + this.list.layout(); return; } query = query.trim(); @@ -590,3 +605,9 @@ function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: s return compareAnything(elementA.saneLabel, elementB.saneLabel, lookFor); } + +class QuickInputAccessibilityProvider implements IAccessibilityProvider { + getAriaLabel(element: ListElement): string | null { + return element.saneAriaLabel; + } +} diff --git a/src/vs/base/parts/quickinput/common/quickInput.ts b/src/vs/base/parts/quickinput/common/quickInput.ts index 5503047f80..d9a88c80b3 100644 --- a/src/vs/base/parts/quickinput/common/quickInput.ts +++ b/src/vs/base/parts/quickinput/common/quickInput.ts @@ -6,14 +6,27 @@ import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { IMatch } from 'vs/base/common/filters'; +import { IItemAccessor } from 'vs/base/common/fuzzyScorer'; +import { Schemas } from 'vs/base/common/network'; + +export interface IQuickPickItemHighlights { + label?: IMatch[]; + description?: IMatch[]; + detail?: IMatch[]; +} export interface IQuickPickItem { type?: 'item'; id?: string; label: string; + ariaLabel?: string; description?: string; detail?: string; iconClasses?: string[]; + italic?: boolean; + highlights?: IQuickPickItemHighlights; buttons?: IQuickInputButton[]; picked?: boolean; alwaysShow?: boolean; @@ -125,7 +138,10 @@ export interface IInputOptions { validateInput?: (input: string) => Promise; } -export interface IQuickInput { +export interface IQuickInput extends IDisposable { + + readonly onDidHide: Event; + readonly onDispose: Event; title: string | undefined; @@ -146,16 +162,20 @@ export interface IQuickInput { show(): void; hide(): void; - - onDidHide: Event; - - dispose(): void; } export interface IQuickPick extends IQuickInput { value: string; + /** + * A method that allows to massage the value used + * for filtering, e.g, to remove certain parts. + */ + filterValue: (value: string) => string; + + ariaLabel: string; + placeholder: string | undefined; readonly onDidChangeValue: Event; @@ -254,3 +274,28 @@ export interface IQuickPickItemButtonContext extends I } export type QuickPickInput = T | IQuickPickSeparator; + + +//region Fuzzy Scorer Support + +export type IQuickPickItemWithResource = IQuickPickItem & { resource: URI | undefined }; + +export const quickPickItemScorerAccessor = new class implements IItemAccessor { + getItemLabel(entry: IQuickPickItemWithResource): string { + return entry.label; + } + + getItemDescription(entry: IQuickPickItemWithResource): string | undefined { + return entry.description; + } + + getItemPath(entry: IQuickPickItemWithResource): string | undefined { + if (entry.resource?.scheme === Schemas.file) { + return entry.resource.fsPath; + } + + return entry.resource?.path; + } +}; + +//#endregion diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts index 366c4915e2..9834c11c38 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts @@ -17,7 +17,7 @@ import { IQuickOpenStyles } from 'vs/base/parts/quickopen/browser/quickOpenWidge import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { OS } from 'vs/base/common/platform'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { IItemAccessor } from 'vs/base/parts/quickopen/common/quickOpenScorer'; +import { IItemAccessor } from 'vs/base/common/fuzzyScorer'; import { coalesce } from 'vs/base/common/arrays'; import { IMatch } from 'vs/base/common/filters'; @@ -35,12 +35,12 @@ let IDS = 0; export class QuickOpenItemAccessorClass implements IItemAccessor { - getItemLabel(entry: QuickOpenEntry): string | null { - return types.withUndefinedAsNull(entry.getLabel()); + getItemLabel(entry: QuickOpenEntry): string | undefined { + return entry.getLabel(); } - getItemDescription(entry: QuickOpenEntry): string | null { - return types.withUndefinedAsNull(entry.getDescription()); + getItemDescription(entry: QuickOpenEntry): string | undefined { + return entry.getDescription(); } getItemPath(entry: QuickOpenEntry): string | undefined { diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts index b70335d62a..98815e17de 100644 --- a/src/vs/base/parts/tree/browser/treeView.ts +++ b/src/vs/base/parts/tree/browser/treeView.ts @@ -218,12 +218,11 @@ export class ViewItem implements IViewItem { } if (this.model.hasTrait('focused')) { const base64Id = strings.safeBtoa(this.model.id); - - this.element.setAttribute('aria-selected', 'true'); this.element.setAttribute('id', base64Id); + this.element.setAttribute('aria-selected', 'true'); } else { - this.element.setAttribute('aria-selected', 'false'); this.element.removeAttribute('id'); + this.element.setAttribute('aria-selected', 'false'); } if (this.model.hasChildren()) { this.element.setAttribute('aria-expanded', String(!!this._styles['expanded'])); @@ -375,11 +374,6 @@ class RootViewItem extends ViewItem { } } -interface IThrottledGestureEvent { - translationX: number; - translationY: number; -} - function reactionEquals(one: _.IDragOverReaction, other: _.IDragOverReaction | null): boolean { if (!one && !other) { return true; @@ -417,7 +411,6 @@ export class TreeView extends HeightMap { private scrollableElement: ScrollableElement; private msGesture: MSGesture | undefined; private lastPointerType: string = ''; - private lastClickTimeStamp: number = 0; private horizontalScrolling: boolean; private contentWidthUpdateDelayer = new Delayer(50); @@ -520,12 +513,7 @@ export class TreeView extends HeightMap { this._onDidScroll.fire(); }); - if (Browser.isIE) { - this.wrapper.style.msTouchAction = 'none'; - this.wrapper.style.msContentZooming = 'none'; - } else { - this.gestureDisposable = Touch.Gesture.addTarget(this.wrapper); - } + this.gestureDisposable = Touch.Gesture.addTarget(this.wrapper); this.rowsContainer = document.createElement('div'); this.rowsContainer.className = 'monaco-tree-rows'; @@ -552,26 +540,6 @@ export class TreeView extends HeightMap { this.viewListeners.push(DOM.addDisposableListener(this.wrapper, Touch.EventType.Tap, (e) => this.onTap(e))); this.viewListeners.push(DOM.addDisposableListener(this.wrapper, Touch.EventType.Change, (e) => this.onTouchChange(e))); - if (Browser.isIE) { - this.viewListeners.push(DOM.addDisposableListener(this.wrapper, 'MSPointerDown', (e) => this.onMsPointerDown(e))); - this.viewListeners.push(DOM.addDisposableListener(this.wrapper, 'MSGestureTap', (e) => this.onMsGestureTap(e))); - - // these events come too fast, we throttle them - this.viewListeners.push(DOM.addDisposableThrottledListener(this.wrapper, 'MSGestureChange', e => this.onThrottledMsGestureChange(e), (lastEvent, event) => { - event.stopPropagation(); - event.preventDefault(); - - let result = { translationY: event.translationY, translationX: event.translationX }; - - if (lastEvent) { - result.translationY += lastEvent.translationY; - result.translationX += lastEvent.translationX; - } - - return result; - })); - } - this.viewListeners.push(DOM.addDisposableListener(window, 'dragover', (e) => this.onDragOver(e))); this.viewListeners.push(DOM.addDisposableListener(this.wrapper, 'drop', (e) => this.onDrop(e))); this.viewListeners.push(DOM.addDisposableListener(window, 'dragend', (e) => this.onDragEnd(e))); @@ -1144,15 +1112,6 @@ export class TreeView extends HeightMap { return; } - if (Browser.isIE && Date.now() - this.lastClickTimeStamp < 300) { - // IE10+ doesn't set the detail property correctly. While IE10 simply - // counts the number of clicks, IE11 reports always 1. To align with - // other browser, we set the value to 2 if clicks events come in a 300ms - // sequence. - event.detail = 2; - } - this.lastClickTimeStamp = Date.now(); - this.context.controller!.onClick(this.context.tree, item.model.getElement(), event); } @@ -1569,39 +1528,6 @@ export class TreeView extends HeightMap { this._onDOMBlur.fire(); } - // MS specific DOM Events - - private onMsPointerDown(event: MSPointerEvent): void { - if (!this.msGesture) { - return; - } - - // Circumvent IE11 breaking change in e.pointerType & TypeScript's stale definitions - let pointerType = event.pointerType; - if (pointerType === ((event).MSPOINTER_TYPE_MOUSE || 'mouse')) { - this.lastPointerType = 'mouse'; - return; - } else if (pointerType === ((event).MSPOINTER_TYPE_TOUCH || 'touch')) { - this.lastPointerType = 'touch'; - } else { - return; - } - - event.stopPropagation(); - event.preventDefault(); - - this.msGesture.addPointer(event.pointerId); - } - - private onThrottledMsGestureChange(event: IThrottledGestureEvent): void { - this.scrollTop -= event.translationY; - } - - private onMsGestureTap(event: MSGestureEvent): void { - (event).initialTarget = document.elementFromPoint(event.clientX, event.clientY); - this.onTap(event); - } - // DOM changes private insertItemInDOM(item: ViewItem): void { diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index c075ae1727..b53ca0fa80 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -6,7 +6,9 @@ import * as assert from 'assert'; import * as marked from 'vs/base/common/marked/marked'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; -import { MarkdownString } from 'vs/base/common/htmlContent'; +import { MarkdownString, IMarkdownString } from 'vs/base/common/htmlContent'; +import { URI } from 'vs/base/common/uri'; +import { parse } from 'vs/base/common/marshalling'; suite('MarkdownRenderer', () => { suite('Images', () => { @@ -98,4 +100,21 @@ suite('MarkdownRenderer', () => { }); + test('npm Hover Run Script not working #90855', function () { + + const md: IMarkdownString = JSON.parse('{"value":"[Run Script](command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22fsPath%22%3A%22c%3A%5C%5CUsers%5C%5Cjrieken%5C%5CCode%5C%5C_sample%5C%5Cfoo%5C%5Cpackage.json%22%2C%22_sep%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22path%22%3A%22%2Fc%3A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22echo%22%7D \\"Run the script as a task\\")","supportThemeIcons":false,"isTrusted":true,"uris":{"__uri_e49443":{"$mid":1,"fsPath":"c:\\\\Users\\\\jrieken\\\\Code\\\\_sample\\\\foo\\\\package.json","_sep":1,"external":"file:///c%3A/Users/jrieken/Code/_sample/foo/package.json","path":"/c:/Users/jrieken/Code/_sample/foo/package.json","scheme":"file"},"command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22fsPath%22%3A%22c%3A%5C%5CUsers%5C%5Cjrieken%5C%5CCode%5C%5C_sample%5C%5Cfoo%5C%5Cpackage.json%22%2C%22_sep%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22path%22%3A%22%2Fc%3A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22echo%22%7D":{"$mid":1,"path":"npm.runScriptFromHover","scheme":"command","query":"{\\"documentUri\\":\\"__uri_e49443\\",\\"script\\":\\"echo\\"}"}}}'); + const element = renderMarkdown(md); + + const anchor = element.querySelector('a')!; + assert.ok(anchor); + assert.ok(anchor.dataset['href']); + + const uri = URI.parse(anchor.dataset['href']!); + + const data = <{ script: string, documentUri: URI }>parse(decodeURIComponent(uri.query)); + assert.ok(data); + assert.equal(data.script, 'echo'); + assert.ok(data.documentUri.toString().startsWith('file:///c%3A/')); + }); + }); diff --git a/src/vs/base/test/common/extpath.test.ts b/src/vs/base/test/common/extpath.test.ts index 67f9ebacee..cfce25ade1 100644 --- a/src/vs/base/test/common/extpath.test.ts +++ b/src/vs/base/test/common/extpath.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import * as extpath from 'vs/base/common/extpath'; import * as platform from 'vs/base/common/platform'; +import { CharCode } from 'vs/base/common/charCode'; suite('Paths', () => { @@ -114,4 +115,19 @@ suite('Paths', () => { assert.ok(!extpath.isRootOrDriveLetter('/path')); } }); + + test('isWindowsDriveLetter', () => { + assert.ok(!extpath.isWindowsDriveLetter(0)); + assert.ok(!extpath.isWindowsDriveLetter(-1)); + assert.ok(extpath.isWindowsDriveLetter(CharCode.A)); + assert.ok(extpath.isWindowsDriveLetter(CharCode.z)); + }); + + test('indexOfPath', () => { + assert.equal(extpath.indexOfPath('/foo', '/bar', true), -1); + assert.equal(extpath.indexOfPath('/foo', '/FOO', false), -1); + assert.equal(extpath.indexOfPath('/foo', '/FOO', true), 0); + assert.equal(extpath.indexOfPath('/some/long/path', '/some/long', false), 0); + assert.equal(extpath.indexOfPath('/some/long/path', '/PATH', true), 10); + }); }); diff --git a/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts b/src/vs/base/test/common/fuzzyScorer.test.ts similarity index 99% rename from src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts rename to src/vs/base/test/common/fuzzyScorer.test.ts index e97a02c35e..56e3ac6309 100644 --- a/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts +++ b/src/vs/base/test/common/fuzzyScorer.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as scorer from 'vs/base/parts/quickopen/common/quickOpenScorer'; +import * as scorer from 'vs/base/common/fuzzyScorer'; import { URI } from 'vs/base/common/uri'; import { basename, dirname, sep } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; @@ -49,14 +49,14 @@ function scoreItem(item: T, query: string, fuzzy: boolean, accessor: scorer.I return scorer.scoreItem(item, scorer.prepareQuery(query), fuzzy, accessor, cache); } -function compareItemsByScore(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor, cache: scorer.ScorerCache, fallbackComparer = scorer.fallbackCompare): number { - return scorer.compareItemsByScore(itemA, itemB, scorer.prepareQuery(query), fuzzy, accessor, cache, fallbackComparer); +function compareItemsByScore(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor, cache: scorer.ScorerCache, fallbackComparer?: (itemA: T, itemB: T, query: scorer.IPreparedQuery, accessor: scorer.IItemAccessor) => number): number { + return scorer.compareItemsByScore(itemA, itemB, scorer.prepareQuery(query), fuzzy, accessor, cache, fallbackComparer as any); } const NullAccessor = new NullAccessorClass(); let cache: scorer.ScorerCache = Object.create(null); -suite('Quick Open Scorer', () => { +suite('Fuzzy Scorer', () => { setup(() => { cache = Object.create(null); diff --git a/src/vs/base/test/common/iterator.test.ts b/src/vs/base/test/common/iterator.test.ts index 8f19312388..41fb8b43ec 100644 --- a/src/vs/base/test/common/iterator.test.ts +++ b/src/vs/base/test/common/iterator.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Iterator } from 'vs/base/common/iterator'; +import { Iterator, Iterable } from 'vs/base/common/iterator'; suite('Iterator', () => { test('concat', () => { @@ -16,4 +16,25 @@ suite('Iterator', () => { assert.deepEqual(actual, [1, 2, 3, 4, 5, 6, 7, 8, 9]); }); -}); \ No newline at end of file +}); + +suite('Iterable', function () { + + const customIterable = new class { + + *[Symbol.iterator]() { + yield 'one'; + yield 'two'; + yield 'three'; + } + }; + + test('first', function () { + + assert.equal(Iterable.first([]), undefined); + assert.equal(Iterable.first([1]), 1); + assert.equal(Iterable.first(customIterable), 'one'); + assert.equal(Iterable.first(customIterable), 'one'); // fresh + }); + +}); diff --git a/src/vs/base/test/common/linkedList.test.ts b/src/vs/base/test/common/linkedList.test.ts index cb24cee50d..08e413b202 100644 --- a/src/vs/base/test/common/linkedList.test.ts +++ b/src/vs/base/test/common/linkedList.test.ts @@ -16,9 +16,12 @@ suite('LinkedList', function () { // assert toArray assert.deepEqual(list.toArray(), elements); - // assert iterator - for (let iter = list.iterator(), element = iter.next(); !element.done; element = iter.next()) { - assert.equal(elements.shift(), element.value); + // assert Symbol.iterator (1) + assert.deepEqual([...list], elements); + + // assert Symbol.iterator (2) + for (const item of list) { + assert.equal(item, elements.shift()); } assert.equal(elements.length, 0); } diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 7d3fff89b5..fbc0bf5d2c 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -6,7 +6,6 @@ import { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache, mapToSerializable, serializableToMap } from 'vs/base/common/map'; import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { IteratorResult } from 'vs/base/common/iterator'; suite('Map', () => { diff --git a/src/vs/base/test/common/normalization.test.ts b/src/vs/base/test/common/normalization.test.ts new file mode 100644 index 0000000000..e263404a1e --- /dev/null +++ b/src/vs/base/test/common/normalization.test.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { removeAccents } from 'vs/base/common/normalization'; + +suite('Normalization', () => { + + test('removeAccents', function () { + assert.equal(removeAccents('joào'), 'joao'); + assert.equal(removeAccents('joáo'), 'joao'); + assert.equal(removeAccents('joâo'), 'joao'); + assert.equal(removeAccents('joäo'), 'joao'); + // assert.equal(strings.removeAccents('joæo'), 'joao'); // not an accent + assert.equal(removeAccents('joão'), 'joao'); + assert.equal(removeAccents('joåo'), 'joao'); + assert.equal(removeAccents('joåo'), 'joao'); + assert.equal(removeAccents('joāo'), 'joao'); + + assert.equal(removeAccents('fôo'), 'foo'); + assert.equal(removeAccents('föo'), 'foo'); + assert.equal(removeAccents('fòo'), 'foo'); + assert.equal(removeAccents('fóo'), 'foo'); + // assert.equal(strings.removeAccents('fœo'), 'foo'); + // assert.equal(strings.removeAccents('føo'), 'foo'); + assert.equal(removeAccents('fōo'), 'foo'); + assert.equal(removeAccents('fõo'), 'foo'); + + assert.equal(removeAccents('andrè'), 'andre'); + assert.equal(removeAccents('andré'), 'andre'); + assert.equal(removeAccents('andrê'), 'andre'); + assert.equal(removeAccents('andrë'), 'andre'); + assert.equal(removeAccents('andrē'), 'andre'); + assert.equal(removeAccents('andrė'), 'andre'); + assert.equal(removeAccents('andrę'), 'andre'); + + assert.equal(removeAccents('hvîc'), 'hvic'); + assert.equal(removeAccents('hvïc'), 'hvic'); + assert.equal(removeAccents('hvíc'), 'hvic'); + assert.equal(removeAccents('hvīc'), 'hvic'); + assert.equal(removeAccents('hvįc'), 'hvic'); + assert.equal(removeAccents('hvìc'), 'hvic'); + + assert.equal(removeAccents('ûdo'), 'udo'); + assert.equal(removeAccents('üdo'), 'udo'); + assert.equal(removeAccents('ùdo'), 'udo'); + assert.equal(removeAccents('údo'), 'udo'); + assert.equal(removeAccents('ūdo'), 'udo'); + + assert.equal(removeAccents('heÿ'), 'hey'); + + // assert.equal(strings.removeAccents('gruß'), 'grus'); + assert.equal(removeAccents('gruś'), 'grus'); + assert.equal(removeAccents('gruš'), 'grus'); + + assert.equal(removeAccents('çool'), 'cool'); + assert.equal(removeAccents('ćool'), 'cool'); + assert.equal(removeAccents('čool'), 'cool'); + + assert.equal(removeAccents('ñice'), 'nice'); + assert.equal(removeAccents('ńice'), 'nice'); + }); +}); diff --git a/src/vs/base/test/common/strings.test.ts b/src/vs/base/test/common/strings.test.ts index 640e4732b1..0c0ef6bf8e 100644 --- a/src/vs/base/test/common/strings.test.ts +++ b/src/vs/base/test/common/strings.test.ts @@ -92,15 +92,6 @@ suite('Strings', () => { assert.strictEqual(strings.format('Foo {0} Bar. {1}', '(foo)', '.test'), 'Foo (foo) Bar. .test'); }); - test('overlap', () => { - assert.equal(strings.overlap('foobar', 'arr, I am a priate'), 2); - assert.equal(strings.overlap('no', 'overlap'), 1); - assert.equal(strings.overlap('no', '0verlap'), 0); - assert.equal(strings.overlap('nothing', ''), 0); - assert.equal(strings.overlap('', 'nothing'), 0); - assert.equal(strings.overlap('full', 'full'), 4); - assert.equal(strings.overlap('full', 'fulloverlap'), 4); - }); test('lcut', () => { assert.strictEqual(strings.lcut('foo bar', 0), ''); assert.strictEqual(strings.lcut('foo bar', 1), 'bar'); @@ -404,61 +395,6 @@ suite('Strings', () => { assert.equal(strings.getNLines('foo', 0), ''); }); - test('removeAccents', function () { - assert.equal(strings.removeAccents('joào'), 'joao'); - assert.equal(strings.removeAccents('joáo'), 'joao'); - assert.equal(strings.removeAccents('joâo'), 'joao'); - assert.equal(strings.removeAccents('joäo'), 'joao'); - // assert.equal(strings.removeAccents('joæo'), 'joao'); // not an accent - assert.equal(strings.removeAccents('joão'), 'joao'); - assert.equal(strings.removeAccents('joåo'), 'joao'); - assert.equal(strings.removeAccents('joåo'), 'joao'); - assert.equal(strings.removeAccents('joāo'), 'joao'); - - assert.equal(strings.removeAccents('fôo'), 'foo'); - assert.equal(strings.removeAccents('föo'), 'foo'); - assert.equal(strings.removeAccents('fòo'), 'foo'); - assert.equal(strings.removeAccents('fóo'), 'foo'); - // assert.equal(strings.removeAccents('fœo'), 'foo'); - // assert.equal(strings.removeAccents('føo'), 'foo'); - assert.equal(strings.removeAccents('fōo'), 'foo'); - assert.equal(strings.removeAccents('fõo'), 'foo'); - - assert.equal(strings.removeAccents('andrè'), 'andre'); - assert.equal(strings.removeAccents('andré'), 'andre'); - assert.equal(strings.removeAccents('andrê'), 'andre'); - assert.equal(strings.removeAccents('andrë'), 'andre'); - assert.equal(strings.removeAccents('andrē'), 'andre'); - assert.equal(strings.removeAccents('andrė'), 'andre'); - assert.equal(strings.removeAccents('andrę'), 'andre'); - - assert.equal(strings.removeAccents('hvîc'), 'hvic'); - assert.equal(strings.removeAccents('hvïc'), 'hvic'); - assert.equal(strings.removeAccents('hvíc'), 'hvic'); - assert.equal(strings.removeAccents('hvīc'), 'hvic'); - assert.equal(strings.removeAccents('hvįc'), 'hvic'); - assert.equal(strings.removeAccents('hvìc'), 'hvic'); - - assert.equal(strings.removeAccents('ûdo'), 'udo'); - assert.equal(strings.removeAccents('üdo'), 'udo'); - assert.equal(strings.removeAccents('ùdo'), 'udo'); - assert.equal(strings.removeAccents('údo'), 'udo'); - assert.equal(strings.removeAccents('ūdo'), 'udo'); - - assert.equal(strings.removeAccents('heÿ'), 'hey'); - - // assert.equal(strings.removeAccents('gruß'), 'grus'); - assert.equal(strings.removeAccents('gruś'), 'grus'); - assert.equal(strings.removeAccents('gruš'), 'grus'); - - assert.equal(strings.removeAccents('çool'), 'cool'); - assert.equal(strings.removeAccents('ćool'), 'cool'); - assert.equal(strings.removeAccents('čool'), 'cool'); - - assert.equal(strings.removeAccents('ñice'), 'nice'); - assert.equal(strings.removeAccents('ńice'), 'nice'); - }); - test('encodeUTF8', function () { function assertEncodeUTF8(str: string, expected: number[]): void { const actual = strings.encodeUTF8(str); diff --git a/src/vs/base/test/common/uuid.test.ts b/src/vs/base/test/common/uuid.test.ts index 37b7f832b3..6870813f36 100644 --- a/src/vs/base/test/common/uuid.test.ts +++ b/src/vs/base/test/common/uuid.test.ts @@ -7,16 +7,17 @@ import * as uuid from 'vs/base/common/uuid'; suite('UUID', () => { test('generation', () => { - const asHex = uuid.v4().asHex(); + const asHex = uuid.generateUuid(); assert.equal(asHex.length, 36); assert.equal(asHex[14], '4'); assert.ok(asHex[19] === '8' || asHex[19] === '9' || asHex[19] === 'a' || asHex[19] === 'b'); }); - test('parse', () => { - const id = uuid.v4(); - const asHext = id.asHex(); - const id2 = uuid.parse(asHext); - assert.equal(id.asHex(), id2.asHex()); + test('self-check', function () { + const t1 = Date.now(); + while (Date.now() - t1 < 50) { + const value = uuid.generateUuid(); + assert.ok(uuid.isUUID(value)); + } }); }); diff --git a/src/vs/base/test/node/glob.test.ts b/src/vs/base/test/node/glob.test.ts index 0570e9a21c..72c8f23ff8 100644 --- a/src/vs/base/test/node/glob.test.ts +++ b/src/vs/base/test/node/glob.test.ts @@ -239,10 +239,7 @@ suite('Glob', () => { assertGlobMatch(p, 'some/folder/project.json'); assertNoGlobMatch(p, 'some/folder/file_project.json'); assertNoGlobMatch(p, 'some/folder/fileproject.json'); - // assertNoGlobMatch(p, '/rrproject.json'); TODO@ben this still fails if T1-3 are disabled assertNoGlobMatch(p, 'some/rrproject.json'); - // assertNoGlobMatch(p, 'rrproject.json'); - // assertNoGlobMatch(p, '\\rrproject.json'); assertNoGlobMatch(p, 'some\\rrproject.json'); p = 'test/**'; diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 478f3f26d1..8f134cac5a 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -39,7 +39,7 @@ import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry import { combinedAppender, LogAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; -import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; const MAX_URL_LENGTH = 2045; @@ -49,7 +49,7 @@ interface SearchResult { state?: string; } -export interface IssueReporterConfiguration extends IWindowConfiguration { +export interface IssueReporterConfiguration extends INativeWindowConfiguration { data: IssueReporterData; features: IssueReporterFeatures; } @@ -81,6 +81,8 @@ export class IssueReporter extends Disposable { this.initServices(configuration); const isSnap = process.platform === 'linux' && process.env.SNAP && process.env.SNAP_REVISION; + + const targetExtension = configuration.data.extensionId ? configuration.data.enabledExtensions.find(extension => extension.id === configuration.data.extensionId) : undefined; this.issueReporterModel = new IssueReporterModel({ issueType: configuration.data.issueType || IssueType.Bug, versionInfo: { @@ -88,8 +90,8 @@ export class IssueReporter extends Disposable { os: `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}` }, extensionsDisabled: !!this.environmentService.disableExtensions, - fileOnExtension: configuration.data.extensionId ? true : undefined, - selectedExtension: configuration.data.extensionId ? configuration.data.enabledExtensions.filter(extension => extension.id === configuration.data.extensionId)[0] : undefined + fileOnExtension: configuration.data.extensionId ? !targetExtension?.isBuiltin : undefined, + selectedExtension: targetExtension, }); const issueReporterElement = this.getElementById('issue-reporter'); @@ -260,19 +262,20 @@ export class IssueReporter extends Disposable { } private handleExtensionData(extensions: IssueReporterExtensionData[]) { - const { nonThemes, themes } = collections.groupBy(extensions, ext => { + const installedExtensions = extensions.filter(x => !x.isBuiltin); + const { nonThemes, themes } = collections.groupBy(installedExtensions, ext => { return ext.isTheme ? 'themes' : 'nonThemes'; }); const numberOfThemeExtesions = themes && themes.length; - this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: extensions }); + this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: installedExtensions }); this.updateExtensionTable(nonThemes, numberOfThemeExtesions); - if (this.environmentService.disableExtensions || extensions.length === 0) { + if (this.environmentService.disableExtensions || installedExtensions.length === 0) { (this.getElementById('disableExtensions')).disabled = true; } - this.updateExtensionSelector(extensions); + this.updateExtensionSelector(installedExtensions); } private handleSettingsSearchData(data: ISettingsSearchIssueReporterData): void { @@ -316,7 +319,7 @@ export class IssueReporter extends Disposable { } } - private initServices(configuration: IWindowConfiguration): void { + private initServices(configuration: INativeWindowConfiguration): void { const serviceCollection = new ServiceCollection(); const mainProcessService = new MainProcessService(configuration.windowId); serviceCollection.set(IMainProcessService, mainProcessService); @@ -749,10 +752,14 @@ export class IssueReporter extends Disposable { private setSourceOptions(): void { const sourceSelect = this.getElementById('issue-source')! as HTMLSelectElement; - const { issueType, fileOnExtension } = this.issueReporterModel.getData(); + const { issueType, fileOnExtension, selectedExtension } = this.issueReporterModel.getData(); let selected = sourceSelect.selectedIndex; - if (selected === -1 && fileOnExtension !== undefined) { - selected = fileOnExtension ? 2 : 1; + if (selected === -1) { + if (fileOnExtension !== undefined) { + selected = fileOnExtension ? 2 : 1; + } else if (selectedExtension?.isBuiltin) { + selected = 1; + } } sourceSelect.innerHTML = ''; diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 99d2fc52a4..4b88f6a784 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -49,10 +49,10 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel, UserDataAutoSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; +import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel, UserDataAutoSyncChannel, UserDataSyncStoreServiceChannel, UserDataSyncBackupStoreServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { LoggerService } from 'vs/platform/log/node/loggerService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; @@ -67,6 +67,7 @@ import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagemen import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService'; import { IAuthenticationTokenService, AuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import { AuthenticationTokenServiceChannel } from 'vs/platform/authentication/common/authenticationIpc'; +import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -194,6 +195,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main'))); services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService)); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); + services.set(IUserDataSyncBackupStoreService, new SyncDescriptor(UserDataSyncBackupStoreService)); services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService)); services.set(ISettingsSyncService, new SyncDescriptor(SettingsSynchroniser)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); @@ -219,6 +221,14 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const authTokenChannel = new AuthenticationTokenServiceChannel(authTokenService); server.registerChannel('authToken', authTokenChannel); + const userDataSyncStoreService = accessor.get(IUserDataSyncStoreService); + const userDataSyncStoreServiceChannel = new UserDataSyncStoreServiceChannel(userDataSyncStoreService); + server.registerChannel('userDataSyncStoreService', userDataSyncStoreServiceChannel); + + const userDataSyncBackupStoreService = accessor.get(IUserDataSyncBackupStoreService); + const userDataSyncBackupStoreServiceChannel = new UserDataSyncBackupStoreServiceChannel(userDataSyncBackupStoreService); + server.registerChannel('userDataSyncBackupStoreService', userDataSyncBackupStoreServiceChannel); + const settingsSyncService = accessor.get(ISettingsSyncService); const settingsSyncChannel = new SettingsSyncChannel(settingsSyncService); server.registerChannel('settingsSync', settingsSyncChannel); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 2fa3645cb2..69e58428d6 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -26,7 +26,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IURLService } from 'vs/platform/url/common/url'; import { URLHandlerChannelClient, URLHandlerRouter } from 'vs/platform/url/common/urlIpc'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService, machineIdKey, trueMachineIdKey } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; @@ -79,10 +79,6 @@ import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { coalesce } from 'vs/base/common/arrays'; export class CodeApplication extends Disposable { - - private static readonly MACHINE_ID_KEY = 'telemetry.machineId'; - private static readonly TRUE_MACHINE_ID_KEY = 'telemetry.trueMachineId'; - private windowsMainService: IWindowsMainService | undefined; private dialogMainService: IDialogMainService | undefined; @@ -133,7 +129,7 @@ export class CodeApplication extends Disposable { // // !!! DO NOT CHANGE without consulting the documentation !!! // - // app.on('remote-get-guest-web-contents', event => event.preventDefault()); // TODO@Ben TODO@Matt revisit this need for + // app.on('remote-get-guest-web-contents', event => event.preventDefault()); // TODO@Matt revisit this need for app.on('remote-require', (event, sender, module) => { this.logService.trace('App#on(remote-require): prevented'); @@ -407,21 +403,21 @@ export class CodeApplication extends Disposable { // We cache the machineId for faster lookups on startup // and resolve it only once initially if not cached - let machineId = this.stateService.getItem(CodeApplication.MACHINE_ID_KEY); + let machineId = this.stateService.getItem(machineIdKey); if (!machineId) { machineId = await getMachineId(); - this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId); + this.stateService.setItem(machineIdKey, machineId); } // Check if machineId is hashed iBridge Device let trueMachineId: string | undefined; if (isMacintosh && machineId === '6c9d2bc8f91b89624add29c0abeae7fb42bf539fa1cdb2e3e57cd668fa9bcead') { - trueMachineId = this.stateService.getItem(CodeApplication.TRUE_MACHINE_ID_KEY); + trueMachineId = this.stateService.getItem(trueMachineIdKey); if (!trueMachineId) { trueMachineId = await getMachineId(); - this.stateService.setItem(CodeApplication.TRUE_MACHINE_ID_KEY, trueMachineId); + this.stateService.setItem(trueMachineIdKey, trueMachineId); } } @@ -511,7 +507,7 @@ export class CodeApplication extends Disposable { type: 'info', message: localize('trace.message', "Successfully created trace."), detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), - buttons: [localize('trace.ok', "Ok")] + buttons: [localize('trace.ok', "OK")] }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); } } else { diff --git a/src/vs/code/electron-main/auth.ts b/src/vs/code/electron-main/auth.ts index 924c649040..0795c701e0 100644 --- a/src/vs/code/electron-main/auth.ts +++ b/src/vs/code/electron-main/auth.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; -import { BrowserWindow, app, AuthInfo, WebContents, Event as ElectronEvent } from 'electron'; +import { BrowserWindow, BrowserWindowConstructorOptions, app, AuthInfo, WebContents, Event as ElectronEvent } from 'electron'; type LoginEvent = { event: ElectronEvent; @@ -49,7 +49,7 @@ export class ProxyAuthHandler extends Disposable { event.preventDefault(); - const opts: any = { + const opts: BrowserWindowConstructorOptions = { alwaysOnTop: true, skipTaskbar: true, resizable: false, diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 195db53e8c..541371e832 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -276,7 +276,7 @@ class CodeMain { // Skip this if we are running with --wait where it is expected that we wait for a while. // Also skip when gathering diagnostics (--status) which can take a longer time. let startupWarningDialogHandle: NodeJS.Timeout | undefined = undefined; - if (!environmentService.wait && !environmentService.status) { + if (!environmentService.args.wait && !environmentService.args.status) { startupWarningDialogHandle = setTimeout(() => { this.showStartupWarningDialog( localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort), diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index d21e0c30cc..985fb8733d 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -14,10 +14,11 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import product from 'vs/platform/product/common/product'; -import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; +import { IWindowSettings, MenuBarVisibility, ReadyState, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; +import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; @@ -85,7 +86,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[]; - private pendingLoadConfig?: IWindowConfiguration; + private pendingLoadConfig?: INativeWindowConfiguration; private marketplaceHeadersPromise: Promise; @@ -231,8 +232,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.registerListeners(); } - private currentConfig: IWindowConfiguration | undefined; - get config(): IWindowConfiguration | undefined { return this.currentConfig; } + private currentConfig: INativeWindowConfiguration | undefined; + get config(): INativeWindowConfiguration | undefined { return this.currentConfig; } private _id: number; get id(): number { return this._id; } @@ -391,6 +392,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.setFullScreen(false); this.setFullScreen(true); } + + this.sendWhenReady('vscode:displayChanged'); }, 100)); const displayChangedListener = () => simpleFullScreenScheduler.schedule(); @@ -552,7 +555,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } - load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void { + load(config: INativeWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void { // If this is the first time the window is loaded, we associate the paths // directly with the window because we assume the loading will just work @@ -612,7 +615,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._onLoad.fire(); } - reload(configurationIn?: IWindowConfiguration, cli?: ParsedArgs): void { + reload(configurationIn?: INativeWindowConfiguration, cli?: ParsedArgs): void { // If config is not provided, copy our current one const configuration = configurationIn ? configurationIn : objects.mixin({}, this.currentConfig); @@ -639,7 +642,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.load(configuration, true, disableExtensions); } - private getUrl(windowConfiguration: IWindowConfiguration): string { + private getUrl(windowConfiguration: INativeWindowConfiguration): string { // Set window ID windowConfiguration.windowId = this._win.id; diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 2102f19fe4..c4f87715d6 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -128,7 +128,7 @@ export async function main(argv: string[]): Promise { delete env['ELECTRON_RUN_AS_NODE']; - const processCallbacks: ((child: ChildProcess) => Promise)[] = []; + const processCallbacks: ((child: ChildProcess) => Promise)[] = []; const verbose = args.verbose || args.status; if (verbose) { diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index 2b5be267ff..ea00a071b5 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -856,18 +856,13 @@ export namespace CoreNavigationCommands { } })); - export const CursorLineStart: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { - constructor() { - super({ - id: 'cursorLineStart', - precondition: undefined, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textInputFocus, - primary: 0, - mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_A } - } - }); + class LineStartCommand extends CoreEditorCommand { + + private readonly _inSelectionMode: boolean; + + constructor(opts: ICommandOptions & { inSelectionMode: boolean; }) { + super(opts); + this._inSelectionMode = opts.inSelectionMode; } public runCoreEditorCommand(cursors: ICursors, args: any): void { @@ -885,11 +880,35 @@ export namespace CoreNavigationCommands { for (let i = 0, len = cursors.length; i < len; i++) { const cursor = cursors[i]; const lineNumber = cursor.modelState.position.lineNumber; - result[i] = CursorState.fromModelState(cursor.modelState.move(false, lineNumber, 1, 0)); + result[i] = CursorState.fromModelState(cursor.modelState.move(this._inSelectionMode, lineNumber, 1, 0)); } return result; } - }); + } + + export const CursorLineStart: CoreEditorCommand = registerEditorCommand(new LineStartCommand({ + inSelectionMode: false, + id: 'cursorLineStart', + precondition: undefined, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + primary: 0, + mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_A } + } + })); + + export const CursorLineStartSelect: CoreEditorCommand = registerEditorCommand(new LineStartCommand({ + inSelectionMode: true, + id: 'cursorLineStartSelect', + precondition: undefined, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + primary: 0, + mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_A } + } + })); class EndCommand extends CoreEditorCommand { @@ -935,18 +954,13 @@ export namespace CoreNavigationCommands { } })); - export const CursorLineEnd: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { - constructor() { - super({ - id: 'cursorLineEnd', - precondition: undefined, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textInputFocus, - primary: 0, - mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_E } - } - }); + class LineEndCommand extends CoreEditorCommand { + + private readonly _inSelectionMode: boolean; + + constructor(opts: ICommandOptions & { inSelectionMode: boolean; }) { + super(opts); + this._inSelectionMode = opts.inSelectionMode; } public runCoreEditorCommand(cursors: ICursors, args: any): void { @@ -965,11 +979,35 @@ export namespace CoreNavigationCommands { const cursor = cursors[i]; const lineNumber = cursor.modelState.position.lineNumber; const maxColumn = context.model.getLineMaxColumn(lineNumber); - result[i] = CursorState.fromModelState(cursor.modelState.move(false, lineNumber, maxColumn, 0)); + result[i] = CursorState.fromModelState(cursor.modelState.move(this._inSelectionMode, lineNumber, maxColumn, 0)); } return result; } - }); + } + + export const CursorLineEnd: CoreEditorCommand = registerEditorCommand(new LineEndCommand({ + inSelectionMode: false, + id: 'cursorLineEnd', + precondition: undefined, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + primary: 0, + mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_E } + } + })); + + export const CursorLineEndSelect: CoreEditorCommand = registerEditorCommand(new LineEndCommand({ + inSelectionMode: true, + id: 'cursorLineEndSelect', + precondition: undefined, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + primary: 0, + mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_E } + } + })); class TopCommand extends CoreEditorCommand { diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 244da5f662..878c9ddb24 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -6,7 +6,7 @@ import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { StandardWheelEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; -import { RunOnceScheduler, TimeoutTimer } from 'vs/base/common/async'; +import { TimeoutTimer } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { HitTestContext, IViewZoneData, MouseTarget, MouseTargetFactory, PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; @@ -69,7 +69,6 @@ export class MouseHandler extends ViewEventHandler { protected viewController: ViewController; protected viewHelper: IPointerHandlerHelper; protected mouseTargetFactory: MouseTargetFactory; - private readonly _asyncFocus: RunOnceScheduler; protected readonly _mouseDownOperation: MouseDownOperation; private lastMouseLeaveTime: number; @@ -89,8 +88,6 @@ export class MouseHandler extends ViewEventHandler { (e) => this._getMouseColumn(e) )); - this._asyncFocus = this._register(new RunOnceScheduler(() => this.viewHelper.focusTextArea(), 0)); - this.lastMouseLeaveTime = -1; const mouseEvents = new EditorMouseEventFactory(this.viewHelper.viewDomNode); @@ -122,7 +119,7 @@ export class MouseHandler extends ViewEventHandler { e.stopPropagation(); } }; - this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, browser.isEdgeOrIE ? 'mousewheel' : 'wheel', onMouseWheel, { capture: true, passive: false })); + this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, browser.isEdge ? 'mousewheel' : 'wheel', onMouseWheel, { capture: true, passive: false })); this._context.addEventHandler(this); } @@ -137,9 +134,7 @@ export class MouseHandler extends ViewEventHandler { this._mouseDownOperation.onCursorStateChanged(e); return false; } - private _isFocused = false; public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean { - this._isFocused = e.isFocused; return false; } public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { @@ -223,15 +218,8 @@ export class MouseHandler extends ViewEventHandler { } const focus = () => { - // In IE11, if the focus is in the browser's address bar and - // then you click in the editor, calling preventDefault() - // will not move focus properly (focus remains the address bar) - if (browser.isIE && !this._isFocused) { - this._asyncFocus.schedule(); - } else { - e.preventDefault(); - this.viewHelper.focusTextArea(); - } + e.preventDefault(); + this.viewHelper.focusTextArea(); }; if (shouldHandle && (targetIsContent || (targetIsLineNumbers && selectOnLineNumbers))) { diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 73e67f1852..5b2e5182a4 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -925,6 +925,23 @@ export class MouseTargetFactory { } } + // For inline decorations, Gecko returns the `` of the line and the offset is the `` with the inline decoration + if (hitResult.offsetNode.nodeType === hitResult.offsetNode.ELEMENT_NODE) { + const parent1 = hitResult.offsetNode.parentNode; // expected to be the view line div + const parent1ClassName = parent1 && parent1.nodeType === parent1.ELEMENT_NODE ? (parent1).className : null; + + if (parent1ClassName === ViewLine.CLASS_NAME) { + const tokenSpan = hitResult.offsetNode.childNodes[Math.min(hitResult.offset, hitResult.offsetNode.childNodes.length - 1)]; + if (tokenSpan) { + const p = ctx.getPositionFromDOMInfo(tokenSpan, 0); + return { + position: p, + hitTarget: null + }; + } + } + } + return { position: null, hitTarget: hitResult.offsetNode diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index c17496f940..3bb7a9ef74 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -53,7 +53,7 @@ class VisibleTextAreaData { } } -const canUseZeroSizeTextarea = (browser.isEdgeOrIE || browser.isFirefox); +const canUseZeroSizeTextarea = (browser.isEdge || browser.isFirefox); export class TextAreaHandler extends ViewPart { @@ -252,13 +252,14 @@ export class TextAreaHandler extends ViewPart { this._viewController.setSelection('keyboard', modelSelection); })); - this._register(this._textAreaInput.onCompositionStart(() => { + this._register(this._textAreaInput.onCompositionStart((e) => { const lineNumber = this._selections[0].startLineNumber; - const column = this._selections[0].startColumn; + const column = this._selections[0].startColumn - (e.moveOneCharacterLeft ? 1 : 0); this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent( 'keyboard', new Range(lineNumber, column, lineNumber, column), + null, viewEvents.VerticalRevealType.Simple, true, ScrollType.Immediate @@ -283,7 +284,7 @@ export class TextAreaHandler extends ViewPart { })); this._register(this._textAreaInput.onCompositionUpdate((e: ICompositionData) => { - if (browser.isEdgeOrIE) { + if (browser.isEdge) { // Due to isEdgeOrIE (where the textarea was not cleared initially) // we cannot assume the text consists only of the composited text this._visibleTextArea = this._visibleTextArea!.setWidth(0); @@ -357,9 +358,9 @@ export class TextAreaHandler extends ViewPart { this._accessibilitySupport = options.get(EditorOption.accessibilitySupport); const accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); if (this._accessibilitySupport === AccessibilitySupport.Enabled && accessibilityPageSize === EditorOptions.accessibilityPageSize.defaultValue) { - // If a screen reader is attached and the default value is not set we shuold automatically increase the page size to 160 for a better experience - // If we put more than 160 lines the nvda can not handle this https://github.com/microsoft/vscode/issues/89717 - this._accessibilityPageSize = 160; + // If a screen reader is attached and the default value is not set we shuold automatically increase the page size to 100 for a better experience + // If we put more than 100 lines the nvda can not handle this https://github.com/microsoft/vscode/issues/89717 + this._accessibilityPageSize = 100; } else { this._accessibilityPageSize = accessibilityPageSize; } diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 6903e61dfa..29549e0180 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -95,6 +95,10 @@ class InMemoryClipboardMetadataManager { } } +export interface ICompositionStartEvent { + moveOneCharacterLeft: boolean; +} + /** * Writes screen reader content to the textarea and is able to analyze its input events to generate: * - onCut @@ -126,8 +130,8 @@ export class TextAreaInput extends Disposable { private _onType = this._register(new Emitter()); public readonly onType: Event = this._onType.event; - private _onCompositionStart = this._register(new Emitter()); - public readonly onCompositionStart: Event = this._onCompositionStart.event; + private _onCompositionStart = this._register(new Emitter()); + public readonly onCompositionStart: Event = this._onCompositionStart.event; private _onCompositionUpdate = this._register(new Emitter()); public readonly onCompositionUpdate: Event = this._onCompositionUpdate.event; @@ -165,9 +169,11 @@ export class TextAreaInput extends Disposable { this._isDoingComposition = false; this._nextCommand = ReadFromTextArea.Type; + let lastKeyDown: IKeyboardEvent | null = null; + this._register(dom.addStandardDisposableListener(textArea.domNode, 'keydown', (e: IKeyboardEvent) => { - if (this._isDoingComposition && - (e.keyCode === KeyCode.KEY_IN_COMPOSITION || e.keyCode === KeyCode.Backspace)) { + if (e.keyCode === KeyCode.KEY_IN_COMPOSITION + || (this._isDoingComposition && e.keyCode === KeyCode.Backspace)) { // Stop propagation for keyDown events if the IME is processing key input e.stopPropagation(); } @@ -177,6 +183,8 @@ export class TextAreaInput extends Disposable { // See https://msdn.microsoft.com/en-us/library/ie/ms536939(v=vs.85).aspx e.preventDefault(); } + + lastKeyDown = e; this._onKeyDown.fire(e); })); @@ -190,12 +198,35 @@ export class TextAreaInput extends Disposable { } this._isDoingComposition = true; - // In IE we cannot set .value when handling 'compositionstart' because the entire composition will get canceled. - if (!browser.isEdgeOrIE) { + let moveOneCharacterLeft = false; + if ( + platform.isMacintosh + && lastKeyDown + && lastKeyDown.equals(KeyCode.KEY_IN_COMPOSITION) + && this._textAreaState.selectionStart === this._textAreaState.selectionEnd + && this._textAreaState.selectionStart > 0 + && this._textAreaState.value.substr(this._textAreaState.selectionStart - 1, 1) === e.data + ) { + // Handling long press case on macOS + arrow key => pretend the character was selected + if (lastKeyDown.code === 'ArrowRight' || lastKeyDown.code === 'ArrowLeft') { + moveOneCharacterLeft = true; + } + } + + if (moveOneCharacterLeft) { + this._textAreaState = new TextAreaState( + this._textAreaState.value, + this._textAreaState.selectionStart - 1, + this._textAreaState.selectionEnd, + this._textAreaState.selectionStartPosition ? new Position(this._textAreaState.selectionStartPosition.lineNumber, this._textAreaState.selectionStartPosition.column - 1) : null, + this._textAreaState.selectionEndPosition + ); + } else if (!browser.isEdge) { + // In IE we cannot set .value when handling 'compositionstart' because the entire composition will get canceled. this._setAndWriteTextAreaState('compositionstart', TextAreaState.EMPTY); } - this._onCompositionStart.fire(); + this._onCompositionStart.fire({ moveOneCharacterLeft }); })); /** @@ -225,15 +256,7 @@ export class TextAreaInput extends Disposable { // Multi-part Japanese compositions reset cursor in Edge/IE, Chinese and Korean IME don't have this issue. // The reason that we can't use this path for all CJK IME is IE and Edge behave differently when handling Korean IME, // which breaks this path of code. - if (browser.isEdgeOrIE && locale === 'ja') { - return true; - } - - // https://github.com/Microsoft/monaco-editor/issues/545 - // On IE11, we can't trust composition data when typing Chinese as IE11 doesn't emit correct - // events when users type numbers in IME. - // Chinese: zh-Hans-CN, zh-Hans-SG, zh-Hant-TW, zh-Hant-HK - if (browser.isIE && locale.indexOf('zh-Han') === 0) { + if (browser.isEdge && locale === 'ja') { return true; } @@ -274,7 +297,7 @@ export class TextAreaInput extends Disposable { // Due to isEdgeOrIE (where the textarea was not cleared initially) and isChrome (the textarea is not updated correctly when composition ends) // we cannot assume the text at the end consists only of the composited text - if (browser.isEdgeOrIE || browser.isChrome) { + if (browser.isEdge || browser.isChrome) { this._textAreaState = TextAreaState.readFromTextArea(this._textArea); } diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index d95fcd3644..369dfc4823 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -13,7 +13,7 @@ import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, ICursorStateComputer } from 'vs/editor/common/model'; +import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, ICursorStateComputer, IWordAtPosition } from 'vs/editor/common/model'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager'; import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout'; @@ -567,6 +567,11 @@ export interface ICodeEditor extends editorCommon.IEditor { */ getRawOptions(): IEditorOptions; + /** + * @internal + */ + getConfiguredWordAtPosition(position: Position): IWordAtPosition | null; + /** * Get value of the current model attached to this editor. * @see `ITextModel.getValue` diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 4361d2eb8e..37503ccbb2 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -15,7 +15,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IConstructorSignature1, ServicesAccessor as InstantiationServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -39,26 +39,26 @@ export interface IDiffEditorContributionDescription { //#region Command export interface ICommandKeybindingsOptions extends IKeybindings { - kbExpr?: ContextKeyExpr | null; + kbExpr?: ContextKeyExpression | null; weight: number; } export interface ICommandMenuOptions { menuId: MenuId; group: string; order: number; - when?: ContextKeyExpr; + when?: ContextKeyExpression; title: string; } export interface ICommandOptions { id: string; - precondition: ContextKeyExpr | undefined; + precondition: ContextKeyExpression | undefined; kbOpts?: ICommandKeybindingsOptions; description?: ICommandHandlerDescription; menuOpts?: ICommandMenuOptions | ICommandMenuOptions[]; } export abstract class Command { public readonly id: string; - public readonly precondition: ContextKeyExpr | undefined; + public readonly precondition: ContextKeyExpression | undefined; private readonly _kbOpts: ICommandKeybindingsOptions | undefined; private readonly _menuOpts: ICommandMenuOptions | ICommandMenuOptions[] | undefined; private readonly _description: ICommandHandlerDescription | undefined; @@ -193,7 +193,7 @@ export abstract class EditorCommand extends Command { export interface IEditorActionContextMenuOptions { group: string; order: number; - when?: ContextKeyExpr; + when?: ContextKeyExpression; menuId?: MenuId; } export interface IActionOptions extends ICommandOptions { @@ -347,7 +347,7 @@ export function registerModelCommand(id: string, handler: (model: ITextModel, .. const model = accessor.get(IModelService).getModel(resource); if (model) { - return handler(model, args.slice(1)); + return handler(model, ...args.slice(1)); } return accessor.get(ITextModelService).createModelReference(resource).then(reference => { diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index 5d10b96410..248ea82728 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -9,7 +9,7 @@ import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; export abstract class AbstractCodeEditorService extends Disposable implements ICodeEditorService { @@ -135,7 +135,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC } abstract getActiveCodeEditor(): ICodeEditor | null; - abstract openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; + abstract openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; } export class ModelTransientSettingWatcher { diff --git a/src/vs/editor/browser/services/codeEditorService.ts b/src/vs/editor/browser/services/codeEditorService.ts index 611a6a3480..cb94babd29 100644 --- a/src/vs/editor/browser/services/codeEditorService.ts +++ b/src/vs/editor/browser/services/codeEditorService.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export const ICodeEditorService = createDecorator('codeEditorService'); @@ -46,5 +46,5 @@ export interface ICodeEditorService { getTransientModelProperties(model: ITextModel): [string, any][] | undefined; getActiveCodeEditor(): ICodeEditor | null; - openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; + openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; } diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index 38029fdbba..805fb5e64f 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -11,8 +11,8 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { AbstractCodeEditorService } from 'vs/editor/browser/services/abstractCodeEditorService'; import { IContentDecorationRenderOptions, IDecorationRenderOptions, IThemeDecorationRenderOptions, isThemeColor } from 'vs/editor/common/editorCommon'; import { IModelDecorationOptions, IModelDecorationOverviewRulerOptions, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; -import { ITheme, IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { IColorTheme, IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService'; class RefCountedStyleSheet { @@ -136,7 +136,7 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { } abstract getActiveCodeEditor(): ICodeEditor | null; - abstract openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; + abstract openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; } interface IModelDecorationOptionsProvider extends IDisposable { @@ -320,7 +320,7 @@ const _CSS_MAP: { [prop: string]: string; } = { class DecorationCSSRules { - private _theme: ITheme; + private _theme: IColorTheme; private readonly _className: string; private readonly _unThemedSelector: string; private _hasContent: boolean; @@ -331,7 +331,7 @@ class DecorationCSSRules { private _usesThemeColors: boolean; public constructor(ruleType: ModelDecorationCSSRuleType, providerArgs: ProviderArguments, themeService: IThemeService) { - this._theme = themeService.getTheme(); + this._theme = themeService.getColorTheme(); this._ruleType = ruleType; this._providerArgs = providerArgs; this._usesThemeColors = false; @@ -349,8 +349,8 @@ class DecorationCSSRules { this._buildCSS(); if (this._usesThemeColors) { - this._themeListener = themeService.onThemeChange(theme => { - this._theme = themeService.getTheme(); + this._themeListener = themeService.onDidColorThemeChange(theme => { + this._theme = themeService.getColorTheme(); this._removeCSS(); this._buildCSS(); }); diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index 16cffd848d..6431ca6f16 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -11,7 +11,7 @@ import { Schemas } from 'vs/base/common/network'; import { normalizePath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions, ResolveExternalUriOptions, IResolvedExternalUri, IExternalOpener, matchesScheme } from 'vs/platform/opener/common/opener'; import { EditorOpenContext } from 'vs/platform/editor/common/editor'; @@ -28,9 +28,6 @@ class CommandOpener implements IOpener { if (typeof target === 'string') { target = URI.parse(target); } - if (!CommandsRegistry.getCommand(target.path)) { - throw new Error(`command '${target.path}' NOT known`); - } // execute as command let args: any = []; try { diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 0330103d59..95bbba63c9 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -115,9 +115,9 @@ export class View extends ViewEventHandler { this.eventDispatcher.addEventHandler(this); // The view context is passed on to most classes (basically to reduce param. counts in ctors) - this._context = new ViewContext(configuration, themeService.getTheme(), model, this.eventDispatcher); + this._context = new ViewContext(configuration, themeService.getColorTheme(), model, this.eventDispatcher); - this._register(themeService.onThemeChange(theme => { + this._register(themeService.onDidColorThemeChange(theme => { this._context.theme.update(theme); this.eventDispatcher.emit(new viewEvents.ViewThemeChangedEvent()); this.render(true, false); diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 0fbd8d8038..5aee084792 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -42,7 +42,9 @@ const canUseFastRenderedViewLine = (function () { return true; })(); -const alwaysRenderInlineSelection = (browser.isEdgeOrIE); +let monospaceAssumptionsAreValid = true; + +const alwaysRenderInlineSelection = (browser.isEdge); export class DomReadingContext { @@ -248,7 +250,7 @@ export class ViewLine implements IVisibleLine { sb.appendASCIIString(''); let renderedViewLine: IRenderedViewLine | null = null; - if (canUseFastRenderedViewLine && lineData.isBasicASCII && options.useMonospaceOptimizations && output.containsForeignElements === ForeignElementType.None) { + if (monospaceAssumptionsAreValid && canUseFastRenderedViewLine && lineData.isBasicASCII && options.useMonospaceOptimizations && output.containsForeignElements === ForeignElementType.None) { if (lineData.content.length < 300 && renderLineInput.lineTokens.getCount() < 100) { // Browser rounding errors have been observed in Chrome and IE, so using the fast // view line only for short lines. Please test before removing the length check... @@ -304,6 +306,29 @@ export class ViewLine implements IVisibleLine { return this._renderedViewLine.getWidthIsFast(); } + public needsMonospaceFontCheck(): boolean { + if (!this._renderedViewLine) { + return false; + } + return (this._renderedViewLine instanceof FastRenderedViewLine); + } + + public monospaceAssumptionsAreValid(): boolean { + if (!this._renderedViewLine) { + return monospaceAssumptionsAreValid; + } + if (this._renderedViewLine instanceof FastRenderedViewLine) { + return this._renderedViewLine.monospaceAssumptionsAreValid(); + } + return monospaceAssumptionsAreValid; + } + + public onMonospaceAssumptionsInvalidated(): void { + if (this._renderedViewLine && this._renderedViewLine instanceof FastRenderedViewLine) { + this._renderedViewLine = this._renderedViewLine.toSlowRenderedLine(); + } + } + public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): VisibleRanges | null { if (!this._renderedViewLine) { return null; @@ -382,6 +407,24 @@ class FastRenderedViewLine implements IRenderedViewLine { return true; } + public monospaceAssumptionsAreValid(): boolean { + if (!this.domNode) { + return monospaceAssumptionsAreValid; + } + const expectedWidth = this.getWidth(); + const actualWidth = (this.domNode.domNode.firstChild).offsetWidth; + if (Math.abs(expectedWidth - actualWidth) >= 2) { + // more than 2px off + console.warn(`monospace assumptions have been violated, therefore disabling monospace optimizations!`); + monospaceAssumptionsAreValid = false; + } + return monospaceAssumptionsAreValid; + } + + public toSlowRenderedLine(): RenderedViewLine { + return createRenderedLine(this.domNode, this.input, this._characterMapping, false, ForeignElementType.None); + } + public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { const startPosition = this._getCharPosition(startColumn); const endPosition = this._getCharPosition(endColumn); diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index d3d7064496..b241ee9ad1 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./viewLines'; +import * as platform from 'vs/base/common/platform'; import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Configuration } from 'vs/editor/browser/config/configuration'; @@ -12,6 +13,7 @@ import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/v import { DomReadingContext, ViewLine, ViewLineOptions } from 'vs/editor/browser/viewParts/lines/viewLine'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { IViewLines, LineVisibleRanges, VisibleRanges, HorizontalPosition } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; @@ -38,25 +40,49 @@ class LastRenderedData { } } -class HorizontalRevealRequest { +class HorizontalRevealRangeRequest { + public readonly type = 'range'; + public readonly minLineNumber: number; + public readonly maxLineNumber: number; - public readonly lineNumber: number; - public readonly startColumn: number; - public readonly endColumn: number; - public readonly startScrollTop: number; - public readonly stopScrollTop: number; - public readonly scrollType: ScrollType; - - constructor(lineNumber: number, startColumn: number, endColumn: number, startScrollTop: number, stopScrollTop: number, scrollType: ScrollType) { - this.lineNumber = lineNumber; - this.startColumn = startColumn; - this.endColumn = endColumn; - this.startScrollTop = startScrollTop; - this.stopScrollTop = stopScrollTop; - this.scrollType = scrollType; + constructor( + public readonly lineNumber: number, + public readonly startColumn: number, + public readonly endColumn: number, + public readonly startScrollTop: number, + public readonly stopScrollTop: number, + public readonly scrollType: ScrollType + ) { + this.minLineNumber = lineNumber; + this.maxLineNumber = lineNumber; } } +class HorizontalRevealSelectionsRequest { + public readonly type = 'selections'; + public readonly minLineNumber: number; + public readonly maxLineNumber: number; + + constructor( + public readonly selections: Selection[], + public readonly startScrollTop: number, + public readonly stopScrollTop: number, + public readonly scrollType: ScrollType + ) { + let minLineNumber = selections[0].startLineNumber; + let maxLineNumber = selections[0].endLineNumber; + for (let i = 1, len = selections.length; i < len; i++) { + const selection = selections[i]; + minLineNumber = Math.min(minLineNumber, selection.startLineNumber); + maxLineNumber = Math.max(maxLineNumber, selection.endLineNumber); + } + this.minLineNumber = minLineNumber; + this.maxLineNumber = maxLineNumber; + } +} + +type HorizontalRevealRequest = HorizontalRevealRangeRequest | HorizontalRevealSelectionsRequest; + export class ViewLines extends ViewPart implements IVisibleLinesHost, IViewLines { /** * Adds this amount of pixels to the right of lines (no-one wants to type near the edge of the viewport) @@ -81,6 +107,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // --- width private _maxLineWidth: number; private readonly _asyncUpdateLineWidths: RunOnceScheduler; + private readonly _asyncCheckMonospaceFontAssumptions: RunOnceScheduler; private _horizontalRevealRequest: HorizontalRevealRequest | null; private readonly _lastRenderedData: LastRenderedData; @@ -115,6 +142,9 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._asyncUpdateLineWidths = new RunOnceScheduler(() => { this._updateLineWidthsSlow(); }, 200); + this._asyncCheckMonospaceFontAssumptions = new RunOnceScheduler(() => { + this._checkMonospaceFontAssumptions(); + }, 2000); this._lastRenderedData = new LastRenderedData(); @@ -123,6 +153,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, public dispose(): void { this._asyncUpdateLineWidths.dispose(); + this._asyncCheckMonospaceFontAssumptions.dispose(); super.dispose(); } @@ -221,21 +252,28 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, public onRevealRangeRequest(e: viewEvents.ViewRevealRangeRequestEvent): boolean { // Using the future viewport here in order to handle multiple // incoming reveal range requests that might all desire to be animated - const desiredScrollTop = this._computeScrollTopToRevealRange(this._context.viewLayout.getFutureViewport(), e.source, e.range, e.verticalType); + const desiredScrollTop = this._computeScrollTopToRevealRange(this._context.viewLayout.getFutureViewport(), e.source, e.range, e.selections, e.verticalType); + + if (desiredScrollTop === -1) { + // marker to abort the reveal range request + return false; + } // validate the new desired scroll top let newScrollPosition = this._context.viewLayout.validateScrollPosition({ scrollTop: desiredScrollTop }); if (e.revealHorizontal) { - if (e.range.startLineNumber !== e.range.endLineNumber) { + if (e.range && e.range.startLineNumber !== e.range.endLineNumber) { // Two or more lines? => scroll to base (That's how you see most of the two lines) newScrollPosition = { scrollTop: newScrollPosition.scrollTop, scrollLeft: 0 }; - } else { + } else if (e.range) { // We don't necessarily know the horizontal offset of this range since the line might not be in the view... - this._horizontalRevealRequest = new HorizontalRevealRequest(e.range.startLineNumber, e.range.startColumn, e.range.endColumn, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType); + this._horizontalRevealRequest = new HorizontalRevealRangeRequest(e.range.startLineNumber, e.range.startColumn, e.range.endColumn, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType); + } else if (e.selections && e.selections.length > 0) { + this._horizontalRevealRequest = new HorizontalRevealSelectionsRequest(e.selections, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType); } } else { this._horizontalRevealRequest = null; @@ -481,6 +519,37 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return allWidthsComputed; } + private _checkMonospaceFontAssumptions(): void { + // Problems with monospace assumptions are more apparent for longer lines, + // as small rounding errors start to sum up, so we will select the longest + // line for a closer inspection + let longestLineNumber = -1; + let longestWidth = -1; + const rendStartLineNumber = this._visibleLines.getStartLineNumber(); + const rendEndLineNumber = this._visibleLines.getEndLineNumber(); + for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) { + const visibleLine = this._visibleLines.getVisibleLine(lineNumber); + if (visibleLine.needsMonospaceFontCheck()) { + const lineWidth = visibleLine.getWidth(); + if (lineWidth > longestWidth) { + longestWidth = lineWidth; + longestLineNumber = lineNumber; + } + } + } + + if (longestLineNumber === -1) { + return; + } + + if (!this._visibleLines.getVisibleLine(longestLineNumber).monospaceAssumptionsAreValid()) { + for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) { + const visibleLine = this._visibleLines.getVisibleLine(lineNumber); + visibleLine.onMonospaceAssumptionsInvalidated(); + } + } + } + public prepareRender(): void { throw new Error('Not supported'); } @@ -501,13 +570,10 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // - it might change `scrollWidth` and `scrollLeft` if (this._horizontalRevealRequest) { - const revealLineNumber = this._horizontalRevealRequest.lineNumber; - const revealStartColumn = this._horizontalRevealRequest.startColumn; - const revealEndColumn = this._horizontalRevealRequest.endColumn; - const scrollType = this._horizontalRevealRequest.scrollType; + const horizontalRevealRequest = this._horizontalRevealRequest; // Check that we have the line that contains the horizontal range in the viewport - if (viewportData.startLineNumber <= revealLineNumber && revealLineNumber <= viewportData.endLineNumber) { + if (viewportData.startLineNumber <= horizontalRevealRequest.minLineNumber && horizontalRevealRequest.maxLineNumber <= viewportData.endLineNumber) { this._horizontalRevealRequest = null; @@ -515,23 +581,23 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this.onDidRender(); // compute new scroll position - const newScrollLeft = this._computeScrollLeftToRevealRange(revealLineNumber, revealStartColumn, revealEndColumn); + const newScrollLeft = this._computeScrollLeftToReveal(horizontalRevealRequest); - const isViewportWrapping = this._isViewportWrapping; - if (!isViewportWrapping) { - // ensure `scrollWidth` is large enough - this._ensureMaxLineWidth(newScrollLeft.maxHorizontalOffset); - } - - // set `scrollLeft` - if (scrollType === ScrollType.Smooth) { - this._context.viewLayout.setScrollPositionSmooth({ - scrollLeft: newScrollLeft.scrollLeft - }); - } else { - this._context.viewLayout.setScrollPositionNow({ - scrollLeft: newScrollLeft.scrollLeft - }); + if (newScrollLeft) { + if (!this._isViewportWrapping) { + // ensure `scrollWidth` is large enough + this._ensureMaxLineWidth(newScrollLeft.maxHorizontalOffset); + } + // set `scrollLeft` + if (horizontalRevealRequest.scrollType === ScrollType.Smooth) { + this._context.viewLayout.setScrollPositionSmooth({ + scrollLeft: newScrollLeft.scrollLeft + }); + } else { + this._context.viewLayout.setScrollPositionNow({ + scrollLeft: newScrollLeft.scrollLeft + }); + } } } } @@ -542,6 +608,18 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._asyncUpdateLineWidths.schedule(); } + if (platform.isLinux && !this._asyncCheckMonospaceFontAssumptions.isScheduled()) { + const rendStartLineNumber = this._visibleLines.getStartLineNumber(); + const rendEndLineNumber = this._visibleLines.getEndLineNumber(); + for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) { + const visibleLine = this._visibleLines.getVisibleLine(lineNumber); + if (visibleLine.needsMonospaceFontCheck()) { + this._asyncCheckMonospaceFontAssumptions.schedule(); + break; + } + } + } + // (3) handle scrolling this._linesContent.setLayerHinting(this._canUseLayerHinting); this._linesContent.setContain('strict'); @@ -560,16 +638,33 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, } } - private _computeScrollTopToRevealRange(viewport: Viewport, source: string, range: Range, verticalType: viewEvents.VerticalRevealType): number { + private _computeScrollTopToRevealRange(viewport: Viewport, source: string, range: Range | null, selections: Selection[] | null, verticalType: viewEvents.VerticalRevealType): number { const viewportStartY = viewport.top; const viewportHeight = viewport.height; const viewportEndY = viewportStartY + viewportHeight; + let boxIsSingleRange: boolean; let boxStartY: number; let boxEndY: number; // Have a box that includes one extra line height (for the horizontal scrollbar) - boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.startLineNumber); - boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.endLineNumber) + this._lineHeight; + if (selections && selections.length > 0) { + let minLineNumber = selections[0].startLineNumber; + let maxLineNumber = selections[0].endLineNumber; + for (let i = 1, len = selections.length; i < len; i++) { + const selection = selections[i]; + minLineNumber = Math.min(minLineNumber, selection.startLineNumber); + maxLineNumber = Math.max(maxLineNumber, selection.endLineNumber); + } + boxIsSingleRange = false; + boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(minLineNumber); + boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(maxLineNumber) + this._lineHeight; + } else if (range) { + boxIsSingleRange = true; + boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.startLineNumber); + boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.endLineNumber) + this._lineHeight; + } else { + return -1; + } const shouldIgnoreScrollOff = source === 'mouse' && this._cursorSurroundingLinesStyle === 'default'; @@ -588,6 +683,10 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, if (boxEndY - boxStartY > viewportHeight) { // the box is larger than the viewport ... scroll to its top + if (!boxIsSingleRange) { + // do not reveal multiple cursors if there are more than fit the viewport + return -1; + } newScrollTop = boxStartY; } else if (verticalType === viewEvents.VerticalRevealType.NearTop || verticalType === viewEvents.VerticalRevealType.NearTopIfOutsideViewport) { if (verticalType === viewEvents.VerticalRevealType.NearTopIfOutsideViewport && viewportStartY <= boxStartY && boxEndY <= viewportEndY) { @@ -618,44 +717,50 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return newScrollTop; } - private _computeScrollLeftToRevealRange(lineNumber: number, startColumn: number, endColumn: number): { scrollLeft: number; maxHorizontalOffset: number; } { - - let maxHorizontalOffset = 0; + private _computeScrollLeftToReveal(horizontalRevealRequest: HorizontalRevealRequest): { scrollLeft: number; maxHorizontalOffset: number; } | null { const viewport = this._context.viewLayout.getCurrentViewport(); const viewportStartX = viewport.left; const viewportEndX = viewportStartX + viewport.width; - const visibleRanges = this._visibleRangesForLineRange(lineNumber, startColumn, endColumn); let boxStartX = Constants.MAX_SAFE_SMALL_INTEGER; let boxEndX = 0; - - if (!visibleRanges) { - // Unknown - return { - scrollLeft: viewportStartX, - maxHorizontalOffset: maxHorizontalOffset - }; - } - - for (const visibleRange of visibleRanges.ranges) { - if (visibleRange.left < boxStartX) { - boxStartX = visibleRange.left; + if (horizontalRevealRequest.type === 'range') { + const visibleRanges = this._visibleRangesForLineRange(horizontalRevealRequest.lineNumber, horizontalRevealRequest.startColumn, horizontalRevealRequest.endColumn); + if (!visibleRanges) { + return null; } - if (visibleRange.left + visibleRange.width > boxEndX) { - boxEndX = visibleRange.left + visibleRange.width; + for (const visibleRange of visibleRanges.ranges) { + boxStartX = Math.min(boxStartX, visibleRange.left); + boxEndX = Math.max(boxEndX, visibleRange.left + visibleRange.width); + } + } else { + for (const selection of horizontalRevealRequest.selections) { + if (selection.startLineNumber !== selection.endLineNumber) { + return null; + } + const visibleRanges = this._visibleRangesForLineRange(selection.startLineNumber, selection.startColumn, selection.endColumn); + if (!visibleRanges) { + return null; + } + for (const visibleRange of visibleRanges.ranges) { + boxStartX = Math.min(boxStartX, visibleRange.left); + boxEndX = Math.max(boxEndX, visibleRange.left + visibleRange.width); + } } } - maxHorizontalOffset = boxEndX; - boxStartX = Math.max(0, boxStartX - ViewLines.HORIZONTAL_EXTRA_PX); boxEndX += this._revealHorizontalRightPadding; + if (horizontalRevealRequest.type === 'selections' && boxEndX - boxStartX > viewport.width) { + return null; + } + const newScrollLeft = this._computeMinimumScrolling(viewportStartX, viewportEndX, boxStartX, boxEndX); return { scrollLeft: newScrollLeft, - maxHorizontalOffset: maxHorizontalOffset + maxHorizontalOffset: boxEndX }; } diff --git a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts index c46c904c66..089ac7ecf9 100644 --- a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts +++ b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts @@ -78,6 +78,10 @@ export class LinesDecorationsOverlay extends DedupOverlay { if (linesDecorationsClassName) { r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, linesDecorationsClassName); } + const firstLineDecorationClassName = d.options.firstLineDecorationClassName; + if (firstLineDecorationClassName) { + r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.startLineNumber, firstLineDecorationClassName); + } } return r; } diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 5b54d1ef89..00fb54efae 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -163,7 +163,7 @@ class MinimapOptions { && this.fontScale === other.fontScale && this.minimapLineHeight === other.minimapLineHeight && this.minimapCharWidth === other.minimapCharWidth - && this.backgroundColor.equals(other.backgroundColor) + && this.backgroundColor && this.backgroundColor.equals(other.backgroundColor) ); } } @@ -1012,6 +1012,7 @@ export class Minimap extends ViewPart implements IMinimapModel { this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent( 'mouse', new Range(lineNumber, 1, lineNumber, 1), + null, viewEvents.VerticalRevealType.Center, false, ScrollType.Smooth diff --git a/src/vs/editor/browser/viewParts/selections/selections.ts b/src/vs/editor/browser/viewParts/selections/selections.ts index a5b67cbe65..a451d44f59 100644 --- a/src/vs/editor/browser/viewParts/selections/selections.ts +++ b/src/vs/editor/browser/viewParts/selections/selections.ts @@ -60,7 +60,7 @@ function toStyled(item: LineVisibleRanges): LineVisibleRangesWithStyle { // TODO@Alex: Remove this once IE11 fixes Bug #524217 // The problem in IE11 is that it does some sort of auto-zooming to accomodate for displays with different pixel density. // Unfortunately, this auto-zooming is buggy around dealing with rounded borders -const isIEWithZoomingIssuesNearRoundedBorders = browser.isEdgeOrIE; +const isIEWithZoomingIssuesNearRoundedBorders = browser.isEdge; export class SelectionsOverlay extends DynamicViewOverlay { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 2e5782a7c0..056d82d623 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -32,7 +32,7 @@ import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { InternalEditorAction } from 'vs/editor/common/editorAction'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { EndOfLinePreference, IIdentifiedSingleEditOperation, IModelDecoration, IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, ICursorStateComputer } from 'vs/editor/common/model'; +import { EndOfLinePreference, IIdentifiedSingleEditOperation, IModelDecoration, IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, ICursorStateComputer, IWordAtPosition } from 'vs/editor/common/model'; import { ClassName } from 'vs/editor/common/model/intervalTree'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; @@ -52,6 +52,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { withNullAsUndefined } from 'vs/base/common/types'; import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; import { DOMLineBreaksComputerFactory } from 'vs/editor/browser/view/domLineBreaksComputer'; +import { WordOperations } from 'vs/editor/common/controller/cursorWordOperations'; let EDITOR_ID = 0; @@ -376,6 +377,13 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._configuration.getRawOptions(); } + public getConfiguredWordAtPosition(position: Position): IWordAtPosition | null { + if (!this._modelData) { + return null; + } + return WordOperations.getWordAtPosition(this._modelData.model, this._configuration.options.get(EditorOption.wordSeparators), position); + } + public getValue(options: { preserveBOM: boolean; lineEnding: string; } | null = null): string { if (!this._modelData) { return ''; @@ -542,7 +550,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE const validatedModelRange = this._modelData.model.validateRange(modelRange); const viewRange = this._modelData.viewModel.coordinatesConverter.convertModelRangeToViewRange(validatedModelRange); - this._modelData.cursor.emitCursorRevealRange('api', viewRange, verticalType, revealHorizontal, scrollType); + this._modelData.cursor.emitCursorRevealRange('api', viewRange, null, verticalType, revealHorizontal, scrollType); } public revealLine(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 44a23f89d1..8c44564cd1 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -39,8 +39,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { defaultInsertColor, defaultRemoveColor, diffBorder, diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, scrollbarShadow, scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, IThemeService, getThemeTypeSelector, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { reverseLineChanges } from 'sql/editor/browser/diffEditorHelper'; // {{SQL CARBON EDIT}} +import { IColorTheme, IThemeService, getThemeTypeSelector, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IDiffLinesChange, InlineDiffMargin } from 'vs/editor/browser/widget/inlineDiffMargin'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -49,6 +48,7 @@ import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs import { onUnexpectedError } from 'vs/base/common/errors'; import { IEditorProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver'; +import { reverseLineChanges } from 'sql/editor/browser/diffEditorHelper'; interface IEditorDiffDecorations { decorations: IModelDeltaDecoration[]; @@ -73,7 +73,7 @@ interface IDiffEditorWidgetStyle { // {{SQL CARBON EDIT}} getEditorsDiffDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalWhitespaces: IEditorWhitespace[], modifiedWhitespaces: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor, reverse?: boolean): IEditorsDiffDecorationsWithZones; setEnableSplitViewResizing(enableSplitViewResizing: boolean): void; - applyColors(theme: ITheme): boolean; + applyColors(theme: IColorTheme): boolean; layout(): number; dispose(): void; } @@ -283,7 +283,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._updateDecorationsRunner = this._register(new RunOnceScheduler(() => this._updateDecorations(), 0)); this._containerDomElement = document.createElement('div'); - this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getTheme(), this._renderSideBySide); + this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getColorTheme(), this._renderSideBySide); this._containerDomElement.style.position = 'relative'; this._containerDomElement.style.height = '100%'; this._domElement.appendChild(this._containerDomElement); @@ -373,11 +373,11 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._setStrategy(new DiffEditorWidgetInline(this._createDataSource(), this._enableSplitViewResizing)); } - this._register(themeService.onThemeChange(t => { + this._register(themeService.onDidColorThemeChange(t => { if (this._strategy && this._strategy.applyColors(t)) { this._updateDecorationsRunner.schedule(); } - this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getTheme(), this._renderSideBySide); + this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getColorTheme(), this._renderSideBySide); })); const contributions: IDiffEditorContributionDescription[] = EditorExtensionsRegistry.getDiffEditorContributions(); @@ -436,7 +436,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._reviewPane.prev(); } - private static _getClassName(theme: ITheme, renderSideBySide: boolean): string { + private static _getClassName(theme: IColorTheme, renderSideBySide: boolean): string { let result = 'monaco-diff-editor monaco-editor-background '; if (renderSideBySide) { result += 'side-by-side '; @@ -679,7 +679,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._setStrategy(new DiffEditorWidgetInline(this._createDataSource(), this._enableSplitViewResizing)); } // Update class name - this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getTheme(), this._renderSideBySide); + this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getColorTheme(), this._renderSideBySide); } } @@ -1178,7 +1178,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } this._strategy = newStrategy; - newStrategy.applyColors(this._themeService.getTheme()); + newStrategy.applyColors(this._themeService.getColorTheme()); if (this._diffComputationResult) { this._updateDecorations(); @@ -1302,7 +1302,7 @@ abstract class DiffEditorWidgetStyle extends Disposable implements IDiffEditorWi this._removeColor = null; } - public applyColors(theme: ITheme): boolean { + public applyColors(theme: IColorTheme): boolean { let newInsertColor = (theme.getColor(diffInserted) || defaultInsertColor).transparent(2); let newRemoveColor = (theme.getColor(diffRemoved) || defaultRemoveColor).transparent(2); let hasChanges = !newInsertColor.equals(this._insertColor) || !newRemoveColor.equals(this._removeColor); diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index 6c9954a9f8..c1d283761a 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -98,7 +98,7 @@ export class DiffReview extends Disposable { this.actionBarContainer.domNode )); - this._actionBar.push(new Action('diffreview.close', nls.localize('label.close', "Close"), 'close-diff-review', true, () => { + this._actionBar.push(new Action('diffreview.close', nls.localize('label.close', "Close"), 'close-diff-review codicon-close', true, () => { this.hide(); return Promise.resolve(null); }), { label: false, icon: true }); diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index bae7e535c4..64dc9e6e0b 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1338,7 +1338,7 @@ export class EditorFontLigatures extends BaseEditorOption 1) { - // no revealing! + this.emitCursorRevealRange(source, null, this._cursors.getViewSelections(), verticalType, revealHorizontal, scrollType); return; } } const viewRange = new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column); - this.emitCursorRevealRange(source, viewRange, verticalType, revealHorizontal, scrollType); + this.emitCursorRevealRange(source, viewRange, null, verticalType, revealHorizontal, scrollType); } - public emitCursorRevealRange(source: string, viewRange: Range, verticalType: viewEvents.VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType) { + public emitCursorRevealRange(source: string, viewRange: Range | null, viewSelections: Selection[] | null, verticalType: viewEvents.VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType) { try { const eventsCollector = this._beginEmit(); - eventsCollector.emit(new viewEvents.ViewRevealRangeRequestEvent(source, viewRange, verticalType, revealHorizontal, scrollType)); + eventsCollector.emit(new viewEvents.ViewRevealRangeRequestEvent(source, viewRange, viewSelections, verticalType, revealHorizontal, scrollType)); } finally { this._endEmit(); } @@ -779,7 +779,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { } private _type(source: string, text: string): void { - if (!this._isDoingComposition && source === 'keyboard') { + if (source === 'keyboard') { // If this event is coming straight from the keyboard, look for electric characters and enter const len = text.length; @@ -790,7 +790,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { // Here we must interpret each typed character individually const autoClosedCharacters = AutoClosedAction.getAllAutoClosedCharacters(this._autoClosedActions); - this._executeEditOperation(TypeOperations.typeWithInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), autoClosedCharacters, chr)); + this._executeEditOperation(TypeOperations.typeWithInterceptors(this._isDoingComposition, this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), autoClosedCharacters, chr)); offset += charLength; } diff --git a/src/vs/editor/common/controller/cursorMoveCommands.ts b/src/vs/editor/common/controller/cursorMoveCommands.ts index ec4a668e70..c38aeb7567 100644 --- a/src/vs/editor/common/controller/cursorMoveCommands.ts +++ b/src/vs/editor/common/controller/cursorMoveCommands.ts @@ -411,7 +411,7 @@ export class CursorMoveCommands { let newViewState = MoveOperations.moveLeft(context.config, context.viewModel, cursor.viewState, inSelectionMode, noOfColumns); - if (noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) { + if (!cursor.viewState.hasSelection() && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) { // moved over to the previous view line const newViewModelPosition = context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(newViewState.position); if (newViewModelPosition.lineNumber === cursor.modelState.position.lineNumber) { @@ -442,7 +442,7 @@ export class CursorMoveCommands { const cursor = cursors[i]; let newViewState = MoveOperations.moveRight(context.config, context.viewModel, cursor.viewState, inSelectionMode, noOfColumns); - if (noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) { + if (!cursor.viewState.hasSelection() && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) { // moved over to the next view line const newViewModelPosition = context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(newViewState.position); if (newViewModelPosition.lineNumber === cursor.modelState.position.lineNumber) { diff --git a/src/vs/editor/common/controller/cursorMoveOperations.ts b/src/vs/editor/common/controller/cursorMoveOperations.ts index cb46240298..e0a32d2077 100644 --- a/src/vs/editor/common/controller/cursorMoveOperations.ts +++ b/src/vs/editor/common/controller/cursorMoveOperations.ts @@ -91,9 +91,10 @@ export class MoveOperations { public static down(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnLastLine: boolean): CursorPosition { const currentVisibleColumn = CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns; + const lineCount = model.getLineCount(); + const wasOnLastPosition = (lineNumber === lineCount && column === model.getLineMaxColumn(lineNumber)); lineNumber = lineNumber + count; - let lineCount = model.getLineCount(); if (lineNumber > lineCount) { lineNumber = lineCount; if (allowMoveOnLastLine) { @@ -105,7 +106,11 @@ export class MoveOperations { column = CursorColumns.columnFromVisibleColumn2(config, model, lineNumber, currentVisibleColumn); } - leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize); + if (wasOnLastPosition) { + leftoverVisibleColumns = 0; + } else { + leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize); + } return new CursorPosition(lineNumber, column, leftoverVisibleColumns); } @@ -144,6 +149,7 @@ export class MoveOperations { public static up(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnFirstLine: boolean): CursorPosition { const currentVisibleColumn = CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns; + const wasOnFirstPosition = (lineNumber === 1 && column === 1); lineNumber = lineNumber - count; if (lineNumber < 1) { @@ -157,7 +163,11 @@ export class MoveOperations { column = CursorColumns.columnFromVisibleColumn2(config, model, lineNumber, currentVisibleColumn); } - leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize); + if (wasOnFirstPosition) { + leftoverVisibleColumns = 0; + } else { + leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize); + } return new CursorPosition(lineNumber, column, leftoverVisibleColumns); } diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index 62b9ad4554..ecbb8bf97d 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -269,9 +269,15 @@ export class TypeOperations { commands[i] = null; continue; } - let pos = selection.getPosition(); - let startColumn = Math.max(1, pos.column - replaceCharCnt); - let range = new Range(pos.lineNumber, startColumn, pos.lineNumber, pos.column); + const pos = selection.getPosition(); + const startColumn = Math.max(1, pos.column - replaceCharCnt); + const range = new Range(pos.lineNumber, startColumn, pos.lineNumber, pos.column); + const oldText = model.getValueInRange(range); + if (oldText === txt) { + // => ignore composition that doesn't do anything + commands[i] = null; + continue; + } commands[i] = new ReplaceCommand(range, txt); } return new EditOperationResult(EditOperationType.Typing, commands, { @@ -796,9 +802,9 @@ export class TypeOperations { return null; } - public static typeWithInterceptors(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): EditOperationResult { + public static typeWithInterceptors(isDoingComposition: boolean, prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): EditOperationResult { - if (ch === '\n') { + if (!isDoingComposition && ch === '\n') { let commands: ICommand[] = []; for (let i = 0, len = selections.length; i < len; i++) { commands[i] = TypeOperations._enter(config, model, false, selections[i]); @@ -809,7 +815,7 @@ export class TypeOperations { }); } - if (this._isAutoIndentType(config, model, selections)) { + if (!isDoingComposition && this._isAutoIndentType(config, model, selections)) { let commands: Array = []; let autoIndentFails = false; for (let i = 0, len = selections.length; i < len; i++) { @@ -827,13 +833,15 @@ export class TypeOperations { } } - if (this._isAutoClosingOvertype(config, model, selections, autoClosedCharacters, ch)) { + if (!isDoingComposition && this._isAutoClosingOvertype(config, model, selections, autoClosedCharacters, ch)) { return this._runAutoClosingOvertype(prevEditOperationType, config, model, selections, ch); } - const autoClosingPairOpenCharType = this._isAutoClosingOpenCharType(config, model, selections, ch, true); - if (autoClosingPairOpenCharType) { - return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, true, autoClosingPairOpenCharType); + if (!isDoingComposition) { + const autoClosingPairOpenCharType = this._isAutoClosingOpenCharType(config, model, selections, ch, true); + if (autoClosingPairOpenCharType) { + return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, true, autoClosingPairOpenCharType); + } } if (this._isSurroundSelectionType(config, model, selections, ch)) { @@ -842,7 +850,7 @@ export class TypeOperations { // Electric characters make sense only when dealing with a single cursor, // as multiple cursors typing brackets for example would interfer with bracket matching - if (this._isTypeInterceptorElectricChar(config, model, selections)) { + if (!isDoingComposition && this._isTypeInterceptorElectricChar(config, model, selections)) { const r = this._typeInterceptorElectricChar(prevEditOperationType, config, model, selections[0], ch); if (r) { return r; diff --git a/src/vs/editor/common/controller/cursorWordOperations.ts b/src/vs/editor/common/controller/cursorWordOperations.ts index 32fdbf2028..ea8fcb8b5e 100644 --- a/src/vs/editor/common/controller/cursorWordOperations.ts +++ b/src/vs/editor/common/controller/cursorWordOperations.ts @@ -10,6 +10,7 @@ import { WordCharacterClass, WordCharacterClassifier, getMapForWordSeparators } import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; +import { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; interface IFindWordResult { /** @@ -535,6 +536,28 @@ export class WordOperations { return new Range(pos.lineNumber, pos.column, toPosition.lineNumber, toPosition.column); } + private static _createWordAtPosition(model: ITextModel, lineNumber: number, word: IFindWordResult): IWordAtPosition { + const range = new Range(lineNumber, word.start + 1, lineNumber, word.end + 1); + return { + word: model.getValueInRange(range), + startColumn: range.startColumn, + endColumn: range.endColumn + }; + } + + public static getWordAtPosition(model: ITextModel, _wordSeparators: string, position: Position): IWordAtPosition | null { + const wordSeparators = getMapForWordSeparators(_wordSeparators); + const prevWord = WordOperations._findPreviousWordOnLine(wordSeparators, model, position); + if (prevWord && prevWord.wordType === WordType.Regular && prevWord.start <= position.column - 1 && position.column - 1 <= prevWord.end) { + return WordOperations._createWordAtPosition(model, position.lineNumber, prevWord); + } + const nextWord = WordOperations._findNextWordOnLine(wordSeparators, model, position); + if (nextWord && nextWord.wordType === WordType.Regular && nextWord.start <= position.column - 1 && position.column - 1 <= nextWord.end) { + return WordOperations._createWordAtPosition(model, position.lineNumber, nextWord); + } + return null; + } + public static word(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, position: Position): SingleCursorState { const wordSeparators = getMapForWordSeparators(config.wordSeparators); let prevWord = WordOperations._findPreviousWordOnLine(wordSeparators, model, position); diff --git a/src/vs/editor/common/editorAction.ts b/src/vs/editor/common/editorAction.ts index c3c5abca3d..0f20500757 100644 --- a/src/vs/editor/common/editorAction.ts +++ b/src/vs/editor/common/editorAction.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IEditorAction } from 'vs/editor/common/editorCommon'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; export class InternalEditorAction implements IEditorAction { @@ -12,7 +12,7 @@ export class InternalEditorAction implements IEditorAction { public readonly label: string; public readonly alias: string; - private readonly _precondition: ContextKeyExpr | undefined; + private readonly _precondition: ContextKeyExpression | undefined; private readonly _run: () => Promise; private readonly _contextKeyService: IContextKeyService; @@ -20,7 +20,7 @@ export class InternalEditorAction implements IEditorAction { id: string, label: string, alias: string, - precondition: ContextKeyExpr | undefined, + precondition: ContextKeyExpression | undefined, run: () => Promise, contextKeyService: IContextKeyService ) { diff --git a/src/vs/editor/common/editorContextKeys.ts b/src/vs/editor/common/editorContextKeys.ts index c70afc3272..724c098003 100644 --- a/src/vs/editor/common/editorContextKeys.ts +++ b/src/vs/editor/common/editorContextKeys.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export namespace EditorContextKeys { @@ -24,13 +24,13 @@ export namespace EditorContextKeys { export const readOnly = new RawContextKey('editorReadonly', false); export const columnSelection = new RawContextKey('editorColumnSelection', false); - export const writable: ContextKeyExpr = readOnly.toNegated(); + export const writable = readOnly.toNegated(); export const hasNonEmptySelection = new RawContextKey('editorHasSelection', false); - export const hasOnlyEmptySelection: ContextKeyExpr = hasNonEmptySelection.toNegated(); + export const hasOnlyEmptySelection = hasNonEmptySelection.toNegated(); export const hasMultipleSelections = new RawContextKey('editorHasMultipleSelections', false); - export const hasSingleSelection: ContextKeyExpr = hasMultipleSelections.toNegated(); + export const hasSingleSelection = hasMultipleSelections.toNegated(); export const tabMovesFocus = new RawContextKey('editorTabMovesFocus', false); - export const tabDoesNotMoveFocus: ContextKeyExpr = tabMovesFocus.toNegated(); + export const tabDoesNotMoveFocus = tabMovesFocus.toNegated(); export const isInEmbeddedEditor = new RawContextKey('isInEmbeddedEditor', false); export const canUndo = new RawContextKey('canUndo', false); export const canRedo = new RawContextKey('canRedo', false); diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index bcee301f41..4840313f67 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -123,6 +123,10 @@ export interface IModelDecorationOptions { * If set, the decoration will be rendered in the lines decorations with this CSS class name. */ linesDecorationsClassName?: string | null; + /** + * If set, the decoration will be rendered in the lines decorations with this CSS class name, but only for the first line in case of line wrapping. + */ + firstLineDecorationClassName?: string | null; /** * If set, the decoration will be rendered in the margin (covering its full width) with this CSS class name. */ diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 0407f8d341..4cb44048c6 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -3053,6 +3053,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions { readonly minimap: ModelDecorationMinimapOptions | null; readonly glyphMarginClassName: string | null; readonly linesDecorationsClassName: string | null; + readonly firstLineDecorationClassName: string | null; readonly marginClassName: string | null; readonly inlineClassName: string | null; readonly inlineClassNameAffectsLetterSpacing: boolean; @@ -3072,6 +3073,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions { this.minimap = options.minimap ? new ModelDecorationMinimapOptions(options.minimap) : null; this.glyphMarginClassName = options.glyphMarginClassName ? cleanClassName(options.glyphMarginClassName) : null; this.linesDecorationsClassName = options.linesDecorationsClassName ? cleanClassName(options.linesDecorationsClassName) : null; + this.firstLineDecorationClassName = options.firstLineDecorationClassName ? cleanClassName(options.firstLineDecorationClassName) : null; this.marginClassName = options.marginClassName ? cleanClassName(options.marginClassName) : null; this.inlineClassName = options.inlineClassName ? cleanClassName(options.inlineClassName) : null; this.inlineClassNameAffectsLetterSpacing = options.inlineClassNameAffectsLetterSpacing || false; diff --git a/src/vs/editor/common/model/textModelSearch.ts b/src/vs/editor/common/model/textModelSearch.ts index 37ff0d86fb..edec152240 100644 --- a/src/vs/editor/common/model/textModelSearch.ts +++ b/src/vs/editor/common/model/textModelSearch.ts @@ -515,7 +515,7 @@ export class Searcher { private _prevMatchStartIndex: number; private _prevMatchLength: number; - constructor(wordSeparators: WordCharacterClassifier | null, searchRegex: RegExp, ) { + constructor(wordSeparators: WordCharacterClassifier | null, searchRegex: RegExp,) { this._wordSeparators = wordSeparators; this._searchRegex = searchRegex; this._prevMatchStartIndex = -1; diff --git a/src/vs/editor/common/model/tokensStore.ts b/src/vs/editor/common/model/tokensStore.ts index 7f5a45f96c..d054fff61c 100644 --- a/src/vs/editor/common/model/tokensStore.ts +++ b/src/vs/editor/common/model/tokensStore.ts @@ -781,6 +781,17 @@ export class TokensStore2 { let aIndex = 0; let result: number[] = [], resultLen = 0; + let lastEndOffset = 0; + + const emitToken = (endOffset: number, metadata: number) => { + if (endOffset === lastEndOffset) { + return; + } + lastEndOffset = endOffset; + result[resultLen++] = endOffset; + result[resultLen++] = metadata; + }; + for (let bIndex = 0; bIndex < bLen; bIndex++) { const bStartCharacter = bTokens.getStartCharacter(bIndex); const bEndCharacter = bTokens.getEndCharacter(bIndex); @@ -797,42 +808,36 @@ export class TokensStore2 { // push any token from `a` that is before `b` while (aIndex < aLen && aTokens.getEndOffset(aIndex) <= bStartCharacter) { - result[resultLen++] = aTokens.getEndOffset(aIndex); - result[resultLen++] = aTokens.getMetadata(aIndex); + emitToken(aTokens.getEndOffset(aIndex), aTokens.getMetadata(aIndex)); aIndex++; } // push the token from `a` if it intersects the token from `b` if (aIndex < aLen && aTokens.getStartOffset(aIndex) < bStartCharacter) { - result[resultLen++] = bStartCharacter; - result[resultLen++] = aTokens.getMetadata(aIndex); + emitToken(bStartCharacter, aTokens.getMetadata(aIndex)); } // skip any tokens from `a` that are contained inside `b` while (aIndex < aLen && aTokens.getEndOffset(aIndex) < bEndCharacter) { - result[resultLen++] = aTokens.getEndOffset(aIndex); - result[resultLen++] = (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask); + emitToken(aTokens.getEndOffset(aIndex), (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask)); aIndex++; } if (aIndex < aLen && aTokens.getEndOffset(aIndex) === bEndCharacter) { // `a` ends exactly at the same spot as `b`! - result[resultLen++] = aTokens.getEndOffset(aIndex); - result[resultLen++] = (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask); + emitToken(aTokens.getEndOffset(aIndex), (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask)); aIndex++; } else { const aMergeIndex = Math.min(Math.max(0, aIndex - 1), aLen - 1); // push the token from `b` - result[resultLen++] = bEndCharacter; - result[resultLen++] = (aTokens.getMetadata(aMergeIndex) & aMask) | (bMetadata & bMask); + emitToken(bEndCharacter, (aTokens.getMetadata(aMergeIndex) & aMask) | (bMetadata & bMask)); } } // push the remaining tokens from `a` while (aIndex < aLen) { - result[resultLen++] = aTokens.getEndOffset(aIndex); - result[resultLen++] = aTokens.getMetadata(aIndex); + emitToken(aTokens.getEndOffset(aIndex), aTokens.getMetadata(aIndex)); aIndex++; } diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index d5391d713f..49f087130b 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1246,11 +1246,11 @@ export interface SelectionRangeProvider { export interface FoldingContext { } /** - * A provider of colors for editor models. + * A provider of folding ranges for editor models. */ export interface FoldingRangeProvider { /** - * Provides the color ranges for a specific model. + * Provides the folding ranges for a specific model. */ provideFoldingRanges(model: model.ITextModel, context: FoldingContext, token: CancellationToken): ProviderResult; } @@ -1589,6 +1589,7 @@ export interface SemanticTokensEdits { } export interface DocumentSemanticTokensProvider { + onDidChange?: Event; getLegend(): SemanticTokensLegend; provideDocumentSemanticTokens(model: model.ITextModel, lastResultId: string | null, token: CancellationToken): ProviderResult; releaseDocumentSemanticTokens(resultId: string | undefined): void; diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index df66955201..f4ddafedce 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -5,7 +5,6 @@ import { mergeSort } from 'vs/base/common/arrays'; import { stringDiff } from 'vs/base/common/diff/diff'; -import { FIN, Iterator, IteratorResult } from 'vs/base/common/iterator'; import { IDisposable } from 'vs/base/common/lifecycle'; import { globals } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; @@ -65,7 +64,7 @@ export interface ICommonModel extends ILinkComputerTarget, IMirrorModel { getLineCount(): number; getLineContent(lineNumber: number): string; getLineWords(lineNumber: number, wordDefinition: RegExp): IWordAtPosition[]; - createWordIterator(wordDefinition: RegExp): Iterator; + words(wordDefinition: RegExp): Iterable; getWordUntilPosition(position: IPosition, wordDefinition: RegExp): IWordAtPosition; getValueInRange(range: IRange): string; getWordAtPosition(position: IPosition, wordDefinition: RegExp): Range | null; @@ -153,36 +152,37 @@ class MirrorModel extends BaseMirrorModel implements ICommonModel { }; } - public createWordIterator(wordDefinition: RegExp): Iterator { - let obj: { done: false; value: string; }; + + public words(wordDefinition: RegExp): Iterable { + + const lines = this._lines; + const wordenize = this._wordenize.bind(this); + let lineNumber = 0; - let lineText: string; + let lineText = ''; let wordRangesIdx = 0; let wordRanges: IWordRange[] = []; - let next = (): IteratorResult => { - if (wordRangesIdx < wordRanges.length) { - const value = lineText.substring(wordRanges[wordRangesIdx].start, wordRanges[wordRangesIdx].end); - wordRangesIdx += 1; - if (!obj) { - obj = { done: false, value: value }; - } else { - obj.value = value; + return { + *[Symbol.iterator]() { + while (true) { + if (wordRangesIdx < wordRanges.length) { + const value = lineText.substring(wordRanges[wordRangesIdx].start, wordRanges[wordRangesIdx].end); + wordRangesIdx += 1; + yield value; + } else { + if (lineNumber < lines.length) { + lineText = lines[lineNumber]; + wordRanges = wordenize(lineText, wordDefinition); + wordRangesIdx = 0; + lineNumber += 1; + } else { + break; + } + } } - return obj; - - } else if (lineNumber >= this._lines.length) { - return FIN; - - } else { - lineText = this._lines[lineNumber]; - wordRanges = this._wordenize(lineText, wordDefinition); - wordRangesIdx = 0; - lineNumber += 1; - return next(); } }; - return { next }; } public getLineWords(lineNumber: number, wordDefinition: RegExp): IWordAtPosition[] { @@ -545,12 +545,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { seen.add(model.getValueInRange(wordAt)); } - for ( - let iter = model.createWordIterator(wordDefRegExp), e = iter.next(); - !e.done && seen.size <= EditorSimpleWorker._suggestionsLimit; - e = iter.next() - ) { - const word = e.value; + for (let word of model.words(wordDefRegExp)) { if (seen.has(word)) { continue; } @@ -559,6 +554,9 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { continue; } words.push(word); + if (seen.size > EditorSimpleWorker._suggestionsLimit) { + break; + } } return words; } diff --git a/src/vs/editor/common/services/modeService.ts b/src/vs/editor/common/services/modeService.ts index c421bdd514..b4f9bef415 100644 --- a/src/vs/editor/common/services/modeService.ts +++ b/src/vs/editor/common/services/modeService.ts @@ -31,6 +31,7 @@ export interface IModeService { _serviceBrand: undefined; onDidCreateMode: Event; + onLanguagesMaybeChanged: Event; // --- reading isRegisteredMode(mimetypeOrModeId: string): boolean; diff --git a/src/vs/editor/common/services/modeServiceImpl.ts b/src/vs/editor/common/services/modeServiceImpl.ts index 59d91b3daf..94fa8fc874 100644 --- a/src/vs/editor/common/services/modeServiceImpl.ts +++ b/src/vs/editor/common/services/modeServiceImpl.ts @@ -50,7 +50,7 @@ export class ModeServiceImpl implements IModeService { public readonly onDidCreateMode: Event = this._onDidCreateMode.event; protected readonly _onLanguagesMaybeChanged = new Emitter(); - private readonly onLanguagesMaybeChanged: Event = this._onLanguagesMaybeChanged.event; + public readonly onLanguagesMaybeChanged: Event = this._onLanguagesMaybeChanged.event; constructor(warnOnOverwrite = false) { this._instantiatedModes = {}; diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index f883b92956..b29df25e7d 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import * as errors from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; @@ -207,11 +207,22 @@ export class ModelServiceImpl extends Disposable implements IModelService { }; } + private _getEOL(resource: URI | undefined, language: string): string { + if (resource) { + return this._resourcePropertiesService.getEOL(resource, language); + } + const eol = this._configurationService.getValue('files.eol', { overrideIdentifier: language }); + if (eol && eol !== 'auto') { + return eol; + } + return platform.OS === platform.OperatingSystem.Linux || platform.OS === platform.OperatingSystem.Macintosh ? '\n' : '\r\n'; + } + public getCreationOptions(language: string, resource: URI | undefined, isForSimpleWidget: boolean): ITextModelCreationOptions { let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource]; if (!creationOptions) { const editor = this._configurationService.getValue('editor', { overrideIdentifier: language, resource }); - const eol = this._resourcePropertiesService.getEOL(resource, language); + const eol = this._getEOL(resource, language); creationOptions = ModelServiceImpl._readModelOptions({ editor, eol }, isForSimpleWidget); this._modelCreationOptionsByLanguageAndResource[language + resource] = creationOptions; } @@ -516,7 +527,7 @@ class SemanticStyling extends Disposable { this._caches = new WeakMap(); if (this._themeService) { // workaround for tests which use undefined... :/ - this._register(this._themeService.onThemeChange(() => { + this._register(this._themeService.onDidColorThemeChange(() => { this._caches = new WeakMap(); })); } @@ -651,7 +662,7 @@ class SemanticColoringProviderStyling { modifierSet = modifierSet >> 1; } - const tokenStyle = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers); + const tokenStyle = this._themeService.getColorTheme().getTokenStyleMetadata(tokenType, tokenModifiers); if (typeof tokenStyle === 'undefined') { metadata = Constants.NO_STYLING; } else { @@ -724,6 +735,7 @@ class ModelSemanticColoring extends Disposable { private readonly _fetchSemanticTokens: RunOnceScheduler; private _currentResponse: SemanticTokensResponse | null; private _currentRequestCancellationTokenSource: CancellationTokenSource | null; + private _providersChangeListeners: IDisposable[]; constructor(model: ITextModel, themeService: IThemeService, stylingProvider: SemanticStyling) { super(); @@ -734,16 +746,31 @@ class ModelSemanticColoring extends Disposable { this._fetchSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchSemanticTokensNow(), 300)); this._currentResponse = null; this._currentRequestCancellationTokenSource = null; + this._providersChangeListeners = []; this._register(this._model.onDidChangeContent(e => { if (!this._fetchSemanticTokens.isScheduled()) { this._fetchSemanticTokens.schedule(); } })); - this._register(DocumentSemanticTokensProviderRegistry.onDidChange(e => this._fetchSemanticTokens.schedule())); + const bindChangeListeners = () => { + dispose(this._providersChangeListeners); + this._providersChangeListeners = []; + for (const provider of DocumentSemanticTokensProviderRegistry.all(model)) { + if (typeof provider.onDidChange === 'function') { + this._providersChangeListeners.push(provider.onDidChange(() => this._fetchSemanticTokens.schedule(0))); + } + } + }; + bindChangeListeners(); + this._register(DocumentSemanticTokensProviderRegistry.onDidChange(e => { + bindChangeListeners(); + this._fetchSemanticTokens.schedule(); + })); + if (themeService) { // workaround for tests which use undefined... :/ - this._register(themeService.onThemeChange(_ => { + this._register(themeService.onDidColorThemeChange(_ => { // clear out existing tokens this._setSemanticTokens(null, null, null, []); this._fetchSemanticTokens.schedule(); diff --git a/src/vs/editor/common/services/textResourceConfigurationService.ts b/src/vs/editor/common/services/textResourceConfigurationService.ts index f690c04821..4c45dbd8ca 100644 --- a/src/vs/editor/common/services/textResourceConfigurationService.ts +++ b/src/vs/editor/common/services/textResourceConfigurationService.ts @@ -75,5 +75,5 @@ export interface ITextResourcePropertiesService { /** * Returns the End of Line characters for the given resource */ - getEOL(resource: URI | undefined, language?: string): string; + getEOL(resource: URI, language?: string): string; } diff --git a/src/vs/editor/common/standalone/promise-polyfill/cgmanifest.json b/src/vs/editor/common/standalone/promise-polyfill/cgmanifest.json deleted file mode 100644 index b62e25bccf..0000000000 --- a/src/vs/editor/common/standalone/promise-polyfill/cgmanifest.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "registrations": [ - { - "component": { - "type": "git", - "git": { - "name": "promise-polyfill", - "repositoryUrl": "https://github.com/taylorhakes/promise-polyfill", - "commitHash": "efe662be6ea569c439ec92a4f8662c0a7faf0b96" - } - }, - "license": "MIT", - "version": "8.0.0" - } - ], - "version": 1 -} diff --git a/src/vs/editor/common/standalone/promise-polyfill/polyfill.js b/src/vs/editor/common/standalone/promise-polyfill/polyfill.js deleted file mode 100644 index 4ddfcab7cd..0000000000 --- a/src/vs/editor/common/standalone/promise-polyfill/polyfill.js +++ /dev/null @@ -1,291 +0,0 @@ -/*! -Copyright (c) 2014 Taylor Hakes -Copyright (c) 2014 Forbes Lindesay - */ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory() : - typeof define === 'function' && define.amd ? define(factory) : - (factory()); -}(this, (function () { - 'use strict'; - - /** - * @this {Promise} - */ - function finallyConstructor(callback) { - var constructor = this.constructor; - return this.then( - function (value) { - return constructor.resolve(callback()).then(function () { - return value; - }); - }, - function (reason) { - return constructor.resolve(callback()).then(function () { - return constructor.reject(reason); - }); - } - ); - } - - // Store setTimeout reference so promise-polyfill will be unaffected by - // other code modifying setTimeout (like sinon.useFakeTimers()) - var setTimeoutFunc = setTimeout; - - function noop() { } - - // Polyfill for Function.prototype.bind - function bind(fn, thisArg) { - return function () { - fn.apply(thisArg, arguments); - }; - } - - /** - * @constructor - * @param {Function} fn - */ - function Promise(fn) { - if (!(this instanceof Promise)) - throw new TypeError('Promises must be constructed via new'); - if (typeof fn !== 'function') throw new TypeError('not a function'); - /** @type {!number} */ - this._state = 0; - /** @type {!boolean} */ - this._handled = false; - /** @type {Promise|undefined} */ - this._value = undefined; - /** @type {!Array} */ - this._deferreds = []; - - doResolve(fn, this); - } - - function handle(self, deferred) { - while (self._state === 3) { - self = self._value; - } - if (self._state === 0) { - self._deferreds.push(deferred); - return; - } - self._handled = true; - Promise._immediateFn(function () { - var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; - if (cb === null) { - (self._state === 1 ? resolve : reject)(deferred.promise, self._value); - return; - } - var ret; - try { - ret = cb(self._value); - } catch (e) { - reject(deferred.promise, e); - return; - } - resolve(deferred.promise, ret); - }); - } - - function resolve(self, newValue) { - try { - // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure - if (newValue === self) - throw new TypeError('A promise cannot be resolved with itself.'); - if ( - newValue && - (typeof newValue === 'object' || typeof newValue === 'function') - ) { - var then = newValue.then; - if (newValue instanceof Promise) { - self._state = 3; - self._value = newValue; - finale(self); - return; - } else if (typeof then === 'function') { - doResolve(bind(then, newValue), self); - return; - } - } - self._state = 1; - self._value = newValue; - finale(self); - } catch (e) { - reject(self, e); - } - } - - function reject(self, newValue) { - self._state = 2; - self._value = newValue; - finale(self); - } - - function finale(self) { - if (self._state === 2 && self._deferreds.length === 0) { - Promise._immediateFn(function () { - if (!self._handled) { - Promise._unhandledRejectionFn(self._value); - } - }); - } - - for (var i = 0, len = self._deferreds.length; i < len; i++) { - handle(self, self._deferreds[i]); - } - self._deferreds = null; - } - - /** - * @constructor - */ - function Handler(onFulfilled, onRejected, promise) { - this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; - this.onRejected = typeof onRejected === 'function' ? onRejected : null; - this.promise = promise; - } - - /** - * Take a potentially misbehaving resolver function and make sure - * onFulfilled and onRejected are only called once. - * - * Makes no guarantees about asynchrony. - */ - function doResolve(fn, self) { - var done = false; - try { - fn( - function (value) { - if (done) return; - done = true; - resolve(self, value); - }, - function (reason) { - if (done) return; - done = true; - reject(self, reason); - } - ); - } catch (ex) { - if (done) return; - done = true; - reject(self, ex); - } - } - - Promise.prototype['catch'] = function (onRejected) { - return this.then(null, onRejected); - }; - - Promise.prototype.then = function (onFulfilled, onRejected) { - // @ts-ignore - var prom = new this.constructor(noop); - - handle(this, new Handler(onFulfilled, onRejected, prom)); - return prom; - }; - - Promise.prototype['finally'] = finallyConstructor; - - Promise.all = function (arr) { - return new Promise(function (resolve, reject) { - if (!arr || typeof arr.length === 'undefined') - throw new TypeError('Promise.all accepts an array'); - var args = Array.prototype.slice.call(arr); - if (args.length === 0) return resolve([]); - var remaining = args.length; - - function res(i, val) { - try { - if (val && (typeof val === 'object' || typeof val === 'function')) { - var then = val.then; - if (typeof then === 'function') { - then.call( - val, - function (val) { - res(i, val); - }, - reject - ); - return; - } - } - args[i] = val; - if (--remaining === 0) { - resolve(args); - } - } catch (ex) { - reject(ex); - } - } - - for (var i = 0; i < args.length; i++) { - res(i, args[i]); - } - }); - }; - - Promise.resolve = function (value) { - if (value && typeof value === 'object' && value.constructor === Promise) { - return value; - } - - return new Promise(function (resolve) { - resolve(value); - }); - }; - - Promise.reject = function (value) { - return new Promise(function (resolve, reject) { - reject(value); - }); - }; - - Promise.race = function (values) { - return new Promise(function (resolve, reject) { - for (var i = 0, len = values.length; i < len; i++) { - values[i].then(resolve, reject); - } - }); - }; - - // Use polyfill for setImmediate for performance gains - Promise._immediateFn = - (typeof setImmediate === 'function' && - function (fn) { - setImmediate(fn); - }) || - function (fn) { - setTimeoutFunc(fn, 0); - }; - - Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) { - if (typeof console !== 'undefined' && console) { - console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console - } - }; - - /** @suppress {undefinedVars} */ - var globalNS = (function () { - // the only reliable means to get the global object is - // `Function('return this')()` - // However, this causes CSP violations in Chrome apps. - if (typeof self !== 'undefined') { - return self; - } - if (typeof window !== 'undefined') { - return window; - } - if (typeof global !== 'undefined') { - return global; - } - throw new Error('unable to locate global object'); - })(); - - if (!('Promise' in globalNS)) { - globalNS['Promise'] = Promise; - } else if (!globalNS.Promise.prototype['finally']) { - globalNS.Promise.prototype['finally'] = finallyConstructor; - } - -}))); diff --git a/src/vs/editor/common/standalone/standaloneBase.ts b/src/vs/editor/common/standalone/standaloneBase.ts index c92c227e07..1ff1a566c3 100644 --- a/src/vs/editor/common/standalone/standaloneBase.ts +++ b/src/vs/editor/common/standalone/standaloneBase.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/editor/common/standalone/promise-polyfill/polyfill'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { KeyChord, KeyMod as ConstKeyMod } from 'vs/base/common/keyCodes'; diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index 768168124b..57618d02e3 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -45,6 +45,10 @@ export namespace GoToLineNLS { export const gotoLineActionLabel = nls.localize('gotoLineActionLabel', "Go to Line..."); } +export namespace QuickHelpNLS { + export const helpQuickAccessActionLabel = nls.localize('helpQuickAccess', "Show all Quick Access Providers"); +} + export namespace QuickCommandNLS { export const ariaLabelEntryWithKey = nls.localize('ariaLabelEntryWithKey', "{0}, {1}, commands"); export const ariaLabelEntry = nls.localize('ariaLabelEntry', "{0}, commands"); @@ -71,7 +75,6 @@ export namespace QuickOutlineNLS { export namespace StandaloneCodeEditorNLS { export const editorViewAccessibleLabel = nls.localize('editorViewAccessibleLabel', "Editor content"); - export const accessibilityHelpMessageIE = nls.localize('accessibilityHelpMessageIE', "Press Ctrl+F1 for Accessibility Options."); export const accessibilityHelpMessage = nls.localize('accessibilityHelpMessage', "Press Alt+F1 for Accessibility Options."); } diff --git a/src/vs/editor/common/view/viewContext.ts b/src/vs/editor/common/view/viewContext.ts index 3e31d7a1fc..a9d970f222 100644 --- a/src/vs/editor/common/view/viewContext.ts +++ b/src/vs/editor/common/view/viewContext.ts @@ -7,23 +7,23 @@ import { IConfiguration } from 'vs/editor/common/editorCommon'; import { ViewEventDispatcher } from 'vs/editor/common/view/viewEventDispatcher'; import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; import { IViewLayout, IViewModel } from 'vs/editor/common/viewModel/viewModel'; -import { ITheme, ThemeType } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, ThemeType } from 'vs/platform/theme/common/themeService'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { Color } from 'vs/base/common/color'; export class EditorTheme { - private _theme: ITheme; + private _theme: IColorTheme; public get type(): ThemeType { return this._theme.type; } - constructor(theme: ITheme) { + constructor(theme: IColorTheme) { this._theme = theme; } - public update(theme: ITheme): void { + public update(theme: IColorTheme): void { this._theme = theme; } @@ -42,7 +42,7 @@ export class ViewContext { constructor( configuration: IConfiguration, - theme: ITheme, + theme: IColorTheme, model: IViewModel, privateViewEventBus: ViewEventDispatcher ) { diff --git a/src/vs/editor/common/view/viewEvents.ts b/src/vs/editor/common/view/viewEvents.ts index 83553f44c7..5378c2db92 100644 --- a/src/vs/editor/common/view/viewEvents.ts +++ b/src/vs/editor/common/view/viewEvents.ts @@ -205,7 +205,12 @@ export class ViewRevealRangeRequestEvent { /** * Range to be reavealed. */ - public readonly range: Range; + public readonly range: Range | null; + + /** + * Selections to be revealed. + */ + public readonly selections: Selection[] | null; public readonly verticalType: VerticalRevealType; /** @@ -221,9 +226,10 @@ export class ViewRevealRangeRequestEvent { */ readonly source: string; - constructor(source: string, range: Range, verticalType: VerticalRevealType, revealHorizontal: boolean, scrollType: ScrollType) { + constructor(source: string, range: Range | null, selections: Selection[] | null, verticalType: VerticalRevealType, revealHorizontal: boolean, scrollType: ScrollType) { this.source = source; this.range = range; + this.selections = selections; this.verticalType = verticalType; this.revealHorizontal = revealHorizontal; this.scrollType = scrollType; diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index 866fc65dc8..aecc2483eb 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -683,7 +683,7 @@ function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len wasInWhitespace = isInWhitespace; - if (charIndex === tokenEndIndex) { + while (charIndex === tokenEndIndex) { tokenIndex++; if (tokenIndex < tokensLength) { tokenType = tokens[tokenIndex].type; diff --git a/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts b/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts index 188058756e..68616df131 100644 --- a/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts +++ b/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts @@ -34,7 +34,7 @@ suite('bracket matching', () => { let model = createTextModel('var x = (3 + (5-7)) + ((5+3)+5);', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); + let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); // start on closing bracket editor.setPosition(new Position(1, 20)); @@ -66,7 +66,7 @@ suite('bracket matching', () => { let model = createTextModel('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); + let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); // start position between brackets editor.setPosition(new Position(1, 16)); @@ -103,7 +103,7 @@ suite('bracket matching', () => { let model = createTextModel('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); + let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); // start position in open brackets @@ -155,7 +155,7 @@ suite('bracket matching', () => { const model = createTextModel(text, undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - const bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); + const bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); editor.setPosition(new Position(3, 5)); bracketMatchingController.jumpToBracket(); @@ -180,7 +180,7 @@ suite('bracket matching', () => { const model = createTextModel(text, undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - const bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); + const bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); editor.setPosition(new Position(3, 5)); bracketMatchingController.selectToBracket(false); @@ -198,7 +198,7 @@ suite('bracket matching', () => { let model = createTextModel('{ } { } { }', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); + let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); // cursors inside brackets become selections of the entire bracket contents editor.setSelections([ diff --git a/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts b/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts index 59e7c05a0d..f97b21385f 100644 --- a/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts +++ b/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts @@ -13,66 +13,43 @@ export class MoveCaretCommand implements ICommand { private readonly _selection: Selection; private readonly _isMovingLeft: boolean; - private _cutStartIndex: number; - private _cutEndIndex: number; - private _moved: boolean; - - private _selectionId: string | null; - constructor(selection: Selection, isMovingLeft: boolean) { this._selection = selection; this._isMovingLeft = isMovingLeft; - this._cutStartIndex = -1; - this._cutEndIndex = -1; - this._moved = false; - this._selectionId = null; } public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { - let s = this._selection; - this._selectionId = builder.trackSelection(s); - if (s.startLineNumber !== s.endLineNumber) { + if (this._selection.startLineNumber !== this._selection.endLineNumber || this._selection.isEmpty()) { return; } - if (this._isMovingLeft && s.startColumn === 0) { - return; - } else if (!this._isMovingLeft && s.endColumn === model.getLineMaxColumn(s.startLineNumber)) { + const lineNumber = this._selection.startLineNumber; + const startColumn = this._selection.startColumn; + const endColumn = this._selection.endColumn; + if (this._isMovingLeft && startColumn === 1) { + return; + } + if (!this._isMovingLeft && endColumn === model.getLineMaxColumn(lineNumber)) { return; } - - let lineNumber = s.selectionStartLineNumber; - let lineContent = model.getLineContent(lineNumber); - - let left: string; - let middle: string; - let right: string; if (this._isMovingLeft) { - left = lineContent.substring(0, s.startColumn - 2); - middle = lineContent.substring(s.startColumn - 1, s.endColumn - 1); - right = lineContent.substring(s.startColumn - 2, s.startColumn - 1) + lineContent.substring(s.endColumn - 1); + const rangeBefore = new Range(lineNumber, startColumn - 1, lineNumber, startColumn); + const charBefore = model.getValueInRange(rangeBefore); + builder.addEditOperation(rangeBefore, null); + builder.addEditOperation(new Range(lineNumber, endColumn, lineNumber, endColumn), charBefore); } else { - left = lineContent.substring(0, s.startColumn - 1) + lineContent.substring(s.endColumn - 1, s.endColumn); - middle = lineContent.substring(s.startColumn - 1, s.endColumn - 1); - right = lineContent.substring(s.endColumn); + const rangeAfter = new Range(lineNumber, endColumn, lineNumber, endColumn + 1); + const charAfter = model.getValueInRange(rangeAfter); + builder.addEditOperation(rangeAfter, null); + builder.addEditOperation(new Range(lineNumber, startColumn, lineNumber, startColumn), charAfter); } - - let newLineContent = left + middle + right; - - builder.addEditOperation(new Range(lineNumber, 1, lineNumber, model.getLineMaxColumn(lineNumber)), null); - builder.addEditOperation(new Range(lineNumber, 1, lineNumber, 1), newLineContent); - - this._cutStartIndex = s.startColumn + (this._isMovingLeft ? -1 : 1); - this._cutEndIndex = this._cutStartIndex + s.endColumn - s.startColumn; - this._moved = true; } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - let result = helper.getTrackedSelection(this._selectionId!); - if (this._moved) { - result = result.setStartPosition(result.startLineNumber, this._cutStartIndex); - result = result.setEndPosition(result.startLineNumber, this._cutEndIndex); + if (this._isMovingLeft) { + return new Selection(this._selection.startLineNumber, this._selection.startColumn - 1, this._selection.endLineNumber, this._selection.endColumn - 1); + } else { + return new Selection(this._selection.startLineNumber, this._selection.startColumn + 1, this._selection.endLineNumber, this._selection.endColumn + 1); } - return result; } } diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index 7b3d5dc929..69ace7010b 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -23,7 +23,7 @@ const CLIPBOARD_CONTEXT_MENU_GROUP = '9_cutcopypaste'; const supportsCut = (platform.isNative || document.queryCommandSupported('cut')); const supportsCopy = (platform.isNative || document.queryCommandSupported('copy')); // IE and Edge have trouble with setting html content in clipboard -const supportsCopyWithSyntaxHighlighting = (supportsCopy && !browser.isEdgeOrIE); +const supportsCopyWithSyntaxHighlighting = (supportsCopy && !browser.isEdge); // Chrome incorrectly returns true for document.queryCommandSupported('paste') // when the paste feature is available but the calling script has insufficient // privileges to actually perform the action diff --git a/src/vs/editor/contrib/codeAction/codeActionUi.ts b/src/vs/editor/contrib/codeAction/codeActionUi.ts index cb550d8827..8c16a25ca5 100644 --- a/src/vs/editor/contrib/codeAction/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/codeActionUi.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; -import { find } from 'vs/base/common/arrays'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; @@ -123,7 +122,7 @@ export class CodeActionUi extends Disposable { if ((trigger.autoApply === CodeActionAutoApply.First && actions.validActions.length === 0) || (trigger.autoApply === CodeActionAutoApply.IfSingle && actions.allActions.length === 1) ) { - return find(actions.allActions, action => action.disabled); + return actions.allActions.find(action => action.disabled); } return undefined; diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts index e63f5428d6..3e789123a6 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts @@ -15,7 +15,7 @@ import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; import * as nls from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; import { Gesture } from 'vs/base/browser/touch'; import type { CodeActionTrigger } from 'vs/editor/contrib/codeAction/types'; @@ -222,7 +222,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Lightbulb Icon const editorLightBulbForegroundColor = theme.getColor(editorLightBulbForeground); diff --git a/src/vs/editor/contrib/codelens/codeLensCache.ts b/src/vs/editor/contrib/codelens/codeLensCache.ts index b2ba7b2753..af4c0b2afc 100644 --- a/src/vs/editor/contrib/codelens/codeLensCache.ts +++ b/src/vs/editor/contrib/codelens/codeLensCache.ts @@ -7,7 +7,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { CodeLensModel } from 'vs/editor/contrib/codelens/codelens'; -import { LRUCache, values } from 'vs/base/common/map'; +import { LRUCache } from 'vs/base/common/map'; import { CodeLensProvider, CodeLensList, CodeLens } from 'vs/editor/common/modes'; import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { Range } from 'vs/editor/common/core/range'; @@ -103,7 +103,7 @@ export class CodeLensCache implements ICodeLensCache { } data[key] = { lineCount: value.lineCount, - lines: values(lines) + lines: [...lines.values()] }; }); return JSON.stringify(data); diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 5e300b45a5..c42d97bce7 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -8,10 +8,10 @@ import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/err import { toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { StableEditorScrollState } from 'vs/editor/browser/core/editorState'; import { ICodeEditor, MouseTargetType, IViewZoneChangeAccessor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { registerEditorContribution, ServicesAccessor, registerEditorAction, EditorAction } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; -import { CodeLensProviderRegistry, CodeLens } from 'vs/editor/common/modes'; +import { CodeLensProviderRegistry, CodeLens, Command } from 'vs/editor/common/modes'; import { CodeLensModel, getCodeLensData, CodeLensItem } from 'vs/editor/contrib/codelens/codelens'; import { CodeLensWidget, CodeLensHelper } from 'vs/editor/contrib/codelens/codelensWidget'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -20,6 +20,9 @@ import { ICodeLensCache } from 'vs/editor/contrib/codelens/codeLensCache'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import * as dom from 'vs/base/browser/dom'; import { hash } from 'vs/base/common/hash'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { localize } from 'vs/nls'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; export class CodeLensContribution implements IEditorContribution { @@ -402,6 +405,70 @@ export class CodeLensContribution implements IEditorContribution { } }); } + + getLenses(): readonly CodeLensWidget[] { + return this._lenses; + } } registerEditorContribution(CodeLensContribution.ID, CodeLensContribution); + +registerEditorAction(class ShowLensesInCurrentLine extends EditorAction { + + constructor() { + super({ + id: 'codelens.showLensesInCurrentLine', + precondition: EditorContextKeys.hasCodeLensProvider, + label: localize('showLensOnLine', "Show Code Lens Command For Current Line"), + alias: 'Show Code Lens Commands For Current Line', + }); + } + + async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + + if (!editor.hasModel()) { + return; + } + + const quickInputService = accessor.get(IQuickInputService); + const commandService = accessor.get(ICommandService); + const notificationService = accessor.get(INotificationService); + + const lineNumber = editor.getSelection().positionLineNumber; + const codelensController = editor.getContribution(CodeLensContribution.ID); + const items: { label: string, command: Command }[] = []; + + for (let lens of codelensController.getLenses()) { + if (lens.getLineNumber() === lineNumber) { + for (let item of lens.getItems()) { + const { command } = item.symbol; + if (command) { + items.push({ + label: command.title, + command: command + }); + } + } + } + } + + if (items.length === 0) { + // We dont want an empty picker + return; + } + + const item = await quickInputService.pick(items, { canPickMany: false }); + if (!item) { + // Nothing picked + return; + } + + try { + await commandService.executeCommand(item.command.id, ...(item.command.arguments || [])); + } catch (err) { + notificationService.error(err); + } + } +}); + + diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index f98241e5b1..4899f14190 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -336,6 +336,10 @@ export class CodeLensWidget { } } } + + getItems(): CodeLensItem[] { + return this._data; + } } registerThemingParticipant((theme, collector) => { diff --git a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts index e05d43aee2..a38a3de9bd 100644 --- a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts @@ -34,7 +34,7 @@ export class ColorPickerHeader extends Disposable { const colorBox = dom.append(this.domNode, $('.original-color')); colorBox.style.backgroundColor = Color.Format.CSS.format(this.model.originalColor) || ''; - this.backgroundColor = themeService.getTheme().getColor(editorHoverBackground) || Color.white; + this.backgroundColor = themeService.getColorTheme().getColor(editorHoverBackground) || Color.white; this._register(registerThemingParticipant((theme, collector) => { this.backgroundColor = theme.getColor(editorHoverBackground) || Color.white; })); diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index 26e6c58b92..8f5be59175 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -19,7 +19,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { OutlineConfigKeys } from 'vs/editor/contrib/documentSymbols/outline'; import { MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { registerColor, listErrorForeground, listWarningForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; import { IdleValue } from 'vs/base/common/async'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; @@ -150,7 +150,7 @@ export class OutlineElementRenderer implements ITreeRenderer { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const symbolIconArrayColor = theme.getColor(SYMBOL_ICON_ARRAY_FOREGROUND); if (symbolIconArrayColor) { diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index 94aa4e5776..10058d5c10 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -39,7 +39,7 @@ export function getSelectionSearchString(editor: ICodeEditor): string | null { // if selection spans multiple lines, default search string to empty if (selection.startLineNumber === selection.endLineNumber) { if (selection.isEmpty()) { - let wordAtPosition = editor.getModel().getWordAtPosition(selection.getStartPosition()); + const wordAtPosition = editor.getConfiguredWordAtPosition(selection.getStartPosition()); if (wordAtPosition) { return wordAtPosition.word; } diff --git a/src/vs/editor/contrib/find/findModel.ts b/src/vs/editor/contrib/find/findModel.ts index 8f09657907..7e4511d705 100644 --- a/src/vs/editor/contrib/find/findModel.ts +++ b/src/vs/editor/contrib/find/findModel.ts @@ -20,12 +20,12 @@ import { FindDecorations } from 'vs/editor/contrib/find/findDecorations'; import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState'; import { ReplaceAllCommand } from 'vs/editor/contrib/find/replaceAllCommand'; import { ReplacePattern, parseReplaceString } from 'vs/editor/contrib/find/replacePattern'; -import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; export const CONTEXT_FIND_WIDGET_VISIBLE = new RawContextKey('findWidgetVisible', false); -export const CONTEXT_FIND_WIDGET_NOT_VISIBLE: ContextKeyExpr = CONTEXT_FIND_WIDGET_VISIBLE.toNegated(); +export const CONTEXT_FIND_WIDGET_NOT_VISIBLE = CONTEXT_FIND_WIDGET_VISIBLE.toNegated(); // Keep ContextKey use of 'Focussed' to not break when clauses export const CONTEXT_FIND_INPUT_FOCUSED = new RawContextKey('findInputFocussed', false); export const CONTEXT_REPLACE_INPUT_FOCUSED = new RawContextKey('replaceInputFocussed', false); diff --git a/src/vs/editor/contrib/find/findOptionsWidget.ts b/src/vs/editor/contrib/find/findOptionsWidget.ts index a74bd30cd6..ee42cffae4 100644 --- a/src/vs/editor/contrib/find/findOptionsWidget.ts +++ b/src/vs/editor/contrib/find/findOptionsWidget.ts @@ -12,7 +12,7 @@ import { FIND_IDS } from 'vs/editor/contrib/find/findModel'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { contrastBorder, editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; export class FindOptionsWidget extends Widget implements IOverlayWidget { @@ -46,8 +46,8 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this._domNode.setAttribute('role', 'presentation'); this._domNode.setAttribute('aria-hidden', 'true'); - const inputActiveOptionBorderColor = themeService.getTheme().getColor(inputActiveOptionBorder); - const inputActiveOptionBackgroundColor = themeService.getTheme().getColor(inputActiveOptionBackground); + const inputActiveOptionBorderColor = themeService.getColorTheme().getColor(inputActiveOptionBorder); + const inputActiveOptionBackgroundColor = themeService.getColorTheme().getColor(inputActiveOptionBackground); this.caseSensitive = this._register(new CaseSensitiveCheckbox({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand), @@ -112,8 +112,8 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this._register(dom.addDisposableNonBubblingMouseOutListener(this._domNode, (e) => this._onMouseOut())); this._register(dom.addDisposableListener(this._domNode, 'mouseover', (e) => this._onMouseOver())); - this._applyTheme(themeService.getTheme()); - this._register(themeService.onThemeChange(this._applyTheme.bind(this))); + this._applyTheme(themeService.getColorTheme()); + this._register(themeService.onDidColorThemeChange(this._applyTheme.bind(this))); } private _keybindingLabelFor(actionId: string): string { @@ -182,7 +182,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this._domNode.style.display = 'none'; } - private _applyTheme(theme: ITheme) { + private _applyTheme(theme: IColorTheme) { let inputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground) diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 2bdf4ad478..0164b77162 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -31,7 +31,7 @@ import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contri import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { contrastBorder, editorFindMatch, editorFindMatchBorder, editorFindMatchHighlight, editorFindMatchHighlightBorder, editorFindRangeHighlight, editorFindRangeHighlightBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetResizeBorder, errorForeground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground, focusBorder } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -244,8 +244,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._viewZone = new FindWidgetViewZone(0); // Put it before the first line then users can scroll beyond the first line. } - this._applyTheme(themeService.getTheme()); - this._register(themeService.onThemeChange(this._applyTheme.bind(this))); + this._applyTheme(themeService.getColorTheme()); + this._register(themeService.onDidColorThemeChange(this._applyTheme.bind(this))); this._register(this._codeEditor.onDidChangeModel(() => { if (!this._isVisible) { @@ -643,7 +643,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas }); } - private _applyTheme(theme: ITheme) { + private _applyTheme(theme: IColorTheme) { let inputStyles: IFindInputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground), diff --git a/src/vs/editor/contrib/find/test/findController.test.ts b/src/vs/editor/contrib/find/test/findController.test.ts index 2d580562bd..175bd9c3fb 100644 --- a/src/vs/editor/contrib/find/test/findController.test.ts +++ b/src/vs/editor/contrib/find/test/findController.test.ts @@ -89,7 +89,7 @@ suite('FindController', () => { assert.ok(true); return; } - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); // I select ABC on the first line editor.setSelection(new Selection(1, 1, 1, 4)); @@ -115,7 +115,7 @@ suite('FindController', () => { return; } - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let findState = findController.getState(); let nextMatchFindAction = new NextMatchFindAction(); @@ -141,7 +141,7 @@ suite('FindController', () => { return; } - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let findState = findController.getState(); findState.change({ searchString: 'ABC' }, true); @@ -161,7 +161,7 @@ suite('FindController', () => { ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; // The cursor is at the very top, of the file, at the first ABC - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let findState = findController.getState(); let startFindAction = new StartFindAction(); let nextMatchFindAction = new NextMatchFindAction(); @@ -215,7 +215,7 @@ suite('FindController', () => { 'import nls = require(\'vs/nls\');' ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let nextMatchFindAction = new NextMatchFindAction(); editor.setPosition({ @@ -240,7 +240,7 @@ suite('FindController', () => { 'var z = (3 * 5)', ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); let nextMatchFindAction = new NextMatchFindAction(); @@ -264,7 +264,7 @@ suite('FindController', () => { 'test', ], { serviceCollection: serviceCollection }, (editor, cursor) => { let testRegexString = 'tes.'; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let nextMatchFindAction = new NextMatchFindAction(); let startFindReplaceAction = new StartFindReplaceAction(); @@ -294,7 +294,7 @@ suite('FindController', () => { 'var z = (3 * 5)', ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.start({ forceRevealReplace: false, seedSearchStringFromSelection: false, @@ -322,7 +322,7 @@ suite('FindController', () => { 'HRESULT OnAmbientPropertyChange(DISPID dispid);' ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); startFindAction.run(null, editor); @@ -349,7 +349,7 @@ suite('FindController', () => { 'line3' ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); startFindAction.run(null, editor); @@ -376,7 +376,7 @@ suite('FindController', () => { '([funny]' ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let nextSelectionMatchFindAction = new NextSelectionMatchFindAction(); // toggle regex @@ -403,7 +403,7 @@ suite('FindController', () => { '([funny]' ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); let nextSelectionMatchFindAction = new NextSelectionMatchFindAction(); @@ -454,7 +454,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection }, (editor, cursor) => { queryState = { 'editor.isRegex': false, 'editor.matchCase': true, 'editor.wholeWord': false }; // The cursor is at the very top, of the file, at the first ABC - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let findState = findController.getState(); let startFindAction = new StartFindAction(); @@ -481,7 +481,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection }, (editor, cursor) => { queryState = { 'editor.isRegex': false, 'editor.matchCase': false, 'editor.wholeWord': true }; // The cursor is at the very top, of the file, at the first ABC - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let findState = findController.getState(); let startFindAction = new StartFindAction(); @@ -506,7 +506,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection }, (editor, cursor) => { queryState = { 'editor.isRegex': false, 'editor.matchCase': false, 'editor.wholeWord': true }; // The cursor is at the very top, of the file, at the first ABC - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.toggleRegex(); assert.equal(queryState['editor.isRegex'], true); @@ -522,7 +522,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 1, 2, 1)); - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.start({ forceRevealReplace: false, @@ -545,7 +545,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 2, 1, 2)); - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.start({ forceRevealReplace: false, @@ -568,7 +568,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 2, 1, 3)); - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.start({ forceRevealReplace: false, @@ -592,7 +592,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'multiline', globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 6, 2, 1)); - let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.start({ forceRevealReplace: false, diff --git a/src/vs/editor/contrib/folding/foldingDecorations.ts b/src/vs/editor/contrib/folding/foldingDecorations.ts index 28224b35c2..e82d5367e7 100644 --- a/src/vs/editor/contrib/folding/foldingDecorations.ts +++ b/src/vs/editor/contrib/folding/foldingDecorations.ts @@ -13,7 +13,8 @@ export class FoldingDecorationProvider implements IDecorationProvider { private static readonly COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, afterContentClassName: 'inline-folded', - linesDecorationsClassName: 'codicon codicon-chevron-right' + isWholeLine: true, + firstLineDecorationClassName: 'codicon codicon-chevron-right' }); private static readonly COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION = ModelDecorationOptions.register({ @@ -21,17 +22,23 @@ export class FoldingDecorationProvider implements IDecorationProvider { afterContentClassName: 'inline-folded', className: 'folded-background', isWholeLine: true, - linesDecorationsClassName: 'codicon codicon-chevron-right' + firstLineDecorationClassName: 'codicon codicon-chevron-right' }); private static readonly EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - linesDecorationsClassName: 'codicon codicon-chevron-down' + isWholeLine: true, + firstLineDecorationClassName: 'codicon codicon-chevron-down' }); private static readonly EXPANDED_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - linesDecorationsClassName: 'codicon codicon-chevron-down alwaysShowFoldIcons' + isWholeLine: true, + firstLineDecorationClassName: 'codicon codicon-chevron-down alwaysShowFoldIcons' + }); + + private static readonly HIDDEN_RANGE_DECORATION = ModelDecorationOptions.register({ + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }); public autoHideFoldingControls: boolean = true; @@ -41,7 +48,10 @@ export class FoldingDecorationProvider implements IDecorationProvider { constructor(private readonly editor: ICodeEditor) { } - getDecorationOption(isCollapsed: boolean): ModelDecorationOptions { + getDecorationOption(isCollapsed: boolean, isHidden: boolean): ModelDecorationOptions { + if (isHidden) { + return FoldingDecorationProvider.HIDDEN_RANGE_DECORATION; + } if (isCollapsed) { return this.showFoldingHighlights ? FoldingDecorationProvider.COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION : FoldingDecorationProvider.COLLAPSED_VISUAL_DECORATION; } else if (this.autoHideFoldingControls) { diff --git a/src/vs/editor/contrib/folding/foldingModel.ts b/src/vs/editor/contrib/folding/foldingModel.ts index 74e9b4cea3..dbb58195e3 100644 --- a/src/vs/editor/contrib/folding/foldingModel.ts +++ b/src/vs/editor/contrib/folding/foldingModel.ts @@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { FoldingRegions, ILineRange, FoldingRegion } from './foldingRanges'; export interface IDecorationProvider { - getDecorationOption(isCollapsed: boolean): IModelDecorationOptions; + getDecorationOption(isCollapsed: boolean, isHidden: boolean): IModelDecorationOptions; deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[]; changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null; } @@ -34,6 +34,7 @@ export class FoldingModel { public get regions(): FoldingRegions { return this._regions; } public get textModel() { return this._textModel; } public get isInitialized() { return this._isInitialized; } + public get decorationProvider() { return this._decorationProvider; } constructor(textModel: ITextModel, decorationProvider: IDecorationProvider) { this._textModel = textModel; @@ -43,24 +44,47 @@ export class FoldingModel { this._isInitialized = false; } - public toggleCollapseState(regions: FoldingRegion[]) { - if (!regions.length) { + public toggleCollapseState(toggledRegions: FoldingRegion[]) { + if (!toggledRegions.length) { return; } - let processed: { [key: string]: boolean | undefined } = {}; + toggledRegions = toggledRegions.sort((r1, r2) => r1.regionIndex - r2.regionIndex); + + const processed: { [key: string]: boolean | undefined } = {}; this._decorationProvider.changeDecorations(accessor => { - for (let region of regions) { + let k = 0; // index from [0 ... this.regions.length] + let dirtyRegionEndLine = -1; // end of the range where decorations need to be updated + let lastHiddenLine = -1; // the end of the last hidden lines + const updateDecorationsUntil = (index: number) => { + while (k < index) { + const endLineNumber = this._regions.getEndLineNumber(k); + const isCollapsed = this._regions.isCollapsed(k); + if (endLineNumber <= dirtyRegionEndLine) { + accessor.changeDecorationOptions(this._editorDecorationIds[k], this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine)); + } + if (isCollapsed && endLineNumber > lastHiddenLine) { + lastHiddenLine = endLineNumber; + } + k++; + } + }; + for (let region of toggledRegions) { let index = region.regionIndex; let editorDecorationId = this._editorDecorationIds[index]; if (editorDecorationId && !processed[editorDecorationId]) { processed[editorDecorationId] = true; + + updateDecorationsUntil(index); // update all decorations up to current index using the old dirtyRegionEndLine + let newCollapseState = !this._regions.isCollapsed(index); this._regions.setCollapsed(index, newCollapseState); - accessor.changeDecorationOptions(editorDecorationId, this._decorationProvider.getDecorationOption(newCollapseState)); + + dirtyRegionEndLine = Math.max(dirtyRegionEndLine, this._regions.getEndLineNumber(index)); } } + updateDecorationsUntil(this._regions.length); }); - this._updateEventEmitter.fire({ model: this, collapseStateChanged: regions }); + this._updateEventEmitter.fire({ model: this, collapseStateChanged: toggledRegions }); } public update(newRegions: FoldingRegions, blockedLineNumers: number[] = []): void { @@ -75,20 +99,27 @@ export class FoldingModel { return false; }; + let lastHiddenLine = -1; + let initRange = (index: number, isCollapsed: boolean) => { - let startLineNumber = newRegions.getStartLineNumber(index); - if (isCollapsed && isBlocked(startLineNumber, newRegions.getEndLineNumber(index))) { + const startLineNumber = newRegions.getStartLineNumber(index); + const endLineNumber = newRegions.getEndLineNumber(index); + if (isCollapsed && isBlocked(startLineNumber, endLineNumber)) { isCollapsed = false; } newRegions.setCollapsed(index, isCollapsed); - let maxColumn = this._textModel.getLineMaxColumn(startLineNumber); - let decorationRange = { + + const maxColumn = this._textModel.getLineMaxColumn(startLineNumber); + const decorationRange = { startLineNumber: startLineNumber, startColumn: maxColumn, endLineNumber: startLineNumber, endColumn: maxColumn }; - newEditorDecorations.push({ range: decorationRange, options: this._decorationProvider.getDecorationOption(isCollapsed) }); + newEditorDecorations.push({ range: decorationRange, options: this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine) }); + if (isCollapsed && endLineNumber > lastHiddenLine) { + lastHiddenLine = endLineNumber; + } }; let i = 0; let nextCollapsed = () => { @@ -318,7 +349,7 @@ export function setCollapseStateLevelsUp(foldingModel: FoldingModel, doCollapse: export function setCollapseStateUp(foldingModel: FoldingModel, doCollapse: boolean, lineNumbers: number[]): void { let toToggle: FoldingRegion[] = []; for (let lineNumber of lineNumbers) { - let regions = foldingModel.getAllRegionsAtLine(lineNumber, (region, ) => region.isCollapsed !== doCollapse); + let regions = foldingModel.getAllRegionsAtLine(lineNumber, (region,) => region.isCollapsed !== doCollapse); if (regions.length > 0) { toToggle.push(regions[0]); } diff --git a/src/vs/editor/contrib/folding/test/foldingModel.test.ts b/src/vs/editor/contrib/folding/test/foldingModel.test.ts index 6786a9c18c..b12f3fa2d6 100644 --- a/src/vs/editor/contrib/folding/test/foldingModel.test.ts +++ b/src/vs/editor/contrib/folding/test/foldingModel.test.ts @@ -21,9 +21,24 @@ interface ExpectedRegion { isCollapsed: boolean; } +interface ExpectedDecoration { + line: number; + type: 'hidden' | 'collapsed' | 'expanded'; +} + export class TestDecorationProvider { - private testDecorator = ModelDecorationOptions.register({ + private static readonly collapsedDecoration = ModelDecorationOptions.register({ + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + linesDecorationsClassName: 'folding' + }); + + private static readonly expandedDecoration = ModelDecorationOptions.register({ + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + linesDecorationsClassName: 'folding' + }); + + private static readonly hiddenDecoration = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, linesDecorationsClassName: 'folding' }); @@ -31,8 +46,14 @@ export class TestDecorationProvider { constructor(private model: ITextModel) { } - getDecorationOption(isCollapsed: boolean): ModelDecorationOptions { - return this.testDecorator; + getDecorationOption(isCollapsed: boolean, isHidden: boolean): ModelDecorationOptions { + if (isHidden) { + return TestDecorationProvider.hiddenDecoration; + } + if (isCollapsed) { + return TestDecorationProvider.collapsedDecoration; + } + return TestDecorationProvider.expandedDecoration; } deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[] { @@ -42,6 +63,21 @@ export class TestDecorationProvider { changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): (T | null) { return this.model.changeDecorations(callback); } + + getDecorations(): ExpectedDecoration[] { + const decorations = this.model.getAllDecorations(); + const res: ExpectedDecoration[] = []; + for (let decoration of decorations) { + if (decoration.options === TestDecorationProvider.hiddenDecoration) { + res.push({ line: decoration.range.startLineNumber, type: 'hidden' }); + } else if (decoration.options === TestDecorationProvider.collapsedDecoration) { + res.push({ line: decoration.range.startLineNumber, type: 'collapsed' }); + } else if (decoration.options === TestDecorationProvider.expandedDecoration) { + res.push({ line: decoration.range.startLineNumber, type: 'expanded' }); + } + } + return res; + } } suite('Folding Model', () => { @@ -49,6 +85,10 @@ suite('Folding Model', () => { return { startLineNumber, endLineNumber, isCollapsed }; } + function d(line: number, type: 'hidden' | 'collapsed' | 'expanded'): ExpectedDecoration { + return { line, type }; + } + function assertRegion(actual: FoldingRegion | null, expected: ExpectedRegion | null, message?: string) { assert.equal(!!actual, !!expected, message); if (actual && expected) { @@ -78,6 +118,11 @@ suite('Folding Model', () => { assert.deepEqual(actualRanges, expectedRegions, message); } + function assertDecorations(foldingModel: FoldingModel, expectedDecoration: ExpectedDecoration[], message?: string) { + const decorationProvider = foldingModel.decorationProvider as TestDecorationProvider; + assert.deepEqual(decorationProvider.getDecorations(), expectedDecoration, message); + } + function assertRegions(actual: FoldingRegion[], expectedRegions: ExpectedRegion[], message?: string) { assert.deepEqual(actual.map(r => ({ startLineNumber: r.startLineNumber, endLineNumber: r.endLineNumber, isCollapsed: r.isCollapsed })), expectedRegions, message); } @@ -672,4 +717,65 @@ suite('Folding Model', () => { }); + test('folding decoration', () => { + let lines = [ + /* 1*/ 'class A {', + /* 2*/ ' void foo() {', + /* 3*/ ' if (true) {', + /* 4*/ ' hoo();', + /* 5*/ ' }', + /* 6*/ ' }', + /* 7*/ '}']; + + let textModel = createTextModel(lines.join('\n')); + try { + let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); + + let ranges = computeRanges(textModel, false, undefined); + foldingModel.update(ranges); + + let r1 = r(1, 6, false); + let r2 = r(2, 5, false); + let r3 = r(3, 4, false); + + assertRanges(foldingModel, [r1, r2, r3]); + assertDecorations(foldingModel, [d(1, 'expanded'), d(2, 'expanded'), d(3, 'expanded')]); + + foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(2)!]); + + assertRanges(foldingModel, [r1, r(2, 5, true), r3]); + assertDecorations(foldingModel, [d(1, 'expanded'), d(2, 'collapsed'), d(3, 'hidden')]); + + foldingModel.update(ranges); + + assertRanges(foldingModel, [r1, r(2, 5, true), r3]); + assertDecorations(foldingModel, [d(1, 'expanded'), d(2, 'collapsed'), d(3, 'hidden')]); + + foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(1)!]); + + assertRanges(foldingModel, [r(1, 6, true), r(2, 5, true), r3]); + assertDecorations(foldingModel, [d(1, 'collapsed'), d(2, 'hidden'), d(3, 'hidden')]); + + foldingModel.update(ranges); + + assertRanges(foldingModel, [r(1, 6, true), r(2, 5, true), r3]); + assertDecorations(foldingModel, [d(1, 'collapsed'), d(2, 'hidden'), d(3, 'hidden')]); + + foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(1)!, foldingModel.getRegionAtLine(3)!]); + + assertRanges(foldingModel, [r1, r(2, 5, true), r(3, 4, true)]); + assertDecorations(foldingModel, [d(1, 'expanded'), d(2, 'collapsed'), d(3, 'hidden')]); + + foldingModel.update(ranges); + + assertRanges(foldingModel, [r1, r(2, 5, true), r(3, 4, true)]); + assertDecorations(foldingModel, [d(1, 'expanded'), d(2, 'collapsed'), d(3, 'hidden')]); + + textModel.dispose(); + } finally { + textModel.dispose(); + } + + }); + }); diff --git a/src/vs/editor/contrib/format/format.ts b/src/vs/editor/contrib/format/format.ts index e9372dbfe6..f186036985 100644 --- a/src/vs/editor/contrib/format/format.ts +++ b/src/vs/editor/contrib/format/format.ts @@ -28,6 +28,7 @@ import { LinkedList } from 'vs/base/common/linkedList'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { assertType } from 'vs/base/common/types'; import { IProgress } from 'vs/platform/progress/common/progress'; +import { Iterable } from 'vs/base/common/iterator'; export function alertFormattingEdits(edits: ISingleEditOperation[]): void { @@ -111,7 +112,7 @@ export abstract class FormattingConflicts { if (formatter.length === 0) { return undefined; } - const { value: selector } = FormattingConflicts._selectors.iterator().next(); + const selector = Iterable.first(FormattingConflicts._selectors); if (selector) { return await selector(formatter, document, mode); } diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 0b50b65649..b7684dbdb7 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -12,7 +12,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerColor, oneOf, textLinkForeground, editorErrorForeground, editorErrorBorder, editorWarningForeground, editorWarningBorder, editorInfoForeground, editorInfoBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IThemeService, ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; @@ -246,13 +246,13 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._severity = MarkerSeverity.Warning; this._backgroundColor = Color.white; - this._applyTheme(_themeService.getTheme()); - this._callOnDispose.add(_themeService.onThemeChange(this._applyTheme.bind(this))); + this._applyTheme(_themeService.getColorTheme()); + this._callOnDispose.add(_themeService.onDidColorThemeChange(this._applyTheme.bind(this))); this.create(); } - private _applyTheme(theme: ITheme) { + private _applyTheme(theme: IColorTheme) { this._backgroundColor = theme.getColor(editorMarkerNavigationBackground); let colorId = editorMarkerNavigationError; if (this._severity === MarkerSeverity.Warning) { @@ -327,7 +327,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { // update frame color (only applied on 'show') this._severity = marker.severity; - this._applyTheme(this._themeService.getTheme()); + this._applyTheme(this._themeService.getColorTheme()); // show let range = Range.lift(marker); diff --git a/src/vs/editor/contrib/gotoSymbol/link/clickLinkGesture.ts b/src/vs/editor/contrib/gotoSymbol/link/clickLinkGesture.ts index 4c3252ea7a..7585948eb7 100644 --- a/src/vs/editor/contrib/gotoSymbol/link/clickLinkGesture.ts +++ b/src/vs/editor/contrib/gotoSymbol/link/clickLinkGesture.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { KeyCode } from 'vs/base/common/keyCodes'; -import * as browser from 'vs/base/browser/browser'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ICodeEditor, IEditorMouseEvent, IMouseTarget } from 'vs/editor/browser/editorBrowser'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -31,7 +30,7 @@ export class ClickLinkMouseEvent { this.target = source.target; this.hasTriggerModifier = hasModifier(source.event, opts.triggerModifier); this.hasSideBySideModifier = hasModifier(source.event, opts.triggerSideBySideModifier); - this.isNoneOrSingleMouseDown = (browser.isIE || source.event.detail <= 1); // IE does not support event.detail properly + this.isNoneOrSingleMouseDown = (source.event.detail <= 1); } } @@ -109,8 +108,9 @@ export class ClickLinkGesture extends Disposable { private readonly _editor: ICodeEditor; private _opts: ClickLinkOptions; - private lastMouseMoveEvent: ClickLinkMouseEvent | null; - private hasTriggerKeyOnMouseDown: boolean; + private _lastMouseMoveEvent: ClickLinkMouseEvent | null; + private _hasTriggerKeyOnMouseDown: boolean; + private _lineNumberOnMouseDown: number; constructor(editor: ICodeEditor) { super(); @@ -118,8 +118,9 @@ export class ClickLinkGesture extends Disposable { this._editor = editor; this._opts = createOptions(this._editor.getOption(EditorOption.multiCursorModifier)); - this.lastMouseMoveEvent = null; - this.hasTriggerKeyOnMouseDown = false; + this._lastMouseMoveEvent = null; + this._hasTriggerKeyOnMouseDown = false; + this._lineNumberOnMouseDown = 0; this._register(this._editor.onDidChangeConfiguration((e) => { if (e.hasChanged(EditorOption.multiCursorModifier)) { @@ -128,77 +129,80 @@ export class ClickLinkGesture extends Disposable { return; } this._opts = newOpts; - this.lastMouseMoveEvent = null; - this.hasTriggerKeyOnMouseDown = false; + this._lastMouseMoveEvent = null; + this._hasTriggerKeyOnMouseDown = false; + this._lineNumberOnMouseDown = 0; this._onCancel.fire(); } })); - this._register(this._editor.onMouseMove((e: IEditorMouseEvent) => this.onEditorMouseMove(new ClickLinkMouseEvent(e, this._opts)))); - this._register(this._editor.onMouseDown((e: IEditorMouseEvent) => this.onEditorMouseDown(new ClickLinkMouseEvent(e, this._opts)))); - this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this.onEditorMouseUp(new ClickLinkMouseEvent(e, this._opts)))); - this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(new ClickLinkKeyboardEvent(e, this._opts)))); - this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(new ClickLinkKeyboardEvent(e, this._opts)))); - this._register(this._editor.onMouseDrag(() => this.resetHandler())); + this._register(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(new ClickLinkMouseEvent(e, this._opts)))); + this._register(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(new ClickLinkMouseEvent(e, this._opts)))); + this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(new ClickLinkMouseEvent(e, this._opts)))); + this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this._onEditorKeyDown(new ClickLinkKeyboardEvent(e, this._opts)))); + this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this._onEditorKeyUp(new ClickLinkKeyboardEvent(e, this._opts)))); + this._register(this._editor.onMouseDrag(() => this._resetHandler())); - this._register(this._editor.onDidChangeCursorSelection((e) => this.onDidChangeCursorSelection(e))); - this._register(this._editor.onDidChangeModel((e) => this.resetHandler())); - this._register(this._editor.onDidChangeModelContent(() => this.resetHandler())); + this._register(this._editor.onDidChangeCursorSelection((e) => this._onDidChangeCursorSelection(e))); + this._register(this._editor.onDidChangeModel((e) => this._resetHandler())); + this._register(this._editor.onDidChangeModelContent(() => this._resetHandler())); this._register(this._editor.onDidScrollChange((e) => { if (e.scrollTopChanged || e.scrollLeftChanged) { - this.resetHandler(); + this._resetHandler(); } })); } - private onDidChangeCursorSelection(e: ICursorSelectionChangedEvent): void { + private _onDidChangeCursorSelection(e: ICursorSelectionChangedEvent): void { if (e.selection && e.selection.startColumn !== e.selection.endColumn) { - this.resetHandler(); // immediately stop this feature if the user starts to select (https://github.com/Microsoft/vscode/issues/7827) + this._resetHandler(); // immediately stop this feature if the user starts to select (https://github.com/Microsoft/vscode/issues/7827) } } - private onEditorMouseMove(mouseEvent: ClickLinkMouseEvent): void { - this.lastMouseMoveEvent = mouseEvent; + private _onEditorMouseMove(mouseEvent: ClickLinkMouseEvent): void { + this._lastMouseMoveEvent = mouseEvent; this._onMouseMoveOrRelevantKeyDown.fire([mouseEvent, null]); } - private onEditorMouseDown(mouseEvent: ClickLinkMouseEvent): void { + private _onEditorMouseDown(mouseEvent: ClickLinkMouseEvent): void { // We need to record if we had the trigger key on mouse down because someone might select something in the editor // holding the mouse down and then while mouse is down start to press Ctrl/Cmd to start a copy operation and then // release the mouse button without wanting to do the navigation. // With this flag we prevent goto definition if the mouse was down before the trigger key was pressed. - this.hasTriggerKeyOnMouseDown = mouseEvent.hasTriggerModifier; + this._hasTriggerKeyOnMouseDown = mouseEvent.hasTriggerModifier; + this._lineNumberOnMouseDown = mouseEvent.target.position ? mouseEvent.target.position.lineNumber : 0; } - private onEditorMouseUp(mouseEvent: ClickLinkMouseEvent): void { - if (this.hasTriggerKeyOnMouseDown) { + private _onEditorMouseUp(mouseEvent: ClickLinkMouseEvent): void { + const currentLineNumber = mouseEvent.target.position ? mouseEvent.target.position.lineNumber : 0; + if (this._hasTriggerKeyOnMouseDown && this._lineNumberOnMouseDown && this._lineNumberOnMouseDown === currentLineNumber) { this._onExecute.fire(mouseEvent); } } - private onEditorKeyDown(e: ClickLinkKeyboardEvent): void { + private _onEditorKeyDown(e: ClickLinkKeyboardEvent): void { if ( - this.lastMouseMoveEvent + this._lastMouseMoveEvent && ( e.keyCodeIsTriggerKey // User just pressed Ctrl/Cmd (normal goto definition) || (e.keyCodeIsSideBySideKey && e.hasTriggerModifier) // User pressed Ctrl/Cmd+Alt (goto definition to the side) ) ) { - this._onMouseMoveOrRelevantKeyDown.fire([this.lastMouseMoveEvent, e]); + this._onMouseMoveOrRelevantKeyDown.fire([this._lastMouseMoveEvent, e]); } else if (e.hasTriggerModifier) { this._onCancel.fire(); // remove decorations if user holds another key with ctrl/cmd to prevent accident goto declaration } } - private onEditorKeyUp(e: ClickLinkKeyboardEvent): void { + private _onEditorKeyUp(e: ClickLinkKeyboardEvent): void { if (e.keyCodeIsTriggerKey) { this._onCancel.fire(); } } - private resetHandler(): void { - this.lastMouseMoveEvent = null; - this.hasTriggerKeyOnMouseDown = false; + private _resetHandler(): void { + this._lastMouseMoveEvent = null; + this._hasTriggerKeyOnMouseDown = false; this._onCancel.fire(); } } diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts index 3958e96b8c..e928659a2e 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts @@ -217,10 +217,10 @@ export abstract class ReferencesController implements IEditorContribution { } closeWidget(focusEditor = true): void { - this._referenceSearchVisible.reset(); - this._disposables.clear(); dispose(this._widget); dispose(this._model); + this._referenceSearchVisible.reset(); + this._disposables.clear(); this._widget = undefined; this._model = undefined; if (focusEditor) { diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts index c4586e7803..35504cecb1 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts @@ -28,7 +28,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ILabelService } from 'vs/platform/label/common/label'; import { WorkbenchAsyncDataTree, IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService'; import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import * as peekView from 'vs/editor/contrib/peekView/peekView'; import { FileReferences, OneReference, ReferencesModel } from '../referencesModel'; import { FuzzyScore } from 'vs/base/common/filters'; @@ -221,8 +221,8 @@ export class ReferenceWidget extends peekView.PeekViewWidget { ) { super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true }); - this._applyTheme(themeService.getTheme()); - this._callOnDispose.add(themeService.onThemeChange(this._applyTheme.bind(this))); + this._applyTheme(themeService.getColorTheme()); + this._callOnDispose.add(themeService.onDidColorThemeChange(this._applyTheme.bind(this))); this._peekViewService.addExclusiveWidget(editor, this); this.create(); } @@ -239,7 +239,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { super.dispose(); } - private _applyTheme(theme: ITheme) { + private _applyTheme(theme: IColorTheme) { const borderColor = theme.getColor(peekView.peekViewBorder) || Color.transparent; this.style({ arrowColor: borderColor, diff --git a/src/vs/editor/contrib/linesOperations/linesOperations.ts b/src/vs/editor/contrib/linesOperations/linesOperations.ts index 80e3d2ae25..03dee6085a 100644 --- a/src/vs/editor/contrib/linesOperations/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/linesOperations.ts @@ -960,7 +960,7 @@ export abstract class AbstractCaseAction extends EditorAction { let selection = selections[i]; if (selection.isEmpty()) { let cursor = selection.getStartPosition(); - let word = model.getWordAtPosition(cursor); + const word = editor.getConfiguredWordAtPosition(cursor); if (!word) { continue; diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index cfd2349263..097528eaed 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -101,7 +101,7 @@ class LinkOccurrence { } } -class LinkDetector implements IEditorContribution { +export class LinkDetector implements IEditorContribution { public static readonly ID: string = 'editor.linkDetector'; diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 69e303ab99..33973134c8 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -286,7 +286,7 @@ export class MultiCursorSession { if (s.isEmpty()) { // selection is empty => expand to current word - const word = editor.getModel().getWordAtPosition(s.getStartPosition()); + const word = editor.getConfiguredWordAtPosition(s.getStartPosition()); if (!word) { return null; } @@ -505,7 +505,7 @@ export class MultiCursorSelectionController extends Disposable implements IEdito if (!selection.isEmpty()) { return selection; } - const word = model.getWordAtPosition(selection.getStartPosition()); + const word = this._editor.getConfiguredWordAtPosition(selection.getStartPosition()); if (!word) { return selection; } diff --git a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts index 44250944fb..76deca95df 100644 --- a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts +++ b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts @@ -80,8 +80,8 @@ suite('Multicursor selection', () => { 'var z = (3 * 5)', ], { serviceCollection: serviceCollection }, (editor, cursor) => { - let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); let selectHighlightsAction = new SelectHighlightsAction(); editor.setSelection(new Selection(2, 9, 2, 16)); @@ -110,8 +110,8 @@ suite('Multicursor selection', () => { 'nothing' ], { serviceCollection: serviceCollection }, (editor, cursor) => { - let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); let selectHighlightsAction = new SelectHighlightsAction(); editor.setSelection(new Selection(1, 1, 1, 1)); @@ -144,8 +144,8 @@ suite('Multicursor selection', () => { 'rty' ], { serviceCollection: serviceCollection }, (editor, cursor) => { - let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); let addSelectionToNextFindMatch = new AddSelectionToNextFindMatchAction(); editor.setSelection(new Selection(2, 1, 3, 4)); @@ -172,8 +172,8 @@ suite('Multicursor selection', () => { 'abcabc', ], { serviceCollection: serviceCollection }, (editor, cursor) => { - let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); let addSelectionToNextFindMatch = new AddSelectionToNextFindMatchAction(); editor.setSelection(new Selection(1, 1, 1, 4)); @@ -229,8 +229,8 @@ suite('Multicursor selection', () => { editor.getModel()!.setEOL(EndOfLineSequence.CRLF); - let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); let addSelectionToNextFindMatch = new AddSelectionToNextFindMatchAction(); editor.setSelection(new Selection(2, 1, 3, 4)); @@ -252,8 +252,8 @@ suite('Multicursor selection', () => { function testMulticursor(text: string[], callback: (editor: TestCodeEditor, findController: CommonFindController) => void): void { withTestCodeEditor(text, { serviceCollection: serviceCollection }, (editor, cursor) => { - let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); callback(editor, findController); diff --git a/src/vs/editor/contrib/peekView/peekView.ts b/src/vs/editor/contrib/peekView/peekView.ts index c210eb85b8..cc0b347171 100644 --- a/src/vs/editor/contrib/peekView/peekView.ts +++ b/src/vs/editor/contrib/peekView/peekView.ts @@ -17,7 +17,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IOptions, IStyles, ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import * as nls from 'vs/nls'; -import { ContextKeyExpr, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -57,7 +57,7 @@ registerSingleton(IPeekViewService, class implements IPeekViewService { export namespace PeekContext { export const inPeekEditor = new RawContextKey('inReferenceSearchEditor', true); - export const notInPeekEditor: ContextKeyExpr = inPeekEditor.toNegated(); + export const notInPeekEditor = inPeekEditor.toNegated(); } class PeekContextController implements IEditorContribution { diff --git a/src/vs/editor/contrib/quickAccess/gotoLine.ts b/src/vs/editor/contrib/quickAccess/gotoLine.ts new file mode 100644 index 0000000000..724f205b2e --- /dev/null +++ b/src/vs/editor/contrib/quickAccess/gotoLine.ts @@ -0,0 +1,206 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IQuickPick, IQuickPickItem, IKeyMods } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { DisposableStore, toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { once } from 'vs/base/common/functional'; +import { IEditor, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; +import { ITextModel } from 'vs/editor/common/model'; +import { isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { IRange } from 'vs/editor/common/core/range'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { AbstractEditorQuickAccessProvider } from 'vs/editor/contrib/quickAccess/quickAccess'; +import { IPosition } from 'vs/editor/common/core/position'; + +interface IGotoLineQuickPickItem extends IQuickPickItem, Partial { } + +export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorQuickAccessProvider { + + static PREFIX = ':'; + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Disable filtering & sorting, we control the results + picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; + + // Provide based on current active editor + let pickerDisposable = this.doProvide(picker, token); + disposables.add(toDisposable(() => pickerDisposable.dispose())); + + // Re-create whenever the active editor changes + disposables.add(this.onDidActiveTextEditorControlChange(() => { + pickerDisposable.dispose(); + pickerDisposable = this.doProvide(picker, token); + })); + + return disposables; + } + + private doProvide(picker: IQuickPick, token: CancellationToken): IDisposable { + + // With text control + if (this.activeTextEditorControl) { + return this.doProvideWithTextEditor(this.activeTextEditorControl, picker, token); + } + + // Without text control + return this.doProvideWithoutTextEditor(picker); + } + + private doProvideWithoutTextEditor(picker: IQuickPick): IDisposable { + const label = localize('cannotRunGotoLine', "Open a text file first to go to a line."); + picker.items = [{ label }]; + picker.ariaLabel = label; + + return Disposable.None; + } + + private doProvideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Restore any view state if this picker was closed + // without actually going to a line + const lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState()); + once(token.onCancellationRequested)(() => { + if (lastKnownEditorViewState) { + editor.restoreViewState(lastKnownEditorViewState); + } + }); + + // Goto line once picked + disposables.add(picker.onDidAccept(() => { + const [item] = picker.selectedItems; + if (item) { + if (!this.isValidLineNumber(editor, item.lineNumber)) { + return; + } + + this.gotoLine(editor, this.toRange(item.lineNumber, item.column), picker.keyMods); + + picker.hide(); + } + })); + + // React to picker changes + const updatePickerAndEditor = () => { + const position = this.parsePosition(editor, picker.value.trim().substr(AbstractGotoLineQuickAccessProvider.PREFIX.length)); + const label = this.getPickLabel(editor, position.lineNumber, position.column); + + // Picker + picker.items = [{ + lineNumber: position.lineNumber, + column: position.column, + label + }]; + + // ARIA Label + picker.ariaLabel = label; + + // Clear decorations for invalid range + if (!this.isValidLineNumber(editor, position.lineNumber)) { + this.clearDecorations(editor); + return; + } + + // Reveal + const range = this.toRange(position.lineNumber, position.column); + editor.revealRangeInCenter(range, ScrollType.Smooth); + + // Decorate + this.addDecorations(editor, range); + }; + updatePickerAndEditor(); + disposables.add(picker.onDidChangeValue(() => updatePickerAndEditor())); + + // Clean up decorations on dispose + disposables.add(toDisposable(() => this.clearDecorations(editor))); + + return disposables; + } + + private toRange(lineNumber = 1, column = 1): IRange { + return { + startLineNumber: lineNumber, + startColumn: column, + endLineNumber: lineNumber, + endColumn: column + }; + } + + private parsePosition(editor: IEditor, value: string): IPosition { + + // Support line-col formats of `line,col`, `line:col`, `line#col` + const numbers = value.split(/,|:|#/).map(part => parseInt(part, 10)).filter(part => !isNaN(part)); + const endLine = this.lineCount(editor) + 1; + + return { + lineNumber: numbers[0] > 0 ? numbers[0] : endLine + numbers[0], + column: numbers[1] + }; + } + + private getPickLabel(editor: IEditor, lineNumber: number, column: number | undefined): string { + + // Location valid: indicate this as picker label + if (this.isValidLineNumber(editor, lineNumber)) { + if (this.isValidColumn(editor, lineNumber, column)) { + return localize('gotoLineColumnLabel', "Go to line {0} and column {1}.", lineNumber, column); + } + + return localize('gotoLineLabel', "Go to line {0}.", lineNumber); + } + + // Location invalid: show generic label + const position = editor.getPosition() || { lineNumber: 1, column: 1 }; + const lineCount = this.lineCount(editor); + if (lineCount > 1) { + return localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}, Column: {1}. Type a line number between 1 and {2} to navigate to.", position.lineNumber, position.column, lineCount); + } + + return localize('gotoLineLabelEmpty', "Current Line: {0}, Column: {1}. Type a line number to navigate to.", position.lineNumber, position.column); + } + + private isValidLineNumber(editor: IEditor, lineNumber: number | undefined): boolean { + if (!lineNumber || typeof lineNumber !== 'number') { + return false; + } + + return lineNumber > 0 && lineNumber <= this.lineCount(editor); + } + + private isValidColumn(editor: IEditor, lineNumber: number, column: number | undefined): boolean { + if (!column || typeof column !== 'number') { + return false; + } + + const model = this.getModel(editor); + if (!model) { + return false; + } + + const positionCandidate = { lineNumber, column }; + + return model.validatePosition(positionCandidate).equals(positionCandidate); + } + + private lineCount(editor: IEditor): number { + return this.getModel(editor)?.getLineCount() ?? 0; + } + + private getModel(editor: IEditor | IDiffEditor): ITextModel | undefined { + return isDiffEditor(editor) ? + editor.getModel()?.modified : + editor.getModel() as ITextModel; + } + + protected gotoLine(editor: IEditor, range: IRange, keyMods: IKeyMods): void { + editor.setSelection(range); + editor.revealRangeInCenter(range, ScrollType.Smooth); + editor.focus(); + } +} diff --git a/src/vs/editor/contrib/quickAccess/quickAccess.ts b/src/vs/editor/contrib/quickAccess/quickAccess.ts new file mode 100644 index 0000000000..08f2b4bf72 --- /dev/null +++ b/src/vs/editor/contrib/quickAccess/quickAccess.ts @@ -0,0 +1,104 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IEditor } from 'vs/editor/common/editorCommon'; +import { IModelDeltaDecoration, OverviewRulerLane } from 'vs/editor/common/model'; +import { IRange } from 'vs/editor/common/core/range'; +import { themeColorFromId } from 'vs/platform/theme/common/themeService'; +import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry'; +import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; + +interface IEditorLineDecoration { + rangeHighlightId: string; + overviewRulerDecorationId: string; +} + +/** + * A reusable quick access provider for the editor with support for adding decorations. + */ +export abstract class AbstractEditorQuickAccessProvider implements IQuickAccessProvider { + + /** + * Subclasses to provide an event when the active editor control changes. + */ + abstract readonly onDidActiveTextEditorControlChange: Event; + + /** + * Subclasses to provide the current active editor control. + */ + abstract activeTextEditorControl: IEditor | undefined; + + /** + * Subclasses to implement the quick access picker. + */ + abstract provide(picker: IQuickPick, token: CancellationToken): IDisposable; + + + //#region Decorations Utils + + private rangeHighlightDecorationId: IEditorLineDecoration | undefined = undefined; + + protected addDecorations(editor: IEditor, range: IRange): void { + editor.changeDecorations(changeAccessor => { + + // Reset old decorations if any + const deleteDecorations: string[] = []; + if (this.rangeHighlightDecorationId) { + deleteDecorations.push(this.rangeHighlightDecorationId.overviewRulerDecorationId); + deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId); + + this.rangeHighlightDecorationId = undefined; + } + + // Add new decorations for the range + const newDecorations: IModelDeltaDecoration[] = [ + + // highlight the entire line on the range + { + range, + options: { + className: 'rangeHighlight', + isWholeLine: true + } + }, + + // also add overview ruler highlight + { + range, + options: { + overviewRuler: { + color: themeColorFromId(overviewRulerRangeHighlight), + position: OverviewRulerLane.Full + } + } + } + ]; + + const [rangeHighlightId, overviewRulerDecorationId] = changeAccessor.deltaDecorations(deleteDecorations, newDecorations); + + this.rangeHighlightDecorationId = { rangeHighlightId, overviewRulerDecorationId }; + }); + } + + protected clearDecorations(editor: IEditor): void { + const rangeHighlightDecorationId = this.rangeHighlightDecorationId; + if (rangeHighlightDecorationId) { + editor.changeDecorations(changeAccessor => { + changeAccessor.deltaDecorations([ + rangeHighlightDecorationId.overviewRulerDecorationId, + rangeHighlightDecorationId.rangeHighlightId + ], []); + }); + + this.rangeHighlightDecorationId = undefined; + } + } + + //#endregion +} diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index 8c944f339d..41834ad2cc 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -12,7 +12,7 @@ import { ScrollType } from 'vs/editor/common/editorCommon'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { inputBackground, inputBorder, inputForeground, widgetShadow, editorWidgetBackground } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { toggleClass } from 'vs/base/browser/dom'; @@ -53,7 +53,7 @@ export class RenameInputField implements IContentWidget { } })); - this._disposables.add(_themeService.onThemeChange(this._updateStyles, this)); + this._disposables.add(_themeService.onDidColorThemeChange(this._updateStyles, this)); } dispose(): void { @@ -88,12 +88,12 @@ export class RenameInputField implements IContentWidget { this._disposables.add(this._keybindingService.onDidUpdateKeybindings(updateLabel)); this._updateFont(); - this._updateStyles(this._themeService.getTheme()); + this._updateStyles(this._themeService.getColorTheme()); } return this._domNode; } - private _updateStyles(theme: ITheme): void { + private _updateStyles(theme: IColorTheme): void { if (!this._input || !this._domNode) { return; } diff --git a/src/vs/editor/contrib/snippet/snippetVariables.ts b/src/vs/editor/contrib/snippet/snippetVariables.ts index 14d1d8338c..736e671bc0 100644 --- a/src/vs/editor/contrib/snippet/snippetVariables.ts +++ b/src/vs/editor/contrib/snippet/snippetVariables.ts @@ -10,7 +10,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { Selection } from 'vs/editor/common/core/selection'; import { VariableResolver, Variable, Text } from 'vs/editor/contrib/snippet/snippetParser'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; -import { getLeadingWhitespace, commonPrefixLength, isFalsyOrWhitespace, pad, endsWith } from 'vs/base/common/strings'; +import { getLeadingWhitespace, commonPrefixLength, isFalsyOrWhitespace } from 'vs/base/common/strings'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier, WORKSPACE_EXTENSION, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -244,15 +244,15 @@ export class TimeBasedVariableResolver implements VariableResolver { } else if (name === 'CURRENT_YEAR_SHORT') { return String(new Date().getFullYear()).slice(-2); } else if (name === 'CURRENT_MONTH') { - return pad((new Date().getMonth().valueOf() + 1), 2); + return String(new Date().getMonth().valueOf() + 1).padStart(2, '0'); } else if (name === 'CURRENT_DATE') { - return pad(new Date().getDate().valueOf(), 2); + return String(new Date().getDate().valueOf()).padStart(2, '0'); } else if (name === 'CURRENT_HOUR') { - return pad(new Date().getHours().valueOf(), 2); + return String(new Date().getHours().valueOf()).padStart(2, '0'); } else if (name === 'CURRENT_MINUTE') { - return pad(new Date().getMinutes().valueOf(), 2); + return String(new Date().getMinutes().valueOf()).padStart(2, '0'); } else if (name === 'CURRENT_SECOND') { - return pad(new Date().getSeconds().valueOf(), 2); + return String(new Date().getSeconds().valueOf()).padStart(2, '0'); } else if (name === 'CURRENT_DAY_NAME') { return TimeBasedVariableResolver.dayNames[new Date().getDay()]; } else if (name === 'CURRENT_DAY_NAME_SHORT') { @@ -300,7 +300,7 @@ export class WorkspaceBasedVariableResolver implements VariableResolver { } let filename = path.basename(workspaceIdentifier.configPath.path); - if (endsWith(filename, WORKSPACE_EXTENSION)) { + if (filename.endsWith(WORKSPACE_EXTENSION)) { filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1); } return filename; @@ -312,7 +312,7 @@ export class WorkspaceBasedVariableResolver implements VariableResolver { let filename = path.basename(workspaceIdentifier.configPath.path); let folderpath = workspaceIdentifier.configPath.fsPath; - if (endsWith(folderpath, filename)) { + if (folderpath.endsWith(filename)) { folderpath = folderpath.substr(0, folderpath.length - filename.length - 1); } return (folderpath ? normalizeDriveLetter(folderpath) : '/'); diff --git a/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts b/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts index bba7ae33fe..591c292a3f 100644 --- a/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts @@ -45,7 +45,7 @@ suite('SnippetController', () => { editor.getModel()!.updateOptions({ insertSpaces: false }); - let snippetController = editor.registerAndInstantiateContribution(TestSnippetController.ID, TestSnippetController); + let snippetController = editor.registerAndInstantiateContribution(TestSnippetController.ID, TestSnippetController); let template = [ 'for (var ${1:index}; $1 < ${2:array}.length; $1++) {', '\tvar element = $2[$1];', diff --git a/src/vs/editor/contrib/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css index c2f7e4be76..1e4acf9955 100644 --- a/src/vs/editor/contrib/suggest/media/suggest.css +++ b/src/vs/editor/contrib/suggest/media/suggest.css @@ -176,7 +176,7 @@ } .monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left > .signature-label { - overflow: auto; + overflow: hidden; text-overflow: ellipsis; } @@ -228,6 +228,7 @@ .monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left { flex-shrink: 1; + flex-grow: 1; overflow: hidden; } .monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left > .monaco-icon-label { diff --git a/src/vs/editor/contrib/suggest/suggestMemory.ts b/src/vs/editor/contrib/suggest/suggestMemory.ts index c40aebb30b..b554190071 100644 --- a/src/vs/editor/contrib/suggest/suggestMemory.ts +++ b/src/vs/editor/contrib/suggest/suggestMemory.ts @@ -9,15 +9,18 @@ import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/ import { ITextModel } from 'vs/editor/common/model'; import { IPosition } from 'vs/editor/common/core/position'; import { CompletionItemKind, completionKindFromString } from 'vs/editor/common/modes'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { CompletionItem } from 'vs/editor/contrib/suggest/suggest'; +import { IModeService } from 'vs/editor/common/services/modeService'; export abstract class Memory { + constructor(readonly name: MemMode) { } + select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number { if (items.length === 0) { return 0; @@ -46,6 +49,10 @@ export abstract class Memory { export class NoMemory extends Memory { + constructor() { + super('first'); + } + memorize(model: ITextModel, pos: IPosition, item: CompletionItem): void { // no-op } @@ -67,6 +74,10 @@ export interface MemItem { export class LRUMemory extends Memory { + constructor() { + super('recentlyUsed'); + } + private _cache = new LRUCache(300, 0.66); private _seq = 0; @@ -143,6 +154,10 @@ export class LRUMemory extends Memory { export class PrefixMemory extends Memory { + constructor() { + super('recentlyUsedByPrefix'); + } + private _trie = TernarySearchTree.forStrings(); private _seq = 0; @@ -206,85 +221,86 @@ export class PrefixMemory extends Memory { export type MemMode = 'first' | 'recentlyUsed' | 'recentlyUsedByPrefix'; -export class SuggestMemoryService extends Disposable implements ISuggestMemoryService { +export class SuggestMemoryService implements ISuggestMemoryService { + + private static readonly _strategyCtors = new Map([ + ['recentlyUsedByPrefix', PrefixMemory], + ['recentlyUsed', LRUMemory], + ['first', NoMemory] + ]); + + private static readonly _storagePrefix = 'suggest/memories'; readonly _serviceBrand: undefined; - private readonly _storagePrefix = 'suggest/memories'; private readonly _persistSoon: RunOnceScheduler; - private _mode!: MemMode; - private _shareMem!: boolean; - private _strategy!: Memory; + private readonly _disposables = new DisposableStore(); + + private _strategy?: Memory; constructor( @IStorageService private readonly _storageService: IStorageService, + @IModeService private readonly _modeService: IModeService, @IConfigurationService private readonly _configService: IConfigurationService, ) { - super(); - - const update = () => { - const mode = this._configService.getValue('editor.suggestSelection'); - const share = this._configService.getValue('editor.suggest.shareSuggestSelections'); - this._update(mode, share, false); - }; - - this._persistSoon = this._register(new RunOnceScheduler(() => this._saveState(), 500)); - this._register(_storageService.onWillSaveState(e => { + this._persistSoon = new RunOnceScheduler(() => this._saveState(), 500); + this._disposables.add(_storageService.onWillSaveState(e => { if (e.reason === WillSaveStateReason.SHUTDOWN) { this._saveState(); } })); - - this._register(this._configService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('editor.suggestSelection') || e.affectsConfiguration('editor.suggest.shareSuggestSelections')) { - update(); - } - })); - this._register(this._storageService.onDidChangeStorage(e => { - if (e.scope === StorageScope.GLOBAL && e.key.indexOf(this._storagePrefix) === 0) { - if (!document.hasFocus()) { - // windows that aren't focused have to drop their current - // storage value and accept what's stored now - this._update(this._mode, this._shareMem, true); - } - } - })); - update(); } - private _update(mode: MemMode, shareMem: boolean, force: boolean): void { - if (!force && this._mode === mode && this._shareMem === shareMem) { - return; - } - this._shareMem = shareMem; - this._mode = mode; - this._strategy = mode === 'recentlyUsedByPrefix' ? new PrefixMemory() : mode === 'recentlyUsed' ? new LRUMemory() : new NoMemory(); - - try { - const scope = shareMem ? StorageScope.GLOBAL : StorageScope.WORKSPACE; - const raw = this._storageService.get(`${this._storagePrefix}/${this._mode}`, scope); - if (raw) { - this._strategy.fromJSON(JSON.parse(raw)); - } - } catch (e) { - // things can go wrong with JSON... - } + dispose(): void { + this._disposables.dispose(); + this._persistSoon.dispose(); } memorize(model: ITextModel, pos: IPosition, item: CompletionItem): void { - this._strategy.memorize(model, pos, item); + this._withStrategy(model, pos).memorize(model, pos, item); this._persistSoon.schedule(); } select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number { - return this._strategy.select(model, pos, items); + return this._withStrategy(model, pos).select(model, pos, items); + } + + private _withStrategy(model: ITextModel, pos: IPosition): Memory { + + const mode = this._configService.getValue('editor.suggestSelection', { + overrideIdentifier: this._modeService.getLanguageIdentifier(model.getLanguageIdAtPosition(pos.lineNumber, pos.column))?.language, + resource: model.uri + }); + + if (this._strategy?.name !== mode) { + + this._saveState(); + const ctor = SuggestMemoryService._strategyCtors.get(mode) || NoMemory; + this._strategy = new ctor(); + + try { + const share = this._configService.getValue('editor.suggest.shareSuggestSelections'); + const scope = share ? StorageScope.GLOBAL : StorageScope.WORKSPACE; + const raw = this._storageService.get(`${SuggestMemoryService._storagePrefix}/${mode}`, scope); + if (raw) { + this._strategy.fromJSON(JSON.parse(raw)); + } + } catch (e) { + // things can go wrong with JSON... + } + } + + return this._strategy; } private _saveState() { - const raw = JSON.stringify(this._strategy); - const scope = this._shareMem ? StorageScope.GLOBAL : StorageScope.WORKSPACE; - this._storageService.store(`${this._storagePrefix}/${this._mode}`, raw, scope); + if (this._strategy) { + const share = this._configService.getValue('editor.suggest.shareSuggestSelections'); + const scope = share ? StorageScope.GLOBAL : StorageScope.WORKSPACE; + const raw = JSON.stringify(this._strategy); + this._storageService.store(`${SuggestMemoryService._storagePrefix}/${this._strategy.name}`, raw, scope); + } } } diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index a730cf819f..eec49f41d5 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -25,7 +25,7 @@ import { Context as SuggestContext, CompletionItem, suggestWidgetStatusbarMenu } import { CompletionModel } from './completionModel'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { attachListStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService, ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { registerColor, editorWidgetBackground, listFocusBackground, activeContrastBorder, listHighlightForeground, editorForeground, editorWidgetBorder, focusBorder, textLinkForeground, textCodeBlockBackground } from 'vs/platform/theme/common/colorRegistry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; @@ -144,11 +144,10 @@ class ItemRenderer implements IListRenderer detailClasses.length ? labelClasses : detailClasses; - } else if (suggestion.kind === CompletionItemKind.Folder && this._themeService.getIconTheme().hasFolderIcons) { + } else if (suggestion.kind === CompletionItemKind.Folder && this._themeService.getFileIconTheme().hasFolderIcons) { // special logic for 'folder' completion items data.icon.className = 'icon hide'; data.iconContainer.className = 'icon hide'; @@ -474,7 +473,8 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate | null = null; @@ -627,12 +627,12 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate this.onThemeChange(t))); + this.toDispose.add(themeService.onDidColorThemeChange(t => this.onThemeChange(t))); this.toDispose.add(editor.onDidLayoutChange(() => this.onEditorLayoutChange())); this.toDispose.add(this.list.onMouseDown(e => this.onListMouseDownOrTap(e))); this.toDispose.add(this.list.onTap(e => this.onListMouseDownOrTap(e))); - this.toDispose.add(this.list.onSelectionChange(e => this.onListSelection(e))); - this.toDispose.add(this.list.onFocusChange(e => this.onListFocus(e))); + this.toDispose.add(this.list.onDidChangeSelection(e => this.onListSelection(e))); + this.toDispose.add(this.list.onDidChangeFocus(e => this.onListFocus(e))); this.toDispose.add(this.editor.onDidChangeCursorSelection(() => this.onCursorSelectionChanged())); this.toDispose.add(this.editor.onDidChangeConfiguration(e => { if (e.hasChanged(EditorOption.suggest)) { @@ -645,10 +645,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate { this._onDetailsKeydown.fire(e); @@ -715,7 +712,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: StandaloneGotoLineQuickAccessProvider, + prefix: AbstractGotoLineQuickAccessProvider.PREFIX, + helpEntries: [{ description: GoToLineNLS.gotoLineActionLabel, needsEditor: true }] +}); diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.ts new file mode 100644 index 0000000000..ebd630299a --- /dev/null +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { QuickHelpNLS } from 'vs/editor/common/standaloneStrings'; +import { HelpQuickAccessProvider } from 'vs/platform/quickinput/browser/helpQuickAccess'; + +Registry.as(Extensions.Quickaccess).defaultProvider = { + ctor: HelpQuickAccessProvider, + prefix: '', + helpEntries: [{ description: QuickHelpNLS.helpQuickAccessActionLabel, needsEditor: true }] +}; diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInput.css b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInput.css new file mode 100644 index 0000000000..e34e658752 --- /dev/null +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInput.css @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.quick-input-widget { + font-size: 13px; +} + +.quick-input-widget .monaco-highlighted-label .highlight, +.quick-input-widget .monaco-highlighted-label .highlight { + color: #0066BF; +} + +.vs-dark .quick-input-widget .monaco-highlighted-label .highlight, +.vs-dark .quick-input-widget .monaco-highlighted-label .highlight { + color: #0097fb; +} + +.hc-black .quick-input-widget .monaco-highlighted-label .highlight, +.hc-black .quick-input-widget .monaco-highlighted-label .highlight { + color: #F38518; +} diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts new file mode 100644 index 0000000000..fc5a098f45 --- /dev/null +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts @@ -0,0 +1,184 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./standaloneQuickInput'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IQuickInputService, IQuickInputButton, IQuickPickItem, IQuickPick, IInputBox, IQuickNavigateConfiguration, IPickOptions, QuickPickInput, IInputOptions } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { QuickInputController } from 'vs/base/parts/quickinput/browser/quickInput'; +import { QuickInputService, IQuickInputControllerHost } from 'vs/platform/quickinput/browser/quickInput'; +import { once } from 'vs/base/common/functional'; +import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess'; + +export class EditorScopedQuickInputServiceImpl extends QuickInputService { + + private host: IQuickInputControllerHost | undefined = undefined; + + constructor( + editor: ICodeEditor, + @IInstantiationService instantiationService: IInstantiationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IThemeService themeService: IThemeService, + @IAccessibilityService accessibilityService: IAccessibilityService, + @ILayoutService layoutService: ILayoutService + ) { + super(instantiationService, contextKeyService, themeService, accessibilityService, layoutService); + + // Use the passed in code editor as host for the quick input widget + const contribution = QuickInputEditorContribution.get(editor); + this.host = { + _serviceBrand: undefined, + get container() { return contribution.widget.getDomNode(); }, + get dimension() { return editor.getLayoutInfo(); }, + get onLayout() { return editor.onDidLayoutChange; }, + focus: () => editor.focus() + }; + } + + protected createController(): QuickInputController { + return super.createController(this.host); + } +} + +export class StandaloneQuickInputServiceImpl implements IQuickInputService { + + _serviceBrand: undefined; + + private mapEditorToService = new Map(); + private get activeService(): IQuickInputService { + const editor = this.codeEditorService.getFocusedCodeEditor(); + if (!editor) { + throw new Error('Quick input service needs a focused editor to work.'); + } + + // Find the quick input implementation for the focused + // editor or create it lazily if not yet created + let quickInputService = this.mapEditorToService.get(editor); + if (!quickInputService) { + const newQuickInputService = quickInputService = this.instantiationService.createInstance(EditorScopedQuickInputServiceImpl, editor); + this.mapEditorToService.set(editor, quickInputService); + + once(editor.onDidDispose)(() => { + newQuickInputService.dispose(); + this.mapEditorToService.delete(editor); + }); + } + + return quickInputService; + } + + get quickAccess(): IQuickAccessController { return this.activeService.quickAccess; } + + get backButton(): IQuickInputButton { return this.activeService.backButton; } + + get onShow() { return this.activeService.onShow; } + get onHide() { return this.activeService.onHide; } + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ICodeEditorService private readonly codeEditorService: ICodeEditorService + ) { + } + + pick>(picks: Promise[]> | QuickPickInput[], options: O = {}, token: CancellationToken = CancellationToken.None): Promise { + return (this.activeService as unknown as QuickInputController /* TS fail */).pick(picks, options, token); + } + + input(options?: IInputOptions | undefined, token?: CancellationToken | undefined): Promise { + return this.activeService.input(options, token); + } + + createQuickPick(): IQuickPick { + return this.activeService.createQuickPick(); + } + + createInputBox(): IInputBox { + return this.activeService.createInputBox(); + } + + focus(): void { + return this.activeService.focus(); + } + + toggle(): void { + return this.activeService.toggle(); + } + + navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration | undefined): void { + return this.activeService.navigate(next, quickNavigate); + } + + accept(): Promise { + return this.activeService.accept(); + } + + back(): Promise { + return this.activeService.back(); + } + + cancel(): Promise { + return this.activeService.cancel(); + } + + hide(focusLost?: boolean | undefined): void { + return this.activeService.hide(focusLost); + } +} + +export class QuickInputEditorContribution implements IEditorContribution { + + static readonly ID = 'editor.controller.quickInput'; + + static get(editor: ICodeEditor): QuickInputEditorContribution { + return editor.getContribution(QuickInputEditorContribution.ID); + } + + readonly widget = new QuickInputEditorWidget(this.editor); + + constructor(private editor: ICodeEditor) { } + + dispose(): void { + this.widget.dispose(); + } +} + +export class QuickInputEditorWidget implements IOverlayWidget { + + private static readonly ID = 'editor.contrib.quickInputWidget'; + + private domNode: HTMLElement; + + constructor(private codeEditor: ICodeEditor) { + this.domNode = document.createElement('div'); + + this.codeEditor.addOverlayWidget(this); + } + + getId(): string { + return QuickInputEditorWidget.ID; + } + + getDomNode(): HTMLElement { + return this.domNode; + } + + getPosition(): IOverlayWidgetPosition | null { + return { preference: OverlayWidgetPositionPreference.TOP_CENTER }; + } + + dispose(): void { + this.codeEditor.removeOverlayWidget(this); + } +} + +registerEditorContribution(QuickInputEditorContribution.ID, QuickInputEditorContribution); diff --git a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts b/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts index 2d6254d0c2..6bb2c1c65d 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts +++ b/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts @@ -4,10 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as strings from 'vs/base/common/strings'; -import * as browser from 'vs/base/browser/browser'; import { onUnexpectedError } from 'vs/base/common/errors'; import { matchesFuzzy } from 'vs/base/common/filters'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeyCode } from 'vs/base/common/keyCodes'; import { IHighlight, QuickOpenEntryGroup, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -85,7 +84,7 @@ export class QuickCommandAction extends BaseEditorQuickOpenAction { precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, - primary: (browser.isIE ? KeyMod.Alt | KeyCode.F1 : KeyCode.F1), + primary: KeyCode.F1, weight: KeybindingWeight.EditorContrib }, contextMenuOpts: { diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 81dcc5e766..d06e66d61c 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -24,10 +24,10 @@ import { TextEdit, WorkspaceEdit, WorkspaceTextEdit } from 'vs/editor/common/mod import { IModelService } from 'vs/editor/common/services/modelService'; import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourceConfigurationService, ITextResourcePropertiesService, ITextResourceConfigurationChangeEvent } from 'vs/editor/common/services/textResourceConfigurationService'; -import { CommandsRegistry, ICommand, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationModel, IConfigurationValue, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService'; @@ -36,16 +36,17 @@ import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingReso import { IKeybindingItem, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; -import { ILabelService, ResourceLabelFormatter } from 'vs/platform/label/common/label'; +import { ILabelService, ResourceLabelFormatter, IFormatterChangeEvent } from 'vs/platform/label/common/label'; import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification, IStatusMessageOptions, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { IProgressRunner, IEditorProgressService } from 'vs/platform/progress/common/progress'; import { ITelemetryInfo, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkbenchState, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { ILayoutService, IDimension } from 'vs/platform/layout/browser/layoutService'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { SimpleServicesNLS } from 'vs/editor/common/standaloneStrings'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; import { basename } from 'vs/base/common/resources'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; export class SimpleModel implements IResolvedTextEditorModel { @@ -254,7 +255,6 @@ export class StandaloneCommandService implements ICommandService { _serviceBrand: undefined; private readonly _instantiationService: IInstantiationService; - private readonly _dynamicCommands: { [id: string]: ICommand; }; private readonly _onWillExecuteCommand = new Emitter(); private readonly _onDidExecuteCommand = new Emitter(); @@ -263,19 +263,10 @@ export class StandaloneCommandService implements ICommandService { constructor(instantiationService: IInstantiationService) { this._instantiationService = instantiationService; - this._dynamicCommands = Object.create(null); - } - - public addCommand(command: ICommand): IDisposable { - const { id } = command; - this._dynamicCommands[id] = command; - return toDisposable(() => { - delete this._dynamicCommands[id]; - }); } public executeCommand(id: string, ...args: any[]): Promise { - const command = (CommandsRegistry.getCommand(id) || this._dynamicCommands[id]); + const command = CommandsRegistry.getCommand(id); if (!command) { return Promise.reject(new Error(`command '${id}' not found`)); } @@ -318,7 +309,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { })); } - public addDynamicKeybinding(commandId: string, _keybinding: number, handler: ICommandHandler, when: ContextKeyExpr | undefined): IDisposable { + public addDynamicKeybinding(commandId: string, _keybinding: number, handler: ICommandHandler, when: ContextKeyExpression | undefined): IDisposable { const keybinding = createKeybinding(_keybinding, OS); const toDispose = new DisposableStore(); @@ -344,15 +335,8 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { })); } - let commandService = this._commandService; - if (commandService instanceof StandaloneCommandService) { - toDispose.add(commandService.addCommand({ - id: commandId, - handler: handler - })); - } else { - throw new Error('Unknown command service!'); - } + toDispose.add(CommandsRegistry.registerCommand(commandId, handler)); + this.updateResolver({ source: KeybindingSource.Default }); return toDispose; @@ -713,8 +697,7 @@ export class SimpleUriLabelService implements ILabelService { _serviceBrand: undefined; - private readonly _onDidRegisterFormatter = new Emitter(); - public readonly onDidChangeFormatters: Event = this._onDidRegisterFormatter.event; + public readonly onDidChangeFormatters: Event = Event.None; public getUriLabel(resource: URI, options?: { relative?: boolean, forceNoTildify?: boolean }): string { if (resource.scheme === 'file') { @@ -749,8 +732,8 @@ export class SimpleLayoutService implements ILayoutService { public onLayout = Event.None; - private _dimension?: IDimension; - get dimension(): IDimension { + private _dimension?: dom.IDimension; + get dimension(): dom.IDimension { if (!this._dimension) { this._dimension = dom.getClientArea(window.document.body); } @@ -762,5 +745,9 @@ export class SimpleLayoutService implements ILayoutService { return this._container; } - constructor(private _container: HTMLElement) { } + focus(): void { + this._codeEditorService.getFocusedCodeEditor()?.focus(); + } + + constructor(private _codeEditorService: ICodeEditorService, private _container: HTMLElement) { } } diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index f4d4d87b94..9ff4b1da24 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as browser from 'vs/base/browser/browser'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; @@ -233,11 +232,7 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon ) { options = options || {}; options.ariaLabel = options.ariaLabel || StandaloneCodeEditorNLS.editorViewAccessibleLabel; - options.ariaLabel = options.ariaLabel + ';' + ( - browser.isIE - ? StandaloneCodeEditorNLS.accessibilityHelpMessageIE - : StandaloneCodeEditorNLS.accessibilityHelpMessage - ); + options.ariaLabel = options.ariaLabel + ';' + (StandaloneCodeEditorNLS.accessibilityHelpMessage); super(domElement, options, {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService); if (keybindingService instanceof StandaloneKeybindingService) { diff --git a/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts index b1ad7b1e2f..2124ceea94 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts @@ -11,7 +11,7 @@ import { CodeEditorServiceImpl } from 'vs/editor/browser/services/codeEditorServ import { IRange } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; export class StandaloneCodeEditorServiceImpl extends CodeEditorServiceImpl { @@ -19,7 +19,7 @@ export class StandaloneCodeEditorServiceImpl extends CodeEditorServiceImpl { return null; // not supported in the standalone case } - public openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + public openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { if (!source) { return Promise.resolve(null); } @@ -27,7 +27,7 @@ export class StandaloneCodeEditorServiceImpl extends CodeEditorServiceImpl { return Promise.resolve(this.doOpenEditor(source, input)); } - private doOpenEditor(editor: ICodeEditor, input: IResourceInput): ICodeEditor | null { + private doOpenEditor(editor: ICodeEditor, input: IResourceEditorInput): ICodeEditor | null { const model = this.findModel(editor, input.resource); if (!model) { if (input.resource) { diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 1851378bc8..87228b1299 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -154,7 +154,7 @@ export class TokenizationSupport2Adapter implements modes.ITokenizationSupport { private _toBinaryTokens(tokens: IToken[], offsetDelta: number): Uint32Array { const languageId = this._languageIdentifier.id; - const tokenTheme = this._standaloneThemeService.getTheme().tokenTheme; + const tokenTheme = this._standaloneThemeService.getColorTheme().tokenTheme; let result: number[] = [], resultLen = 0; let previousStartIndex: number = 0; diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 58df924e9b..445b800053 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -52,6 +52,8 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { StandaloneQuickInputServiceImpl } from 'vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export interface IEditorOverrideServices { [index: string]: any; @@ -206,7 +208,9 @@ export class DynamicStandaloneServices extends Disposable { let keybindingService = ensure(IKeybindingService, () => this._register(new StandaloneKeybindingService(contextKeyService, commandService, telemetryService, notificationService, domElement))); - let layoutService = ensure(ILayoutService, () => new SimpleLayoutService(domElement)); + let layoutService = ensure(ILayoutService, () => new SimpleLayoutService(StaticServices.codeEditorService.get(ICodeEditorService), domElement)); + + ensure(IQuickInputService, () => new StandaloneQuickInputServiceImpl(_instantiationService, StaticServices.codeEditorService.get(ICodeEditorService))); let contextViewService = ensure(IContextViewService, () => this._register(new ContextViewService(layoutService))); diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 53f62edda7..0e215ed9c9 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -13,7 +13,7 @@ import { hc_black, vs, vs_dark } from 'vs/editor/standalone/common/themes'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Registry } from 'vs/platform/registry/common/platform'; import { ColorIdentifier, Extensions, IColorRegistry } from 'vs/platform/theme/common/colorRegistry'; -import { Extensions as ThemingExtensions, ICssStyleCollector, IIconTheme, IThemingRegistry, ITokenStyle } from 'vs/platform/theme/common/themeService'; +import { Extensions as ThemingExtensions, ICssStyleCollector, IFileIconTheme, IThemingRegistry, ITokenStyle } from 'vs/platform/theme/common/themeService'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; const VS_THEME_NAME = 'vs'; @@ -168,11 +168,11 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon _serviceBrand: undefined; - private readonly _onThemeChange = this._register(new Emitter()); - public readonly onThemeChange = this._onThemeChange.event; + private readonly _onColorThemeChange = this._register(new Emitter()); + public readonly onDidColorThemeChange = this._onColorThemeChange.event; - private readonly _onIconThemeChange = this._register(new Emitter()); - public readonly onIconThemeChange = this._onIconThemeChange.event; + private readonly _onFileIconThemeChange = this._register(new Emitter()); + public readonly onDidFileIconThemeChange = this._onFileIconThemeChange.event; private readonly _environment: IEnvironmentService = Object.create(null); private readonly _knownThemes: Map; @@ -250,7 +250,7 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon } } - public getTheme(): IStandaloneTheme { + public getColorTheme(): IStandaloneTheme { return this._theme; } @@ -287,12 +287,12 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon this._styleElements.forEach(styleElement => styleElement.innerHTML = this._css); TokenizationRegistry.setColorMap(colorMap); - this._onThemeChange.fire(theme); + this._onColorThemeChange.fire(theme); return theme.id; } - public getIconTheme(): IIconTheme { + public getFileIconTheme(): IFileIconTheme { return { hasFileIcons: false, hasFolderIcons: false, diff --git a/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts b/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts index 7bfb82fe07..9388b3f41b 100644 --- a/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts +++ b/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts @@ -29,7 +29,7 @@ class ToggleHighContrast extends EditorAction { standaloneThemeService.setTheme(this._originalThemeName); this._originalThemeName = null; } else { - this._originalThemeName = standaloneThemeService.getTheme().themeName; + this._originalThemeName = standaloneThemeService.getColorTheme().themeName; standaloneThemeService.setTheme('hc-black'); } } diff --git a/src/vs/editor/standalone/common/monarch/monarchLexer.ts b/src/vs/editor/standalone/common/monarch/monarchLexer.ts index 003a608a97..a5aa847402 100644 --- a/src/vs/editor/standalone/common/monarch/monarchLexer.ts +++ b/src/vs/editor/standalone/common/monarch/monarchLexer.ts @@ -463,7 +463,7 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { } public tokenize2(line: string, lineState: modes.IState, offsetDelta: number): TokenizationResult2 { - let tokensCollector = new MonarchModernTokensCollector(this._modeService, this._standaloneThemeService.getTheme().tokenTheme); + let tokensCollector = new MonarchModernTokensCollector(this._modeService, this._standaloneThemeService.getColorTheme().tokenTheme); let endLineState = this._tokenize(line, lineState, offsetDelta, tokensCollector); return tokensCollector.finalize(endLineState); } diff --git a/src/vs/editor/standalone/common/standaloneThemeService.ts b/src/vs/editor/standalone/common/standaloneThemeService.ts index cc8dbff4c3..5c2396e2ad 100644 --- a/src/vs/editor/standalone/common/standaloneThemeService.ts +++ b/src/vs/editor/standalone/common/standaloneThemeService.ts @@ -5,7 +5,7 @@ import { ITokenThemeRule, TokenTheme } from 'vs/editor/common/modes/supports/tokenization'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; export const IStandaloneThemeService = createDecorator('themeService'); @@ -20,7 +20,7 @@ export interface IStandaloneThemeData { colors: IColors; } -export interface IStandaloneTheme extends ITheme { +export interface IStandaloneTheme extends IColorTheme { tokenTheme: TokenTheme; themeName: string; } @@ -32,5 +32,5 @@ export interface IStandaloneThemeService extends IThemeService { defineTheme(themeName: string, themeData: IStandaloneThemeData): void; - getTheme(): IStandaloneTheme; + getColorTheme(): IStandaloneTheme; } diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index d1339a5532..e7f6ab414a 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -12,7 +12,7 @@ import { TokenTheme } from 'vs/editor/common/modes/supports/tokenization'; import { ILineTokens, IToken, TokenizationSupport2Adapter, TokensProvider } from 'vs/editor/standalone/browser/standaloneLanguages'; import { IStandaloneTheme, IStandaloneThemeData, IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; -import { IIconTheme, ITheme, LIGHT, ITokenStyle } from 'vs/platform/theme/common/themeService'; +import { IFileIconTheme, IColorTheme, LIGHT, ITokenStyle } from 'vs/platform/theme/common/themeService'; suite('TokenizationSupport2Adapter', () => { @@ -40,7 +40,7 @@ suite('TokenizationSupport2Adapter', () => { public defineTheme(themeName: string, themeData: IStandaloneThemeData): void { throw new Error('Not implemented'); } - public getTheme(): IStandaloneTheme { + public getColorTheme(): IStandaloneTheme { return { tokenTheme: new MockTokenTheme(), @@ -64,15 +64,15 @@ suite('TokenizationSupport2Adapter', () => { }; } - public getIconTheme(): IIconTheme { + public getFileIconTheme(): IFileIconTheme { return { hasFileIcons: false, hasFolderIcons: false, hidesExplorerArrows: false }; } - public readonly onThemeChange = new Emitter().event; - public readonly onIconThemeChange = new Emitter().event; + public readonly onDidColorThemeChange = new Emitter().event; + public readonly onDidFileIconThemeChange = new Emitter().event; } class MockState implements IState { diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 7473a3894e..9c674e253f 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -397,6 +397,25 @@ suite('Editor Controller - Cursor', () => { assertCursor(thisCursor, new Position(1, LINE1.length + 1)); }); + test('issue #44465: cursor position not correct when move', () => { + thisCursor.setSelections('test', [new Selection(1, 5, 1, 5)]); + // going once up on the first line remembers the offset visual columns + moveUp(thisCursor); + assertCursor(thisCursor, new Position(1, 1)); + moveDown(thisCursor); + assertCursor(thisCursor, new Position(2, 2)); + moveUp(thisCursor); + assertCursor(thisCursor, new Position(1, 5)); + + // going twice up on the first line discards the offset visual columns + moveUp(thisCursor); + assertCursor(thisCursor, new Position(1, 1)); + moveUp(thisCursor); + assertCursor(thisCursor, new Position(1, 1)); + moveDown(thisCursor); + assertCursor(thisCursor, new Position(2, 1)); + }); + // --------- move to beginning of line test('move to beginning of line', () => { @@ -5044,6 +5063,28 @@ suite('autoClosingPairs', () => { mode.dispose(); }); + test('issue #90016: allow accents on mac US intl keyboard to surround selection', () => { + let mode = new AutoClosingMode(); + usingCursor({ + text: [ + 'test' + ], + languageIdentifier: mode.getLanguageIdentifier() + }, (model, cursor) => { + cursor.setSelections('test', [new Selection(1, 1, 1, 5)]); + + // Typing ` + e on the mac US intl kb layout + cursorCommand(cursor, H.CompositionStart, null, 'keyboard'); + cursorCommand(cursor, H.Type, { text: '\'' }, 'keyboard'); + cursorCommand(cursor, H.ReplacePreviousChar, { replaceCharCnt: 1, text: '\'' }, 'keyboard'); + cursorCommand(cursor, H.ReplacePreviousChar, { replaceCharCnt: 1, text: '\'' }, 'keyboard'); + cursorCommand(cursor, H.CompositionEnd, null, 'keyboard'); + + assert.equal(model.getValue(), '\'test\''); + }); + mode.dispose(); + }); + test('issue #53357: Over typing ignores characters after backslash', () => { let mode = new AutoClosingMode(); usingCursor({ diff --git a/src/vs/editor/test/browser/editorTestServices.ts b/src/vs/editor/test/browser/editorTestServices.ts index a522e63b7e..50251bf3a8 100644 --- a/src/vs/editor/test/browser/editorTestServices.ts +++ b/src/vs/editor/test/browser/editorTestServices.ts @@ -9,13 +9,13 @@ import { AbstractCodeEditorService } from 'vs/editor/browser/services/abstractCo import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; import { IModelDecorationOptions } from 'vs/editor/common/model'; import { CommandsRegistry, ICommandEvent, ICommandService } from 'vs/platform/commands/common/commands'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class TestCodeEditorService extends AbstractCodeEditorService { - public lastInput?: IResourceInput; + public lastInput?: IResourceEditorInput; public getActiveCodeEditor(): ICodeEditor | null { return null; } - public openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + public openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { this.lastInput = input; return Promise.resolve(null); } diff --git a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts index cb9ab8ad44..57d8b46e15 100644 --- a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts +++ b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts @@ -10,8 +10,8 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { CodeEditorServiceImpl } from 'vs/editor/browser/services/codeEditorServiceImpl'; import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; -import { TestTheme, TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { TestColorTheme, TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; const themeServiceMock = new TestThemeService(); @@ -20,7 +20,7 @@ export class TestCodeEditorServiceImpl extends CodeEditorServiceImpl { return null; } - openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { return Promise.resolve(null); } } @@ -76,7 +76,7 @@ suite.skip('Decoration Render Options', () => { // {{SQL CARBON EDIT}} skip suit }; let styleSheet = dom.createStyleSheet(); - let themeService = new TestThemeService(new TestTheme(colors)); + let themeService = new TestThemeService(new TestColorTheme(colors)); let s = new TestCodeEditorServiceImpl(themeService, styleSheet); s.registerDecorationType('example', options); let sheet = readStyleSheet(styleSheet); @@ -86,7 +86,7 @@ suite.skip('Decoration Render Options', () => { // {{SQL CARBON EDIT}} skip suit editorBackground: '#EE0000', editorBorder: '#00FFFF' }; - themeService.setTheme(new TestTheme(colors)); + themeService.setTheme(new TestColorTheme(colors)); sheet = readStyleSheet(styleSheet); assert.equal(sheet, '.monaco-editor .ced-example-0 { background-color: rgb(238, 0, 0); border-color: rgb(0, 255, 255); box-sizing: border-box; }'); @@ -115,7 +115,7 @@ suite.skip('Decoration Render Options', () => { // {{SQL CARBON EDIT}} skip suit }; let styleSheet = dom.createStyleSheet(); - let themeService = new TestThemeService(new TestTheme(colors)); + let themeService = new TestThemeService(new TestColorTheme(colors)); let s = new TestCodeEditorServiceImpl(themeService, styleSheet); s.registerDecorationType('example', options); let sheet = readStyleSheet(styleSheet); diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index aa7deeb72a..9c45df36f9 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IEditorContributionCtor } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { View } from 'vs/editor/browser/view/viewImpl'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -11,13 +12,13 @@ import * as editorOptions from 'vs/editor/common/config/editorOptions'; import { Cursor } from 'vs/editor/common/controller/cursor'; import { IConfiguration, IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { TestCodeEditorService, TestCommandService } from 'vs/editor/test/browser/editorTestServices'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { BrandedService, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; @@ -42,8 +43,8 @@ export class TestCodeEditor extends CodeEditorWidget implements ICodeEditor { public getCursor(): Cursor | undefined { return this._modelData ? this._modelData.cursor : undefined; } - public registerAndInstantiateContribution(id: string, ctor: any): T { - let r = this._instantiationService.createInstance(ctor, this); + public registerAndInstantiateContribution(id: string, ctor: new (editor: ICodeEditor, ...services: Services) => T): T { + const r: T = this._instantiationService.createInstance(ctor as IEditorContributionCtor, this); this._contributions[id] = r; return r; } diff --git a/src/vs/editor/test/common/model/tokensStore.test.ts b/src/vs/editor/test/common/model/tokensStore.test.ts index f5efc2dec3..88ed900d0f 100644 --- a/src/vs/editor/test/common/model/tokensStore.test.ts +++ b/src/vs/editor/test/common/model/tokensStore.test.ts @@ -169,4 +169,47 @@ suite('TokensStore', () => { ); }); + test('issue #91936: Semantic token color highlighting fails on line with selected text', () => { + const model = createTextModel(' else if ($s = 08) then \'\\b\''); + model.setSemanticTokens([ + new MultilineTokens2(1, new SparseEncodedTokens(new Uint32Array([ + 0, 20, 24, 245768, + 0, 25, 27, 245768, + 0, 28, 29, 16392, + 0, 29, 31, 262152, + 0, 32, 33, 16392, + 0, 34, 36, 98312, + 0, 36, 37, 16392, + 0, 38, 42, 245768, + 0, 43, 47, 180232, + ]))) + ]); + const lineTokens = model.getLineTokens(1); + let decodedTokens: number[] = []; + for (let i = 0, len = lineTokens.getCount(); i < len; i++) { + decodedTokens.push(lineTokens.getEndOffset(i), lineTokens.getMetadata(i)); + } + + assert.deepEqual(decodedTokens, [ + 20, 16793600, + 24, 17022976, + 25, 16793600, + 27, 17022976, + 28, 16793600, + 29, 16793600, + 31, 17039360, + 32, 16793600, + 33, 16793600, + 34, 16793600, + 36, 16875520, + 37, 16793600, + 38, 16793600, + 42, 17022976, + 43, 16793600, + 47, 16957440 + ]); + + model.dispose(); + }); + }); diff --git a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts index 93eb5e988d..8534341ab5 100644 --- a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts +++ b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts @@ -184,11 +184,7 @@ suite('EditorSimpleWorker', () => { 'and now we are done' ]); - let words: string[] = []; - - for (let iter = model.createWordIterator(/[a-z]+/img), e = iter.next(); !e.done; e = iter.next()) { - words.push(e.value); - } + let words: string[] = [...model.words(/[a-z]+/img)]; assert.deepEqual(words, ['one', 'line', 'two', 'line', 'past', 'empty', 'single', 'and', 'now', 'we', 'are', 'done']); }); diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 00d816d23c..067c8d1a08 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -1861,6 +1861,92 @@ suite('viewLineRenderer.renderLine 2', () => { assert.deepEqual(actual.html, expected); }); + test('issue #91936: Semantic token color highlighting fails on line with selected text', () => { + let actual = renderViewLine(new RenderLineInput( + false, + true, + ' else if ($s = 08) then \'\\b\'', + false, + true, + false, + 0, + createViewLineTokens([ + createPart(20, 1), + createPart(24, 15), + createPart(25, 1), + createPart(27, 15), + createPart(28, 1), + createPart(29, 1), + createPart(29, 1), + createPart(31, 16), + createPart(32, 1), + createPart(33, 1), + createPart(34, 1), + createPart(36, 6), + createPart(36, 1), + createPart(37, 1), + createPart(38, 1), + createPart(42, 15), + createPart(43, 1), + createPart(47, 11) + ]), + [], + 4, + 0, + 10, + 11, + 11, + 10000, + 'selection', + false, + false, + [new LineRange(0, 47)] + )); + + let expected = [ + '', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + '\u00b7', + 'else', + '\u00b7', + 'if', + '\u00b7', + '(', + '$s', + '\u00b7', + '=', + '\u00b7', + '08', + ')', + '\u00b7', + 'then', + '\u00b7', + '\'\\b\'', + '' + ].join(''); + + assert.deepEqual(actual.html, expected); + }); + + function createTestGetColumnOfLinePartOffset(lineContent: string, tabSize: number, parts: ViewLineToken[], expectedPartLengths: number[]): (partIndex: number, partLength: number, offset: number, expected: number) => void { let renderLineOutput = renderViewLine(new RenderLineInput( false, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 4d57e4101c..84306fe343 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -174,6 +174,14 @@ declare namespace monaco { query?: string; fragment?: string; }): Uri; + /** + * Join a Uri path with path fragments and normalizes the resulting path. + * + * @param resource The input Uri. + * @param pathFragment The path fragment to add to the Uri path. + * @returns The resulting Uri. + */ + static joinPaths(resource: Uri, ...pathFragment: string[]): Uri; /** * Creates a string representation for this Uri. It's guaranteed that calling * `Uri.parse` with the result of this function creates an Uri which is equal @@ -381,7 +389,6 @@ declare namespace monaco { */ MAX_VALUE = 112 } - export class KeyMod { static readonly CtrlCmd: number; static readonly Shift: number; @@ -1370,6 +1377,10 @@ declare namespace monaco.editor { * If set, the decoration will be rendered in the lines decorations with this CSS class name. */ linesDecorationsClassName?: string | null; + /** + * If set, the decoration will be rendered in the lines decorations with this CSS class name, but only for the first line in case of line wrapping. + */ + firstLineDecorationClassName?: string | null; /** * If set, the decoration will be rendered in the margin (covering its full width) with this CSS class name. */ @@ -6040,11 +6051,11 @@ declare namespace monaco.languages { } /** - * A provider of colors for editor models. + * A provider of folding ranges for editor models. */ export interface FoldingRangeProvider { /** - * Provides the color ranges for a specific model. + * Provides the folding ranges for a specific model. */ provideFoldingRanges(model: editor.ITextModel, context: FoldingContext, token: CancellationToken): ProviderResult; } @@ -6187,6 +6198,7 @@ declare namespace monaco.languages { } export interface DocumentSemanticTokensProvider { + onDidChange?: IEvent; getLegend(): SemanticTokensLegend; provideDocumentSemanticTokens(model: editor.ITextModel, lastResultId: string | null, token: CancellationToken): ProviderResult; releaseDocumentSemanticTokens(resultId: string | undefined): void; diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index da07bf33ee..98a84824a5 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -7,7 +7,7 @@ import { Action } from 'vs/base/common/actions'; import { SyncDescriptor0, createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IConstructorSignature2, createDecorator, BrandedService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindings, KeybindingsRegistry, IKeybindingRule } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService, CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; @@ -25,8 +25,8 @@ export interface ICommandAction { title: string | ILocalizedString; category?: string | ILocalizedString; icon?: { dark?: URI; light?: URI; } | ThemeIcon; - precondition?: ContextKeyExpr; - toggled?: ContextKeyExpr; + precondition?: ContextKeyExpression; + toggled?: ContextKeyExpression; } export type ISerializableCommandAction = UriDto; @@ -34,7 +34,7 @@ export type ISerializableCommandAction = UriDto; export interface IMenuItem { command: ICommandAction; alt?: ICommandAction; - when?: ContextKeyExpr; + when?: ContextKeyExpression; group?: 'navigation' | string; order?: number; } @@ -42,7 +42,7 @@ export interface IMenuItem { export interface ISubmenuItem { title: string | ILocalizedString; submenu: MenuId; - when?: ContextKeyExpr; + when?: ContextKeyExpression; group?: 'navigation' | string; order?: number; } @@ -319,17 +319,17 @@ export class SyncActionDescriptor { private readonly _id: string; private readonly _label?: string; private readonly _keybindings: IKeybindings | undefined; - private readonly _keybindingContext: ContextKeyExpr | undefined; + private readonly _keybindingContext: ContextKeyExpression | undefined; private readonly _keybindingWeight: number | undefined; public static create(ctor: { new(id: string, label: string, ...services: Services): Action }, - id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpr, keybindingWeight?: number + id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpression, keybindingWeight?: number ): SyncActionDescriptor { return new SyncActionDescriptor(ctor as IConstructorSignature2, id, label, keybindings, keybindingContext, keybindingWeight); } private constructor(ctor: IConstructorSignature2, - id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpr, keybindingWeight?: number + id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpression, keybindingWeight?: number ) { this._id = id; this._label = label; @@ -355,7 +355,7 @@ export class SyncActionDescriptor { return this._keybindings; } - public get keybindingContext(): ContextKeyExpr | undefined { + public get keybindingContext(): ContextKeyExpression | undefined { return this._keybindingContext; } @@ -378,7 +378,7 @@ export interface IAction2Options extends ICommandAction { /** * One or many menu items. */ - menu?: OneOrN<{ id: MenuId } & Omit>; + menu?: OneOrN<{ id: MenuId } & Omit & { command?: Partial> }>; /** * One keybinding. @@ -401,39 +401,43 @@ export function registerAction2(ctor: { new(): Action2 }): IDisposable { const disposables = new DisposableStore(); const action = new ctor(); + const { f1, menu: menus, keybinding, description, ...command } = action.desc; + // command disposables.add(CommandsRegistry.registerCommand({ - id: action.desc.id, + id: command.id, handler: (accessor, ...args) => action.run(accessor, ...args), - description: action.desc.description, + description: description, })); // menu - if (Array.isArray(action.desc.menu)) { - for (let item of action.desc.menu) { - disposables.add(MenuRegistry.appendMenuItem(item.id, { command: action.desc, ...item })); + if (Array.isArray(menus)) { + for (let item of menus) { + const { command: commandOverrides, ...menu } = item; + disposables.add(MenuRegistry.appendMenuItem(item.id, { command: { ...command, ...commandOverrides }, ...menu })); } - } else if (action.desc.menu) { - disposables.add(MenuRegistry.appendMenuItem(action.desc.menu.id, { command: action.desc, ...action.desc.menu })); + } else if (menus) { + const { command: commandOverrides, ...menu } = menus; + disposables.add(MenuRegistry.appendMenuItem(menu.id, { command: { ...command, ...commandOverrides }, ...menu })); } - if (action.desc.f1) { - disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: action.desc, ...action.desc })); + if (f1) { + disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: command })); } // keybinding - if (Array.isArray(action.desc.keybinding)) { - for (let item of action.desc.keybinding) { + if (Array.isArray(keybinding)) { + for (let item of keybinding) { KeybindingsRegistry.registerKeybindingRule({ ...item, - id: action.desc.id, - when: ContextKeyExpr.and(action.desc.precondition, item.when) + id: command.id, + when: ContextKeyExpr.and(command.precondition, item.when) }); } - } else if (action.desc.keybinding) { + } else if (keybinding) { KeybindingsRegistry.registerKeybindingRule({ - ...action.desc.keybinding, - id: action.desc.id, - when: ContextKeyExpr.and(action.desc.precondition, action.desc.keybinding.when) + ...keybinding, + id: command.id, + when: ContextKeyExpr.and(command.precondition, keybinding.when) }); } diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index 2368d78c9a..4a0f15c72c 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -7,7 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IMenu, IMenuActionOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction, ILocalizedString } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr, IContextKeyService, IContextKeyChangeEvent } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKeyChangeEvent, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; export class MenuService implements IMenuService { @@ -125,7 +125,7 @@ class Menu implements IMenu { return result; } - private static _fillInKbExprKeys(exp: ContextKeyExpr | undefined, set: Set): void { + private static _fillInKbExprKeys(exp: ContextKeyExpression | undefined, set: Set): void { if (exp) { for (let key of exp.keys()) { set.add(key); diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index 283e068d2c..6c200c7039 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -10,6 +10,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { LinkedList } from 'vs/base/common/linkedList'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { keys } from 'vs/base/common/map'; +import { Iterable } from 'vs/base/common/iterator'; export const ICommandService = createDecorator('commandService'); @@ -119,7 +120,7 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR if (!list || list.isEmpty()) { return undefined; } - return list.iterator().next().value; + return Iterable.first(list); } getCommands(): ICommandsMap { diff --git a/src/vs/platform/contextkey/browser/contextKeyService.ts b/src/vs/platform/contextkey/browser/contextKeyService.ts index 26b066efa3..89cc71a53d 100644 --- a/src/vs/platform/contextkey/browser/contextKeyService.ts +++ b/src/vs/platform/contextkey/browser/contextKeyService.ts @@ -8,7 +8,7 @@ import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { keys } from 'vs/base/common/map'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyExpr, IContext, IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget, IReadableSet, SET_CONTEXT_COMMAND_ID } from 'vs/platform/contextkey/common/contextkey'; +import { IContext, IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget, IReadableSet, SET_CONTEXT_COMMAND_ID, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; const KEYBINDING_CONTEXT_ATTR = 'data-keybinding-context'; @@ -142,6 +142,10 @@ class ConfigAwareContextValuesContainer extends Context { case 'string': value = configValue; break; + default: + if (Array.isArray(configValue)) { + value = JSON.stringify(configValue); + } } this._values.set(key, value); @@ -265,7 +269,7 @@ export abstract class AbstractContextKeyService implements IContextKeyService { return new ScopedContextKeyService(this, domNode); } - public contextMatchesRules(rules: ContextKeyExpr | undefined): boolean { + public contextMatchesRules(rules: ContextKeyExpression | undefined): boolean { if (this._isDisposed) { throw new Error(`AbstractContextKeyService has been disposed`); } diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index 12d20dd743..4a6f879fbd 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -6,69 +6,106 @@ import { Event } from 'vs/base/common/event'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform'; + +const STATIC_VALUES = new Map(); +STATIC_VALUES.set('false', false); +STATIC_VALUES.set('true', true); +STATIC_VALUES.set('isMac', isMacintosh); +STATIC_VALUES.set('isLinux', isLinux); +STATIC_VALUES.set('isWindows', isWindows); +STATIC_VALUES.set('isWeb', isWeb); +STATIC_VALUES.set('isMacNative', isMacintosh && !isWeb); export const enum ContextKeyExprType { - Defined = 1, - Not = 2, - Equals = 3, - NotEquals = 4, - And = 5, - Regex = 6, - NotRegex = 7, - Or = 8, - GreaterThanEquals = 9, // {{SQL CARBON EDIT}} add value - LessThanEquals = 10 // {{SQL CARBON EDIT}} add value + False = 0, + True = 1, + Defined = 2, + Not = 3, + Equals = 4, + NotEquals = 5, + And = 6, + Regex = 7, + NotRegex = 8, + Or = 9, + GreaterThanEquals = 10, // {{SQL CARBON EDIT}} add value + LessThanEquals = 11 // {{SQL CARBON EDIT}} add value } export interface IContextKeyExprMapper { - mapDefined(key: string): ContextKeyExpr; - mapNot(key: string): ContextKeyExpr; - mapEquals(key: string, value: any): ContextKeyExpr; - mapNotEquals(key: string, value: any): ContextKeyExpr; + mapDefined(key: string): ContextKeyExpression; + mapNot(key: string): ContextKeyExpression; + mapEquals(key: string, value: any): ContextKeyExpression; + mapNotEquals(key: string, value: any): ContextKeyExpression; mapRegex(key: string, regexp: RegExp | null): ContextKeyRegexExpr; } +export interface IContextKeyExpression { + cmp(other: ContextKeyExpression): number; + equals(other: ContextKeyExpression): boolean; + evaluate(context: IContext): boolean; + serialize(): string; + keys(): string[]; + map(mapFnc: IContextKeyExprMapper): ContextKeyExpression; + negate(): ContextKeyExpression; + +} + +export type ContextKeyExpression = ( + ContextKeyFalseExpr | ContextKeyTrueExpr | ContextKeyDefinedExpr | ContextKeyNotExpr + | ContextKeyEqualsExpr | ContextKeyNotEqualsExpr | ContextKeyRegexExpr + | ContextKeyNotRegexExpr | ContextKeyAndExpr | ContextKeyOrExpr | ContextKeyGreaterThanEqualsExpr | ContextKeyLessThanEqualsExpr +); + export abstract class ContextKeyExpr { - public static has(key: string): ContextKeyExpr { + public static false(): ContextKeyExpression { + return ContextKeyFalseExpr.INSTANCE; + } + + public static true(): ContextKeyExpression { + return ContextKeyTrueExpr.INSTANCE; + } + + public static has(key: string): ContextKeyExpression { return ContextKeyDefinedExpr.create(key); } - public static equals(key: string, value: any): ContextKeyExpr { + public static equals(key: string, value: any): ContextKeyExpression { return ContextKeyEqualsExpr.create(key, value); } - public static notEquals(key: string, value: any): ContextKeyExpr { + public static notEquals(key: string, value: any): ContextKeyExpression { return ContextKeyNotEqualsExpr.create(key, value); } - public static regex(key: string, value: RegExp): ContextKeyExpr { + public static regex(key: string, value: RegExp): ContextKeyExpression { return ContextKeyRegexExpr.create(key, value); } - public static not(key: string): ContextKeyExpr { + public static not(key: string): ContextKeyExpression { return ContextKeyNotExpr.create(key); } - public static and(...expr: Array): ContextKeyExpr | undefined { + public static and(...expr: Array): ContextKeyExpression | undefined { return ContextKeyAndExpr.create(expr); } - public static or(...expr: Array): ContextKeyExpr | undefined { + public static or(...expr: Array): ContextKeyExpression | undefined { return ContextKeyOrExpr.create(expr); } // {{SQL CARBON EDIT}} - public static greaterThanEquals(key: string, value: any): ContextKeyExpr { - return new ContextKeyGreaterThanEqualsExpr(key, value); + public static greaterThanEquals(key: string, value: any): ContextKeyExpression { + return ContextKeyGreaterThanEqualsExpr.create(key, value); } - public static lessThanEquals(key: string, value: any): ContextKeyExpr { - return new ContextKeyLessThanEqualsExpr(key, value); + public static lessThanEquals(key: string, value: any): ContextKeyExpression { + return ContextKeyLessThanEqualsExpr.create(key, value); } // - public static deserialize(serialized: string | null | undefined, strict: boolean = false): ContextKeyExpr | undefined { + public static deserialize(serialized: string | null | undefined, strict: boolean = false): ContextKeyExpression | undefined { if (!serialized) { return undefined; } @@ -76,17 +113,17 @@ export abstract class ContextKeyExpr { return this._deserializeOrExpression(serialized, strict); } - private static _deserializeOrExpression(serialized: string, strict: boolean): ContextKeyExpr | undefined { + private static _deserializeOrExpression(serialized: string, strict: boolean): ContextKeyExpression | undefined { let pieces = serialized.split('||'); return ContextKeyOrExpr.create(pieces.map(p => this._deserializeAndExpression(p, strict))); } - private static _deserializeAndExpression(serialized: string, strict: boolean): ContextKeyExpr | undefined { + private static _deserializeAndExpression(serialized: string, strict: boolean): ContextKeyExpression | undefined { let pieces = serialized.split('&&'); return ContextKeyAndExpr.create(pieces.map(p => this._deserializeOne(p, strict))); } - private static _deserializeOne(serializedOne: string, strict: boolean): ContextKeyExpr { + private static _deserializeOne(serializedOne: string, strict: boolean): ContextKeyExpression { serializedOne = serializedOne.trim(); if (serializedOne.indexOf('!=') >= 0) { @@ -107,11 +144,11 @@ export abstract class ContextKeyExpr { // {{SQL CARBON EDIT}} if (serializedOne.indexOf('>=') >= 0) { let pieces = serializedOne.split('>='); - return new ContextKeyGreaterThanEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); + return ContextKeyGreaterThanEqualsExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); } if (serializedOne.indexOf('<=') >= 0) { let pieces = serializedOne.split('<='); - return new ContextKeyLessThanEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); + return ContextKeyLessThanEqualsExpr.create(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); } // @@ -176,59 +213,104 @@ export abstract class ContextKeyExpr { return null; } } - - public abstract getType(): ContextKeyExprType; - public abstract equals(other: ContextKeyExpr): boolean; - public abstract evaluate(context: IContext): boolean; - public abstract serialize(): string; - public abstract keys(): string[]; - public abstract map(mapFnc: IContextKeyExprMapper): ContextKeyExpr; - public abstract negate(): ContextKeyExpr; } -function cmp(a: ContextKeyExpr, b: ContextKeyExpr): number { - let aType = a.getType(); - let bType = b.getType(); - if (aType !== bType) { - return aType - bType; +function cmp(a: ContextKeyExpression, b: ContextKeyExpression): number { + return a.cmp(b); +} + +export class ContextKeyFalseExpr implements IContextKeyExpression { + public static INSTANCE = new ContextKeyFalseExpr(); + + public readonly type = ContextKeyExprType.False; + + protected constructor() { } - switch (aType) { - case ContextKeyExprType.Defined: - return (a).cmp(b); - case ContextKeyExprType.Not: - return (a).cmp(b); - case ContextKeyExprType.Equals: - return (a).cmp(b); - case ContextKeyExprType.NotEquals: - return (a).cmp(b); - case ContextKeyExprType.Regex: - return (a).cmp(b); - case ContextKeyExprType.GreaterThanEquals: // {{SQL CARBON EDIT}} add case - return (a).cmp(b); - case ContextKeyExprType.LessThanEquals: // {{SQL CARBON EDIT}} add case - return (a).cmp(b); - case ContextKeyExprType.NotRegex: - return (a).cmp(b); - case ContextKeyExprType.And: - return (a).cmp(b); - default: - throw new Error('Unknown ContextKeyExpr!'); + + public cmp(other: ContextKeyExpression): number { + return this.type - other.type; + } + + public equals(other: ContextKeyExpression): boolean { + return (other.type === this.type); + } + + public evaluate(context: IContext): boolean { + return false; + } + + public serialize(): string { + return 'false'; + } + + public keys(): string[] { + return []; + } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { + return this; + } + + public negate(): ContextKeyExpression { + return ContextKeyTrueExpr.INSTANCE; } } -export class ContextKeyDefinedExpr implements ContextKeyExpr { - public static create(key: string): ContextKeyDefinedExpr { +export class ContextKeyTrueExpr implements IContextKeyExpression { + public static INSTANCE = new ContextKeyTrueExpr(); + + public readonly type = ContextKeyExprType.True; + + protected constructor() { + } + + public cmp(other: ContextKeyExpression): number { + return this.type - other.type; + } + + public equals(other: ContextKeyExpression): boolean { + return (other.type === this.type); + } + + public evaluate(context: IContext): boolean { + return true; + } + + public serialize(): string { + return 'true'; + } + + public keys(): string[] { + return []; + } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { + return this; + } + + public negate(): ContextKeyExpression { + return ContextKeyFalseExpr.INSTANCE; + } +} + +export class ContextKeyDefinedExpr implements IContextKeyExpression { + public static create(key: string): ContextKeyExpression { + const staticValue = STATIC_VALUES.get(key); + if (typeof staticValue === 'boolean') { + return staticValue ? ContextKeyTrueExpr.INSTANCE : ContextKeyFalseExpr.INSTANCE; + } return new ContextKeyDefinedExpr(key); } - protected constructor(protected key: string) { + public readonly type = ContextKeyExprType.Defined; + + protected constructor(protected readonly key: string) { } - public getType(): ContextKeyExprType { - return ContextKeyExprType.Defined; - } - - public cmp(other: ContextKeyDefinedExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } if (this.key < other.key) { return -1; } @@ -238,8 +320,8 @@ export class ContextKeyDefinedExpr implements ContextKeyExpr { return 0; } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyDefinedExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { return (this.key === other.key); } return false; @@ -257,35 +339,38 @@ export class ContextKeyDefinedExpr implements ContextKeyExpr { return [this.key]; } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapDefined(this.key); } - public negate(): ContextKeyExpr { + public negate(): ContextKeyExpression { return ContextKeyNotExpr.create(this.key); } } -export class ContextKeyEqualsExpr implements ContextKeyExpr { +export class ContextKeyEqualsExpr implements IContextKeyExpression { - public static create(key: string, value: any): ContextKeyExpr { + public static create(key: string, value: any): ContextKeyExpression { if (typeof value === 'boolean') { - if (value) { - return ContextKeyDefinedExpr.create(key); - } - return ContextKeyNotExpr.create(key); + return (value ? ContextKeyDefinedExpr.create(key) : ContextKeyNotExpr.create(key)); + } + const staticValue = STATIC_VALUES.get(key); + if (typeof staticValue === 'boolean') { + const trueValue = staticValue ? 'true' : 'false'; + return (value === trueValue ? ContextKeyTrueExpr.INSTANCE : ContextKeyFalseExpr.INSTANCE); } return new ContextKeyEqualsExpr(key, value); } + public readonly type = ContextKeyExprType.Equals; + private constructor(private readonly key: string, private readonly value: any) { } - public getType(): ContextKeyExprType { - return ContextKeyExprType.Equals; - } - - public cmp(other: ContextKeyEqualsExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } if (this.key < other.key) { return -1; } @@ -301,8 +386,8 @@ export class ContextKeyEqualsExpr implements ContextKeyExpr { return 0; } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyEqualsExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { return (this.key === other.key && this.value === other.value); } return false; @@ -322,35 +407,41 @@ export class ContextKeyEqualsExpr implements ContextKeyExpr { return [this.key]; } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapEquals(this.key, this.value); } - public negate(): ContextKeyExpr { + public negate(): ContextKeyExpression { return ContextKeyNotEqualsExpr.create(this.key, this.value); } } -export class ContextKeyNotEqualsExpr implements ContextKeyExpr { +export class ContextKeyNotEqualsExpr implements IContextKeyExpression { - public static create(key: string, value: any): ContextKeyExpr { + public static create(key: string, value: any): ContextKeyExpression { if (typeof value === 'boolean') { if (value) { return ContextKeyNotExpr.create(key); } return ContextKeyDefinedExpr.create(key); } + const staticValue = STATIC_VALUES.get(key); + if (typeof staticValue === 'boolean') { + const falseValue = staticValue ? 'true' : 'false'; + return (value === falseValue ? ContextKeyFalseExpr.INSTANCE : ContextKeyTrueExpr.INSTANCE); + } return new ContextKeyNotEqualsExpr(key, value); } - private constructor(private key: string, private value: any) { + public readonly type = ContextKeyExprType.NotEquals; + + private constructor(private readonly key: string, private readonly value: any) { } - public getType(): ContextKeyExprType { - return ContextKeyExprType.NotEquals; - } - - public cmp(other: ContextKeyNotEqualsExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } if (this.key < other.key) { return -1; } @@ -366,8 +457,8 @@ export class ContextKeyNotEqualsExpr implements ContextKeyExpr { return 0; } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyNotEqualsExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { return (this.key === other.key && this.value === other.value); } return false; @@ -387,29 +478,34 @@ export class ContextKeyNotEqualsExpr implements ContextKeyExpr { return [this.key]; } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapNotEquals(this.key, this.value); } - public negate(): ContextKeyExpr { + public negate(): ContextKeyExpression { return ContextKeyEqualsExpr.create(this.key, this.value); } } -export class ContextKeyNotExpr implements ContextKeyExpr { +export class ContextKeyNotExpr implements IContextKeyExpression { - public static create(key: string): ContextKeyExpr { + public static create(key: string): ContextKeyExpression { + const staticValue = STATIC_VALUES.get(key); + if (typeof staticValue === 'boolean') { + return (staticValue ? ContextKeyFalseExpr.INSTANCE : ContextKeyTrueExpr.INSTANCE); + } return new ContextKeyNotExpr(key); } - private constructor(private key: string) { + public readonly type = ContextKeyExprType.Not; + + private constructor(private readonly key: string) { } - public getType(): ContextKeyExprType { - return ContextKeyExprType.Not; - } - - public cmp(other: ContextKeyNotExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } if (this.key < other.key) { return -1; } @@ -419,8 +515,8 @@ export class ContextKeyNotExpr implements ContextKeyExpr { return 0; } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyNotExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { return (this.key === other.key); } return false; @@ -438,30 +534,31 @@ export class ContextKeyNotExpr implements ContextKeyExpr { return [this.key]; } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapNot(this.key); } - public negate(): ContextKeyExpr { + public negate(): ContextKeyExpression { return ContextKeyDefinedExpr.create(this.key); } } -export class ContextKeyRegexExpr implements ContextKeyExpr { +export class ContextKeyRegexExpr implements IContextKeyExpression { public static create(key: string, regexp: RegExp | null): ContextKeyRegexExpr { return new ContextKeyRegexExpr(key, regexp); } - private constructor(private key: string, private regexp: RegExp | null) { + public readonly type = ContextKeyExprType.Regex; + + private constructor(private readonly key: string, private readonly regexp: RegExp | null) { // } - public getType(): ContextKeyExprType { - return ContextKeyExprType.Regex; - } - - public cmp(other: ContextKeyRegexExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } if (this.key < other.key) { return -1; } @@ -479,8 +576,8 @@ export class ContextKeyRegexExpr implements ContextKeyExpr { return 0; } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyRegexExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { const thisSource = this.regexp ? this.regexp.source : ''; const otherSource = other.regexp ? other.regexp.source : ''; return (this.key === other.key && thisSource === otherSource); @@ -508,31 +605,32 @@ export class ContextKeyRegexExpr implements ContextKeyExpr { return mapFnc.mapRegex(this.key, this.regexp); } - public negate(): ContextKeyExpr { + public negate(): ContextKeyExpression { return ContextKeyNotRegexExpr.create(this); } } -export class ContextKeyNotRegexExpr implements ContextKeyExpr { +export class ContextKeyNotRegexExpr implements IContextKeyExpression { - public static create(actual: ContextKeyRegexExpr): ContextKeyExpr { + public static create(actual: ContextKeyRegexExpr): ContextKeyExpression { return new ContextKeyNotRegexExpr(actual); } + public readonly type = ContextKeyExprType.NotRegex; + private constructor(private readonly _actual: ContextKeyRegexExpr) { // } - public getType(): ContextKeyExprType { - return ContextKeyExprType.NotRegex; - } - - public cmp(other: ContextKeyNotRegexExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } return this._actual.cmp(other._actual); } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyNotRegexExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { return this._actual.equals(other._actual); } return false; @@ -550,18 +648,18 @@ export class ContextKeyNotRegexExpr implements ContextKeyExpr { return this._actual.keys(); } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return new ContextKeyNotRegexExpr(this._actual.map(mapFnc)); } - public negate(): ContextKeyExpr { + public negate(): ContextKeyExpression { return this._actual; } } -export class ContextKeyAndExpr implements ContextKeyExpr { +export class ContextKeyAndExpr implements IContextKeyExpression { - public static create(_expr: ReadonlyArray): ContextKeyExpr | undefined { + public static create(_expr: ReadonlyArray): ContextKeyExpression | undefined { const expr = ContextKeyAndExpr._normalizeArr(_expr); if (expr.length === 0) { return undefined; @@ -574,14 +672,15 @@ export class ContextKeyAndExpr implements ContextKeyExpr { return new ContextKeyAndExpr(expr); } - private constructor(public readonly expr: ContextKeyExpr[]) { + public readonly type = ContextKeyExprType.And; + + private constructor(public readonly expr: ContextKeyExpression[]) { } - public getType(): ContextKeyExprType { - return ContextKeyExprType.And; - } - - public cmp(other: ContextKeyAndExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } if (this.expr.length < other.expr.length) { return -1; } @@ -597,8 +696,8 @@ export class ContextKeyAndExpr implements ContextKeyExpr { return 0; } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyAndExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { if (this.expr.length !== other.expr.length) { return false; } @@ -621,20 +720,32 @@ export class ContextKeyAndExpr implements ContextKeyExpr { return true; } - private static _normalizeArr(arr: ReadonlyArray): ContextKeyExpr[] { - const expr: ContextKeyExpr[] = []; + private static _normalizeArr(arr: ReadonlyArray): ContextKeyExpression[] { + const expr: ContextKeyExpression[] = []; + let hasTrue = false; for (const e of arr) { if (!e) { continue; } - if (e instanceof ContextKeyAndExpr) { + if (e.type === ContextKeyExprType.True) { + // anything && true ==> anything + hasTrue = true; + continue; + } + + if (e.type === ContextKeyExprType.False) { + // anything && false ==> false + return [ContextKeyFalseExpr.INSTANCE]; + } + + if (e.type === ContextKeyExprType.And) { expr.push(...e.expr); continue; } - if (e instanceof ContextKeyOrExpr) { + if (e.type === ContextKeyExprType.Or) { // Not allowed, because we don't have parens! throw new Error(`It is not allowed to have an or expression here due to lack of parens! For example "a && (b||c)" is not supported, use "(a&&b) || (a&&c)" instead.`); } @@ -642,6 +753,10 @@ export class ContextKeyAndExpr implements ContextKeyExpr { expr.push(e); } + if (expr.length === 0 && hasTrue) { + return [ContextKeyTrueExpr.INSTANCE]; + } + expr.sort(cmp); return expr; @@ -659,12 +774,12 @@ export class ContextKeyAndExpr implements ContextKeyExpr { return result; } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return new ContextKeyAndExpr(this.expr.map(expr => expr.map(mapFnc))); } - public negate(): ContextKeyExpr { - let result: ContextKeyExpr[] = []; + public negate(): ContextKeyExpression { + let result: ContextKeyExpression[] = []; for (let expr of this.expr) { result.push(expr.negate()); } @@ -672,9 +787,9 @@ export class ContextKeyAndExpr implements ContextKeyExpr { } } -export class ContextKeyOrExpr implements ContextKeyExpr { +export class ContextKeyOrExpr implements IContextKeyExpression { - public static create(_expr: ReadonlyArray): ContextKeyExpr | undefined { + public static create(_expr: ReadonlyArray): ContextKeyExpression | undefined { const expr = ContextKeyOrExpr._normalizeArr(_expr); if (expr.length === 0) { return undefined; @@ -687,15 +802,32 @@ export class ContextKeyOrExpr implements ContextKeyExpr { return new ContextKeyOrExpr(expr); } - private constructor(public readonly expr: ContextKeyExpr[]) { + public readonly type = ContextKeyExprType.Or; + + private constructor(public readonly expr: ContextKeyExpression[]) { } - public getType(): ContextKeyExprType { - return ContextKeyExprType.Or; + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } + if (this.expr.length < other.expr.length) { + return -1; + } + if (this.expr.length > other.expr.length) { + return 1; + } + for (let i = 0, len = this.expr.length; i < len; i++) { + const r = cmp(this.expr[i], other.expr[i]); + if (r !== 0) { + return r; + } + } + return 0; } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyOrExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { if (this.expr.length !== other.expr.length) { return false; } @@ -718,17 +850,29 @@ export class ContextKeyOrExpr implements ContextKeyExpr { return false; } - private static _normalizeArr(arr: ReadonlyArray): ContextKeyExpr[] { - let expr: ContextKeyExpr[] = []; + private static _normalizeArr(arr: ReadonlyArray): ContextKeyExpression[] { + let expr: ContextKeyExpression[] = []; + let hasFalse = false; if (arr) { for (let i = 0, len = arr.length; i < len; i++) { - let e: ContextKeyExpr | null | undefined = arr[i]; + const e = arr[i]; if (!e) { continue; } - if (e instanceof ContextKeyOrExpr) { + if (e.type === ContextKeyExprType.False) { + // anything || false ==> anything + hasFalse = true; + continue; + } + + if (e.type === ContextKeyExprType.True) { + // anything || true ==> true + return [ContextKeyTrueExpr.INSTANCE]; + } + + if (e.type === ContextKeyExprType.Or) { expr = expr.concat(e.expr); continue; } @@ -736,6 +880,10 @@ export class ContextKeyOrExpr implements ContextKeyExpr { expr.push(e); } + if (expr.length === 0 && hasFalse) { + return [ContextKeyFalseExpr.INSTANCE]; + } + expr.sort(cmp); } @@ -754,18 +902,18 @@ export class ContextKeyOrExpr implements ContextKeyExpr { return result; } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return new ContextKeyOrExpr(this.expr.map(expr => expr.map(mapFnc))); } - public negate(): ContextKeyExpr { - let result: ContextKeyExpr[] = []; + public negate(): ContextKeyExpression { + let result: ContextKeyExpression[] = []; for (let expr of this.expr) { result.push(expr.negate()); } - const terminals = (node: ContextKeyExpr) => { - if (node instanceof ContextKeyOrExpr) { + const terminals = (node: ContextKeyExpression) => { + if (node.type === ContextKeyExprType.Or) { return node.expr; } return [node]; @@ -777,7 +925,7 @@ export class ContextKeyOrExpr implements ContextKeyExpr { const LEFT = result.shift()!; const RIGHT = result.shift()!; - const all: ContextKeyExpr[] = []; + const all: ContextKeyExpression[] = []; for (const left of terminals(LEFT)) { for (const right of terminals(RIGHT)) { all.push(ContextKeyExpr.and(left, right)!); @@ -791,15 +939,29 @@ export class ContextKeyOrExpr implements ContextKeyExpr { } // {{SQL CARBON EDIT}} -export class ContextKeyGreaterThanEqualsExpr implements ContextKeyExpr { +export class ContextKeyGreaterThanEqualsExpr implements IContextKeyExpression { + + public static create(key: string, value: any): ContextKeyExpression { + if (typeof value === 'boolean') { + return (value ? ContextKeyDefinedExpr.create(key) : ContextKeyNotExpr.create(key)); + } + const staticValue = STATIC_VALUES.get(key); + if (typeof staticValue === 'boolean') { + const trueValue = staticValue ? 'true' : 'false'; + return (value === trueValue ? ContextKeyTrueExpr.INSTANCE : ContextKeyFalseExpr.INSTANCE); + } + return new ContextKeyGreaterThanEqualsExpr(key, value); + } + + public readonly type = ContextKeyExprType.GreaterThanEquals; + constructor(private key: string, private value: any) { } - public getType(): ContextKeyExprType { - return ContextKeyExprType.GreaterThanEquals; - } - - public cmp(other: ContextKeyGreaterThanEqualsExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } if (this.key < other.key) { return -1; } @@ -815,14 +977,14 @@ export class ContextKeyGreaterThanEqualsExpr implements ContextKeyExpr { return 0; } - public equals(other: ContextKeyExpr): boolean { - if (other instanceof ContextKeyGreaterThanEqualsExpr) { + public equals(other: ContextKeyExpression): boolean { + if (other.type === this.type) { return (this.key === other.key && this.value === other.value); } return false; } - public negate(): ContextKeyExpr { + public negate(): ContextKeyExpression { throw new Error('Method not implemented.'); // @TODO anthonydresser need to figure out what to do in this case } @@ -836,10 +998,6 @@ export class ContextKeyGreaterThanEqualsExpr implements ContextKeyExpr { return (keyInt >= valueInt); } - public normalize(): ContextKeyExpr { - return this; - } - public serialize(): string { return this.key + ' >= \'' + this.value + '\''; } @@ -848,21 +1006,35 @@ export class ContextKeyGreaterThanEqualsExpr implements ContextKeyExpr { return [this.key]; } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapEquals(this.key, this.value); } } // {{SQL CARBON EDIT}} -export class ContextKeyLessThanEqualsExpr implements ContextKeyExpr { +export class ContextKeyLessThanEqualsExpr implements IContextKeyExpression { + + public static create(key: string, value: any): ContextKeyExpression { + if (typeof value === 'boolean') { + return (value ? ContextKeyDefinedExpr.create(key) : ContextKeyNotExpr.create(key)); + } + const staticValue = STATIC_VALUES.get(key); + if (typeof staticValue === 'boolean') { + const trueValue = staticValue ? 'true' : 'false'; + return (value === trueValue ? ContextKeyTrueExpr.INSTANCE : ContextKeyFalseExpr.INSTANCE); + } + return new ContextKeyLessThanEqualsExpr(key, value); + } + constructor(private key: string, private value: any) { } - public getType(): ContextKeyExprType { - return ContextKeyExprType.LessThanEquals; - } + public readonly type = ContextKeyExprType.LessThanEquals; - public cmp(other: ContextKeyLessThanEqualsExpr): number { + public cmp(other: ContextKeyExpression): number { + if (other.type !== this.type) { + return this.type - other.type; + } if (this.key < other.key) { return -1; } @@ -878,7 +1050,7 @@ export class ContextKeyLessThanEqualsExpr implements ContextKeyExpr { return 0; } - public equals(other: ContextKeyExpr): boolean { + public equals(other: ContextKeyExpression): boolean { if (other instanceof ContextKeyLessThanEqualsExpr) { return (this.key === other.key && this.value === other.value); } @@ -895,11 +1067,7 @@ export class ContextKeyLessThanEqualsExpr implements ContextKeyExpr { return (keyInt <= valueInt); } - public normalize(): ContextKeyExpr { - return this; - } - - public negate(): ContextKeyExpr { + public negate(): ContextKeyExpression { throw new Error('Method not implemented.'); // @TODO anthonydresser need to figure out what to do in this case } @@ -911,14 +1079,14 @@ export class ContextKeyLessThanEqualsExpr implements ContextKeyExpr { return [this.key]; } - public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression { return mapFnc.mapEquals(this.key, this.value); } } export class RawContextKey extends ContextKeyDefinedExpr { - private _defaultValue: T | undefined; + private readonly _defaultValue: T | undefined; constructor(key: string, defaultValue: T | undefined) { super(key); @@ -933,15 +1101,15 @@ export class RawContextKey extends ContextKeyDefinedExpr { return target.getContextKeyValue(this.key); } - public toNegated(): ContextKeyExpr { + public toNegated(): ContextKeyExpression { return ContextKeyExpr.not(this.key); } - public isEqualTo(value: string): ContextKeyExpr { + public isEqualTo(value: string): ContextKeyExpression { return ContextKeyExpr.equals(this.key, value); } - public notEqualsTo(value: string): ContextKeyExpr { + public notEqualsTo(value: string): ContextKeyExpression { return ContextKeyExpr.notEquals(this.key, value); } } @@ -983,7 +1151,7 @@ export interface IContextKeyService { createKey(key: string, defaultValue: T | undefined): IContextKey; - contextMatchesRules(rules: ContextKeyExpr | undefined): boolean; + contextMatchesRules(rules: ContextKeyExpression | undefined): boolean; getContextKeyValue(key: string): T | undefined; createScoped(target?: IContextKeyServiceTarget): IContextKeyService; diff --git a/src/vs/platform/contextkey/common/contextkeys.ts b/src/vs/platform/contextkey/common/contextkeys.ts index e3a4671328..ca10c48513 100644 --- a/src/vs/platform/contextkey/common/contextkeys.ts +++ b/src/vs/platform/contextkey/common/contextkeys.ts @@ -3,9 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform'; + +export const IsMacContext = new RawContextKey('isMac', isMacintosh); +export const IsLinuxContext = new RawContextKey('isLinux', isLinux); +export const IsWindowsContext = new RawContextKey('isWindows', isWindows); + +export const IsWebContext = new RawContextKey('isWeb', isWeb); +export const IsMacNativeContext = new RawContextKey('isMacNative', isMacintosh && !isWeb); + +export const IsDevelopmentContext = new RawContextKey('isDevelopment', false); export const InputFocusedContextKey = 'inputFocus'; export const InputFocusedContext = new RawContextKey(InputFocusedContextKey, false); - -export const FalseContext: ContextKeyExpr = new RawContextKey('__false', false); \ No newline at end of file diff --git a/src/vs/platform/contextkey/test/common/contextkey.test.ts b/src/vs/platform/contextkey/test/common/contextkey.test.ts index be5ac957bb..bcf23e3bb6 100644 --- a/src/vs/platform/contextkey/test/common/contextkey.test.ts +++ b/src/vs/platform/contextkey/test/common/contextkey.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { isMacintosh, isLinux, isWindows } from 'vs/base/common/platform'; function createContext(ctx: any) { return { @@ -101,6 +102,8 @@ suite('ContextKeyExpr', () => { testBatch('d', 'd'); testBatch('z', undefined); + testExpression('true', true); + testExpression('false', false); testExpression('a && !b', true && !false); testExpression('a && b', true && false); testExpression('a && !b && c == 5', true && !false && '5' === '5'); @@ -119,10 +122,30 @@ suite('ContextKeyExpr', () => { const actual = ContextKeyExpr.deserialize(expr)!.negate().serialize(); assert.strictEqual(actual, expected); } + testNegate('true', 'false'); + testNegate('false', 'true'); testNegate('a', '!a'); testNegate('a && b || c', '!a && !c || !b && !c'); testNegate('a && b || c || d', '!a && !c && !d || !b && !c && !d'); testNegate('!a && !b || !c && !d', 'a && c || a && d || b && c || b && d'); testNegate('!a && !b || !c && !d || !e && !f', 'a && c && e || a && c && f || a && d && e || a && d && f || b && c && e || b && c && f || b && d && e || b && d && f'); }); + + test('false, true', () => { + function testNormalize(expr: string, expected: string): void { + const actual = ContextKeyExpr.deserialize(expr)!.serialize(); + assert.strictEqual(actual, expected); + } + testNormalize('true', 'true'); + testNormalize('!true', 'false'); + testNormalize('false', 'false'); + testNormalize('!false', 'true'); + testNormalize('a && true', 'a'); + testNormalize('a && false', 'false'); + testNormalize('a || true', 'true'); + testNormalize('a || false', 'a'); + testNormalize('isMac', isMacintosh ? 'true' : 'false'); + testNormalize('isLinux', isLinux ? 'true' : 'false'); + testNormalize('isWindows', isWindows ? 'true' : 'false'); + }); }); diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index 086ce2b4a3..389aad6482 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -24,7 +24,7 @@ export interface IEditorModel { dispose(): void; } -export interface IBaseResourceInput { +export interface IBaseResourceEditorInput { /** * Optional options to use when opening the text input. @@ -60,12 +60,12 @@ export interface IBaseResourceInput { readonly forceUntitled?: boolean; } -export interface IResourceInput extends IBaseResourceInput { +export interface IResourceEditorInput extends IBaseResourceEditorInput { /** * The resource URI of the resource to open. */ - resource: URI; + readonly resource: URI; /** * The encoding of the text input if known. diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 73ce40d8c2..eb1b6a6206 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -20,8 +20,9 @@ import { dirExists } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; -export interface IElectronMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } +export interface IElectronMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } export const IElectronMainService = createDecorator('electronMainService'); @@ -34,7 +35,8 @@ export class ElectronMainService implements IElectronMainService { @IDialogMainService private readonly dialogMainService: IDialogMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @ILogService private readonly logService: ILogService ) { } @@ -392,6 +394,7 @@ export class ElectronMainService implements IElectronMainService { async startCrashReporter(windowId: number | undefined, options: CrashReporterStartOptions): Promise { crashReporter.start(options); + this.logService.trace('ElectronMainService#crashReporter', JSON.stringify(options)); } //#endregion diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index b79f90247c..4f38258ae8 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -118,7 +118,6 @@ export interface IEnvironmentService extends IUserHomeProvider { args: ParsedArgs; execPath: string; - cliPath: string; appRoot: string; userHome: string; @@ -139,7 +138,6 @@ export interface IEnvironmentService extends IUserHomeProvider { settingsSyncPreviewResource: URI; keybindingsSyncPreviewResource: URI; - machineSettingsHome: URI; machineSettingsResource: URI; globalStorageHome: string; @@ -161,10 +159,7 @@ export interface IEnvironmentService extends IUserHomeProvider { debugExtensionHost: IExtensionHostDebugParams; isBuilt: boolean; - wait: boolean; - status: boolean; - log?: string; logsPath: string; verbose: boolean; @@ -175,7 +170,6 @@ export interface IEnvironmentService extends IUserHomeProvider { installSourcePath: string; disableUpdates: boolean; - disableCrashReporter: boolean; driverHandle?: string; driverVerbose: boolean; diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 10595116c9..922cad38a4 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -124,10 +124,7 @@ export class EnvironmentService implements IEnvironmentService { get userDataSyncLogResource(): URI { return URI.file(path.join(this.logsPath, 'userDataSync.log')); } @memoize - get machineSettingsHome(): URI { return URI.file(path.join(this.userDataPath, 'Machine')); } - - @memoize - get machineSettingsResource(): URI { return resources.joinPath(this.machineSettingsHome, 'settings.json'); } + get machineSettingsResource(): URI { return resources.joinPath(URI.file(path.join(this.userDataPath, 'Machine')), 'settings.json'); } @memoize get globalStorageHome(): string { return path.join(this.appSettingsHome.fsPath, 'globalStorage'); } @@ -248,10 +245,6 @@ export class EnvironmentService implements IEnvironmentService { get verbose(): boolean { return !!this._args.verbose; } get log(): string | undefined { return this._args.log; } - get wait(): boolean { return !!this._args.wait; } - - get status(): boolean { return !!this._args.status; } - @memoize get mainIPCHandle(): string { return getIPCHandle(this.userDataPath, 'main'); } diff --git a/src/vs/platform/environment/node/stdin.ts b/src/vs/platform/environment/node/stdin.ts index 401e1eaf54..ed051c5819 100644 --- a/src/vs/platform/environment/node/stdin.ts +++ b/src/vs/platform/environment/node/stdin.ts @@ -39,18 +39,20 @@ export function getStdinFilePath(): string { return paths.join(os.tmpdir(), `code-stdin-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 3)}.txt`); } -export function readFromStdin(targetPath: string, verbose: boolean): Promise { +export async function readFromStdin(targetPath: string, verbose: boolean): Promise { + // open tmp file for writing const stdinFileStream = fs.createWriteStream(targetPath); - // Pipe into tmp file using terminals encoding - return resolveTerminalEncoding(verbose).then(async encoding => { - const iconv = await import('iconv-lite'); - if (!iconv.encodingExists(encoding)) { - console.log(`Unsupported terminal encoding: ${encoding}, falling back to UTF-8.`); - encoding = 'utf8'; - } - const converterStream = iconv.decodeStream(encoding); - process.stdin.pipe(converterStream).pipe(stdinFileStream); - }); + let encoding = await resolveTerminalEncoding(verbose); + + const iconv = await import('iconv-lite'); + if (!iconv.encodingExists(encoding)) { + console.log(`Unsupported terminal encoding: ${encoding}, falling back to UTF-8.`); + encoding = 'utf8'; + } + + // Pipe into tmp file using terminals encoding + const converterStream = iconv.decodeStream(encoding); + process.stdin.pipe(converterStream).pipe(stdinFileStream); } diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index b1c563557b..c492018fdb 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -45,7 +45,7 @@ export interface IGrammar { } export interface IJSONValidation { - fileMatch: string; + fileMatch: string | string[]; url: string; } diff --git a/src/vs/platform/files/test/node/diskFileService.test.ts b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts similarity index 98% rename from src/vs/platform/files/test/node/diskFileService.test.ts rename to src/vs/platform/files/test/electron-browser/diskFileService.test.ts index e528458c4e..482f51df6c 100644 --- a/src/vs/platform/files/test/node/diskFileService.test.ts +++ b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { tmpdir } from 'os'; import { FileService } from 'vs/platform/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; -import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; +import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { generateUuid } from 'vs/base/common/uuid'; import { join, basename, dirname, posix } from 'vs/base/common/path'; @@ -67,6 +67,7 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.FileOpenReadWriteClose | FileSystemProviderCapabilities.FileReadStream | + FileSystemProviderCapabilities.Trash | FileSystemProviderCapabilities.FileFolderCopy; if (isLinux) { @@ -131,10 +132,12 @@ suite('Disk File Service', function () { const disposables = new DisposableStore(); // Given issues such as https://github.com/microsoft/vscode/issues/78602 - // we see random test failures when accessing the native file system. To - // diagnose further, we retry node.js file access tests up to 3 times to - // rule out any random disk issue. + // and https://github.com/microsoft/vscode/issues/92334 we see random test + // failures when accessing the native file system. To diagnose further, we + // retry node.js file access tests up to 3 times to rule out any random disk + // issue and increase the timeout. this.retries(3); + this.timeout(1000 * 10); setup(async () => { const logService = new NullLogService(); @@ -459,13 +462,21 @@ suite('Disk File Service', function () { }); test('deleteFile', async () => { + return testDeleteFile(false); + }); + + (isLinux /* trash is unreliable on Linux */ ? test.skip : test)('deleteFile (useTrash)', async () => { + return testDeleteFile(true); + }); + + async function testDeleteFile(useTrash: boolean): Promise { let event: FileOperationEvent; disposables.add(service.onDidRunOperation(e => event = e)); const resource = URI.file(join(testDir, 'deep', 'conway.js')); const source = await service.resolve(resource); - await service.del(source.resource); + await service.del(source.resource, { useTrash }); assert.equal(existsSync(source.resource.fsPath), false); @@ -475,14 +486,14 @@ suite('Disk File Service', function () { let error: Error | undefined = undefined; try { - await service.del(source.resource); + await service.del(source.resource, { useTrash }); } catch (e) { error = e; } assert.ok(error); assert.equal((error).fileOperationResult, FileOperationResult.FILE_NOT_FOUND); - }); + } test('deleteFile - symbolic link (exists)', async () => { if (isWindows) { @@ -531,19 +542,27 @@ suite('Disk File Service', function () { }); test('deleteFolder (recursive)', async () => { + return testDeleteFolderRecursive(false); + }); + + (isLinux /* trash is unreliable on Linux */ ? test.skip : test)('deleteFolder (recursive, useTrash)', async () => { + return testDeleteFolderRecursive(true); + }); + + async function testDeleteFolderRecursive(useTrash: boolean): Promise { let event: FileOperationEvent; disposables.add(service.onDidRunOperation(e => event = e)); const resource = URI.file(join(testDir, 'deep')); const source = await service.resolve(resource); - await service.del(source.resource, { recursive: true }); + await service.del(source.resource, { recursive: true, useTrash }); assert.equal(existsSync(source.resource.fsPath), false); assert.ok(event!); assert.equal(event!.resource.fsPath, resource.fsPath); assert.equal(event!.operation, FileOperation.DELETE); - }); + } test('deleteFolder (non recursive)', async () => { const resource = URI.file(join(testDir, 'deep')); diff --git a/src/vs/platform/files/test/node/fixtures/resolver/examples/company.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/company.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/examples/company.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/company.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/examples/conway.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/conway.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/examples/conway.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/conway.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/examples/employee.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/employee.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/examples/employee.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/employee.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/examples/small.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/small.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/examples/small.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/examples/small.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/index.html b/src/vs/platform/files/test/electron-browser/fixtures/resolver/index.html similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/index.html rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/index.html diff --git a/src/vs/platform/files/test/node/fixtures/resolver/other/deep/company.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/company.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/other/deep/company.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/company.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/other/deep/conway.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/conway.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/other/deep/conway.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/conway.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/other/deep/employee.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/employee.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/other/deep/employee.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/employee.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/other/deep/small.js b/src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/small.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/other/deep/small.js rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/other/deep/small.js diff --git a/src/vs/platform/files/test/node/fixtures/resolver/site.css b/src/vs/platform/files/test/electron-browser/fixtures/resolver/site.css similarity index 100% rename from src/vs/platform/files/test/node/fixtures/resolver/site.css rename to src/vs/platform/files/test/electron-browser/fixtures/resolver/site.css diff --git a/src/vs/platform/files/test/node/fixtures/service/binary.txt b/src/vs/platform/files/test/electron-browser/fixtures/service/binary.txt similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/binary.txt rename to src/vs/platform/files/test/electron-browser/fixtures/service/binary.txt diff --git a/src/vs/platform/files/test/node/fixtures/service/deep/company.js b/src/vs/platform/files/test/electron-browser/fixtures/service/deep/company.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/deep/company.js rename to src/vs/platform/files/test/electron-browser/fixtures/service/deep/company.js diff --git a/src/vs/platform/files/test/node/fixtures/service/deep/conway.js b/src/vs/platform/files/test/electron-browser/fixtures/service/deep/conway.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/deep/conway.js rename to src/vs/platform/files/test/electron-browser/fixtures/service/deep/conway.js diff --git a/src/vs/platform/files/test/node/fixtures/service/deep/employee.js b/src/vs/platform/files/test/electron-browser/fixtures/service/deep/employee.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/deep/employee.js rename to src/vs/platform/files/test/electron-browser/fixtures/service/deep/employee.js diff --git a/src/vs/platform/files/test/node/fixtures/service/deep/small.js b/src/vs/platform/files/test/electron-browser/fixtures/service/deep/small.js similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/deep/small.js rename to src/vs/platform/files/test/electron-browser/fixtures/service/deep/small.js diff --git a/src/vs/platform/files/test/node/fixtures/service/index.html b/src/vs/platform/files/test/electron-browser/fixtures/service/index.html similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/index.html rename to src/vs/platform/files/test/electron-browser/fixtures/service/index.html diff --git a/src/vs/platform/files/test/node/fixtures/service/lorem.txt b/src/vs/platform/files/test/electron-browser/fixtures/service/lorem.txt similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/lorem.txt rename to src/vs/platform/files/test/electron-browser/fixtures/service/lorem.txt diff --git a/src/vs/platform/files/test/node/fixtures/service/small.txt b/src/vs/platform/files/test/electron-browser/fixtures/service/small.txt similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/small.txt rename to src/vs/platform/files/test/electron-browser/fixtures/service/small.txt diff --git a/src/vs/platform/files/test/node/fixtures/service/small_umlaut.txt b/src/vs/platform/files/test/electron-browser/fixtures/service/small_umlaut.txt similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/small_umlaut.txt rename to src/vs/platform/files/test/electron-browser/fixtures/service/small_umlaut.txt diff --git a/src/vs/platform/files/test/node/fixtures/service/some_utf16le.css b/src/vs/platform/files/test/electron-browser/fixtures/service/some_utf16le.css similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/some_utf16le.css rename to src/vs/platform/files/test/electron-browser/fixtures/service/some_utf16le.css diff --git a/src/vs/platform/files/test/node/fixtures/service/some_utf8_bom.txt b/src/vs/platform/files/test/electron-browser/fixtures/service/some_utf8_bom.txt similarity index 100% rename from src/vs/platform/files/test/node/fixtures/service/some_utf8_bom.txt rename to src/vs/platform/files/test/electron-browser/fixtures/service/some_utf8_bom.txt diff --git a/src/vs/platform/files/test/node/normalizer.test.ts b/src/vs/platform/files/test/electron-browser/normalizer.test.ts similarity index 100% rename from src/vs/platform/files/test/node/normalizer.test.ts rename to src/vs/platform/files/test/electron-browser/normalizer.test.ts diff --git a/src/vs/platform/instantiation/common/instantiation.ts b/src/vs/platform/instantiation/common/instantiation.ts index 987eb81f69..f057eacd46 100644 --- a/src/vs/platform/instantiation/common/instantiation.ts +++ b/src/vs/platform/instantiation/common/instantiation.ts @@ -102,7 +102,6 @@ export interface IInstantiationService { createInstance(descriptor: descriptors.SyncDescriptor8, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8): T; createInstance any, R extends InstanceType>(t: Ctor, ...args: GetLeadingNonServiceArgs>): R; - createInstance any, R extends InstanceType>(t: Ctor): R; /** * diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index a0310c0c14..fe3bb37b34 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -13,12 +13,6 @@ import { IdleValue } from 'vs/base/common/async'; // TRACING const _enableTracing = false; -// PROXY -// Ghetto-declare of the global Proxy object. This isn't the proper way -// but allows us to run this code in the browser without IE11. -declare const Proxy: any; -const _canUseProxy = typeof Proxy === 'function'; - class CyclicDependencyError extends Error { constructor(graph: Graph) { super('cyclic dependency between services'); @@ -211,8 +205,8 @@ export class InstantiationService implements IInstantiationService { } private _createServiceInstance(ctor: any, args: any[] = [], _supportsDelayedInstantiation: boolean, _trace: Trace): T { - if (!_supportsDelayedInstantiation || !_canUseProxy) { - // eager instantiation or no support JS proxies (e.g. IE11) + if (!_supportsDelayedInstantiation) { + // eager instantiation return this._createInstance(ctor, args, _trace); } else { diff --git a/src/vs/platform/issue/node/issue.ts b/src/vs/platform/issue/node/issue.ts index 7529c7cdd1..aa0478656d 100644 --- a/src/vs/platform/issue/node/issue.ts +++ b/src/vs/platform/issue/node/issue.ts @@ -49,6 +49,7 @@ export interface IssueReporterExtensionData { version: string; id: string; isTheme: boolean; + isBuiltin: boolean; displayName: string | undefined; repositoryUrl: string | undefined; bugsUrl: string | undefined; diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index 102650bc7b..23066a9b03 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -6,7 +6,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays'; import { MenuRegistry } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr, IContext, ContextKeyOrExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IContext, ContextKeyExpression, ContextKeyExprType } from 'vs/platform/contextkey/common/contextkey'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { keys } from 'vs/base/common/map'; @@ -49,12 +49,17 @@ export class KeybindingResolver { continue; } + if (k.when && k.when.type === ContextKeyExprType.False) { + // when condition is false + continue; + } + // TODO@chords this._addKeyPress(k.keypressParts[0], k); } } - private static _isTargetedForRemoval(defaultKb: ResolvedKeybindingItem, keypressFirstPart: string | null, keypressChordPart: string | null, command: string, when: ContextKeyExpr | undefined): boolean { + private static _isTargetedForRemoval(defaultKb: ResolvedKeybindingItem, keypressFirstPart: string | null, keypressChordPart: string | null, command: string, when: ContextKeyExpression | undefined): boolean { if (defaultKb.command !== command) { return false; } @@ -175,7 +180,7 @@ export class KeybindingResolver { /** * Returns true if it is provable `a` implies `b`. */ - public static whenIsEntirelyIncluded(a: ContextKeyExpr | null | undefined, b: ContextKeyExpr | null | undefined): boolean { + public static whenIsEntirelyIncluded(a: ContextKeyExpression | null | undefined, b: ContextKeyExpression | null | undefined): boolean { if (!b) { return true; } @@ -189,11 +194,11 @@ export class KeybindingResolver { /** * Returns true if it is provable `p` implies `q`. */ - private static _implies(p: ContextKeyExpr, q: ContextKeyExpr): boolean { + private static _implies(p: ContextKeyExpression, q: ContextKeyExpression): boolean { const notP = p.negate(); - const terminals = (node: ContextKeyExpr) => { - if (node instanceof ContextKeyOrExpr) { + const terminals = (node: ContextKeyExpression) => { + if (node.type === ContextKeyExprType.Or) { return node.expr; } return [node]; @@ -318,7 +323,7 @@ export class KeybindingResolver { return null; } - public static contextMatchesRules(context: IContext, rules: ContextKeyExpr | null | undefined): boolean { + public static contextMatchesRules(context: IContext, rules: ContextKeyExpression | null | undefined): boolean { if (!rules) { return true; } diff --git a/src/vs/platform/keybinding/common/keybindingsRegistry.ts b/src/vs/platform/keybinding/common/keybindingsRegistry.ts index bdb9741d39..916bd5566e 100644 --- a/src/vs/platform/keybinding/common/keybindingsRegistry.ts +++ b/src/vs/platform/keybinding/common/keybindingsRegistry.ts @@ -6,14 +6,14 @@ import { KeyCode, Keybinding, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { CommandsRegistry, ICommandHandler, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; export interface IKeybindingItem { keybinding: Keybinding; command: string; commandArgs?: any; - when: ContextKeyExpr | null | undefined; + when: ContextKeyExpression | null | undefined; weight1: number; weight2: number; } @@ -39,7 +39,7 @@ export interface IKeybindingRule extends IKeybindings { id: string; weight: number; args?: any; - when: ContextKeyExpr | null | undefined; + when: ContextKeyExpression | null | undefined; } export interface IKeybindingRule2 { @@ -50,7 +50,7 @@ export interface IKeybindingRule2 { id: string; args?: any; weight: number; - when: ContextKeyExpr | undefined; + when: ContextKeyExpression | undefined; } export const enum KeybindingWeight { @@ -209,7 +209,7 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry { } } - private _registerDefaultKeybinding(keybinding: Keybinding, commandId: string, commandArgs: any, weight1: number, weight2: number, when: ContextKeyExpr | null | undefined): void { + private _registerDefaultKeybinding(keybinding: Keybinding, commandId: string, commandArgs: any, weight1: number, weight2: number, when: ContextKeyExpression | null | undefined): void { if (OS === OperatingSystem.Windows) { this._assertNoCtrlAlt(keybinding.parts[0], commandId); } diff --git a/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts b/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts index d4087f26bc..0eea9c04b3 100644 --- a/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts +++ b/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts @@ -5,7 +5,7 @@ import { CharCode } from 'vs/base/common/charCode'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; export class ResolvedKeybindingItem { _resolvedKeybindingItemBrand: void; @@ -15,10 +15,10 @@ export class ResolvedKeybindingItem { public readonly bubble: boolean; public readonly command: string | null; public readonly commandArgs: any; - public readonly when: ContextKeyExpr | undefined; + public readonly when: ContextKeyExpression | undefined; public readonly isDefault: boolean; - constructor(resolvedKeybinding: ResolvedKeybinding | undefined, command: string | null, commandArgs: any, when: ContextKeyExpr | undefined, isDefault: boolean) { + constructor(resolvedKeybinding: ResolvedKeybinding | undefined, command: string | null, commandArgs: any, when: ContextKeyExpression | undefined, isDefault: boolean) { this.resolvedKeybinding = resolvedKeybinding; this.keypressParts = resolvedKeybinding ? removeElementsAfterNulls(resolvedKeybinding.getDispatchParts()) : []; this.bubble = (command ? command.charCodeAt(0) === CharCode.Caret : false); diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index c1fba7c67b..e7972286d5 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -7,7 +7,7 @@ import { KeyChord, KeyCode, KeyMod, Keybinding, ResolvedKeybinding, SimpleKeybin import { OS } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr, IContext, IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContext, IContextKeyService, IContextKeyServiceTarget, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService'; import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; @@ -182,7 +182,7 @@ suite('AbstractKeybindingService', () => { statusMessageCallsDisposed = null; }); - function kbItem(keybinding: number, command: string, when?: ContextKeyExpr): ResolvedKeybindingItem { + function kbItem(keybinding: number, command: string, when?: ContextKeyExpression): ResolvedKeybindingItem { const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS) : undefined); return new ResolvedKeybindingItem( resolvedKeybinding, diff --git a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts index 9a61d91b4f..03fbbf8fe3 100644 --- a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { KeyChord, KeyCode, KeyMod, SimpleKeybinding, createKeybinding, createSimpleKeybinding } from 'vs/base/common/keyCodes'; import { OS } from 'vs/base/common/platform'; -import { ContextKeyExpr, IContext } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContext, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; @@ -20,7 +20,7 @@ function createContext(ctx: any) { suite('KeybindingResolver', () => { - function kbItem(keybinding: number, command: string, commandArgs: any, when: ContextKeyExpr | undefined, isDefault: boolean): ResolvedKeybindingItem { + function kbItem(keybinding: number, command: string, commandArgs: any, when: ContextKeyExpression | undefined, isDefault: boolean): ResolvedKeybindingItem { const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS) : undefined); return new ResolvedKeybindingItem( resolvedKeybinding, @@ -225,7 +225,7 @@ suite('KeybindingResolver', () => { test('resolve command', function () { - function _kbItem(keybinding: number, command: string, when: ContextKeyExpr | undefined): ResolvedKeybindingItem { + function _kbItem(keybinding: number, command: string, when: ContextKeyExpression | undefined): ResolvedKeybindingItem { return kbItem(keybinding, command, null, when, true); } diff --git a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts index 42d38eba8d..1909b0e3d8 100644 --- a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts +++ b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { Keybinding, ResolvedKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes'; import { OS } from 'vs/base/common/platform'; -import { ContextKeyExpr, IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingEvent, IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; @@ -47,7 +47,7 @@ export class MockContextKeyService implements IContextKeyService { this._keys.set(key, ret); return ret; } - public contextMatchesRules(rules: ContextKeyExpr): boolean { + public contextMatchesRules(rules: ContextKeyExpression): boolean { return false; } public get onDidChangeContext(): Event { diff --git a/src/vs/platform/label/common/label.ts b/src/vs/platform/label/common/label.ts index f4988822c8..61d301c111 100644 --- a/src/vs/platform/label/common/label.ts +++ b/src/vs/platform/label/common/label.ts @@ -26,7 +26,11 @@ export interface ILabelService { getHostLabel(scheme: string, authority?: string): string; getSeparator(scheme: string, authority?: string): '/' | '\\'; registerFormatter(formatter: ResourceLabelFormatter): IDisposable; - onDidChangeFormatters: Event; + onDidChangeFormatters: Event; +} + +export interface IFormatterChangeEvent { + scheme: string; } export interface ResourceLabelFormatter { diff --git a/src/vs/platform/layout/browser/layoutService.ts b/src/vs/platform/layout/browser/layoutService.ts index cb168dca82..cba10389fb 100644 --- a/src/vs/platform/layout/browser/layoutService.ts +++ b/src/vs/platform/layout/browser/layoutService.ts @@ -5,14 +5,10 @@ import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IDimension } from 'vs/base/browser/dom'; export const ILayoutService = createDecorator('layoutService'); -export interface IDimension { - readonly width: number; - readonly height: number; -} - export interface ILayoutService { _serviceBrand: undefined; @@ -27,9 +23,19 @@ export interface ILayoutService { */ readonly container: HTMLElement; + /** + * An offset to use for positioning elements inside the container. + */ + readonly offset?: { top: number }; + /** * An event that is emitted when the container is layed out. The * event carries the dimensions of the container as part of it. */ readonly onLayout: Event; + + /** + * Focus the primary component of the container. + */ + focus(): void; } diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 535e5e23cb..07e4a680ff 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -26,7 +26,7 @@ import { attachListStyler, computeStyles, defaultListStyles, IColorMapping } fro import { IThemeService } from 'vs/platform/theme/common/themeService'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ObjectTree, IObjectTreeOptions, ICompressibleTreeRenderer, CompressibleObjectTree, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; -import { ITreeEvent, ITreeRenderer, IAsyncDataSource, IDataSource, ITreeMouseEvent } from 'vs/base/browser/ui/tree/tree'; +import { ITreeRenderer, IAsyncDataSource, IDataSource } from 'vs/base/browser/ui/tree/tree'; import { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree, ITreeCompressionDelegate, ICompressibleAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree'; import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree'; import { IKeyboardNavigationEventFilter, IAbstractTreeOptions, RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree'; @@ -270,7 +270,7 @@ export class WorkbenchList extends List { super(user, container, delegate, renderers, { keyboardSupport: false, - ...computeStyles(themeService.getTheme(), defaultListStyles), + ...computeStyles(themeService.getColorTheme(), defaultListStyles), ...workbenchListOptions, horizontalScrolling } @@ -298,7 +298,7 @@ export class WorkbenchList extends List { this.updateStyles(options.overrideStyles); } - this.disposables.add(this.onSelectionChange(() => { + this.disposables.add(this.onDidChangeSelection(() => { const selection = this.getSelection(); const focus = this.getFocus(); @@ -306,7 +306,7 @@ export class WorkbenchList extends List { this.listMultiSelection.set(selection.length > 1); this.listDoubleSelection.set(selection.length === 2); })); - this.disposables.add(this.onFocusChange(() => { + this.disposables.add(this.onDidChangeFocus(() => { const selection = this.getSelection(); const focus = this.getFocus(); @@ -378,7 +378,7 @@ export class WorkbenchPagedList extends PagedList { super(user, container, delegate, renderers, { keyboardSupport: false, - ...computeStyles(themeService.getTheme(), defaultListStyles), + ...computeStyles(themeService.getColorTheme(), defaultListStyles), ...workbenchListOptions, horizontalScrolling } @@ -482,7 +482,7 @@ export class WorkbenchTree extends Tree { const opts = { horizontalScrollMode, keyboardSupport: false, - ...computeStyles(themeService.getTheme(), defaultListStyles), + ...computeStyles(themeService.getColorTheme(), defaultListStyles), ...options }; @@ -618,9 +618,10 @@ export interface IOpenEvent { browserEvent?: UIEvent; } -export interface ITreeResourceNavigatorOptions { +export interface IResourceNavigatorOptions { readonly openOnFocus?: boolean; readonly openOnSelection?: boolean; + readonly openOnSingleClick?: boolean; } export interface SelectionKeyboardEvent extends KeyboardEvent { @@ -634,16 +635,45 @@ export function getSelectionKeyboardEvent(typeArg = 'keydown', preserveFocus?: b return e; } -export class TreeResourceNavigator extends Disposable { +export abstract class ResourceNavigator extends Disposable { - private options: ITreeResourceNavigatorOptions; + static createListResourceNavigator(list: WorkbenchList | WorkbenchPagedList, options?: IResourceNavigatorOptions): ResourceNavigator { + return new class extends ResourceNavigator { + constructor() { + super(list, options); + } + }(); + } + + static createTreeResourceNavigator(tree: WorkbenchObjectTree | WorkbenchCompressibleObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, + options?: IResourceNavigatorOptions): ResourceNavigator { + return new class extends ResourceNavigator { + constructor() { + super(tree, { + ...{ + openOnSingleClick: tree.openOnSingleClick + }, + ...(options || {}) + }); + } + }(); + } + + private readonly options: IResourceNavigatorOptions; private readonly _onDidOpenResource = new Emitter>(); readonly onDidOpenResource: Event> = this._onDidOpenResource.event; constructor( - private tree: WorkbenchObjectTree | WorkbenchCompressibleObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, - options?: ITreeResourceNavigatorOptions + private readonly treeOrList: { + getFocus(): (T | null)[], + getSelection(): (T | null)[], + setSelection(elements: (T | null)[], browserEvent?: UIEvent): void, + onDidChangeFocus: Event<{ browserEvent?: UIEvent }>, + onDidChangeSelection: Event<{ browserEvent?: UIEvent }>, + onDidOpen: Event<{ browserEvent?: UIEvent }>, + }, + options?: IResourceNavigatorOptions ) { super(); @@ -659,50 +689,50 @@ export class TreeResourceNavigator extends Disposable { private registerListeners(): void { if (this.options && this.options.openOnFocus) { - this._register(this.tree.onDidChangeFocus(e => this.onFocus(e))); + this._register(this.treeOrList.onDidChangeFocus(e => this.onFocus(e.browserEvent))); } if (this.options && this.options.openOnSelection) { - this._register(this.tree.onDidChangeSelection(e => this.onSelection(e))); + this._register(this.treeOrList.onDidChangeSelection(e => this.onSelection(e.browserEvent))); } - this._register(this.tree.onDidOpen(e => this.onSelection(e))); + this._register(this.treeOrList.onDidOpen(e => this.onSelection(e.browserEvent))); } - private onFocus(e: ITreeEvent): void { - const focus = this.tree.getFocus(); - this.tree.setSelection(focus as T[], e.browserEvent); + private onFocus(browserEvent?: UIEvent): void { + const focus = this.treeOrList.getFocus(); + this.treeOrList.setSelection(focus, browserEvent); - if (!e.browserEvent) { + if (!browserEvent) { return; } - const isMouseEvent = e.browserEvent && e.browserEvent instanceof MouseEvent; + const isMouseEvent = browserEvent && browserEvent instanceof MouseEvent; if (!isMouseEvent) { - const preserveFocus = (e.browserEvent instanceof KeyboardEvent && typeof (e.browserEvent).preserveFocus === 'boolean') ? - !!(e.browserEvent).preserveFocus : + const preserveFocus = (browserEvent instanceof KeyboardEvent && typeof (browserEvent).preserveFocus === 'boolean') ? + !!(browserEvent).preserveFocus : true; - this.open(preserveFocus, false, false, e.browserEvent); + this.open(preserveFocus, false, false, browserEvent); } } - private onSelection(e: ITreeEvent | ITreeMouseEvent, doubleClick = false): void { - if (!e.browserEvent || e.browserEvent.type === 'contextmenu') { + private onSelection(browserEvent?: MouseEvent | UIEvent): void { + if (!browserEvent || browserEvent.type === 'contextmenu') { return; } - const isKeyboardEvent = e.browserEvent instanceof KeyboardEvent; - const isMiddleClick = e.browserEvent instanceof MouseEvent ? e.browserEvent.button === 1 : false; - const isDoubleClick = e.browserEvent.detail === 2; - const preserveFocus = (e.browserEvent instanceof KeyboardEvent && typeof (e.browserEvent).preserveFocus === 'boolean') ? - !!(e.browserEvent).preserveFocus : + const isKeyboardEvent = browserEvent instanceof KeyboardEvent; + const isMiddleClick = browserEvent instanceof MouseEvent ? browserEvent.button === 1 : false; + const isDoubleClick = browserEvent.detail === 2; + const preserveFocus = (browserEvent instanceof KeyboardEvent && typeof (browserEvent).preserveFocus === 'boolean') ? + !!(browserEvent).preserveFocus : !isDoubleClick; - if (this.tree.openOnSingleClick || isDoubleClick || isKeyboardEvent) { - const sideBySide = e.browserEvent instanceof MouseEvent && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey); - this.open(preserveFocus, isDoubleClick || isMiddleClick, sideBySide, e.browserEvent); + if (this.options.openOnSingleClick || isDoubleClick || isKeyboardEvent) { + const sideBySide = browserEvent instanceof MouseEvent && (browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey); + this.open(preserveFocus, isDoubleClick || isMiddleClick, sideBySide, browserEvent); } } @@ -714,7 +744,7 @@ export class TreeResourceNavigator extends Disposable { revealIfVisible: true }, sideBySide, - element: this.tree.getSelection()[0], + element: this.treeOrList.getSelection()[0], browserEvent }); } @@ -1123,7 +1153,7 @@ configurationRegistry.registerConfiguration({ [horizontalScrollingKey]: { 'type': 'boolean', 'default': false, - 'description': localize('horizontalScrolling setting', "Controls whether lists and trees support horizontal scrolling in the workbench.") + 'description': localize('horizontalScrolling setting', "Controls whether lists and trees support horizontal scrolling in the workbench. Warning: turning on this setting has a performance implication.") }, 'workbench.tree.horizontalScrolling': { 'type': 'boolean', diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index 9ab153e5bd..8c8efb64c5 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -21,20 +21,20 @@ export interface INotificationProperties { * Sticky notifications are not automatically removed after a certain timeout. By * default, notifications with primary actions and severity error are always sticky. */ - sticky?: boolean; + readonly sticky?: boolean; /** * Silent notifications are not shown to the user unless the notification center * is opened. The status bar will still indicate all number of notifications to * catch some attention. */ - silent?: boolean; + readonly silent?: boolean; /** * Adds an action to never show the notification again. The choice will be persisted * such as future requests will not cause the notification to show again. */ - neverShowAgain?: INeverShowAgainOptions; + readonly neverShowAgain?: INeverShowAgainOptions; } export enum NeverShowAgainScope { @@ -55,19 +55,19 @@ export interface INeverShowAgainOptions { /** * The id is used to persist the selection of not showing the notification again. */ - id: string; + readonly id: string; /** * By default the action will show up as primary action. Setting this to true will * make it a secondary action instead. */ - isSecondary?: boolean; + readonly isSecondary?: boolean; /** * Whether to persist the choice in the current workspace or for all workspaces. By * default it will be persisted for all workspaces. */ - scope?: NeverShowAgainScope; + readonly scope?: NeverShowAgainScope; } export interface INotification extends INotificationProperties { @@ -75,18 +75,18 @@ export interface INotification extends INotificationProperties { /** * The severity of the notification. Either `Info`, `Warning` or `Error`. */ - severity: Severity; + readonly severity: Severity; /** * The message of the notification. This can either be a `string` or `Error`. Messages * can optionally include links in the format: `[text](link)` */ - message: NotificationMessage; + readonly message: NotificationMessage; /** * The source of the notification appears as additional information. */ - source?: string; + readonly source?: string; /** * Actions to show as part of the notification. Primary actions show up as @@ -106,7 +106,7 @@ export interface INotification extends INotificationProperties { * The initial set of progress properties for the notification. To update progress * later on, access the `INotificationHandle.progress` property. */ - progress?: INotificationProgressProperties; + readonly progress?: INotificationProgressProperties; } export interface INotificationActions { @@ -115,14 +115,14 @@ export interface INotificationActions { * Primary actions show up as buttons as part of the message and will close * the notification once clicked. */ - primary?: ReadonlyArray; + readonly primary?: ReadonlyArray; /** * Secondary actions are meant to provide additional configuration or context * for the notification and will show up less prominent. A notification does not * close automatically when invoking a secondary action. */ - secondary?: ReadonlyArray; + readonly secondary?: ReadonlyArray; } export interface INotificationProgressProperties { @@ -130,17 +130,17 @@ export interface INotificationProgressProperties { /** * Causes the progress bar to spin infinitley. */ - infinite?: boolean; + readonly infinite?: boolean; /** * Indicate the total amount of work. */ - total?: number; + readonly total?: number; /** * Indicate that a specific chunk of work is done. */ - worked?: number; + readonly worked?: number; } export interface INotificationProgress { @@ -176,7 +176,7 @@ export interface INotificationHandle { /** * Will be fired whenever the visibility of the notification changes. * A notification can either be visible as toast or inside the notification - * center if it is visible. + * center if it is visible. */ readonly onDidChangeVisibility: Event; @@ -214,19 +214,19 @@ export interface IPromptChoice { /** * Label to show for the choice to the user. */ - label: string; + readonly label: string; /** * Primary choices show up as buttons in the notification below the message. * Secondary choices show up under the gear icon in the header of the notification. */ - isSecondary?: boolean; + readonly isSecondary?: boolean; /** * Whether to keep the notification open after the choice was selected * by the user. By default, will close the notification upon click. */ - keepOpen?: boolean; + readonly keepOpen?: boolean; /** * Triggered when the user selects the choice. @@ -249,13 +249,13 @@ export interface IStatusMessageOptions { * An optional timeout after which the status message should show. By default * the status message will show immediately. */ - showAfter?: number; + readonly showAfter?: number; /** * An optional timeout after which the status message is to be hidden. By default * the status message will not hide until another status message is displayed. */ - hideAfter?: number; + readonly hideAfter?: number; } export enum NotificationsFilter { diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 6bd7488ea6..5f22694105 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -95,7 +95,7 @@ export interface IProductConfiguration { readonly checksums?: { [path: string]: string; }; readonly checksumFailMoreInfoUrl?: string; - readonly hockeyApp?: { + readonly appCenter?: { readonly 'win32-ia32': string; readonly 'win32-x64': string; readonly 'linux-x64': string; diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index ab92509f84..271f16c80d 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -17,7 +17,7 @@ export interface IProgressService { _serviceBrand: undefined; - withProgress( + withProgress( options: IProgressOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions, task: (progress: IProgress) => Promise, onDidCancel?: (choice?: number) => void @@ -36,7 +36,7 @@ export interface IProgressIndicator { * Indicate progress for the duration of the provided promise. Progress will stop in * any case of promise completion, error or cancellation. */ - showWhile(promise: Promise, delay?: number): Promise; + showWhile(promise: Promise, delay?: number): Promise; } export const enum ProgressLocation { @@ -98,7 +98,7 @@ export interface IProgress { export class Progress implements IProgress { - static readonly None: IProgress = Object.freeze({ report() { } }); + static readonly None: IProgress = Object.freeze({ report() { } }); private _value?: T; get value(): T | undefined { return this._value; } diff --git a/src/vs/platform/quickinput/browser/helpQuickAccess.ts b/src/vs/platform/quickinput/browser/helpQuickAccess.ts new file mode 100644 index 0000000000..3ee5de55f2 --- /dev/null +++ b/src/vs/platform/quickinput/browser/helpQuickAccess.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickPick, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickAccessProvider, IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { localize } from 'vs/nls'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; + +interface IHelpQuickAccessPickItem extends IQuickPickItem { + prefix: string; +} + +export class HelpQuickAccessProvider implements IQuickAccessProvider { + + static PREFIX = '?'; + + private readonly registry = Registry.as(Extensions.Quickaccess); + + constructor(@IQuickInputService private readonly quickInputService: IQuickInputService) { } + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Open a picker with the selected value if picked + disposables.add(picker.onDidAccept(() => { + const [item] = picker.selectedItems; + if (item) { + this.quickInputService.quickAccess.show(item.prefix); + } + })); + + // Fill in all providers separated by editor/global scope + const { editorProviders, globalProviders } = this.getQuickAccessProviders(); + picker.items = editorProviders.length === 0 || globalProviders.length === 0 ? + + // Without groups + [ + ...(editorProviders.length === 0 ? globalProviders : editorProviders) + ] : + + // With groups + [ + { label: localize('globalCommands', "global commands"), type: 'separator' }, + ...globalProviders, + { label: localize('editorCommands', "editor commands"), type: 'separator' }, + ...editorProviders + ]; + + return disposables; + } + + private getQuickAccessProviders(): { editorProviders: IHelpQuickAccessPickItem[], globalProviders: IHelpQuickAccessPickItem[] } { + const globalProviders: IHelpQuickAccessPickItem[] = []; + const editorProviders: IHelpQuickAccessPickItem[] = []; + + for (const provider of this.registry.getQuickAccessProviders().sort((p1, p2) => p1.prefix.localeCompare(p2.prefix))) { + for (const helpEntry of provider.helpEntries) { + const prefix = helpEntry.prefix || provider.prefix; + const label = prefix || '\u2026' /* ... */; + + (helpEntry.needsEditor ? editorProviders : globalProviders).push({ + prefix, + label, + description: helpEntry.description, + ariaLabel: localize('entryAriaLabel', "{0}, picker help", label) + }); + } + } + + return { editorProviders, globalProviders }; + } +} + diff --git a/src/vs/platform/quickinput/browser/quickAccess.ts b/src/vs/platform/quickinput/browser/quickAccess.ts new file mode 100644 index 0000000000..a1c83bfdd9 --- /dev/null +++ b/src/vs/platform/quickinput/browser/quickAccess.ts @@ -0,0 +1,95 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { IQuickAccessController, IQuickAccessProvider, IQuickAccessRegistry, Extensions, IQuickAccessProviderDescriptor } from 'vs/platform/quickinput/common/quickAccess'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { once } from 'vs/base/common/functional'; + +export class QuickAccessController extends Disposable implements IQuickAccessController { + + private readonly registry = Registry.as(Extensions.Quickaccess); + private readonly mapProviderToDescriptor = new Map(); + + private lastActivePicker: IQuickPick | undefined = undefined; + + constructor( + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(); + } + + show(value = ''): void { + const disposables = new DisposableStore(); + + // Hide any previous picker if any + this.lastActivePicker?.hide(); + + // Find provider for the value to show + const [provider, descriptor] = this.getOrInstantiateProvider(value); + + // Create a picker for the provider to use with the initial value + // and adjust the filtering to exclude the prefix from filtering + const picker = disposables.add(this.quickInputService.createQuickPick()); + picker.placeholder = descriptor.placeholder; + picker.value = value; + picker.valueSelection = [value.length, value.length]; + picker.contextKey = descriptor.contextKey; + picker.filterValue = (value: string) => value.substring(descriptor.prefix.length); + + // Remember as last active picker and clean up once picker get's disposed + this.lastActivePicker = picker; + disposables.add(toDisposable(() => { + if (picker === this.lastActivePicker) { + this.lastActivePicker = undefined; + } + })); + + // Create a cancellation token source that is valid as long as the + // picker has not been closed without picking an item + const cts = disposables.add(new CancellationTokenSource()); + once(picker.onDidHide)(() => { + if (picker.selectedItems.length === 0) { + cts.cancel(); + } + + // Start to dispose once picker hides + disposables.dispose(); + }); + + // Whenever the value changes, check if the provider has + // changed and if so - re-create the picker from the beginning + disposables.add(picker.onDidChangeValue(value => { + const [providerForValue] = this.getOrInstantiateProvider(value); + if (providerForValue !== provider) { + this.show(value); + } + })); + + // Ask provider to fill the picker as needed + disposables.add(provider.provide(picker, cts.token)); + + // Finally, show the picker. This is important because a provider + // may not call this and then our disposables would leak that rely + // on the onDidHide event. + picker.show(); + } + + private getOrInstantiateProvider(value: string): [IQuickAccessProvider, IQuickAccessProviderDescriptor] { + const providerDescriptor = this.registry.getQuickAccessProvider(value) || this.registry.defaultProvider; + + let provider = this.mapProviderToDescriptor.get(providerDescriptor); + if (!provider) { + provider = this.instantiationService.createInstance(providerDescriptor.ctor); + this.mapProviderToDescriptor.set(providerDescriptor, provider); + } + + return [provider, providerDescriptor]; + } +} diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts similarity index 50% rename from src/vs/workbench/browser/parts/quickinput/quickInput.ts rename to src/vs/platform/quickinput/browser/quickInput.ts index 88b4855c82..78b1bb3c54 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -3,72 +3,72 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Component } from 'vs/workbench/common/component'; import { IQuickInputService, IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInputButton, IInputBox, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { inputBackground, inputForeground, inputBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationInfoBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorForeground, inputValidationErrorBorder, badgeBackground, badgeForeground, contrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, progressBarBackground, widgetShadow, listFocusForeground, listFocusBackground, activeContrastBorder, pickerGroupBorder, pickerGroupForeground } from 'vs/platform/theme/common/colorRegistry'; -import { QUICK_INPUT_BACKGROUND, QUICK_INPUT_FOREGROUND } from 'vs/workbench/common/theme'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; +import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; +import { inputBackground, inputForeground, inputBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationInfoBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorForeground, inputValidationErrorBorder, badgeBackground, badgeForeground, contrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, progressBarBackground, widgetShadow, listFocusForeground, listFocusBackground, activeContrastBorder, pickerGroupBorder, pickerGroupForeground, quickInputForeground, quickInputBackground, quickInputTitleBackground } from 'vs/platform/theme/common/colorRegistry'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { CLOSE_ON_FOCUS_LOST_CONFIG } from 'vs/workbench/browser/quickopen'; import { computeStyles } from 'vs/platform/theme/common/styler'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ICommandAndKeybindingRule, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { inQuickOpenContext, InQuickOpenContextKey } from 'vs/workbench/browser/parts/quickopen/quickopen'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IStorageService } from 'vs/platform/storage/common/storage'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { QuickInputController, IQuickInputStyles } from 'vs/base/parts/quickinput/browser/quickInput'; +import { QuickInputController, IQuickInputStyles, IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { List, IListOptions } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; +import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess'; +import { QuickAccessController } from 'vs/platform/quickinput/browser/quickAccess'; -export class QuickInputService extends Component implements IQuickInputService { +export interface IQuickInputControllerHost extends ILayoutService { } - public _serviceBrand: undefined; +export class QuickInputService extends Themable implements IQuickInputService { - public backButton: IQuickInputButton; + _serviceBrand: undefined; - private static readonly ID = 'workbench.component.quickinput'; + get backButton(): IQuickInputButton { return this.controller.backButton; } + get onShow() { return this.controller.onShow; } + get onHide() { return this.controller.onHide; } - private inQuickOpenWidgets: Record = {}; - private inQuickOpenContext: IContextKey; - private contexts: Map> = new Map(); - private controller: QuickInputController; + private _controller: QuickInputController | undefined; + private get controller(): QuickInputController { + if (!this._controller) { + this._controller = this._register(this.createController()); + } + + return this._controller; + } + + private _quickAccess: IQuickAccessController | undefined; + get quickAccess(): IQuickAccessController { + if (!this._quickAccess) { + this._quickAccess = this._register(this.instantiationService.createInstance(QuickAccessController)); + } + + return this._quickAccess; + } + + private readonly contexts = new Map>(); constructor( - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IConfigurationService private readonly configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IKeybindingService private readonly keybindingService: IKeybindingService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @IStorageService storageService: IStorageService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + @ILayoutService protected readonly layoutService: ILayoutService ) { - super(QuickInputService.ID, themeService, storageService); - this.inQuickOpenContext = InQuickOpenContextKey.bindTo(contextKeyService); - this._register(this.quickOpenService.onShow(() => this.inQuickOpen('quickOpen', true))); - this._register(this.quickOpenService.onHide(() => this.inQuickOpen('quickOpen', false))); - this.controller = new QuickInputController({ + super(themeService); + } + + protected createController(host: IQuickInputControllerHost = this.layoutService, options?: Partial): QuickInputController { + const defaultOptions: IQuickInputOptions = { idPrefix: 'quickInput_', // Constant since there is still only one. - container: this.layoutService.getWorkbenchElement(), - ignoreFocusOut: () => this.environmentService.args['sticky-quickopen'] || !this.configurationService.getValue(CLOSE_ON_FOCUS_LOST_CONFIG), + container: host.container, + ignoreFocusOut: () => false, isScreenReaderOptimized: () => this.accessibilityService.isScreenReaderOptimized(), - backKeybindingLabel: () => this.keybindingService.lookupKeybinding(QuickPickBack.id)?.getLabel() || undefined, + backKeybindingLabel: () => undefined, setContextKey: (id?: string) => this.setContextKey(id), - returnFocus: () => this.editorGroupService.activeGroup.focus(), + returnFocus: () => host.focus(), createList: ( user: string, container: HTMLElement, @@ -76,38 +76,24 @@ export class QuickInputService extends Component implements IQuickInputService { renderers: IListRenderer[], options: IListOptions, ) => this.instantiationService.createInstance(WorkbenchList, user, container, delegate, renderers, options) as List, - styles: this.computeStyles(), - }); - this.backButton = this.controller.backButton; - this._register(this.layoutService.onLayout(dimension => this.controller.layout(dimension, this.layoutService.getTitleBarOffset()))); - this.controller.layout(this.layoutService.dimension, this.layoutService.getTitleBarOffset()); - this._register(this.quickOpenService.onShow(() => this.controller.hide(true))); - this._register(this.controller.onShow(() => { - this.quickOpenService.close(); - this.inQuickOpen('quickInput', true); - this.resetContextKeys(); - })); - this._register(this.controller.onHide(() => { - this.inQuickOpen('quickInput', false); - this.resetContextKeys(); - })); - } + styles: this.computeStyles() + }; - private inQuickOpen(widget: 'quickInput' | 'quickOpen', open: boolean) { - if (open) { - this.inQuickOpenWidgets[widget] = true; - } else { - delete this.inQuickOpenWidgets[widget]; - } - if (Object.keys(this.inQuickOpenWidgets).length) { - if (!this.inQuickOpenContext.get()) { - this.inQuickOpenContext.set(true); - } - } else { - if (this.inQuickOpenContext.get()) { - this.inQuickOpenContext.reset(); - } - } + const controller = this._register(new QuickInputController({ + ...defaultOptions, + ...options + })); + + controller.layout(host.dimension, host.offset?.top ?? 0); + + // Layout changes + this._register(host.onLayout(dimension => controller.layout(dimension, host.offset?.top ?? 0))); + + // Context keys + this._register(controller.onShow(() => this.resetContextKeys())); + this._register(controller.onHide(() => this.resetContextKeys())); + + return controller; } private setContextKey(id?: string) { @@ -180,6 +166,10 @@ export class QuickInputService extends Component implements IQuickInputService { return this.controller.cancel(); } + hide(focusLost?: boolean): void { + return this.controller.hide(focusLost); + } + protected updateStyles() { this.controller.applyStyles(this.computeStyles()); } @@ -187,12 +177,12 @@ export class QuickInputService extends Component implements IQuickInputService { private computeStyles(): IQuickInputStyles { return { widget: { - titleColor: { dark: 'rgba(255, 255, 255, 0.105)', light: 'rgba(0,0,0,.06)', hc: 'black' }[this.theme.type], // TODO ...computeStyles(this.theme, { - quickInputBackground: QUICK_INPUT_BACKGROUND, - quickInputForeground: QUICK_INPUT_FOREGROUND, + quickInputBackground, + quickInputForeground, + quickInputTitleBackground, contrastBorder, - widgetShadow, + widgetShadow }), }, inputBox: computeStyles(this.theme, { @@ -207,7 +197,7 @@ export class QuickInputService extends Component implements IQuickInputService { inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorForeground, - inputValidationErrorBorder, + inputValidationErrorBorder }), countBadge: computeStyles(this.theme, { badgeBackground, @@ -224,42 +214,15 @@ export class QuickInputService extends Component implements IQuickInputService { progressBarBackground }), list: computeStyles(this.theme, { - listBackground: QUICK_INPUT_BACKGROUND, + listBackground: quickInputBackground, // Look like focused when inactive. listInactiveFocusForeground: listFocusForeground, listInactiveFocusBackground: listFocusBackground, listFocusOutline: activeContrastBorder, listInactiveFocusOutline: activeContrastBorder, pickerGroupBorder, - pickerGroupForeground, - }), + pickerGroupForeground + }) }; } } - -export const QuickPickManyToggle: ICommandAndKeybindingRule = { - id: 'workbench.action.quickPickManyToggle', - weight: KeybindingWeight.WorkbenchContrib, - when: inQuickOpenContext, - primary: 0, - handler: accessor => { - const quickInputService = accessor.get(IQuickInputService); - quickInputService.toggle(); - } -}; - -export const QuickPickBack: ICommandAndKeybindingRule = { - id: 'workbench.action.quickInputBack', - weight: KeybindingWeight.WorkbenchContrib + 50, - when: inQuickOpenContext, - primary: 0, - win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, - mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, - linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS }, - handler: accessor => { - const quickInputService = accessor.get(IQuickInputService); - quickInputService.back(); - } -}; - -registerSingleton(IQuickInputService, QuickInputService, true); diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts new file mode 100644 index 0000000000..472c2879c7 --- /dev/null +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -0,0 +1,242 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { first } from 'vs/base/common/arrays'; +import { startsWith } from 'vs/base/common/strings'; +import { assertIsDefined } from 'vs/base/common/types'; +import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IQuickPickSeparator } from 'vs/base/parts/quickinput/common/quickInput'; + +export interface IQuickAccessController { + + /** + * Open the quick access picker with the optional value prefilled. + */ + show(value?: string): void; +} + +export interface IQuickAccessProvider { + + /** + * Called whenever a prefix was typed into quick pick that matches the provider. + * + * @param picker the picker to use for showing provider results. The picker is + * automatically shown after the method returns, no need to call `show()`. + * @param token providers have to check the cancellation token everytime after + * a long running operation or from event handlers because it could be that the + * picker has been closed or changed meanwhile. The token can be used to find out + * that the picker was closed without picking an entry (e.g. was canceled by the user). + * @return a disposable that will automatically be disposed when the picker + * closes or is replaced by another picker. + */ + provide(picker: IQuickPick, token: CancellationToken): IDisposable; +} + +export interface IQuickAccessProviderHelp { + + /** + * The prefix to show for the help entry. If not provided, + * the prefix used for registration will be taken. + */ + prefix?: string; + + /** + * A description text to help understand the intent of the provider. + */ + description: string; + + /** + * Separation between provider for editors and global ones. + */ + needsEditor: boolean; +} + +export interface IQuickAccessProviderDescriptor { + + /** + * The actual provider that will be instantiated as needed. + */ + readonly ctor: { new(...services: any /* TS BrandedService but no clue how to type this properly */[]): IQuickAccessProvider }; + + /** + * The prefix for quick access picker to use the provider for. + */ + readonly prefix: string; + + /** + * A placeholder to use for the input field when the provider is active. + * This will also be read out by screen readers and thus helps for + * accessibility. + */ + readonly placeholder?: string; + + /** + * Documentation for the provider in the quick access help. + */ + readonly helpEntries: IQuickAccessProviderHelp[]; + + /** + * A context key that will be set automatically when the + * picker for the provider is showing. + */ + readonly contextKey?: string; +} + +export const Extensions = { + Quickaccess: 'workbench.contributions.quickaccess' +}; + +export interface IQuickAccessRegistry { + + /** + * The default provider to use when no other provider matches. + */ + defaultProvider: IQuickAccessProviderDescriptor; + + /** + * Registers a quick access provider to the platform. + */ + registerQuickAccessProvider(provider: IQuickAccessProviderDescriptor): IDisposable; + + /** + * Get all registered quick access providers. + */ + getQuickAccessProviders(): IQuickAccessProviderDescriptor[]; + + /** + * Get a specific quick access provider for a given prefix. + */ + getQuickAccessProvider(prefix: string): IQuickAccessProviderDescriptor | undefined; +} + +class QuickAccessRegistry implements IQuickAccessRegistry { + private providers: IQuickAccessProviderDescriptor[] = []; + + private _defaultProvider: IQuickAccessProviderDescriptor | undefined = undefined; + get defaultProvider(): IQuickAccessProviderDescriptor { return assertIsDefined(this._defaultProvider); } + set defaultProvider(provider: IQuickAccessProviderDescriptor) { this._defaultProvider = provider; } + + registerQuickAccessProvider(provider: IQuickAccessProviderDescriptor): IDisposable { + this.providers.push(provider); + + // sort the providers by decreasing prefix length, such that longer + // prefixes take priority: 'ext' vs 'ext install' - the latter should win + this.providers.sort((providerA, providerB) => providerB.prefix.length - providerA.prefix.length); + + return toDisposable(() => this.providers.splice(this.providers.indexOf(provider), 1)); + } + + getQuickAccessProviders(): IQuickAccessProviderDescriptor[] { + return [this.defaultProvider, ...this.providers]; + } + + getQuickAccessProvider(prefix: string): IQuickAccessProviderDescriptor | undefined { + return prefix ? (first(this.providers, provider => startsWith(prefix, provider.prefix)) || undefined) : undefined; + } +} + +Registry.add(Extensions.Quickaccess, new QuickAccessRegistry()); + +//#region Helper class for simple picker based providers + +export interface IPickerQuickAccessItem extends IQuickPickItem { + + /** + * A method that will be executed when the pick item is accepted from + * the picker. The picker will close automatically before running this. + */ + accept?(): void; + + /** + * A method that will be executed when a button of the pick item was + * clicked on. The picker will only close if `true` is returned. + * + * @param buttonIndex index of the button of the item that + * was clicked. + * + * @returns a valud indicating if the picker should close or not. + */ + trigger?(buttonIndex: number): boolean; +} + +export abstract class PickerQuickAccessProvider implements IQuickAccessProvider { + + constructor(private prefix: string) { } + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Disable filtering & sorting, we control the results + picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; + + // Set initial picks and update on type + let picksCts: CancellationTokenSource | undefined = undefined; + const updatePickerItems = async () => { + + // Cancel any previous ask for picks and busy + picksCts?.dispose(true); + picker.busy = false; + + // Create new cancellation source for this run + picksCts = new CancellationTokenSource(token); + + // Collect picks and support both long running and short + const res = this.getPicks(picker.value.substr(this.prefix.length).trim(), picksCts.token); + if (Array.isArray(res)) { + picker.items = res; + } else { + picker.busy = true; + try { + picker.items = await res; + } finally { + picker.busy = false; + } + } + + this.getPicks(picker.value.substr(this.prefix.length).trim(), picksCts.token); + }; + disposables.add(picker.onDidChangeValue(() => updatePickerItems())); + updatePickerItems(); + + // Accept the pick on accept and hide picker + disposables.add(picker.onDidAccept(() => { + const [item] = picker.selectedItems; + if (typeof item?.accept === 'function') { + picker.hide(); + item.accept(); + } + })); + + // Trigger the pick with button index if button triggered + disposables.add(picker.onDidTriggerItemButton(({ button, item }) => { + if (typeof item.trigger === 'function') { + const buttonIndex = item.buttons?.indexOf(button) ?? -1; + if (buttonIndex >= 0) { + const hide = item.trigger(buttonIndex); + if (hide !== false) { + picker.hide(); + } + } + } + })); + + return disposables; + } + + /** + * Returns an array of picks and separators as needed. If the picks are resolved + * long running, the provided cancellation token should be used to cancel the + * operation when the token signals this. + * + * The implementor is responsible for filtering and sorting the picks given the + * provided `filter`. + */ + protected abstract getPicks(filter: string, token: CancellationToken): Array | Promise>; +} + +//#endregion diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index a32a8185e2..a4056cc493 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -3,11 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInputButton, IInputBox, QuickPickInput } from 'vs/base/parts/quickinput/common/quickInput'; +import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess'; -export { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods } from 'vs/base/parts/quickinput/common/quickInput'; +export * from 'vs/base/parts/quickinput/common/quickInput'; export const IQuickInputService = createDecorator('quickInputService'); @@ -18,7 +20,28 @@ export interface IQuickInputService { _serviceBrand: undefined; /** - * Opens the quick input box for selecting items and returns a promise with the user selected item(s) if any. + * Provides access to the back button in quick input. + */ + readonly backButton: IQuickInputButton; + + /** + * Provides access to the quick access providers. + */ + readonly quickAccess: IQuickAccessController; + + /** + * Allows to register on the event that quick input is showing. + */ + readonly onShow: Event; + + /** + * Allows to register on the event that quick input is hiding. + */ + readonly onHide: Event; + + /** + * Opens the quick input box for selecting items and returns a promise + * with the user selected item(s) if any. */ pick(picks: Promise[]> | QuickPickInput[], options?: IPickOptions & { canPickMany: true }, token?: CancellationToken): Promise; pick(picks: Promise[]> | QuickPickInput[], options?: IPickOptions & { canPickMany: false }, token?: CancellationToken): Promise; @@ -29,20 +52,46 @@ export interface IQuickInputService { */ input(options?: IInputOptions, token?: CancellationToken): Promise; - backButton: IQuickInputButton; - + /** + * Provides raw access to the quick pick controller. + */ createQuickPick(): IQuickPick; + + /** + * Provides raw access to the quick input controller. + */ createInputBox(): IInputBox; + /** + * Moves focus into quick input. + */ focus(): void; + /** + * Toggle the checked state of the selected item. + */ toggle(): void; + /** + * Navigate inside the opened quick input list. + */ navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void; - accept(): Promise; - + /** + * Navigate back in a multi-step quick input. + */ back(): Promise; + /** + * Accept the selected item. + */ + accept(): Promise; + + /** + * Cancels quick input and closes it. + */ cancel(): Promise; + + // TODO@Ben remove once quick open is gone + hide(focusLost?: boolean): void; } diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index 05e640178d..a2322cb501 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -19,7 +19,7 @@ export interface ResolvedOptions { export interface TunnelDescription { remoteAddress: { port: number, host: string }; - localAddress: string; + localAddress: { port: number, host: string } | string; } export interface TunnelInformation { environmentTunnels?: TunnelDescription[]; @@ -40,28 +40,24 @@ export enum RemoteAuthorityResolverErrorCode { export class RemoteAuthorityResolverError extends Error { - public static isHandledNotAvailable(err: any): boolean { - if (err instanceof RemoteAuthorityResolverError) { - if (err._code === RemoteAuthorityResolverErrorCode.NotAvailable && err._detail === true) { - return true; - } - } - - return this.isTemporarilyNotAvailable(err); - } - public static isTemporarilyNotAvailable(err: any): boolean { return (err instanceof RemoteAuthorityResolverError) && err._code === RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable; } - public static isNoResolverFound(err: any): boolean { + public static isNoResolverFound(err: any): err is RemoteAuthorityResolverError { return (err instanceof RemoteAuthorityResolverError) && err._code === RemoteAuthorityResolverErrorCode.NoResolverFound; } + public static isHandled(err: any): boolean { + return (err instanceof RemoteAuthorityResolverError) && err.isHandled; + } + public readonly _message: string | undefined; public readonly _code: RemoteAuthorityResolverErrorCode; public readonly _detail: any; + public isHandled: boolean; + constructor(message?: string, code: RemoteAuthorityResolverErrorCode = RemoteAuthorityResolverErrorCode.Unknown, detail?: any) { super(message); @@ -69,6 +65,8 @@ export class RemoteAuthorityResolverError extends Error { this._code = code; this._detail = detail; + this.isHandled = (code === RemoteAuthorityResolverErrorCode.NotAvailable) && detail === true; + // workaround when extending builtin objects and when compiling to ES5, see: // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work if (typeof (Object).setPrototypeOf === 'function') { diff --git a/src/vs/platform/remote/common/tunnel.ts b/src/vs/platform/remote/common/tunnel.ts index 8bccd69e12..5cf39447a1 100644 --- a/src/vs/platform/remote/common/tunnel.ts +++ b/src/vs/platform/remote/common/tunnel.ts @@ -15,7 +15,7 @@ export interface RemoteTunnel { readonly tunnelRemoteHost: string; readonly tunnelLocalPort?: number; readonly localAddress: string; - dispose(): void; + dispose(silent?: boolean): void; } export interface TunnelOptions { diff --git a/src/vs/platform/storage/node/storageIpc.ts b/src/vs/platform/storage/node/storageIpc.ts index 76858dcde2..c5809930d5 100644 --- a/src/vs/platform/storage/node/storageIpc.ts +++ b/src/vs/platform/storage/node/storageIpc.ts @@ -12,7 +12,7 @@ import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ILogService } from 'vs/platform/log/common/log'; import { generateUuid } from 'vs/base/common/uuid'; -import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; +import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey, crashReporterIdStorageKey } from 'vs/platform/telemetry/common/telemetry'; type Key = string; type Value = string; @@ -54,6 +54,16 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC this.logService.error(error); } + // This is unique to the application instance and thereby + // should be written from the main process once. + // + // THIS SHOULD NEVER BE SENT TO TELEMETRY. + // + const crashReporterId = this.storageMainService.get(crashReporterIdStorageKey, undefined); + if (crashReporterId === undefined) { + this.storageMainService.store(crashReporterIdStorageKey, generateUuid()); + } + // Apply global telemetry values as part of the initialization // These are global across all windows and thereby should be // written from the main process once. diff --git a/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts b/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts index d2f574382c..c597e9e9b7 100644 --- a/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts +++ b/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts @@ -4,17 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; - -export const instanceStorageKey = 'telemetry.instanceId'; -export const currentSessionDateStorageKey = 'telemetry.currentSessionDate'; -export const firstSessionDateStorageKey = 'telemetry.firstSessionDate'; -export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; -export const machineIdKey = 'telemetry.machineId'; - import * as Platform from 'vs/base/common/platform'; import * as uuid from 'vs/base/common/uuid'; import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils'; import { mixin } from 'vs/base/common/objects'; +import { firstSessionDateStorageKey, lastSessionDateStorageKey, machineIdKey } from 'vs/platform/telemetry/common/telemetry'; export async function resolveWorkbenchCommonProperties( storageService: IStorageService, diff --git a/src/vs/platform/telemetry/common/telemetry.ts b/src/vs/platform/telemetry/common/telemetry.ts index 7fe82cf8db..6043228b0d 100644 --- a/src/vs/platform/telemetry/common/telemetry.ts +++ b/src/vs/platform/telemetry/common/telemetry.ts @@ -45,3 +45,6 @@ export const instanceStorageKey = 'telemetry.instanceId'; export const currentSessionDateStorageKey = 'telemetry.currentSessionDate'; export const firstSessionDateStorageKey = 'telemetry.firstSessionDate'; export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; +export const machineIdKey = 'telemetry.machineId'; +export const trueMachineIdKey = 'telemetry.trueMachineId'; +export const crashReporterIdStorageKey = 'crashReporter.guid'; diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 2957214362..3077246299 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -6,9 +6,8 @@ import * as platform from 'vs/platform/registry/common/platform'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { Color, RGBA } from 'vs/base/common/color'; -import { ITheme } from 'vs/platform/theme/common/themeService'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; - import * as nls from 'vs/nls'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -27,7 +26,7 @@ export interface ColorContribution { export interface ColorFunction { - (theme: ITheme): Color | undefined; + (theme: IColorTheme): Color | undefined; } export interface ColorDefaults { @@ -71,7 +70,7 @@ export interface IColorRegistry { /** * Gets the default color of the given id */ - resolveDefaultColor(id: ColorIdentifier, theme: ITheme): Color | undefined; + resolveDefaultColor(id: ColorIdentifier, theme: IColorTheme): Color | undefined; /** * JSON schema for an object to assign color values to one of the color contributions. @@ -85,8 +84,6 @@ export interface IColorRegistry { } - - class ColorRegistry implements IColorRegistry { private readonly _onDidChangeSchema = new Emitter(); @@ -131,7 +128,7 @@ class ColorRegistry implements IColorRegistry { return Object.keys(this.colorsById).map(id => this.colorsById[id]); } - public resolveDefaultColor(id: ColorIdentifier, theme: ITheme): Color | undefined { + public resolveDefaultColor(id: ColorIdentifier, theme: IColorTheme): Color | undefined { const colorDesc = this.colorsById[id]; if (colorDesc && colorDesc.defaults) { const colorValue = colorDesc.defaults[theme.type]; @@ -229,10 +226,6 @@ export const simpleCheckboxBackground = registerColor('checkbox.background', { d export const simpleCheckboxForeground = registerColor('checkbox.foreground', { dark: selectForeground, light: selectForeground, hc: selectForeground }, nls.localize('checkbox.foreground', "Foreground color of checkbox widget.")); export const simpleCheckboxBorder = registerColor('checkbox.border', { dark: selectBorder, light: selectBorder, hc: selectBorder }, nls.localize('checkbox.border', "Border color of checkbox widget.")); - -export const pickerGroupForeground = registerColor('pickerGroup.foreground', { dark: '#3794FF', light: '#0066BF', hc: Color.white }, nls.localize('pickerGroupForeground', "Quick picker color for grouping labels.")); -export const pickerGroupBorder = registerColor('pickerGroup.border', { dark: '#3F3F46', light: '#CCCEDB', hc: Color.white }, nls.localize('pickerGroupBorder', "Quick picker color for grouping borders.")); - export const buttonForeground = registerColor('button.foreground', { dark: Color.white, light: Color.white, hc: Color.white }, nls.localize('buttonForeground', "Button foreground color.")); export const buttonBackground = registerColor('button.background', { dark: '#0E639C', light: '#007ACC', hc: null }, nls.localize('buttonBackground', "Button background color.")); export const buttonHoverBackground = registerColor('button.hoverBackground', { dark: lighten(buttonBackground, 0.2), light: darken(buttonBackground, 0.2), hc: null }, nls.localize('buttonHoverBackground', "Button background color when hovering.")); @@ -281,6 +274,15 @@ export const editorWidgetBorder = registerColor('editorWidget.border', { dark: ' export const editorWidgetResizeBorder = registerColor('editorWidget.resizeBorder', { light: null, dark: null, hc: null }, nls.localize('editorWidgetResizeBorder', "Border color of the resize bar of editor widgets. The color is only used if the widget chooses to have a resize border and if the color is not overridden by a widget.")); +/** + * Quick pick widget + */ +export const quickInputBackground = registerColor('quickInput.background', { dark: editorWidgetBackground, light: editorWidgetBackground, hc: editorWidgetBackground }, nls.localize('pickerBackground', "Quick picker background color. The quick picker widget is the container for pickers like the command palette.")); +export const quickInputForeground = registerColor('quickInput.foreground', { dark: editorWidgetForeground, light: editorWidgetForeground, hc: editorWidgetForeground }, nls.localize('pickerForeground', "Quick picker foreground color. The quick picker widget is the container for pickers like the command palette.")); +export const quickInputTitleBackground = registerColor('quickInputTitle.background', { dark: new Color(new RGBA(255, 255, 255, 0.105)), light: new Color(new RGBA(0, 0, 0, 0.06)), hc: '#000000' }, nls.localize('pickerTitleBackground', "Quick picker title background color. The quick picker widget is the container for pickers like the command palette.")); +export const pickerGroupForeground = registerColor('pickerGroup.foreground', { dark: '#3794FF', light: '#0066BF', hc: Color.white }, nls.localize('pickerGroupForeground', "Quick picker color for grouping labels.")); +export const pickerGroupBorder = registerColor('pickerGroup.border', { dark: '#3F3F46', light: '#CCCEDB', hc: Color.white }, nls.localize('pickerGroupBorder', "Quick picker color for grouping borders.")); + /** * Editor selection colors. */ @@ -365,6 +367,7 @@ export const listFilterWidgetNoMatchesOutline = registerColor('listFilterWidget. export const listFilterMatchHighlight = registerColor('list.filterMatchBackground', { dark: editorFindMatchHighlight, light: editorFindMatchHighlight, hc: null }, nls.localize('listFilterMatchHighlight', 'Background color of the filtered match.')); export const listFilterMatchHighlightBorder = registerColor('list.filterMatchBorder', { dark: editorFindMatchHighlightBorder, light: editorFindMatchHighlightBorder, hc: contrastBorder }, nls.localize('listFilterMatchHighlightBorder', 'Border color of the filtered match.')); export const treeIndentGuidesStroke = registerColor('tree.indentGuidesStroke', { dark: '#585858', light: '#a9a9a9', hc: '#a9a9a9' }, nls.localize('treeIndentGuidesStroke', "Tree stroke color for the indentation guides.")); +export const listDeemphasizedForeground = registerColor('list.deemphasizedForeground', { dark: '#8C8C8C', light: '#8E8E90', hc: '#A7A8A9' }, nls.localize('listDeemphasizedForeground', "List/Tree foreground color for items that are deemphasized. ")); /** * Menu colors @@ -502,7 +505,7 @@ function lessProminent(colorValue: ColorValue, backgroundColorValue: ColorValue, /** * @param colorValue Resolve a color value in the context of a theme */ -export function resolveColorValue(colorValue: ColorValue | null, theme: ITheme): Color | undefined { +export function resolveColorValue(colorValue: ColorValue | null, theme: IColorTheme): Color | undefined { if (colorValue === null) { return undefined; } else if (typeof colorValue === 'string') { diff --git a/src/vs/platform/theme/common/iconRegistry.ts b/src/vs/platform/theme/common/iconRegistry.ts new file mode 100644 index 0000000000..2bc349db76 --- /dev/null +++ b/src/vs/platform/theme/common/iconRegistry.ts @@ -0,0 +1,543 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as platform from 'vs/platform/registry/common/platform'; +import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { Event, Emitter } from 'vs/base/common/event'; +import { localize } from 'vs/nls'; + +// ------ API types + + +// color registry +export const Extensions = { + IconContribution: 'base.contributions.icons' +}; + +export interface IconDefaults { + font?: string; + character: string; +} + +export interface IconContribution { + id: string; + description: string; + deprecationMessage?: string; + defaults: IconDefaults; +} + +export interface IIconRegistry { + + readonly onDidChangeSchema: Event; + + /** + * Register a icon to the registry. + * @param id The icon id + * @param defaults The default values + * @description the description + */ + registerIcon(id: string, defaults: IconDefaults, description: string): ThemeIcon; + + /** + * Register a icon to the registry. + */ + deregisterIcon(id: string): void; + + /** + * Get all icon contributions + */ + getIcons(): IconContribution[]; + + /** + * JSON schema for an object to assign icon values to one of the color contributions. + */ + getIconSchema(): IJSONSchema; + + /** + * JSON schema to for a reference to a icon contribution. + */ + getIconReferenceSchema(): IJSONSchema; + +} + +class IconRegistry implements IIconRegistry { + + private readonly _onDidChangeSchema = new Emitter(); + readonly onDidChangeSchema: Event = this._onDidChangeSchema.event; + + private iconsById: { [key: string]: IconContribution }; + private iconSchema: IJSONSchema & { properties: IJSONSchemaMap } = { type: 'object', properties: {} }; + private iconReferenceSchema: IJSONSchema & { enum: string[], enumDescriptions: string[] } = { type: 'string', enum: [], enumDescriptions: [] }; + + constructor() { + this.iconsById = {}; + } + + public registerIcon(id: string, defaults: IconDefaults, description: string, deprecationMessage?: string): ThemeIcon { + let iconContribution: IconContribution = { id, description, defaults, deprecationMessage }; + this.iconsById[id] = iconContribution; + let propertySchema: IJSONSchema = { type: 'object', description, properties: { font: { type: 'string' }, fontCharacter: { type: 'string' } }, defaultSnippets: [{ body: { fontCharacter: '\\\\e030' } }] }; + if (deprecationMessage) { + propertySchema.deprecationMessage = deprecationMessage; + } + this.iconSchema.properties[id] = propertySchema; + this.iconReferenceSchema.enum.push(id); + this.iconReferenceSchema.enumDescriptions.push(description); + + this._onDidChangeSchema.fire(); + return { id }; + } + + + public deregisterIcon(id: string): void { + delete this.iconsById[id]; + delete this.iconSchema.properties[id]; + const index = this.iconReferenceSchema.enum.indexOf(id); + if (index !== -1) { + this.iconReferenceSchema.enum.splice(index, 1); + this.iconReferenceSchema.enumDescriptions.splice(index, 1); + } + this._onDidChangeSchema.fire(); + } + + public getIcons(): IconContribution[] { + return Object.keys(this.iconsById).map(id => this.iconsById[id]); + } + + public getIconSchema(): IJSONSchema { + return this.iconSchema; + } + + public getIconReferenceSchema(): IJSONSchema { + return this.iconReferenceSchema; + } + + public toString() { + let sorter = (a: string, b: string) => { + let cat1 = a.indexOf('.') === -1 ? 0 : 1; + let cat2 = b.indexOf('.') === -1 ? 0 : 1; + if (cat1 !== cat2) { + return cat1 - cat2; + } + return a.localeCompare(b); + }; + + return Object.keys(this.iconsById).sort(sorter).map(k => `- \`${k}\`: ${this.iconsById[k].description}`).join('\n'); + } + +} + +const iconRegistry = new IconRegistry(); +platform.Registry.add(Extensions.IconContribution, iconRegistry); + +export function registerIcon(id: string, defaults: IconDefaults, description: string, deprecationMessage?: string): ThemeIcon { + return iconRegistry.registerIcon(id, defaults, description, deprecationMessage); +} + +export function getIconRegistry(): IIconRegistry { + return iconRegistry; +} + +registerIcon('add', { character: '\ea60' }, localize('add', '')); +registerIcon('plus', { character: '\ea60' }, localize('plus', '')); +registerIcon('gist-new', { character: '\ea60' }, localize('gist-new', '')); +registerIcon('repo-create', { character: '\ea60' }, localize('repo-create', '')); +registerIcon('lightbulb', { character: '\ea61' }, localize('lightbulb', '')); +registerIcon('light-bulb', { character: '\ea61' }, localize('light-bulb', '')); +registerIcon('repo', { character: '\ea62' }, localize('repo', '')); +registerIcon('repo-delete', { character: '\ea62' }, localize('repo-delete', '')); +registerIcon('gist-fork', { character: '\ea63' }, localize('gist-fork', '')); +registerIcon('repo-forked', { character: '\ea63' }, localize('repo-forked', '')); +registerIcon('git-pull-request', { character: '\ea64' }, localize('git-pull-request', '')); +registerIcon('git-pull-request-abandoned', { character: '\ea64' }, localize('git-pull-request-abandoned', '')); +registerIcon('record-keys', { character: '\ea65' }, localize('record-keys', '')); +registerIcon('keyboard', { character: '\ea65' }, localize('keyboard', '')); +registerIcon('tag', { character: '\ea66' }, localize('tag', '')); +registerIcon('tag-add', { character: '\ea66' }, localize('tag-add', '')); +registerIcon('tag-remove', { character: '\ea66' }, localize('tag-remove', '')); +registerIcon('person', { character: '\ea67' }, localize('person', '')); +registerIcon('person-add', { character: '\ea67' }, localize('person-add', '')); +registerIcon('person-follow', { character: '\ea67' }, localize('person-follow', '')); +registerIcon('person-outline', { character: '\ea67' }, localize('person-outline', '')); +registerIcon('person-filled', { character: '\ea67' }, localize('person-filled', '')); +registerIcon('git-branch', { character: '\ea68' }, localize('git-branch', '')); +registerIcon('git-branch-create', { character: '\ea68' }, localize('git-branch-create', '')); +registerIcon('git-branch-delete', { character: '\ea68' }, localize('git-branch-delete', '')); +registerIcon('source-control', { character: '\ea68' }, localize('source-control', '')); +registerIcon('mirror', { character: '\ea69' }, localize('mirror', '')); +registerIcon('mirror-public', { character: '\ea69' }, localize('mirror-public', '')); +registerIcon('star', { character: '\ea6a' }, localize('star', '')); +registerIcon('star-add', { character: '\ea6a' }, localize('star-add', '')); +registerIcon('star-delete', { character: '\ea6a' }, localize('star-delete', '')); +registerIcon('star-empty', { character: '\ea6a' }, localize('star-empty', '')); +registerIcon('comment', { character: '\ea6b' }, localize('comment', '')); +registerIcon('comment-add', { character: '\ea6b' }, localize('comment-add', '')); +registerIcon('alert', { character: '\ea6c' }, localize('alert', '')); +registerIcon('warning', { character: '\ea6c' }, localize('warning', '')); +registerIcon('search', { character: '\ea6d' }, localize('search', '')); +registerIcon('search-save', { character: '\ea6d' }, localize('search-save', '')); +registerIcon('log-out', { character: '\ea6e' }, localize('log-out', '')); +registerIcon('sign-out', { character: '\ea6e' }, localize('sign-out', '')); +registerIcon('log-in', { character: '\ea6f' }, localize('log-in', '')); +registerIcon('sign-in', { character: '\ea6f' }, localize('sign-in', '')); +registerIcon('eye', { character: '\ea70' }, localize('eye', '')); +registerIcon('eye-unwatch', { character: '\ea70' }, localize('eye-unwatch', '')); +registerIcon('eye-watch', { character: '\ea70' }, localize('eye-watch', '')); +registerIcon('circle-filled', { character: '\ea71' }, localize('circle-filled', '')); +registerIcon('primitive-dot', { character: '\ea71' }, localize('primitive-dot', '')); +registerIcon('close-dirty', { character: '\ea71' }, localize('close-dirty', '')); +registerIcon('debug-breakpoint', { character: '\ea71' }, localize('debug-breakpoint', '')); +registerIcon('debug-breakpoint-disabled', { character: '\ea71' }, localize('debug-breakpoint-disabled', '')); +registerIcon('debug-hint', { character: '\ea71' }, localize('debug-hint', '')); +registerIcon('primitive-square', { character: '\ea72' }, localize('primitive-square', '')); +registerIcon('edit', { character: '\ea73' }, localize('edit', '')); +registerIcon('pencil', { character: '\ea73' }, localize('pencil', '')); +registerIcon('info', { character: '\ea74' }, localize('info', '')); +registerIcon('issue-opened', { character: '\ea74' }, localize('issue-opened', '')); +registerIcon('gist-private', { character: '\ea75' }, localize('gist-private', '')); +registerIcon('git-fork-private', { character: '\ea75' }, localize('git-fork-private', '')); +registerIcon('lock', { character: '\ea75' }, localize('lock', '')); +registerIcon('mirror-private', { character: '\ea75' }, localize('mirror-private', '')); +registerIcon('close', { character: '\ea76' }, localize('close', '')); +registerIcon('remove-close', { character: '\ea76' }, localize('remove-close', '')); +registerIcon('x', { character: '\ea76' }, localize('x', '')); +registerIcon('repo-sync', { character: '\ea77' }, localize('repo-sync', '')); +registerIcon('sync', { character: '\ea77' }, localize('sync', '')); +registerIcon('clone', { character: '\ea78' }, localize('clone', '')); +registerIcon('desktop-download', { character: '\ea78' }, localize('desktop-download', '')); +registerIcon('beaker', { character: '\ea79' }, localize('beaker', '')); +registerIcon('microscope', { character: '\ea79' }, localize('microscope', '')); +registerIcon('vm', { character: '\ea7a' }, localize('vm', '')); +registerIcon('device-desktop', { character: '\ea7a' }, localize('device-desktop', '')); +registerIcon('file', { character: '\ea7b' }, localize('file', '')); +registerIcon('file-text', { character: '\ea7b' }, localize('file-text', '')); +registerIcon('more', { character: '\ea7c' }, localize('more', '')); +registerIcon('ellipsis', { character: '\ea7c' }, localize('ellipsis', '')); +registerIcon('kebab-horizontal', { character: '\ea7c' }, localize('kebab-horizontal', '')); +registerIcon('mail-reply', { character: '\ea7d' }, localize('mail-reply', '')); +registerIcon('reply', { character: '\ea7d' }, localize('reply', '')); +registerIcon('organization', { character: '\ea7e' }, localize('organization', '')); +registerIcon('organization-filled', { character: '\ea7e' }, localize('organization-filled', '')); +registerIcon('organization-outline', { character: '\ea7e' }, localize('organization-outline', '')); +registerIcon('new-file', { character: '\ea7f' }, localize('new-file', '')); +registerIcon('file-add', { character: '\ea7f' }, localize('file-add', '')); +registerIcon('new-folder', { character: '\ea80' }, localize('new-folder', '')); +registerIcon('file-directory-create', { character: '\ea80' }, localize('file-directory-create', '')); +registerIcon('trash', { character: '\ea81' }, localize('trash', '')); +registerIcon('trashcan', { character: '\ea81' }, localize('trashcan', '')); +registerIcon('history', { character: '\ea82' }, localize('history', '')); +registerIcon('clock', { character: '\ea82' }, localize('clock', '')); +registerIcon('folder', { character: '\ea83' }, localize('folder', '')); +registerIcon('file-directory', { character: '\ea83' }, localize('file-directory', '')); +registerIcon('symbol-folder', { character: '\ea83' }, localize('symbol-folder', '')); +registerIcon('logo-github', { character: '\ea84' }, localize('logo-github', '')); +registerIcon('mark-github', { character: '\ea84' }, localize('mark-github', '')); +registerIcon('github', { character: '\ea84' }, localize('github', '')); +registerIcon('terminal', { character: '\ea85' }, localize('terminal', '')); +registerIcon('console', { character: '\ea85' }, localize('console', '')); +registerIcon('repl', { character: '\ea85' }, localize('repl', '')); +registerIcon('zap', { character: '\ea86' }, localize('zap', '')); +registerIcon('symbol-event', { character: '\ea86' }, localize('symbol-event', '')); +registerIcon('error', { character: '\ea87' }, localize('error', '')); +registerIcon('stop', { character: '\ea87' }, localize('stop', '')); +registerIcon('variable', { character: '\ea88' }, localize('variable', '')); +registerIcon('symbol-variable', { character: '\ea88' }, localize('symbol-variable', '')); +registerIcon('array', { character: '\ea8a' }, localize('array', '')); +registerIcon('symbol-array', { character: '\ea8a' }, localize('symbol-array', '')); +registerIcon('symbol-module', { character: '\ea8b' }, localize('symbol-module', '')); +registerIcon('symbol-package', { character: '\ea8b' }, localize('symbol-package', '')); +registerIcon('symbol-namespace', { character: '\ea8b' }, localize('symbol-namespace', '')); +registerIcon('symbol-object', { character: '\ea8b' }, localize('symbol-object', '')); +registerIcon('symbol-method', { character: '\ea8c' }, localize('symbol-method', '')); +registerIcon('symbol-function', { character: '\ea8c' }, localize('symbol-function', '')); +registerIcon('symbol-constructor', { character: '\ea8c' }, localize('symbol-constructor', '')); +registerIcon('symbol-boolean', { character: '\ea8f' }, localize('symbol-boolean', '')); +registerIcon('symbol-null', { character: '\ea8f' }, localize('symbol-null', '')); +registerIcon('symbol-numeric', { character: '\ea90' }, localize('symbol-numeric', '')); +registerIcon('symbol-number', { character: '\ea90' }, localize('symbol-number', '')); +registerIcon('symbol-structure', { character: '\ea91' }, localize('symbol-structure', '')); +registerIcon('symbol-struct', { character: '\ea91' }, localize('symbol-struct', '')); +registerIcon('symbol-parameter', { character: '\ea92' }, localize('symbol-parameter', '')); +registerIcon('symbol-type-parameter', { character: '\ea92' }, localize('symbol-type-parameter', '')); +registerIcon('symbol-key', { character: '\ea93' }, localize('symbol-key', '')); +registerIcon('symbol-text', { character: '\ea93' }, localize('symbol-text', '')); +registerIcon('symbol-reference', { character: '\ea94' }, localize('symbol-reference', '')); +registerIcon('go-to-file', { character: '\ea94' }, localize('go-to-file', '')); +registerIcon('symbol-enum', { character: '\ea95' }, localize('symbol-enum', '')); +registerIcon('symbol-value', { character: '\ea95' }, localize('symbol-value', '')); +registerIcon('symbol-ruler', { character: '\ea96' }, localize('symbol-ruler', '')); +registerIcon('symbol-unit', { character: '\ea96' }, localize('symbol-unit', '')); +registerIcon('activate-breakpoints', { character: '\ea97' }, localize('activate-breakpoints', '')); +registerIcon('archive', { character: '\ea98' }, localize('archive', '')); +registerIcon('arrow-both', { character: '\ea99' }, localize('arrow-both', '')); +registerIcon('arrow-down', { character: '\ea9a' }, localize('arrow-down', '')); +registerIcon('arrow-left', { character: '\ea9b' }, localize('arrow-left', '')); +registerIcon('arrow-right', { character: '\ea9c' }, localize('arrow-right', '')); +registerIcon('arrow-small-down', { character: '\ea9d' }, localize('arrow-small-down', '')); +registerIcon('arrow-small-left', { character: '\ea9e' }, localize('arrow-small-left', '')); +registerIcon('arrow-small-right', { character: '\ea9f' }, localize('arrow-small-right', '')); +registerIcon('arrow-small-up', { character: '\eaa0' }, localize('arrow-small-up', '')); +registerIcon('arrow-up', { character: '\eaa1' }, localize('arrow-up', '')); +registerIcon('bell', { character: '\eaa2' }, localize('bell', '')); +registerIcon('bold', { character: '\eaa3' }, localize('bold', '')); +registerIcon('book', { character: '\eaa4' }, localize('book', '')); +registerIcon('bookmark', { character: '\eaa5' }, localize('bookmark', '')); +registerIcon('debug-breakpoint-conditional-unverified', { character: '\eaa6' }, localize('debug-breakpoint-conditional-unverified', '')); +registerIcon('debug-breakpoint-conditional', { character: '\eaa7' }, localize('debug-breakpoint-conditional', '')); +registerIcon('debug-breakpoint-conditional-disabled', { character: '\eaa7' }, localize('debug-breakpoint-conditional-disabled', '')); +registerIcon('debug-breakpoint-data-unverified', { character: '\eaa8' }, localize('debug-breakpoint-data-unverified', '')); +registerIcon('debug-breakpoint-data', { character: '\eaa9' }, localize('debug-breakpoint-data', '')); +registerIcon('debug-breakpoint-data-disabled', { character: '\eaa9' }, localize('debug-breakpoint-data-disabled', '')); +registerIcon('debug-breakpoint-log-unverified', { character: '\eaaa' }, localize('debug-breakpoint-log-unverified', '')); +registerIcon('debug-breakpoint-log', { character: '\eaab' }, localize('debug-breakpoint-log', '')); +registerIcon('debug-breakpoint-log-disabled', { character: '\eaab' }, localize('debug-breakpoint-log-disabled', '')); +registerIcon('briefcase', { character: '\eaac' }, localize('briefcase', '')); +registerIcon('broadcast', { character: '\eaad' }, localize('broadcast', '')); +registerIcon('browser', { character: '\eaae' }, localize('browser', '')); +registerIcon('bug', { character: '\eaaf' }, localize('bug', '')); +registerIcon('calendar', { character: '\eab0' }, localize('calendar', '')); +registerIcon('case-sensitive', { character: '\eab1' }, localize('case-sensitive', '')); +registerIcon('check', { character: '\eab2' }, localize('check', '')); +registerIcon('checklist', { character: '\eab3' }, localize('checklist', '')); +registerIcon('chevron-down', { character: '\eab4' }, localize('chevron-down', '')); +registerIcon('chevron-left', { character: '\eab5' }, localize('chevron-left', '')); +registerIcon('chevron-right', { character: '\eab6' }, localize('chevron-right', '')); +registerIcon('chevron-up', { character: '\eab7' }, localize('chevron-up', '')); +registerIcon('chrome-close', { character: '\eab8' }, localize('chrome-close', '')); +registerIcon('chrome-maximize', { character: '\eab9' }, localize('chrome-maximize', '')); +registerIcon('chrome-minimize', { character: '\eaba' }, localize('chrome-minimize', '')); +registerIcon('chrome-restore', { character: '\eabb' }, localize('chrome-restore', '')); +registerIcon('circle-outline', { character: '\eabc' }, localize('circle-outline', '')); +registerIcon('debug-breakpoint-unverified', { character: '\eabc' }, localize('debug-breakpoint-unverified', '')); +registerIcon('circle-slash', { character: '\eabd' }, localize('circle-slash', '')); +registerIcon('circuit-board', { character: '\eabe' }, localize('circuit-board', '')); +registerIcon('clear-all', { character: '\eabf' }, localize('clear-all', '')); +registerIcon('clippy', { character: '\eac0' }, localize('clippy', '')); +registerIcon('close-all', { character: '\eac1' }, localize('close-all', '')); +registerIcon('cloud-download', { character: '\eac2' }, localize('cloud-download', '')); +registerIcon('cloud-upload', { character: '\eac3' }, localize('cloud-upload', '')); +registerIcon('code', { character: '\eac4' }, localize('code', '')); +registerIcon('collapse-all', { character: '\eac5' }, localize('collapse-all', '')); +registerIcon('color-mode', { character: '\eac6' }, localize('color-mode', '')); +registerIcon('comment-discussion', { character: '\eac7' }, localize('comment-discussion', '')); +registerIcon('compare-changes', { character: '\eac8' }, localize('compare-changes', '')); +registerIcon('credit-card', { character: '\eac9' }, localize('credit-card', '')); +registerIcon('dash', { character: '\eacc' }, localize('dash', '')); +registerIcon('dashboard', { character: '\eacd' }, localize('dashboard', '')); +registerIcon('database', { character: '\eace' }, localize('database', '')); +registerIcon('debug-continue', { character: '\eacf' }, localize('debug-continue', '')); +registerIcon('debug-disconnect', { character: '\ead0' }, localize('debug-disconnect', '')); +registerIcon('debug-pause', { character: '\ead1' }, localize('debug-pause', '')); +registerIcon('debug-restart', { character: '\ead2' }, localize('debug-restart', '')); +registerIcon('debug-start', { character: '\ead3' }, localize('debug-start', '')); +registerIcon('debug-step-into', { character: '\ead4' }, localize('debug-step-into', '')); +registerIcon('debug-step-out', { character: '\ead5' }, localize('debug-step-out', '')); +registerIcon('debug-step-over', { character: '\ead6' }, localize('debug-step-over', '')); +registerIcon('debug-stop', { character: '\ead7' }, localize('debug-stop', '')); +registerIcon('debug', { character: '\ead8' }, localize('debug', '')); +registerIcon('device-camera-video', { character: '\ead9' }, localize('device-camera-video', '')); +registerIcon('device-camera', { character: '\eada' }, localize('device-camera', '')); +registerIcon('device-mobile', { character: '\eadb' }, localize('device-mobile', '')); +registerIcon('diff-added', { character: '\eadc' }, localize('diff-added', '')); +registerIcon('diff-ignored', { character: '\eadd' }, localize('diff-ignored', '')); +registerIcon('diff-modified', { character: '\eade' }, localize('diff-modified', '')); +registerIcon('diff-removed', { character: '\eadf' }, localize('diff-removed', '')); +registerIcon('diff-renamed', { character: '\eae0' }, localize('diff-renamed', '')); +registerIcon('diff', { character: '\eae1' }, localize('diff', '')); +registerIcon('discard', { character: '\eae2' }, localize('discard', '')); +registerIcon('editor-layout', { character: '\eae3' }, localize('editor-layout', '')); +registerIcon('empty-window', { character: '\eae4' }, localize('empty-window', '')); +registerIcon('exclude', { character: '\eae5' }, localize('exclude', '')); +registerIcon('extensions', { character: '\eae6' }, localize('extensions', '')); +registerIcon('eye-closed', { character: '\eae7' }, localize('eye-closed', '')); +registerIcon('file-binary', { character: '\eae8' }, localize('file-binary', '')); +registerIcon('file-code', { character: '\eae9' }, localize('file-code', '')); +registerIcon('file-media', { character: '\eaea' }, localize('file-media', '')); +registerIcon('file-pdf', { character: '\eaeb' }, localize('file-pdf', '')); +registerIcon('file-submodule', { character: '\eaec' }, localize('file-submodule', '')); +registerIcon('file-symlink-directory', { character: '\eaed' }, localize('file-symlink-directory', '')); +registerIcon('file-symlink-file', { character: '\eaee' }, localize('file-symlink-file', '')); +registerIcon('file-zip', { character: '\eaef' }, localize('file-zip', '')); +registerIcon('files', { character: '\eaf0' }, localize('files', '')); +registerIcon('filter', { character: '\eaf1' }, localize('filter', '')); +registerIcon('flame', { character: '\eaf2' }, localize('flame', '')); +registerIcon('fold-down', { character: '\eaf3' }, localize('fold-down', '')); +registerIcon('fold-up', { character: '\eaf4' }, localize('fold-up', '')); +registerIcon('fold', { character: '\eaf5' }, localize('fold', '')); +registerIcon('folder-active', { character: '\eaf6' }, localize('folder-active', '')); +registerIcon('folder-opened', { character: '\eaf7' }, localize('folder-opened', '')); +registerIcon('gear', { character: '\eaf8' }, localize('gear', '')); +registerIcon('gift', { character: '\eaf9' }, localize('gift', '')); +registerIcon('gist-secret', { character: '\eafa' }, localize('gist-secret', '')); +registerIcon('gist', { character: '\eafb' }, localize('gist', '')); +registerIcon('git-commit', { character: '\eafc' }, localize('git-commit', '')); +registerIcon('git-compare', { character: '\eafd' }, localize('git-compare', '')); +registerIcon('git-merge', { character: '\eafe' }, localize('git-merge', '')); +registerIcon('github-action', { character: '\eaff' }, localize('github-action', '')); +registerIcon('github-alt', { character: '\eb00' }, localize('github-alt', '')); +registerIcon('globe', { character: '\eb01' }, localize('globe', '')); +registerIcon('grabber', { character: '\eb02' }, localize('grabber', '')); +registerIcon('graph', { character: '\eb03' }, localize('graph', '')); +registerIcon('gripper', { character: '\eb04' }, localize('gripper', '')); +registerIcon('heart', { character: '\eb05' }, localize('heart', '')); +registerIcon('home', { character: '\eb06' }, localize('home', '')); +registerIcon('horizontal-rule', { character: '\eb07' }, localize('horizontal-rule', '')); +registerIcon('hubot', { character: '\eb08' }, localize('hubot', '')); +registerIcon('inbox', { character: '\eb09' }, localize('inbox', '')); +registerIcon('issue-closed', { character: '\eb0a' }, localize('issue-closed', '')); +registerIcon('issue-reopened', { character: '\eb0b' }, localize('issue-reopened', '')); +registerIcon('issues', { character: '\eb0c' }, localize('issues', '')); +registerIcon('italic', { character: '\eb0d' }, localize('italic', '')); +registerIcon('jersey', { character: '\eb0e' }, localize('jersey', '')); +registerIcon('json', { character: '\eb0f' }, localize('json', '')); +registerIcon('kebab-vertical', { character: '\eb10' }, localize('kebab-vertical', '')); +registerIcon('key', { character: '\eb11' }, localize('key', '')); +registerIcon('law', { character: '\eb12' }, localize('law', '')); +registerIcon('lightbulb-autofix', { character: '\eb13' }, localize('lightbulb-autofix', '')); +registerIcon('link-external', { character: '\eb14' }, localize('link-external', '')); +registerIcon('link', { character: '\eb15' }, localize('link', '')); +registerIcon('list-ordered', { character: '\eb16' }, localize('list-ordered', '')); +registerIcon('list-unordered', { character: '\eb17' }, localize('list-unordered', '')); +registerIcon('live-share', { character: '\eb18' }, localize('live-share', '')); +registerIcon('loading', { character: '\eb19' }, localize('loading', '')); +registerIcon('location', { character: '\eb1a' }, localize('location', '')); +registerIcon('mail-read', { character: '\eb1b' }, localize('mail-read', '')); +registerIcon('mail', { character: '\eb1c' }, localize('mail', '')); +registerIcon('markdown', { character: '\eb1d' }, localize('markdown', '')); +registerIcon('megaphone', { character: '\eb1e' }, localize('megaphone', '')); +registerIcon('mention', { character: '\eb1f' }, localize('mention', '')); +registerIcon('milestone', { character: '\eb20' }, localize('milestone', '')); +registerIcon('mortar-board', { character: '\eb21' }, localize('mortar-board', '')); +registerIcon('move', { character: '\eb22' }, localize('move', '')); +registerIcon('multiple-windows', { character: '\eb23' }, localize('multiple-windows', '')); +registerIcon('mute', { character: '\eb24' }, localize('mute', '')); +registerIcon('no-newline', { character: '\eb25' }, localize('no-newline', '')); +registerIcon('note', { character: '\eb26' }, localize('note', '')); +registerIcon('octoface', { character: '\eb27' }, localize('octoface', '')); +registerIcon('open-preview', { character: '\eb28' }, localize('open-preview', '')); +registerIcon('package', { character: '\eb29' }, localize('package', '')); +registerIcon('paintcan', { character: '\eb2a' }, localize('paintcan', '')); +registerIcon('pin', { character: '\eb2b' }, localize('pin', '')); +registerIcon('play', { character: '\eb2c' }, localize('play', '')); +registerIcon('run', { character: '\eb2c' }, localize('run', '')); +registerIcon('plug', { character: '\eb2d' }, localize('plug', '')); +registerIcon('preserve-case', { character: '\eb2e' }, localize('preserve-case', '')); +registerIcon('preview', { character: '\eb2f' }, localize('preview', '')); +registerIcon('project', { character: '\eb30' }, localize('project', '')); +registerIcon('pulse', { character: '\eb31' }, localize('pulse', '')); +registerIcon('question', { character: '\eb32' }, localize('question', '')); +registerIcon('quote', { character: '\eb33' }, localize('quote', '')); +registerIcon('radio-tower', { character: '\eb34' }, localize('radio-tower', '')); +registerIcon('reactions', { character: '\eb35' }, localize('reactions', '')); +registerIcon('references', { character: '\eb36' }, localize('references', '')); +registerIcon('refresh', { character: '\eb37' }, localize('refresh', '')); +registerIcon('regex', { character: '\eb38' }, localize('regex', '')); +registerIcon('remote-explorer', { character: '\eb39' }, localize('remote-explorer', '')); +registerIcon('remote', { character: '\eb3a' }, localize('remote', '')); +registerIcon('remove', { character: '\eb3b' }, localize('remove', '')); +registerIcon('replace-all', { character: '\eb3c' }, localize('replace-all', '')); +registerIcon('replace', { character: '\eb3d' }, localize('replace', '')); +registerIcon('repo-clone', { character: '\eb3e' }, localize('repo-clone', '')); +registerIcon('repo-force-push', { character: '\eb3f' }, localize('repo-force-push', '')); +registerIcon('repo-pull', { character: '\eb40' }, localize('repo-pull', '')); +registerIcon('repo-push', { character: '\eb41' }, localize('repo-push', '')); +registerIcon('report', { character: '\eb42' }, localize('report', '')); +registerIcon('request-changes', { character: '\eb43' }, localize('request-changes', '')); +registerIcon('rocket', { character: '\eb44' }, localize('rocket', '')); +registerIcon('root-folder-opened', { character: '\eb45' }, localize('root-folder-opened', '')); +registerIcon('root-folder', { character: '\eb46' }, localize('root-folder', '')); +registerIcon('rss', { character: '\eb47' }, localize('rss', '')); +registerIcon('ruby', { character: '\eb48' }, localize('ruby', '')); +registerIcon('save-all', { character: '\eb49' }, localize('save-all', '')); +registerIcon('save-as', { character: '\eb4a' }, localize('save-as', '')); +registerIcon('save', { character: '\eb4b' }, localize('save', '')); +registerIcon('screen-full', { character: '\eb4c' }, localize('screen-full', '')); +registerIcon('screen-normal', { character: '\eb4d' }, localize('screen-normal', '')); +registerIcon('search-stop', { character: '\eb4e' }, localize('search-stop', '')); +registerIcon('server', { character: '\eb50' }, localize('server', '')); +registerIcon('settings-gear', { character: '\eb51' }, localize('settings-gear', '')); +registerIcon('settings', { character: '\eb52' }, localize('settings', '')); +registerIcon('shield', { character: '\eb53' }, localize('shield', '')); +registerIcon('smiley', { character: '\eb54' }, localize('smiley', '')); +registerIcon('sort-precedence', { character: '\eb55' }, localize('sort-precedence', '')); +registerIcon('split-horizontal', { character: '\eb56' }, localize('split-horizontal', '')); +registerIcon('split-vertical', { character: '\eb57' }, localize('split-vertical', '')); +registerIcon('squirrel', { character: '\eb58' }, localize('squirrel', '')); +registerIcon('star-full', { character: '\eb59' }, localize('star-full', '')); +registerIcon('star-half', { character: '\eb5a' }, localize('star-half', '')); +registerIcon('symbol-class', { character: '\eb5b' }, localize('symbol-class', '')); +registerIcon('symbol-color', { character: '\eb5c' }, localize('symbol-color', '')); +registerIcon('symbol-constant', { character: '\eb5d' }, localize('symbol-constant', '')); +registerIcon('symbol-enum-member', { character: '\eb5e' }, localize('symbol-enum-member', '')); +registerIcon('symbol-field', { character: '\eb5f' }, localize('symbol-field', '')); +registerIcon('symbol-file', { character: '\eb60' }, localize('symbol-file', '')); +registerIcon('symbol-interface', { character: '\eb61' }, localize('symbol-interface', '')); +registerIcon('symbol-keyword', { character: '\eb62' }, localize('symbol-keyword', '')); +registerIcon('symbol-misc', { character: '\eb63' }, localize('symbol-misc', '')); +registerIcon('symbol-operator', { character: '\eb64' }, localize('symbol-operator', '')); +registerIcon('symbol-property', { character: '\eb65' }, localize('symbol-property', '')); +registerIcon('wrench', { character: '\eb65' }, localize('wrench', '')); +registerIcon('wrench-subaction', { character: '\eb65' }, localize('wrench-subaction', '')); +registerIcon('symbol-snippet', { character: '\eb66' }, localize('symbol-snippet', '')); +registerIcon('tasklist', { character: '\eb67' }, localize('tasklist', '')); +registerIcon('telescope', { character: '\eb68' }, localize('telescope', '')); +registerIcon('text-size', { character: '\eb69' }, localize('text-size', '')); +registerIcon('three-bars', { character: '\eb6a' }, localize('three-bars', '')); +registerIcon('thumbsdown', { character: '\eb6b' }, localize('thumbsdown', '')); +registerIcon('thumbsup', { character: '\eb6c' }, localize('thumbsup', '')); +registerIcon('tools', { character: '\eb6d' }, localize('tools', '')); +registerIcon('triangle-down', { character: '\eb6e' }, localize('triangle-down', '')); +registerIcon('triangle-left', { character: '\eb6f' }, localize('triangle-left', '')); +registerIcon('triangle-right', { character: '\eb70' }, localize('triangle-right', '')); +registerIcon('triangle-up', { character: '\eb71' }, localize('triangle-up', '')); +registerIcon('twitter', { character: '\eb72' }, localize('twitter', '')); +registerIcon('unfold', { character: '\eb73' }, localize('unfold', '')); +registerIcon('unlock', { character: '\eb74' }, localize('unlock', '')); +registerIcon('unmute', { character: '\eb75' }, localize('unmute', '')); +registerIcon('unverified', { character: '\eb76' }, localize('unverified', '')); +registerIcon('verified', { character: '\eb77' }, localize('verified', '')); +registerIcon('versions', { character: '\eb78' }, localize('versions', '')); +registerIcon('vm-active', { character: '\eb79' }, localize('vm-active', '')); +registerIcon('vm-outline', { character: '\eb7a' }, localize('vm-outline', '')); +registerIcon('vm-running', { character: '\eb7b' }, localize('vm-running', '')); +registerIcon('watch', { character: '\eb7c' }, localize('watch', '')); +registerIcon('whitespace', { character: '\eb7d' }, localize('whitespace', '')); +registerIcon('whole-word', { character: '\eb7e' }, localize('whole-word', '')); +registerIcon('window', { character: '\eb7f' }, localize('window', '')); +registerIcon('word-wrap', { character: '\eb80' }, localize('word-wrap', '')); +registerIcon('zoom-in', { character: '\eb81' }, localize('zoom-in', '')); +registerIcon('zoom-out', { character: '\eb82' }, localize('zoom-out', '')); +registerIcon('list-filter', { character: '\eb83' }, localize('list-filter', '')); +registerIcon('list-flat', { character: '\eb84' }, localize('list-flat', '')); +registerIcon('list-selection', { character: '\eb85' }, localize('list-selection', '')); +registerIcon('selection', { character: '\eb85' }, localize('selection', '')); +registerIcon('list-tree', { character: '\eb86' }, localize('list-tree', '')); +registerIcon('debug-breakpoint-function-unverified', { character: '\eb87' }, localize('debug-breakpoint-function-unverified', '')); +registerIcon('debug-breakpoint-function', { character: '\eb88' }, localize('debug-breakpoint-function', '')); +registerIcon('debug-breakpoint-function-disabled', { character: '\eb88' }, localize('debug-breakpoint-function-disabled', '')); +registerIcon('debug-stackframe-active', { character: '\eb89' }, localize('debug-stackframe-active', '')); +registerIcon('debug-stackframe-dot', { character: '\eb8a' }, localize('debug-stackframe-dot', '')); +registerIcon('debug-stackframe', { character: '\eb8b' }, localize('debug-stackframe', '')); +registerIcon('debug-stackframe-focused', { character: '\eb8b' }, localize('debug-stackframe-focused', '')); +registerIcon('debug-breakpoint-unsupported', { character: '\eb8c' }, localize('debug-breakpoint-unsupported', '')); +registerIcon('symbol-string', { character: '\eb8d' }, localize('symbol-string', '')); +registerIcon('debug-reverse-continue', { character: '\eb8e' }, localize('debug-reverse-continue', '')); +registerIcon('debug-step-back', { character: '\eb8f' }, localize('debug-step-back', '')); +registerIcon('debug-restart-frame', { character: '\eb90' }, localize('debug-restart-frame', '')); +registerIcon('debug-alternate', { character: '\eb91' }, localize('debug-alternate', '')); +registerIcon('call-incoming', { character: '\eb92' }, localize('call-incoming', '')); +registerIcon('call-outgoing', { character: '\eb93' }, localize('call-outgoing', '')); +registerIcon('menu', { character: '\eb94' }, localize('menu', '')); +registerIcon('expand-all', { character: '\eb95' }, localize('expand-all', '')); +registerIcon('feedback', { character: '\eb96' }, localize('feedback', '')); +registerIcon('group-by-ref-type', { character: '\eb97' }, localize('group-by-ref-type', '')); +registerIcon('ungroup-by-ref-type', { character: '\eb98' }, localize('ungroup-by-ref-type', '')); +registerIcon('bell-dot', { character: '\f101' }, localize('bell-dot', '')); +registerIcon('debug-alt-2', { character: '\f102' }, localize('debug-alt-2', '')); +registerIcon('debug-alt', { character: '\f103' }, localize('debug-alt', '')); + + +// setTimeout(_ => console.log(colorRegistry.toString()), 5000); diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index 02e8e1783b..554ce5b424 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground, ColorValue, resolveColorValue, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; @@ -21,7 +21,7 @@ export interface IComputedStyles { [color: string]: Color | undefined; } -export function computeStyles(theme: ITheme, styleMap: IColorMapping): IComputedStyles { +export function computeStyles(theme: IColorTheme, styleMap: IColorMapping): IComputedStyles { const styles = Object.create(null) as IComputedStyles; for (let key in styleMap) { const value = styleMap[key]; @@ -34,8 +34,8 @@ export function computeStyles(theme: ITheme, styleMap: IColorMapping): IComputed } export function attachStyler(themeService: IThemeService, styleMap: T, widgetOrCallback: IThemable | styleFn): IDisposable { - function applyStyles(theme: ITheme): void { - const styles = computeStyles(themeService.getTheme(), styleMap); + function applyStyles(theme: IColorTheme): void { + const styles = computeStyles(themeService.getColorTheme(), styleMap); if (typeof widgetOrCallback === 'function') { widgetOrCallback(styles); @@ -44,9 +44,9 @@ export function attachStyler(themeService: IThemeServic } } - applyStyles(themeService.getTheme()); + applyStyles(themeService.getColorTheme()); - return themeService.onThemeChange(applyStyles); + return themeService.onDidColorThemeChange(applyStyles); } export interface ICheckboxStyleOverrides extends IStyleOverrides { diff --git a/src/vs/platform/theme/common/themeService.ts b/src/vs/platform/theme/common/themeService.ts index 1a92831dfa..596f0e7a44 100644 --- a/src/vs/platform/theme/common/themeService.ts +++ b/src/vs/platform/theme/common/themeService.ts @@ -5,7 +5,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Color } from 'vs/base/common/color'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/platform/registry/common/platform'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { Event, Emitter } from 'vs/base/common/event'; @@ -86,7 +86,7 @@ export interface ITokenStyle { readonly italic?: boolean; } -export interface ITheme { +export interface IColorTheme { readonly type: ThemeType; /** @@ -114,7 +114,7 @@ export interface ITheme { readonly tokenColorMap: string[]; } -export interface IIconTheme { +export interface IFileIconTheme { readonly hasFileIcons: boolean; readonly hasFolderIcons: boolean; readonly hidesExplorerArrows: boolean; @@ -125,19 +125,19 @@ export interface ICssStyleCollector { } export interface IThemingParticipant { - (theme: ITheme, collector: ICssStyleCollector, environment: IEnvironmentService): void; + (theme: IColorTheme, collector: ICssStyleCollector, environment: IEnvironmentService): void; } export interface IThemeService { _serviceBrand: undefined; - getTheme(): ITheme; + getColorTheme(): IColorTheme; - readonly onThemeChange: Event; + readonly onDidColorThemeChange: Event; - getIconTheme(): IIconTheme; + getFileIconTheme(): IFileIconTheme; - readonly onIconThemeChange: Event; + readonly onDidFileIconThemeChange: Event; } @@ -151,7 +151,7 @@ export interface IThemingRegistry { /** * Register a theming participant that is invoked on every theme change. */ - onThemeChange(participant: IThemingParticipant): IDisposable; + onColorThemeChange(participant: IThemingParticipant): IDisposable; getThemingParticipants(): IThemingParticipant[]; @@ -167,7 +167,7 @@ class ThemingRegistry implements IThemingRegistry { this.onThemingParticipantAddedEmitter = new Emitter(); } - public onThemeChange(participant: IThemingParticipant): IDisposable { + public onColorThemeChange(participant: IThemingParticipant): IDisposable { this.themingParticipants.push(participant); this.onThemingParticipantAddedEmitter.fire(participant); return toDisposable(() => { @@ -189,5 +189,43 @@ let themingRegistry = new ThemingRegistry(); platform.Registry.add(Extensions.ThemingContribution, themingRegistry); export function registerThemingParticipant(participant: IThemingParticipant): IDisposable { - return themingRegistry.onThemeChange(participant); + return themingRegistry.onColorThemeChange(participant); +} + +/** + * Utility base class for all themable components. + */ +export class Themable extends Disposable { + protected theme: IColorTheme; + + constructor( + protected themeService: IThemeService + ) { + super(); + + this.theme = themeService.getColorTheme(); + + // Hook up to theme changes + this._register(this.themeService.onDidColorThemeChange(theme => this.onThemeChange(theme))); + } + + protected onThemeChange(theme: IColorTheme): void { + this.theme = theme; + + this.updateStyles(); + } + + protected updateStyles(): void { + // Subclasses to override + } + + protected getColor(id: string, modify?: (color: Color, theme: IColorTheme) => Color): string | null { + let color = this.theme.getColor(id); + + if (color && modify) { + color = modify(color, this.theme); + } + + return color ? color.toString() : null; + } } diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 0f91a88871..a41c9b69e1 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -5,7 +5,7 @@ import * as platform from 'vs/platform/registry/common/platform'; import { Color } from 'vs/base/common/color'; -import { ITheme } from 'vs/platform/theme/common/themeService'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; import * as nls from 'vs/nls'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -85,7 +85,7 @@ export namespace TokenStyle { export type ProbeScope = string[]; export interface TokenStyleFunction { - (theme: ITheme): TokenStyle | undefined; + (theme: IColorTheme): TokenStyle | undefined; } export interface TokenStyleDefaults { diff --git a/src/vs/platform/theme/test/common/testThemeService.ts b/src/vs/platform/theme/test/common/testThemeService.ts index 1186cb1970..029dbafcd3 100644 --- a/src/vs/platform/theme/test/common/testThemeService.ts +++ b/src/vs/platform/theme/test/common/testThemeService.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { IThemeService, ITheme, DARK, IIconTheme, ITokenStyle } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme, DARK, IFileIconTheme, ITokenStyle } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; -export class TestTheme implements ITheme { +export class TestColorTheme implements IColorTheme { constructor(private colors: { [id: string]: string; } = {}, public type = DARK) { } @@ -33,7 +33,7 @@ export class TestTheme implements ITheme { } } -export class TestIconTheme implements IIconTheme { +export class TestFileIconTheme implements IFileIconTheme { hasFileIcons = false; hasFolderIcons = false; hidesExplorerArrows = false; @@ -42,38 +42,38 @@ export class TestIconTheme implements IIconTheme { export class TestThemeService implements IThemeService { _serviceBrand: undefined; - _theme: ITheme; - _iconTheme: IIconTheme; - _onThemeChange = new Emitter(); - _onIconThemeChange = new Emitter(); + _colorTheme: IColorTheme; + _fileIconTheme: IFileIconTheme; + _onThemeChange = new Emitter(); + _onFileIconThemeChange = new Emitter(); - constructor(theme = new TestTheme(), iconTheme = new TestIconTheme()) { - this._theme = theme; - this._iconTheme = iconTheme; + constructor(theme = new TestColorTheme(), iconTheme = new TestFileIconTheme()) { + this._colorTheme = theme; + this._fileIconTheme = iconTheme; } - getTheme(): ITheme { - return this._theme; + getColorTheme(): IColorTheme { + return this._colorTheme; } - setTheme(theme: ITheme) { - this._theme = theme; + setTheme(theme: IColorTheme) { + this._colorTheme = theme; this.fireThemeChange(); } fireThemeChange() { - this._onThemeChange.fire(this._theme); + this._onThemeChange.fire(this._colorTheme); } - public get onThemeChange(): Event { + public get onDidColorThemeChange(): Event { return this._onThemeChange.event; } - getIconTheme(): IIconTheme { - return this._iconTheme; + getFileIconTheme(): IFileIconTheme { + return this._fileIconTheme; } - public get onIconThemeChange(): Event { - return this._onIconThemeChange.event; + public get onDidFileIconThemeChange(): Event { + return this._onFileIconThemeChange.event; } } diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index e5467f5fd3..dcf132ec79 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -7,11 +7,10 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IFileService, IFileContent, FileChangesEvent, FileSystemProviderError, FileSystemProviderErrorCode, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; import { URI } from 'vs/base/common/uri'; -import { SyncSource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncSource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, ResourceKey, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { joinPath, dirname } from 'vs/base/common/resources'; -import { toLocalISOString } from 'vs/base/common/date'; -import { ThrottledDelayer, CancelablePromise } from 'vs/base/common/async'; +import { CancelablePromise } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ParseError, parse } from 'vs/base/common/json'; @@ -19,6 +18,7 @@ import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { IStringDictionary } from 'vs/base/common/collections'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { isString } from 'vs/base/common/types'; type SyncSourceClassification = { source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -44,7 +44,6 @@ function isSyncData(thing: any): thing is ISyncData { export abstract class AbstractSynchroniser extends Disposable { protected readonly syncFolder: URI; - private cleanUpDelayer: ThrottledDelayer; private _status: SyncStatus = SyncStatus.Idle; get status(): SyncStatus { return this._status; } @@ -58,9 +57,11 @@ export abstract class AbstractSynchroniser extends Disposable { constructor( readonly source: SyncSource, + readonly resourceKey: ResourceKey, @IFileService protected readonly fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService protected readonly userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncEnablementService protected readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService, @@ -68,9 +69,7 @@ export abstract class AbstractSynchroniser extends Disposable { ) { super(); this.syncFolder = joinPath(environmentService.userDataSyncHome, source); - this.lastSyncResource = joinPath(this.syncFolder, `lastSync${source}.json`); - this.cleanUpDelayer = new ThrottledDelayer(50); - this.cleanUpBackup(); + this.lastSyncResource = joinPath(this.syncFolder, `lastSync${this.resourceKey}.json`); } protected setStatus(status: SyncStatus): void { @@ -89,10 +88,10 @@ export abstract class AbstractSynchroniser extends Disposable { } } - protected get enabled(): boolean { return this.userDataSyncEnablementService.isResourceEnabled(this.resourceKey); } + protected isEnabled(): boolean { return this.userDataSyncEnablementService.isResourceEnabled(this.resourceKey); } - async sync(ref?: string, donotUseLastSyncUserData?: boolean): Promise { - if (!this.enabled) { + async sync(ref?: string): Promise { + if (!this.isEnabled()) { this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as it is disabled.`); return; } @@ -108,24 +107,40 @@ export abstract class AbstractSynchroniser extends Disposable { this.logService.trace(`${this.source}: Started synchronizing ${this.source.toLowerCase()}...`); this.setStatus(SyncStatus.Syncing); - const lastSyncUserData = donotUseLastSyncUserData ? null : await this.getLastSyncUserData(); + const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = ref && lastSyncUserData && lastSyncUserData.ref === ref ? lastSyncUserData : await this.getRemoteUserData(lastSyncUserData); + let status: SyncStatus = SyncStatus.Idle; + try { + status = await this.doSync(remoteUserData, lastSyncUserData); + if (status === SyncStatus.HasConflicts) { + this.logService.info(`${this.source}: Detected conflicts while synchronizing ${this.source.toLowerCase()}.`); + } else if (status === SyncStatus.Idle) { + this.logService.trace(`${this.source}: Finished synchronizing ${this.source.toLowerCase()}.`); + } + } finally { + this.setStatus(status); + } + } + + protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) { // current version is not compatible with cloud version this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/incompatible', { source: this.source }); throw new UserDataSyncError(localize('incompatible', "Cannot sync {0} as its version {1} is not compatible with cloud {2}", this.source, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.Incompatible, this.source); } - try { - await this.doSync(remoteUserData, lastSyncUserData); + const status = await this.performSync(remoteUserData, lastSyncUserData); + return status; } catch (e) { if (e instanceof UserDataSyncError) { switch (e.code) { case UserDataSyncErrorCode.RemotePreconditionFailed: // Rejected as there is a new remote version. Syncing again, this.logService.info(`${this.source}: Failed to synchronize as there is a new remote version available. Synchronizing again...`); - return this.sync(undefined, true); + // Avoid cache and get latest remote user data - https://github.com/microsoft/vscode/issues/90624 + remoteUserData = await this.getRemoteUserData(null); + return this.doSync(remoteUserData, lastSyncUserData); } } throw e; @@ -137,10 +152,18 @@ export abstract class AbstractSynchroniser extends Disposable { return !!lastSyncData; } - async getRemoteContent(): Promise { - const lastSyncData = await this.getLastSyncUserData(); - const { syncData } = await this.getRemoteUserData(lastSyncData); - return syncData ? syncData.content : null; + async getRemoteContentFromPreview(): Promise { + return null; + } + + async getRemoteContent(ref?: string): Promise { + const refOrLastSyncUserData: string | IRemoteUserData | null = ref || await this.getLastSyncUserData(); + const { content } = await this.getUserData(refOrLastSyncUserData); + return content; + } + + async getLocalBackupContent(ref?: string): Promise { + return this.userDataSyncBackupStoreService.resolveContent(this.resourceKey, ref); } async resetLocal(): Promise { @@ -176,78 +199,53 @@ export abstract class AbstractSynchroniser extends Disposable { } protected async getRemoteUserData(lastSyncData: IRemoteUserData | null): Promise { - const lastSyncUserData: IUserData | null = lastSyncData ? { ref: lastSyncData.ref, content: lastSyncData.syncData ? JSON.stringify(lastSyncData.syncData) : null } : null; - const { ref, content } = await this.userDataSyncStoreService.read(this.resourceKey, lastSyncUserData, this.source); + const { ref, content } = await this.getUserData(lastSyncData); let syncData: ISyncData | null = null; if (content !== null) { - try { - syncData = JSON.parse(content); - - // Migration from old content to sync data - if (!isSyncData(syncData)) { - syncData = { version: this.version, content }; - } - - } catch (e) { - this.logService.error(e); - } + syncData = this.parseSyncData(content); } return { ref, syncData }; } + protected parseSyncData(content: string): ISyncData | null { + let syncData: ISyncData | null = null; + try { + syncData = JSON.parse(content); + + // Migration from old content to sync data + if (!isSyncData(syncData)) { + syncData = { version: this.version, content }; + } + + } catch (e) { + this.logService.error(e); + } + return syncData; + } + + private async getUserData(refOrLastSyncData: string | IRemoteUserData | null): Promise { + if (isString(refOrLastSyncData)) { + const content = await this.userDataSyncStoreService.resolveContent(this.resourceKey, refOrLastSyncData); + return { ref: refOrLastSyncData, content }; + } else { + const lastSyncUserData: IUserData | null = refOrLastSyncData ? { ref: refOrLastSyncData.ref, content: refOrLastSyncData.syncData ? JSON.stringify(refOrLastSyncData.syncData) : null } : null; + return this.userDataSyncStoreService.read(this.resourceKey, lastSyncUserData, this.source); + } + } + protected async updateRemoteUserData(content: string, ref: string | null): Promise { const syncData: ISyncData = { version: this.version, content }; ref = await this.userDataSyncStoreService.write(this.resourceKey, JSON.stringify(syncData), ref, this.source); return { ref, syncData }; } - protected async backupLocal(content: VSBuffer): Promise { - const resource = joinPath(this.syncFolder, `${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}.json`); - try { - await this.fileService.writeFile(resource, content); - } catch (e) { - this.logService.error(e); - } - this.cleanUpDelayer.trigger(() => this.cleanUpBackup()); + protected async backupLocal(content: string): Promise { + const syncData: ISyncData = { version: this.version, content }; + return this.userDataSyncBackupStoreService.backup(this.resourceKey, JSON.stringify(syncData)); } - private async cleanUpBackup(): Promise { - try { - if (!(await this.fileService.exists(this.syncFolder))) { - return; - } - const stat = await this.fileService.resolve(this.syncFolder); - if (stat.children) { - const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort(); - const backUpMaxAge = 1000 * 60 * 60 * 24 * (this.configurationService.getValue('sync.localBackupDuration') || 30 /* Default 30 days */); - let toDelete = all.filter(stat => { - const ctime = stat.ctime || new Date( - parseInt(stat.name.substring(0, 4)), - parseInt(stat.name.substring(4, 6)) - 1, - parseInt(stat.name.substring(6, 8)), - parseInt(stat.name.substring(9, 11)), - parseInt(stat.name.substring(11, 13)), - parseInt(stat.name.substring(13, 15)) - ).getTime(); - return Date.now() - ctime > backUpMaxAge; - }); - const remaining = all.length - toDelete.length; - if (remaining < 10) { - toDelete = toDelete.slice(10 - remaining); - } - await Promise.all(toDelete.map(stat => { - this.logService.info('Deleting from backup', stat.resource.path); - this.fileService.del(stat.resource); - })); - } - } catch (e) { - this.logService.error(e); - } - } - - abstract readonly resourceKey: ResourceKey; protected abstract readonly version: number; - protected abstract doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise; + protected abstract performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise; } export interface IFileSyncPreviewResult { @@ -267,15 +265,17 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { constructor( protected readonly file: URI, source: SyncSource, + resourceKey: ResourceKey, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, ) { - super(source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(source, resourceKey, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register(this.fileService.watch(dirname(file))); this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e))); } @@ -289,14 +289,12 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { this.setStatus(SyncStatus.Idle); } - async getRemoteContent(preview?: boolean): Promise { - if (preview) { - if (this.syncPreviewResultPromise) { - const result = await this.syncPreviewResultPromise; - return result.remoteUserData && result.remoteUserData.syncData ? result.remoteUserData.syncData.content : null; - } + async getRemoteContentFromPreview(): Promise { + if (this.syncPreviewResultPromise) { + const result = await this.syncPreviewResultPromise; + return result.remoteUserData && result.remoteUserData.syncData ? result.remoteUserData.syncData.content : null; } - return super.getRemoteContent(); + return null; } protected async getLocalFileContent(): Promise { @@ -311,7 +309,6 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { try { if (oldContent) { // file exists already - await this.backupLocal(oldContent.value); await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), oldContent); } else { // file does not exist @@ -332,7 +329,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { return; } - if (!this.enabled) { + if (!this.isEnabled()) { return; } @@ -340,7 +337,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { if (this.status === SyncStatus.HasConflicts) { this.syncPreviewResultPromise?.then(result => { this.cancel(); - this.doSync(result.remoteUserData, result.lastSyncUserData); + this.doSync(result.remoteUserData, result.lastSyncUserData).then(status => this.setStatus(status)); }); } @@ -366,16 +363,18 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni constructor( file: URI, source: SyncSource, + resourceKey: ResourceKey, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IUserDataSyncUtilService protected readonly userDataSyncUtilService: IUserDataSyncUtilService, @IConfigurationService configurationService: IConfigurationService, ) { - super(file, source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(file, source, resourceKey, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); } protected hasErrors(content: string): boolean { diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index e0433b82b9..24e61399a2 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -16,7 +16,6 @@ import { merge } from 'vs/platform/userDataSync/common/extensionsMerge'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { AbstractSynchroniser, IRemoteUserData, ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { VSBuffer } from 'vs/base/common/buffer'; interface ISyncPreviewResult { readonly localExtensions: ISyncExtension[]; @@ -35,13 +34,14 @@ interface ILastSyncUserData extends IRemoteUserData { export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { - readonly resourceKey: ResourceKey = 'extensions'; protected readonly version: number = 2; + protected isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); } constructor( @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @@ -50,7 +50,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(SyncSource.Extensions, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(SyncSource.Extensions, 'extensions', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register( Event.debounce( Event.any( @@ -61,7 +61,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } async pull(): Promise { - if (!this.enabled) { + if (!this.isEnabled()) { this.logService.info('Extensions: Skipped pulling extensions as it is disabled.'); return; } @@ -94,7 +94,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } async push(): Promise { - if (!this.enabled) { + if (!this.isEnabled()) { this.logService.info('Extensions: Skipped pushing extensions as it is disabled.'); return; } @@ -118,15 +118,34 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } - async sync(ref?: string): Promise { - if (!this.extensionGalleryService.isEnabled()) { - this.logService.info('Extensions: Skipping synchronizing extensions as gallery is disabled.'); - return; + async stop(): Promise { } + + async getRemoteContent(ref?: string, fragment?: string): Promise { + const content = await super.getRemoteContent(ref); + if (content !== null && fragment) { + return this.getFragment(content, fragment); } - return super.sync(ref); + return content; } - async stop(): Promise { } + async getLocalBackupContent(ref?: string, fragment?: string): Promise { + let content = await super.getLocalBackupContent(ref); + if (content !== null && fragment) { + return this.getFragment(content, fragment); + } + return content; + } + + private getFragment(content: string, fragment: string): string | null { + const syncData = this.parseSyncData(content); + if (syncData) { + switch (fragment) { + case 'extensions': + return syncData.content; + } + } + return null; + } accept(content: string): Promise { throw new Error('Extensions: Conflicts should not occur'); @@ -144,21 +163,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return false; } - async getRemoteContent(): Promise { - return null; - } - - protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { - try { - const previewResult = await this.getPreview(remoteUserData, lastSyncUserData); - await this.apply(previewResult); - } catch (e) { - this.setStatus(SyncStatus.Idle); - throw e; - } - - this.logService.trace('Extensions: Finished synchronizing extensions.'); - this.setStatus(SyncStatus.Idle); + protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { + const previewResult = await this.getPreview(remoteUserData, lastSyncUserData); + await this.apply(previewResult); + return SyncStatus.Idle; } private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { @@ -194,7 +202,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (added.length || removed.length || updated.length) { // back up all disabled or market place extensions const backUpExtensions = localExtensions.filter(e => e.disabled || !!e.identifier.uuid); - await this.backupLocal(VSBuffer.fromString(JSON.stringify(backUpExtensions, null, '\t'))); + await this.backupLocal(JSON.stringify(backUpExtensions)); skippedExtensions = await this.updateLocalExtensions(added, removed, updated, skippedExtensions); } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 8e369b2243..f7a5d38e4d 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -29,25 +29,25 @@ interface ISyncPreviewResult { export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { - readonly resourceKey: ResourceKey = 'globalState'; protected readonly version: number = 1; constructor( @IFileService fileService: IFileService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IConfigurationService configurationService: IConfigurationService, ) { - super(SyncSource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(SyncSource.GlobalState, 'globalState', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register(this.fileService.watch(dirname(this.environmentService.argvResource))); this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire())); } async pull(): Promise { - if (!this.enabled) { + if (!this.isEnabled()) { this.logService.info('UI State: Skipped pulling ui state as it is disabled.'); return; } @@ -79,7 +79,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } async push(): Promise { - if (!this.enabled) { + if (!this.isEnabled()) { this.logService.info('UI State: Skipped pushing UI State as it is disabled.'); return; } @@ -104,6 +104,33 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs async stop(): Promise { } + async getRemoteContent(ref?: string, fragment?: string): Promise { + let content = await super.getRemoteContent(ref); + if (content !== null && fragment) { + return this.getFragment(content, fragment); + } + return content; + } + + async getLocalBackupContent(ref?: string, fragment?: string): Promise { + let content = await super.getLocalBackupContent(ref); + if (content !== null && fragment) { + return this.getFragment(content, fragment); + } + return content; + } + + private getFragment(content: string, fragment: string): string | null { + const syncData = this.parseSyncData(content); + if (syncData) { + switch (fragment) { + case 'globalState': + return syncData.content; + } + } + return null; + } + accept(content: string): Promise { throw new Error('UI State: Conflicts should not occur'); } @@ -120,24 +147,13 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return false; } - async getRemoteContent(): Promise { - return null; + protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { + const result = await this.getPreview(remoteUserData, lastSyncUserData); + await this.apply(result); + return SyncStatus.Idle; } - protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { - try { - const result = await this.getPreview(remoteUserData, lastSyncUserData); - await this.apply(result); - this.logService.trace('UI State: Finished synchronizing ui state.'); - } catch (e) { - this.setStatus(SyncStatus.Idle); - throw e; - } finally { - this.setStatus(SyncStatus.Idle); - } - } - - private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, ): Promise { + private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null; const lastSyncGlobalState = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null; @@ -165,7 +181,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs if (local) { // update local this.logService.trace('UI State: Updating local ui state...'); - await this.backupLocal(VSBuffer.fromString(JSON.stringify(localUserData, null, '\t'))); + await this.backupLocal(JSON.stringify(localUserData)); await this.writeLocalGlobalState(local); this.logService.info('UI State: Updated local ui state'); } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index bab4ddc7d0..2040210a39 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse } from 'vs/base/common/json'; @@ -29,12 +29,12 @@ interface ISyncContent { export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser { - readonly resourceKey: ResourceKey = 'keybindings'; protected get conflictsPreviewResource(): URI { return this.environmentService.keybindingsSyncPreviewResource; } protected readonly version: number = 1; constructor( @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @@ -43,11 +43,11 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(environmentService.keybindingsResource, SyncSource.Keybindings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); + super(environmentService.keybindingsResource, SyncSource.Keybindings, 'keybindings', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } async pull(): Promise { - if (!this.enabled) { + if (!this.isEnabled()) { this.logService.info('Keybindings: Skipped pulling keybindings as it is disabled.'); return; } @@ -89,7 +89,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem } async push(): Promise { - if (!this.enabled) { + if (!this.isEnabled()) { this.logService.info('Keybindings: Skipped pushing keybindings as it is disabled.'); return; } @@ -156,34 +156,54 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem return false; } - async getRemoteContent(preview?: boolean): Promise { - const content = await super.getRemoteContent(preview); + async getRemoteContentFromPreview(): Promise { + const content = await super.getRemoteContentFromPreview(); return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null; } - protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { + async getRemoteContent(ref?: string, fragment?: string): Promise { + const content = await super.getRemoteContent(ref); + if (content !== null && fragment) { + return this.getFragment(content, fragment); + } + return content; + } + + async getLocalBackupContent(ref?: string, fragment?: string): Promise { + let content = await super.getLocalBackupContent(ref); + if (content !== null && fragment) { + return this.getFragment(content, fragment); + } + return content; + } + + private getFragment(content: string, fragment: string): string | null { + const syncData = this.parseSyncData(content); + if (syncData) { + switch (fragment) { + case 'keybindings': + return this.getKeybindingsContentFromSyncContent(syncData.content); + } + } + return null; + } + + protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { try { const result = await this.getPreview(remoteUserData, lastSyncUserData); if (result.hasConflicts) { - this.logService.info('Keybindings: Detected conflicts while synchronizing keybindings.'); - this.setStatus(SyncStatus.HasConflicts); - return; - } - try { - await this.apply(); - this.logService.trace('Keybindings: Finished synchronizing keybindings...'); - } finally { - this.setStatus(SyncStatus.Idle); + return SyncStatus.HasConflicts; } + await this.apply(); + return SyncStatus.Idle; } catch (e) { this.syncPreviewResultPromise = null; - this.setStatus(SyncStatus.Idle); if (e instanceof UserDataSyncError) { switch (e.code) { case UserDataSyncErrorCode.LocalPreconditionFailed: // Rejected as there is a new local version. Syncing again. this.logService.info('Keybindings: Failed to synchronize keybindings as there is a new local version available. Synchronizing again...'); - return this.sync(remoteUserData.ref); + return this.performSync(remoteUserData, lastSyncUserData); } } throw e; @@ -204,13 +224,14 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem if (hasLocalChanged) { this.logService.trace('Keybindings: Updating local keybindings...'); + await this.backupLocal(this.toSyncContent(content, null)); await this.updateLocalFileContent(content, fileContent); this.logService.info('Keybindings: Updated local keybindings'); } if (hasRemoteChanged) { this.logService.trace('Keybindings: Updating remote keybindings...'); - const remoteContents = this.updateSyncContent(content, remoteUserData.syncData ? remoteUserData.syncData.content : null); + const remoteContents = this.toSyncContent(content, remoteUserData.syncData ? remoteUserData.syncData.content : null); remoteUserData = await this.updateRemoteUserData(remoteContents, forcePush ? null : remoteUserData.ref); this.logService.info('Keybindings: Updated remote keybindings'); } @@ -225,7 +246,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem if (lastSyncUserData?.ref !== remoteUserData.ref && (content !== null || fileContent !== null)) { this.logService.trace('Keybindings: Updating last synchronized keybindings...'); - const lastSyncContent = this.updateSyncContent(content !== null ? content : fileContent!.value.toString(), null); + const lastSyncContent = this.toSyncContent(content !== null ? content : fileContent!.value.toString(), null); await this.updateLastSyncUserData({ ref: remoteUserData.ref, syncData: { version: remoteUserData.syncData!.version, content: lastSyncContent } }); this.logService.info('Keybindings: Updated last synchronized keybindings'); } @@ -308,7 +329,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem } } - private updateSyncContent(keybindingsContent: string, syncContent: string | null): string { + private toSyncContent(keybindingsContent: string, syncContent: string | null): string { let parsed: ISyncContent = {}; try { parsed = JSON.parse(syncContent || '{}'); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 2bc8f38fa6..3a237c4f60 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse } from 'vs/base/common/json'; import { localize } from 'vs/nls'; @@ -23,7 +23,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { URI } from 'vs/base/common/uri'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -interface ISettingsSyncContent { +export interface ISettingsSyncContent { settings: string; } @@ -37,7 +37,6 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement _serviceBrand: any; - readonly resourceKey: ResourceKey = 'settings'; protected readonly version: number = 1; protected get conflictsPreviewResource(): URI { return this.environmentService.settingsSyncPreviewResource; } @@ -50,6 +49,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement @IFileService fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @IConfigurationService configurationService: IConfigurationService, @@ -57,7 +57,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement @ITelemetryService telemetryService: ITelemetryService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, ) { - super(environmentService.settingsResource, SyncSource.Settings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); + super(environmentService.settingsResource, SyncSource.Settings, 'settings', fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } protected setStatus(status: SyncStatus): void { @@ -77,7 +77,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement } async pull(): Promise { - if (!this.enabled) { + if (!this.isEnabled()) { this.logService.info('Settings: Skipped pulling settings as it is disabled.'); return; } @@ -123,7 +123,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement } async push(): Promise { - if (!this.enabled) { + if (!this.isEnabled()) { this.logService.info('Settings: Skipped pushing settings as it is disabled.'); return; } @@ -187,13 +187,13 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement return false; } - async getRemoteContent(preview?: boolean): Promise { - let content = await super.getRemoteContent(preview); + async getRemoteContentFromPreview(): Promise { + let content = await super.getRemoteContentFromPreview(); if (content !== null) { const settingsSyncContent = this.parseSettingsSyncContent(content); content = settingsSyncContent ? settingsSyncContent.settings : null; } - if (preview && content !== null) { + if (content !== null) { const formatUtils = await this.getFormattingOptions(); // remove ignored settings from the remote content for preview const ignoredSettings = await this.getIgnoredSettings(); @@ -202,6 +202,36 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement return content; } + async getRemoteContent(ref?: string, fragment?: string): Promise { + let content = await super.getRemoteContent(ref); + if (content !== null && fragment) { + return this.getFragment(content, fragment); + } + return content; + } + + async getLocalBackupContent(ref?: string, fragment?: string): Promise { + let content = await super.getLocalBackupContent(ref); + if (content !== null && fragment) { + return this.getFragment(content, fragment); + } + return content; + } + + private getFragment(content: string, fragment: string): string | null { + const syncData = this.parseSyncData(content); + if (syncData) { + const settingsSyncContent = this.parseSettingsSyncContent(syncData.content); + if (settingsSyncContent) { + switch (fragment) { + case 'settings': + return settingsSyncContent.settings; + } + } + } + return null; + } + async accept(content: string): Promise { if (this.status === SyncStatus.HasConflicts) { const preview = await this.syncPreviewResultPromise!; @@ -220,33 +250,26 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement if (this.status === SyncStatus.HasConflicts) { const preview = await this.syncPreviewResultPromise!; this.cancel(); - await this.doSync(preview.remoteUserData, preview.lastSyncUserData, resolvedConflicts); + await this.performSync(preview.remoteUserData, preview.lastSyncUserData, resolvedConflicts); } } - protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any | undefined }[] = []): Promise { + protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any | undefined }[] = []): Promise { try { const result = await this.getPreview(remoteUserData, lastSyncUserData, resolvedConflicts); if (result.hasConflicts) { - this.logService.info('Settings: Detected conflicts while synchronizing settings.'); - this.setStatus(SyncStatus.HasConflicts); - return; - } - try { - await this.apply(); - this.logService.trace('Settings: Finished synchronizing settings.'); - } finally { - this.setStatus(SyncStatus.Idle); + return SyncStatus.HasConflicts; } + await this.apply(); + return SyncStatus.Idle; } catch (e) { this.syncPreviewResultPromise = null; - this.setStatus(SyncStatus.Idle); if (e instanceof UserDataSyncError) { switch (e.code) { case UserDataSyncErrorCode.LocalPreconditionFailed: // Rejected as there is a new local version. Syncing again. this.logService.info('Settings: Failed to synchronize settings as there is a new local version available. Synchronizing again...'); - return this.sync(remoteUserData.ref); + return this.performSync(remoteUserData, lastSyncUserData, resolvedConflicts); } } throw e; @@ -266,6 +289,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement if (hasLocalChanged) { this.logService.trace('Settings: Updating local settings...'); + await this.backupLocal(JSON.stringify(this.toSettingsSyncContent(content))); await this.updateLocalFileContent(content, fileContent); this.logService.info('Settings: Updated local settings'); } @@ -276,7 +300,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement const ignoredSettings = await this.getIgnoredSettings(content); content = updateIgnoredSettings(content, remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}', ignoredSettings, formatUtils); this.logService.trace('Settings: Updating remote settings...'); - remoteUserData = await this.updateRemoteUserData(JSON.stringify({ settings: content }), forcePush ? null : remoteUserData.ref); + remoteUserData = await this.updateRemoteUserData(JSON.stringify(this.toSettingsSyncContent(content)), forcePush ? null : remoteUserData.ref); this.logService.info('Settings: Updated remote settings'); } @@ -361,6 +385,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement return null; } + private toSettingsSyncContent(settings: string): ISettingsSyncContent { + return { settings }; + } + private _defaultIgnoredSettings: Promise | undefined = undefined; protected async getIgnoredSettings(content?: string): Promise { if (!this._defaultIgnoredSettings) { diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index a315eef6e3..7ef487541b 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -18,7 +18,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { URI } from 'vs/base/common/uri'; -import { isEqual, joinPath } from 'vs/base/common/resources'; +import { isEqual, joinPath, dirname, basename } from 'vs/base/common/resources'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IProductService } from 'vs/platform/product/common/productService'; import { distinct } from 'vs/base/common/arrays'; @@ -65,7 +65,7 @@ export function registerConfiguration(): IDisposable { description: localize('sync.keybindingsPerPlatform', "Synchronize keybindings per platform."), default: true, scope: ConfigurationScope.APPLICATION, - tags: ['sync'] + tags: ['sync', 'usesOnlineServices'] }, 'sync.ignoredExtensions': { 'type': 'array', @@ -75,7 +75,7 @@ export function registerConfiguration(): IDisposable { 'scope': ConfigurationScope.APPLICATION, uniqueItems: true, disallowSyncIgnore: true, - tags: ['sync'] + tags: ['sync', 'usesOnlineServices'] }, 'sync.ignoredSettings': { 'type': 'array', @@ -86,7 +86,7 @@ export function registerConfiguration(): IDisposable { additionalProperties: true, uniqueItems: true, disallowSyncIgnore: true, - tags: ['sync'] + tags: ['sync', 'usesOnlineServices'] } } }); @@ -143,6 +143,11 @@ export interface IUserDataManifest { session: string; } +export interface IResourceRefHandle { + ref: string; + created: number; +} + export const IUserDataSyncStoreService = createDecorator('IUserDataSyncStoreService'); export interface IUserDataSyncStoreService { _serviceBrand: undefined; @@ -151,6 +156,17 @@ export interface IUserDataSyncStoreService { write(key: ResourceKey, content: string, ref: string | null, source?: SyncSource): Promise; manifest(): Promise; clear(): Promise; + getAllRefs(key: ResourceKey): Promise; + resolveContent(key: ResourceKey, ref: string): Promise; + delete(key: ResourceKey): Promise; +} + +export const IUserDataSyncBackupStoreService = createDecorator('IUserDataSyncBackupStoreService'); +export interface IUserDataSyncBackupStoreService { + _serviceBrand: undefined; + backup(resourceKey: ResourceKey, content: string): Promise; + getAllRefs(key: ResourceKey): Promise; + resolveContent(key: ResourceKey, ref?: string): Promise; } //#endregion @@ -245,7 +261,9 @@ export interface IUserDataSynchroniser { hasLocalData(): Promise; resetLocal(): Promise; - getRemoteContent(preivew?: boolean): Promise; + getRemoteContentFromPreview(): Promise; + getRemoteContent(ref?: string, fragment?: string): Promise; + getLocalBackupContent(ref?: string, fragment?: string): Promise; accept(content: string): Promise; } @@ -290,7 +308,7 @@ export interface IUserDataSyncService { resetLocal(): Promise; isFirstTimeSyncWithMerge(): Promise; - getRemoteContent(source: SyncSource, preview: boolean): Promise; + resolveContent(resource: URI): Promise; accept(source: SyncSource, content: string): Promise; } @@ -332,12 +350,27 @@ export const CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncSt export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey('syncEnabled', false); export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync'; -export function toRemoteContentResource(source: SyncSource): URI { - return URI.from({ scheme: USER_DATA_SYNC_SCHEME, path: `${source}/remoteContent` }); +export const PREVIEW_QUERY = 'preview=true'; +export function toRemoteSyncResourceFromSource(source: SyncSource, ref?: string): URI { + return toRemoteSyncResource(getResourceKeyFromSyncSource(source), ref); } -export function getSyncSourceFromRemoteContentResource(uri: URI): SyncSource | undefined { - return [SyncSource.Settings, SyncSource.Keybindings, SyncSource.Extensions, SyncSource.GlobalState].filter(source => isEqual(uri, toRemoteContentResource(source)))[0]; +export function toRemoteSyncResource(resourceKey: ResourceKey, ref?: string): URI { + return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote', path: `/${resourceKey}/${ref ? ref : 'latest'}` }); } +export function toLocalBackupSyncResource(resourceKey: ResourceKey, ref?: string): URI { + return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local-backup', path: `/${resourceKey}/${ref ? ref : 'latest'}` }); +} + +export function resolveSyncResource(resource: URI): { remote: boolean, resourceKey: ResourceKey, ref?: string } | null { + const remote = resource.authority === 'remote'; + const resourceKey: ResourceKey = basename(dirname(resource)) as ResourceKey; + const ref = basename(resource); + if (resourceKey && ref) { + return { remote, resourceKey, ref: ref !== 'latest' ? ref : undefined }; + } + return null; +} + export function getSyncSourceFromPreviewResource(uri: URI, environmentService: IEnvironmentService): SyncSource | undefined { if (isEqual(uri, environmentService.settingsSyncPreviewResource)) { return SyncSource.Settings; @@ -347,3 +380,21 @@ export function getSyncSourceFromPreviewResource(uri: URI, environmentService: I } return undefined; } + +export function getResourceKeyFromSyncSource(source: SyncSource): ResourceKey { + switch (source) { + case SyncSource.Settings: return 'settings'; + case SyncSource.Keybindings: return 'keybindings'; + case SyncSource.Extensions: return 'extensions'; + case SyncSource.GlobalState: return 'globalState'; + } +} + +export function getSyncSourceFromResourceKey(resourceKey: ResourceKey): SyncSource { + switch (resourceKey) { + case 'settings': return SyncSource.Settings; + case 'keybindings': return SyncSource.Keybindings; + case 'extensions': return SyncSource.Extensions; + case 'globalState': return SyncSource.GlobalState; + } +} diff --git a/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts new file mode 100644 index 0000000000..b1e2c50404 --- /dev/null +++ b/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts @@ -0,0 +1,109 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, } from 'vs/base/common/lifecycle'; +import { IUserDataSyncLogService, ResourceKey, ALL_RESOURCE_KEYS, IUserDataSyncBackupStoreService, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; +import { joinPath } from 'vs/base/common/resources'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IFileService, IFileStat } from 'vs/platform/files/common/files'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { toLocalISOString } from 'vs/base/common/date'; +import { VSBuffer } from 'vs/base/common/buffer'; + +export class UserDataSyncBackupStoreService extends Disposable implements IUserDataSyncBackupStoreService { + + _serviceBrand: any; + + constructor( + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IFileService private readonly fileService: IFileService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + ) { + super(); + ALL_RESOURCE_KEYS.forEach(resourceKey => this.cleanUpBackup(resourceKey)); + } + + async getAllRefs(resourceKey: ResourceKey): Promise { + const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey); + const stat = await this.fileService.resolve(folder); + if (stat.children) { + const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort().reverse(); + return all.map(stat => ({ + ref: stat.name, + created: this.getCreationTime(stat) + })); + } + return []; + } + + async resolveContent(resourceKey: ResourceKey, ref?: string): Promise { + if (!ref) { + const refs = await this.getAllRefs(resourceKey); + if (refs.length) { + ref = refs[refs.length - 1].ref; + } + } + if (ref) { + const file = joinPath(this.environmentService.userDataSyncHome, resourceKey, ref); + const content = await this.fileService.readFile(file); + return content.value.toString(); + } + return null; + } + + async backup(resourceKey: ResourceKey, content: string): Promise { + const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey); + const resource = joinPath(folder, `${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}.json`); + try { + await this.fileService.writeFile(resource, VSBuffer.fromString(content)); + } catch (e) { + this.logService.error(e); + } + try { + this.cleanUpBackup(resourceKey); + } catch (e) { /* Ignore */ } + } + + private async cleanUpBackup(resourceKey: ResourceKey): Promise { + const folder = joinPath(this.environmentService.userDataSyncHome, resourceKey); + try { + try { + if (!(await this.fileService.exists(folder))) { + return; + } + } catch (e) { + return; + } + const stat = await this.fileService.resolve(folder); + if (stat.children) { + const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort(); + const backUpMaxAge = 1000 * 60 * 60 * 24 * (this.configurationService.getValue('sync.localBackupDuration') || 30 /* Default 30 days */); + let toDelete = all.filter(stat => Date.now() - this.getCreationTime(stat) > backUpMaxAge); + const remaining = all.length - toDelete.length; + if (remaining < 10) { + toDelete = toDelete.slice(10 - remaining); + } + await Promise.all(toDelete.map(stat => { + this.logService.info('Deleting from backup', stat.resource.path); + this.fileService.del(stat.resource); + })); + } + } catch (e) { + this.logService.error(e); + } + } + + private getCreationTime(stat: IFileStat) { + return stat.ctime || new Date( + parseInt(stat.name.substring(0, 4)), + parseInt(stat.name.substring(4, 6)) - 1, + parseInt(stat.name.substring(6, 8)), + parseInt(stat.name.substring(9, 11)), + parseInt(stat.name.substring(11, 13)), + parseInt(stat.name.substring(13, 15)) + ).getTime(); + } +} diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 3a0b48a8e5..73af2c0286 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -5,7 +5,7 @@ import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; -import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAutoSyncService, IUserDataSyncStoreService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { URI } from 'vs/base/common/uri'; import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; @@ -34,7 +34,7 @@ export class UserDataSyncChannel implements IServerChannel { case 'stop': this.service.stop(); return Promise.resolve(); case 'reset': return this.service.reset(); case 'resetLocal': return this.service.resetLocal(); - case 'getRemoteContent': return this.service.getRemoteContent(args[0], args[1]); + case 'resolveContent': return this.service.resolveContent(URI.revive(args[0])); case 'isFirstTimeSyncWithMerge': return this.service.isFirstTimeSyncWithMerge(); } throw new Error('Invalid call'); @@ -67,7 +67,9 @@ export class SettingsSyncChannel implements IServerChannel { case 'hasPreviouslySynced': return this.service.hasPreviouslySynced(); case 'hasLocalData': return this.service.hasLocalData(); case 'resolveSettingsConflicts': return this.service.resolveSettingsConflicts(args[0]); - case 'getRemoteContent': return this.service.getRemoteContent(args[0]); + case 'getRemoteContentFromPreview': return this.service.getRemoteContentFromPreview(); + case 'getRemoteContent': return this.service.getRemoteContent(args[0], args[1]); + case 'getLocalBackupContent': return this.service.getLocalBackupContent(args[0], args[1]); } throw new Error('Invalid call'); } @@ -131,3 +133,37 @@ export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService { } +export class UserDataSyncStoreServiceChannel implements IServerChannel { + + constructor(private readonly service: IUserDataSyncStoreService) { } + + listen(_: unknown, event: string): Event { + throw new Error(`Event not found: ${event}`); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case 'getAllRefs': return this.service.getAllRefs(args[0]); + case 'resolveContent': return this.service.resolveContent(args[0], args[1]); + case 'delete': return this.service.delete(args[0]); + } + throw new Error('Invalid call'); + } +} + +export class UserDataSyncBackupStoreServiceChannel implements IServerChannel { + + constructor(private readonly service: IUserDataSyncBackupStoreService) { } + + listen(_: unknown, event: string): Event { + throw new Error(`Event not found: ${event}`); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case 'getAllRefs': return this.service.getAllRefs(args[0]); + case 'resolveContent': return this.service.resolveContent(args[0], args[1]); + } + throw new Error('Invalid call'); + } +} diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 73b28d8604..152a216054 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, resolveSyncResource, PREVIEW_QUERY } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -15,6 +15,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { equals } from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { URI } from 'vs/base/common/uri'; type SyncErrorClassification = { source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -176,11 +177,15 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ await synchroniser.accept(content); } - async getRemoteContent(source: SyncSource, preview: boolean): Promise { - await this.checkEnablement(); - for (const synchroniser of this.synchronisers) { - if (synchroniser.source === source) { - return synchroniser.getRemoteContent(preview); + async resolveContent(resource: URI): Promise { + const result = resolveSyncResource(resource); + if (result) { + const synchronizer = this.synchronisers.filter(s => s.resourceKey === result.resourceKey)[0]; + if (synchronizer) { + if (PREVIEW_QUERY === resource.query) { + return result.remote ? synchronizer.getRemoteContentFromPreview() : null; + } + return result.remote ? synchronizer.getRemoteContent(result.ref, resource.fragment) : synchronizer.getLocalBackupContent(result.ref, resource.fragment); } } return null; @@ -302,13 +307,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts).map(s => s.source); } - private getSynchroniser(source: SyncSource): IUserDataSynchroniser { - switch (source) { - case SyncSource.Settings: return this.settingsSynchroniser; - case SyncSource.Keybindings: return this.keybindingsSynchroniser; - case SyncSource.Extensions: return this.extensionsSynchroniser; - case SyncSource.GlobalState: return this.globalStateSynchroniser; - } + getSynchroniser(source: SyncSource): IUserDataSynchroniser { + return this.synchronisers.filter(s => s.source === source)[0]; } private async checkEnablement(): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 2aa0634556..2438765138 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -4,14 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, SyncSource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, SyncSource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, ResourceKey, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; import { IRequestService, asText, isSuccess, asJson } from 'vs/platform/request/common/request'; -import { joinPath } from 'vs/base/common/resources'; +import { joinPath, relativePath } from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IHeaders, IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import { IProductService } from 'vs/platform/product/common/productService'; +import { URI } from 'vs/base/common/uri'; export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService { @@ -30,7 +31,58 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn this.userDataSyncStore = getUserDataSyncStore(productService, configurationService); } - async read(key: string, oldValue: IUserData | null, source?: SyncSource): Promise { + async getAllRefs(key: ResourceKey): Promise { + if (!this.userDataSyncStore) { + throw new Error('No settings sync store url configured.'); + } + + const uri = joinPath(this.userDataSyncStore.url, 'resource', key); + const headers: IHeaders = {}; + + const context = await this.request({ type: 'GET', url: uri.toString(), headers }, undefined, CancellationToken.None); + + if (!isSuccess(context)) { + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, undefined); + } + + const result = await asJson<{ url: string, created: number }[]>(context) || []; + return result.map(({ url, created }) => ({ ref: relativePath(uri, URI.parse(url))!, created: created })); + } + + async resolveContent(key: ResourceKey, ref: string): Promise { + if (!this.userDataSyncStore) { + throw new Error('No settings sync store url configured.'); + } + + const url = joinPath(this.userDataSyncStore.url, 'resource', key, ref).toString(); + const headers: IHeaders = {}; + + const context = await this.request({ type: 'GET', url, headers }, undefined, CancellationToken.None); + + if (!isSuccess(context)) { + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, undefined); + } + + const content = await asText(context); + return content; + } + + async delete(key: ResourceKey): Promise { + if (!this.userDataSyncStore) { + throw new Error('No settings sync store url configured.'); + } + + const url = joinPath(this.userDataSyncStore.url, 'resource', key).toString(); + const headers: IHeaders = {}; + + const context = await this.request({ type: 'DELETE', url, headers }, undefined, CancellationToken.None); + + if (!isSuccess(context)) { + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, undefined); + } + } + + async read(key: ResourceKey, oldValue: IUserData | null, source?: SyncSource): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } @@ -62,7 +114,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn return { ref, content }; } - async write(key: string, data: string, ref: string | null, source?: SyncSource): Promise { + async write(key: ResourceKey, data: string, ref: string | null, source?: SyncSource): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } diff --git a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts new file mode 100644 index 0000000000..669c22fa4d --- /dev/null +++ b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts @@ -0,0 +1,355 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { IUserDataSyncStoreService, IUserDataSyncService, SyncSource, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { SettingsSynchroniser, ISettingsSyncContent } from 'vs/platform/userDataSync/common/settingsSync'; +import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; + +suite('SettingsSync', () => { + + const disposableStore = new DisposableStore(); + const server = new UserDataSyncTestServer(); + let client: UserDataSyncClient; + + let testObject: SettingsSynchroniser; + + suiteSetup(() => { + Registry.as(Extensions.Configuration).registerConfiguration({ + 'id': 'settingsSync', + 'type': 'object', + 'properties': { + 'settingsSync.machine': { + 'type': 'string', + 'scope': ConfigurationScope.MACHINE + }, + 'settingsSync.machineOverridable': { + 'type': 'string', + 'scope': ConfigurationScope.MACHINE_OVERRIDABLE + } + } + }); + }); + + setup(async () => { + client = disposableStore.add(new UserDataSyncClient(server)); + await client.setUp(); + testObject = (client.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncSource.Settings) as SettingsSynchroniser; + disposableStore.add(toDisposable(() => client.instantiationService.get(IUserDataSyncStoreService).clear())); + }); + + teardown(() => disposableStore.clear()); + + test('sync for first time to the server', async () => { + const expected = + `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + "workbench.tree.indent": 20, + "workbench.colorCustomizations": { + "editorLineNumber.activeForeground": "#ff0000", + "[GitHub Sharp]": { + "statusBarItem.remoteBackground": "#24292E", + "editorPane.background": "#f3f1f11a" + } + }, + + "gitBranch.base": "remote-repo/master", + + // Experimental + "workbench.view.experimental.allowMovingToNewContainer": true, +}`; + + await updateSettings(expected); + await testObject.sync(); + + const { content } = await client.read(testObject.resourceKey); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, expected); + }); + + test('do not sync machine settings', async () => { + const settingsContent = + `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + + // Machine + "settingsSync.machine": "someValue", + "settingsSync.machineOverridable": "someValue" +}`; + await updateSettings(settingsContent); + + await testObject.sync(); + + const { content } = await client.read(testObject.resourceKey); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp" +}`); + }); + + test('do not sync machine settings when spread across file', async () => { + const settingsContent = + `{ + // Always + "files.autoSave": "afterDelay", + "settingsSync.machine": "someValue", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + + // Machine + "settingsSync.machineOverridable": "someValue" +}`; + await updateSettings(settingsContent); + + await testObject.sync(); + + const { content } = await client.read(testObject.resourceKey); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp" +}`); + }); + + test('do not sync machine settings when spread across file - 2', async () => { + const settingsContent = + `{ + // Always + "files.autoSave": "afterDelay", + "settingsSync.machine": "someValue", + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + + // Machine + "settingsSync.machineOverridable": "someValue", + "files.simpleDialog.enable": true, +}`; + await updateSettings(settingsContent); + + await testObject.sync(); + + const { content } = await client.read(testObject.resourceKey); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, `{ + // Always + "files.autoSave": "afterDelay", + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + "files.simpleDialog.enable": true, +}`); + }); + + test('sync when all settings are machine settings', async () => { + const settingsContent = + `{ + // Machine + "settingsSync.machine": "someValue", + "settingsSync.machineOverridable": "someValue" +}`; + await updateSettings(settingsContent); + + await testObject.sync(); + + const { content } = await client.read(testObject.resourceKey); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, `{ +}`); + }); + + test('sync when all settings are machine settings with trailing comma', async () => { + const settingsContent = + `{ + // Machine + "settingsSync.machine": "someValue", + "settingsSync.machineOverridable": "someValue", +}`; + await updateSettings(settingsContent); + + await testObject.sync(); + + const { content } = await client.read(testObject.resourceKey); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, `{ + , +}`); + }); + + test('do not sync ignored settings', async () => { + const settingsContent = + `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Editor + "editor.fontFamily": "Fira Code", + + // Terminal + "terminal.integrated.shell.osx": "some path", + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + + // Ignored + "sync.ignoredSettings": [ + "editor.fontFamily", + "terminal.integrated.shell.osx" + ] +}`; + await updateSettings(settingsContent); + + await testObject.sync(); + + const { content } = await client.read(testObject.resourceKey); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + + // Ignored + "sync.ignoredSettings": [ + "editor.fontFamily", + "terminal.integrated.shell.osx" + ] +}`); + }); + + test('do not sync ignored and machine settings', async () => { + const settingsContent = + `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Editor + "editor.fontFamily": "Fira Code", + + // Terminal + "terminal.integrated.shell.osx": "some path", + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + + // Ignored + "sync.ignoredSettings": [ + "editor.fontFamily", + "terminal.integrated.shell.osx" + ], + + // Machine + "settingsSync.machine": "someValue", +}`; + await updateSettings(settingsContent); + + await testObject.sync(); + + const { content } = await client.read(testObject.resourceKey); + assert.ok(content !== null); + const actual = parseSettings(content!); + assert.deepEqual(actual, `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + + // Ignored + "sync.ignoredSettings": [ + "editor.fontFamily", + "terminal.integrated.shell.osx" + ], +}`); + }); + + test('sync throws invalid content error', async () => { + const expected = + `{ + // Always + "files.autoSave": "afterDelay", + "files.simpleDialog.enable": true, + + // Workbench + "workbench.colorTheme": "GitHub Sharp", + "workbench.tree.indent": 20, + "workbench.colorCustomizations": { + "editorLineNumber.activeForeground": "#ff0000", + "[GitHub Sharp]": { + "statusBarItem.remoteBackground": "#24292E", + "editorPane.background": "#f3f1f11a" + } + } + + "gitBranch.base": "remote-repo/master", + + // Experimental + "workbench.view.experimental.allowMovingToNewContainer": true, +}`; + + await updateSettings(expected); + + try { + await testObject.sync(); + assert.fail('should fail with invalid content error'); + } catch (e) { + assert.ok(e instanceof UserDataSyncError); + assert.deepEqual((e).code, UserDataSyncErrorCode.LocalInvalidContent); + } + }); + + function parseSettings(content: string): string { + const syncData: ISyncData = JSON.parse(content); + const settingsSyncContent: ISettingsSyncContent = JSON.parse(syncData.content); + return settingsSyncContent.settings; + } + + async function updateSettings(content: string): Promise { + await client.instantiationService.get(IFileService).writeFile(client.instantiationService.get(IEnvironmentService).settingsResource, VSBuffer.fromString(content)); + } + + +}); diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts new file mode 100644 index 0000000000..386c187d33 --- /dev/null +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -0,0 +1,200 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { ResourceKey, IUserDataSyncStoreService, SyncSource, SyncStatus, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { Barrier } from 'vs/base/common/async'; +import { Emitter, Event } from 'vs/base/common/event'; + +class TestSynchroniser extends AbstractSynchroniser { + + syncBarrier: Barrier = new Barrier(); + syncResult: { status?: SyncStatus, error?: boolean } = {}; + onDoSyncCall: Emitter = this._register(new Emitter()); + + readonly resourceKey: ResourceKey = 'settings'; + protected readonly version: number = 1; + + private cancelled: boolean = false; + + protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { + this.cancelled = false; + this.onDoSyncCall.fire(); + await this.syncBarrier.wait(); + + if (this.cancelled) { + return SyncStatus.Idle; + } + + if (this.syncResult.error) { + throw new Error('failed'); + } + + await this.apply(remoteUserData.ref); + return this.syncResult.status || SyncStatus.Idle; + } + + async apply(ref: string): Promise { + ref = await this.userDataSyncStoreService.write(this.resourceKey, '', ref); + await this.updateLastSyncUserData({ ref, syncData: { content: '', version: this.version } }); + } + + stop(): void { + this.cancelled = true; + this.syncBarrier.open(); + } + +} + +suite('TestSynchronizer', () => { + + const disposableStore = new DisposableStore(); + const server = new UserDataSyncTestServer(); + let client: UserDataSyncClient; + let userDataSyncStoreService: IUserDataSyncStoreService; + + setup(async () => { + client = disposableStore.add(new UserDataSyncClient(server)); + await client.setUp(); + userDataSyncStoreService = client.instantiationService.get(IUserDataSyncStoreService); + disposableStore.add(toDisposable(() => userDataSyncStoreService.clear())); + }); + + teardown(() => disposableStore.clear()); + + test('status is syncing', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + + const actual: SyncStatus[] = []; + disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); + + const promise = Event.toPromise(testObject.onDoSyncCall.event); + + testObject.sync(); + await promise; + + assert.deepEqual(actual, [SyncStatus.Syncing]); + assert.deepEqual(testObject.status, SyncStatus.Syncing); + + testObject.stop(); + }); + + test('status is set correctly when sync is finished', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + testObject.syncBarrier.open(); + + const actual: SyncStatus[] = []; + disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); + await testObject.sync(); + + assert.deepEqual(actual, [SyncStatus.Syncing, SyncStatus.Idle]); + assert.deepEqual(testObject.status, SyncStatus.Idle); + }); + + test('status is set correctly when sync has conflicts', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + testObject.syncResult = { status: SyncStatus.HasConflicts }; + testObject.syncBarrier.open(); + + const actual: SyncStatus[] = []; + disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); + await testObject.sync(); + + assert.deepEqual(actual, [SyncStatus.Syncing, SyncStatus.HasConflicts]); + assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + }); + + test('status is set correctly when sync has errors', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + testObject.syncResult = { error: true }; + testObject.syncBarrier.open(); + + const actual: SyncStatus[] = []; + disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); + + try { + await testObject.sync(); + assert.fail('Should fail'); + } catch (e) { + assert.deepEqual(actual, [SyncStatus.Syncing, SyncStatus.Idle]); + assert.deepEqual(testObject.status, SyncStatus.Idle); + } + }); + + test('sync should not run if syncing already', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + const promise = Event.toPromise(testObject.onDoSyncCall.event); + + testObject.sync(); + await promise; + + const actual: SyncStatus[] = []; + disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); + await testObject.sync(); + + assert.deepEqual(actual, []); + assert.deepEqual(testObject.status, SyncStatus.Syncing); + + testObject.stop(); + }); + + test('sync should not run if disabled', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + client.instantiationService.get(IUserDataSyncEnablementService).setResourceEnablement(testObject.resourceKey, false); + + const actual: SyncStatus[] = []; + disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); + + await testObject.sync(); + + assert.deepEqual(actual, []); + assert.deepEqual(testObject.status, SyncStatus.Idle); + }); + + test('sync should not run if there are conflicts', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + testObject.syncResult = { status: SyncStatus.HasConflicts }; + testObject.syncBarrier.open(); + await testObject.sync(); + + const actual: SyncStatus[] = []; + disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status))); + await testObject.sync(); + + assert.deepEqual(actual, []); + assert.deepEqual(testObject.status, SyncStatus.HasConflicts); + }); + + test('request latest data on precondition failure', async () => { + const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncSource.Settings, 'settings'); + // Sync once + testObject.syncBarrier.open(); + await testObject.sync(); + testObject.syncBarrier = new Barrier(); + + // update remote data before syncing so that 412 is thrown by server + const disposable = testObject.onDoSyncCall.event(async () => { + disposable.dispose(); + await testObject.apply(ref); + server.reset(); + testObject.syncBarrier.open(); + }); + + // Start sycing + const { ref } = await userDataSyncStoreService.read(testObject.resourceKey, null); + await testObject.sync(ref); + + assert.deepEqual(server.requests, [ + { type: 'POST', url: `${server.url}/v1/resource/${testObject.resourceKey}`, headers: { 'If-Match': ref } }, + { type: 'GET', url: `${server.url}/v1/resource/${testObject.resourceKey}/latest`, headers: {} }, + { type: 'POST', url: `${server.url}/v1/resource/${testObject.resourceKey}`, headers: { 'If-Match': `${parseInt(ref) + 1}` } }, + ]); + }); + + +}); diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 9c60dd34ae..43005fc782 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -6,7 +6,7 @@ import { IRequestService } from 'vs/platform/request/common/request'; import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IUserData, ResourceKey, IUserDataManifest, ALL_RESOURCE_KEYS, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService, getDefaultIgnoredSettings } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, ResourceKey, IUserDataManifest, ALL_RESOURCE_KEYS, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService, getDefaultIgnoredSettings, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { bufferToStream, VSBuffer } from 'vs/base/common/buffer'; import { generateUuid } from 'vs/base/common/uuid'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; @@ -36,6 +36,7 @@ import { Emitter } from 'vs/base/common/event'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; +import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; export class UserDataSyncClient extends Disposable { @@ -61,7 +62,14 @@ export class UserDataSyncClient extends Disposable { const logService = new NullLogService(); this.instantiationService.stub(ILogService, logService); - this.instantiationService.stub(IProductService, { _serviceBrand: undefined, ...product }); + this.instantiationService.stub(IProductService, { + _serviceBrand: undefined, ...product, ...{ + 'configurationSync.store': { + url: this.testServer.url, + authenticationProviderId: 'test' + } + } + }); const fileService = this._register(new FileService(logService)); fileService.registerProvider(Schemas.inMemory, new InMemoryFileSystemProvider()); @@ -69,13 +77,6 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IStorageService, new InMemoryStorageService()); - await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ - 'configurationSync.store': { - url: this.testServer.url, - authenticationProviderId: 'test' - } - }))); - const configurationService = new ConfigurationService(environmentService.settingsResource, fileService); await configurationService.initialize(); this.instantiationService.stub(IConfigurationService, configurationService); @@ -89,6 +90,7 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IUserDataSyncLogService, logService); this.instantiationService.stub(ITelemetryService, NullTelemetryService); this.instantiationService.stub(IUserDataSyncStoreService, this.instantiationService.createInstance(UserDataSyncStoreService)); + this.instantiationService.stub(IUserDataSyncBackupStoreService, this.instantiationService.createInstance(UserDataSyncBackupStoreService)); this.instantiationService.stub(IUserDataSyncUtilService, new TestUserDataSyncUtilService()); this.instantiationService.stub(IUserDataSyncEnablementService, this.instantiationService.createInstance(UserDataSyncEnablementService)); @@ -106,15 +108,22 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(ISettingsSyncService, this.instantiationService.createInstance(SettingsSynchroniser)); this.instantiationService.stub(IUserDataSyncService, this.instantiationService.createInstance(UserDataSyncService)); - if (empty) { - await fileService.del(environmentService.settingsResource); - } else { + if (!empty) { + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({}))); await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([]))); await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'en' }))); } await configurationService.reloadConfiguration(); } + sync(): Promise { + return this.instantiationService.get(IUserDataSyncService).sync(); + } + + read(key: ResourceKey): Promise { + return this.instantiationService.get(IUserDataSyncStoreService).read(key, null); + } + } export class UserDataSyncTestServer implements IRequestService { diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index e6b3584c96..a436ea3866 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -3,11 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IProcessEnvironment, isMacintosh, isLinux, isWeb } from 'vs/base/common/platform'; +import { isMacintosh, isLinux, isWeb } from 'vs/base/common/platform'; import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { ExportData } from 'vs/base/common/performance'; -import { LogLevel } from 'vs/platform/log/common/log'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -220,41 +218,17 @@ export interface IAddFoldersRequest { } export interface IWindowConfiguration extends ParsedArgs { - machineId?: string; // NOTE: This is undefined in the web, the telemetry service directly resolves this. - windowId: number; // TODO: should we deprecate this in favor of sessionId? sessionId: string; - logLevel: LogLevel; - mainPid: number; - - appRoot: string; - execPath: string; - isInitialStartup?: boolean; - - userEnv: IProcessEnvironment; - nodeCachedDataDir?: string; - - backupPath?: string; backupWorkspaceResource?: URI; - workspace?: IWorkspaceIdentifier; - folderUri?: ISingleFolderWorkspaceIdentifier; - remoteAuthority?: string; connectionToken?: string; - zoomLevel?: number; - fullscreen?: boolean; - maximized?: boolean; highContrast?: boolean; - accessibilitySupport?: boolean; - partsSplashPath?: string; - - perfEntries: ExportData; filesToOpenOrCreate?: IPath[]; filesToDiff?: IPath[]; - filesToWait?: IPathsToWaitFor; } export interface IRunActionInWindowRequest { diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 027a321db0..7659022753 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -3,7 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OpenContext, IWindowConfiguration, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; +import { OpenContext, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; +import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -39,7 +40,7 @@ export interface ICodeWindow extends IDisposable { readonly id: number; readonly win: BrowserWindow; - readonly config: IWindowConfiguration | undefined; + readonly config: INativeWindowConfiguration | undefined; readonly openedFolderUri?: URI; readonly openedWorkspace?: IWorkspaceIdentifier; @@ -60,8 +61,8 @@ export interface ICodeWindow extends IDisposable { addTabbedWindow(window: ICodeWindow): void; - load(config: IWindowConfiguration, isReload?: boolean): void; - reload(configuration?: IWindowConfiguration, cli?: ParsedArgs): void; + load(config: INativeWindowConfiguration, isReload?: boolean): void; + reload(configuration?: INativeWindowConfiguration, cli?: ParsedArgs): void; focus(): void; close(): void; diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 401c2e17f7..7857e69918 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -18,8 +18,8 @@ import { parseLineAndColumnAware } from 'vs/code/node/paths'; import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, IPathsToWaitFor, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest } from 'vs/platform/windows/common/windows'; -import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/platform/windows/node/window'; +import { IWindowSettings, OpenContext, IPath, IPathsToWaitFor, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest } from 'vs/platform/windows/common/windows'; +import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri, INativeWindowConfiguration } from 'vs/platform/windows/node/window'; import { Emitter } from 'vs/base/common/event'; import product from 'vs/platform/product/common/product'; import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; @@ -1354,8 +1354,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow { - // Build IWindowConfiguration from config and options - const configuration: IWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI + // Build INativeWindowConfiguration from config and options + const configuration: INativeWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI configuration.appRoot = this.environmentService.appRoot; configuration.machineId = this.machineId; configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir; @@ -1482,7 +1482,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic return window; } - private doOpenInBrowserWindow(window: ICodeWindow, configuration: IWindowConfiguration, options: IOpenBrowserWindowOptions): void { + private doOpenInBrowserWindow(window: ICodeWindow, configuration: INativeWindowConfiguration, options: IOpenBrowserWindowOptions): void { // Register window for backups if (!configuration.extensionDevelopmentPath) { @@ -1500,7 +1500,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic window.load(configuration); } - private getNewWindowState(configuration: IWindowConfiguration): INewWindowState { + private getNewWindowState(configuration: INativeWindowConfiguration): INewWindowState { const lastActive = this.getLastActiveWindow(); // Restore state unless we are running extension tests diff --git a/src/vs/platform/windows/node/window.ts b/src/vs/platform/windows/node/window.ts index a0b4eb75e4..65ca4e3da3 100644 --- a/src/vs/platform/windows/node/window.ts +++ b/src/vs/platform/windows/node/window.ts @@ -3,12 +3,42 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OpenContext, IOpenWindowOptions } from 'vs/platform/windows/common/windows'; +import { OpenContext, IOpenWindowOptions, IWindowConfiguration, IPathsToWaitFor } from 'vs/platform/windows/common/windows'; import { URI } from 'vs/base/common/uri'; import * as platform from 'vs/base/common/platform'; import * as extpath from 'vs/base/common/extpath'; import { IWorkspaceIdentifier, IResolvedWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { isEqual, isEqualOrParent } from 'vs/base/common/resources'; +import { LogLevel } from 'vs/platform/log/common/log'; +import { ExportData } from 'vs/base/common/performance'; + +export interface INativeWindowConfiguration extends IWindowConfiguration { + mainPid: number; + + windowId: number; + machineId: string; + + appRoot: string; + execPath: string; + backupPath?: string; + + nodeCachedDataDir?: string; + partsSplashPath: string; + + workspace?: IWorkspaceIdentifier; + folderUri?: ISingleFolderWorkspaceIdentifier; + + isInitialStartup?: boolean; + logLevel: LogLevel; + zoomLevel?: number; + fullscreen?: boolean; + maximized?: boolean; + accessibilitySupport?: boolean; + perfEntries: ExportData; + + userEnv: platform.IProcessEnvironment; + filesToWait?: IPathsToWaitFor; +} export interface INativeOpenWindowOptions extends IOpenWindowOptions { diffMode?: boolean; diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index 49271d25b2..8d7e988a81 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -81,7 +81,7 @@ export interface IWorkspaceFoldersChangeEvent { } export namespace IWorkspace { - export function isIWorkspace(thing: any): thing is IWorkspace { + export function isIWorkspace(thing: unknown): thing is IWorkspace { return thing && typeof thing === 'object' && typeof (thing as IWorkspace).id === 'string' && Array.isArray((thing as IWorkspace).folders); @@ -115,7 +115,7 @@ export interface IWorkspaceFolderData { /** * The name of this workspace folder. Defaults to - * the basename its [uri-path](#Uri.path) + * the basename of its [uri-path](#Uri.path) */ readonly name: string; @@ -126,7 +126,7 @@ export interface IWorkspaceFolderData { } export namespace IWorkspaceFolder { - export function isIWorkspaceFolder(thing: any): thing is IWorkspaceFolder { + export function isIWorkspaceFolder(thing: unknown): thing is IWorkspaceFolder { return thing && typeof thing === 'object' && URI.isUri((thing as IWorkspaceFolder).uri) && typeof (thing as IWorkspaceFolder).name === 'string' diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index 3be7174e81..fc6134c36e 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -93,7 +93,7 @@ export function reviveWorkspaceIdentifier(workspace: { id: string, configPath: U return { id: workspace.id, configPath: URI.revive(workspace.configPath) }; } -export function isStoredWorkspaceFolder(thing: any): thing is IStoredWorkspaceFolder { +export function isStoredWorkspaceFolder(thing: unknown): thing is IStoredWorkspaceFolder { return isRawFileWorkspaceFolder(thing) || isRawUriWorkspaceFolder(thing); } @@ -148,11 +148,11 @@ export interface IEnterWorkspaceResult { backupPath?: string; } -export function isSingleFolderWorkspaceIdentifier(obj: any): obj is ISingleFolderWorkspaceIdentifier { +export function isSingleFolderWorkspaceIdentifier(obj: unknown): obj is ISingleFolderWorkspaceIdentifier { return obj instanceof URI; } -export function isWorkspaceIdentifier(obj: any): obj is IWorkspaceIdentifier { +export function isWorkspaceIdentifier(obj: unknown): obj is IWorkspaceIdentifier { const workspaceIdentifier = obj as IWorkspaceIdentifier; return workspaceIdentifier && typeof workspaceIdentifier.id === 'string' && workspaceIdentifier.configPath instanceof URI; diff --git a/src/vs/platform/workspaces/electron-main/workspacesService.ts b/src/vs/platform/workspaces/electron-main/workspacesService.ts index d9cac37445..5936b99651 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesService.ts @@ -10,7 +10,7 @@ import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/wor import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; -export class WorkspacesService implements AddFirstParameterToFunctions /* only methods, not events */, number /* window ID */> { +export class WorkspacesService implements AddFirstParameterToFunctions /* only methods, not events */, number /* window ID */> { _serviceBrand: undefined; diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 3ae0fc1e87..dda06cc172 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -4975,10 +4975,11 @@ declare module 'vscode' { color: string | ThemeColor | undefined; /** - * The identifier of a command to run on click. The command must be - * [known](#commands.getCommands). + * [`Command`](#Command) or identifier of a command to run on click. + * + * The command must be [known](#commands.getCommands). */ - command: string | undefined; + command: string | Command | undefined; /** * Shows the entry in the status bar. @@ -6324,7 +6325,7 @@ declare module 'vscode' { } /** - * A webview displays html content, like an iframe. + * Displays html content, similarly to an iframe. */ export interface Webview { /** @@ -6333,9 +6334,29 @@ declare module 'vscode' { options: WebviewOptions; /** - * Contents of the webview. + * HTML contents of the webview. * - * Should be a complete html document. + * This should be a complete, valid html document. Changing this property causes the webview to be reloaded. + * + * Webviews are sandboxed from normal extension process, so all communication with the webview must use + * message passing. To send a message from the extension to the webview, use [`postMessage`](#Webview.postMessage). + * To send message from the webview back to an extension, use the `acquireVsCodeApi` function inside the webview + * to get a handle to VS Code's api and then call `.postMessage()`: + * + * ```html + * + * ``` + * + * To load a resources from the workspace inside a webview, use the `[asWebviewUri](#Webview.asWebviewUri)` method + * and ensure the resource's directory is listed in [`WebviewOptions.localResourceRoots`](#WebviewOptions.localResourceRoots). + * + * Keep in mind that even though webviews are sandboxed, they still allow running scripts and loading arbitrary content, + * so extensions must follow all standard web security best practices when working with webviews. This includes + * properly sanitizing all untrusted input (including content from the workspace) and + * setting a [content security policy](https://aka.ms/vscode-api-webview-csp). */ html: string; @@ -6433,7 +6454,7 @@ declare module 'vscode' { iconPath?: Uri | { light: Uri; dark: Uri }; /** - * Webview belonging to the panel. + * [`Webview`](#Webview) belonging to the panel. */ readonly webview: Webview; diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 0b2cdb7fe5..aa6ff024e5 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -35,7 +35,7 @@ declare module 'vscode' { readonly added: string[]; /** - * The ids of the [authenticationProvider](#AuthenticationProvider)s that have been removed.. + * The ids of the [authenticationProvider](#AuthenticationProvider)s that have been removed. */ readonly removed: string[]; } @@ -43,14 +43,14 @@ declare module 'vscode' { export interface AuthenticationProvider { /** * Used as an identifier for extensions trying to work with a particular - * provider: 'Microsoft', 'GitHub', etc. id must be unique, registering + * provider: 'microsoft', 'github', etc. id must be unique, registering * another provider with the same id will fail. */ readonly id: string; readonly displayName: string; /** - * A [enent](#Event) which fires when the array of sessions has changed, or data + * An [event](#Event) which fires when the array of sessions has changed, or data * within a session has changed. */ readonly onDidChangeSessions: Event; @@ -75,7 +75,31 @@ declare module 'vscode' { */ export const onDidChangeAuthenticationProviders: Event; - export const providers: ReadonlyArray; + /** + * An array of the ids of authentication providers that are currently registered. + */ + export const providerIds: string[]; + + /** + * Get existing authentication sessions. Rejects if a provider with providerId is not + * registered, or if the user does not consent to sharing authentication information with + * the extension. + */ + export function getSessions(providerId: string, scopes: string[]): Thenable; + + /** + * Prompt a user to login to create a new authenticaiton session. Rejects if a provider with + * providerId is not registered, or if the user does not consent to sharing authentication + * information with the extension. + */ + export function login(providerId: string, scopes: string[]): Thenable; + + /** + * An [event](#Event) which fires when the array of sessions has changed, or data + * within a session has changed for a provider. Fires with the ids of the providers + * that have had session data change. + */ + export const onDidChangeSessions: Event; } //#endregion @@ -107,7 +131,7 @@ declare module 'vscode' { export interface TunnelDescription { remoteAddress: { port: number, host: string }; //The complete local address(ex. localhost:1234) - localAddress: string; + localAddress: { port: number, host: string } | string; } export interface Tunnel extends TunnelDescription { @@ -168,13 +192,6 @@ declare module 'vscode' { */ export let tunnels: Thenable; - /** - * Fired when the list of tunnels has changed. - * @deprecated use onDidChangeTunnels instead - */ - // TODO@alexr - // eslint-disable-next-line vscode-dts-event-naming - export const onDidTunnelsChange: Event; /** * Fired when the list of tunnels has changed. */ @@ -257,6 +274,11 @@ declare module 'vscode' { * semantic tokens. */ export interface DocumentSemanticTokensProvider { + /** + * An optional event to signal that the semantic tokens from this provider have changed. + */ + onDidChangeSemanticTokens?: Event; + /** * A file can contain many tokens, perhaps even hundreds of thousands of tokens. Therefore, to improve * the memory consumption around describing semantic tokens, we have decided to avoid allocating an object @@ -314,6 +336,9 @@ declare module 'vscode' { * // 1st token, 2nd token, 3rd token * [ 2,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] * ``` + * + * *NOTE*: When doing edits, it is possible that multiple edits occur until VS Code decides to invoke the semantic tokens provider. + * *NOTE*: If the provider cannot temporarily compute semantic tokens, it can indicate this by throwing an error with the message 'Busy'. */ provideDocumentSemanticTokens(document: TextDocument, token: CancellationToken): ProviderResult; @@ -366,7 +391,6 @@ declare module 'vscode' { * edit: { start: 10, deleteCount: 1, data: [1,3,5,0,2,2] } // replace integer at offset 10 with [1,3,5,0,2,2] * ``` * - * *NOTE*: When doing edits, it is possible that multiple edits occur until VS Code decides to invoke the semantic tokens provider. * *NOTE*: If the provider cannot compute `SemanticTokensEdits`, it can "give up" and return all the tokens in the document again. * *NOTE*: All edits in `SemanticTokensEdits` contain indices in the old integers array, so they all refer to the previous result state. */ @@ -749,10 +773,15 @@ declare module 'vscode' { /** * A [glob pattern](#GlobPattern) that defines files and folders to exclude. The glob pattern - * will be matched against the file paths of resulting matches relative to their workspace. When `undefined` only default excludes will - * apply, when `null` no excludes will apply. + * will be matched against the file paths of resulting matches relative to their workspace. When `undefined`, default excludes will + * apply. */ - exclude?: GlobPattern | null; + exclude?: GlobPattern; + + /** + * Whether to use the default and user-configured excludes. Defaults to true. + */ + useDefaultExcludes?: boolean; /** * The maximum number of results to search for @@ -1215,9 +1244,11 @@ declare module 'vscode' { /** * Save the resource. * + * @param cancellation Token that signals the save is no longer required (for example, if another save was triggered). + * * @return Thenable signaling that the save has completed. */ - save(): Thenable; + save(cancellation: CancellationToken): Thenable; /** * Save the existing resource at a new path. @@ -1247,7 +1278,7 @@ declare module 'vscode' { /** * Undo a set of edits. * - * This is triggered when a user undoes an edit or when revert is called on a file. + * This is triggered when a user undoes an edit. * * @param edit Array of edits. Sorted from most recent to oldest. * @@ -1255,6 +1286,18 @@ declare module 'vscode' { */ undoEdits(edits: readonly EditType[]): Thenable; + /** + * Revert the file to its last saved state. + * + * @param change Added or applied edits. + * + * @return Thenable signaling that the change has completed. + */ + revert(change: { + readonly undoneEdits: readonly EditType[]; + readonly appliedEdits: readonly EditType[]; + }): Thenable; + /** * Back up the resource in its current state. * @@ -1277,10 +1320,10 @@ declare module 'vscode' { } /** - * Represents a custom document for a custom webview editor. + * Represents a custom document used by a `CustomEditorProvider`. * * Custom documents are only used within a given `CustomEditorProvider`. The lifecycle of a - * `CustomDocument` is managed by VS Code. When more more references remain to a given `CustomDocument` + * `CustomDocument` is managed by VS Code. When no more references remain to a given `CustomDocument`, * then it is disposed of. * * @param UserDataType Type of custom object that extensions can store on the document. @@ -1323,6 +1366,11 @@ declare module 'vscode' { /** * Resolve the model for a given resource. * + * `resolveCustomDocument` is called when the first editor for a given resource is opened, and the resolve document + * is passed to `resolveCustomEditor`. The resolved `CustomDocument` is re-used for subsequent editor opens. + * If all editors for a given resource are closed, the `CustomDocument` is disposed of. Opening an editor at + * this point will trigger another call to `resolveCustomDocument`. + * * @param document Document to resolve. * * @return The capabilities of the resolved document. @@ -1332,11 +1380,15 @@ declare module 'vscode' { /** * Resolve a webview editor for a given resource. * + * This is called when a user first opens a resource for a `CustomTextEditorProvider`, or if they reopen an + * existing editor using this `CustomTextEditorProvider`. + * * To resolve a webview editor, the provider must fill in its initial html content and hook up all - * the event listeners it is interested it. The provider should also take ownership of the passed in `WebviewPanel`. + * the event listeners it is interested it. The provider can also hold onto the `WebviewPanel` to use later, + * for example in a command. See [`WebviewPanel`](#WebviewPanel) for additional details * * @param document Document for the resource being resolved. - * @param webviewPanel Webview to resolve. The provider should take ownership of this webview. + * @param webviewPanel Webview to resolve. * * @return Thenable indicating that the webview editor has been resolved. */ @@ -1355,13 +1407,17 @@ declare module 'vscode' { */ export interface CustomTextEditorProvider { /** - * Resolve a webview editor for a given resource. + * Resolve a webview editor for a given text resource. + * + * This is called when a user first opens a resource for a `CustomTextEditorProvider`, or if they reopen an + * existing editor using this `CustomTextEditorProvider`. * * To resolve a webview editor, the provider must fill in its initial html content and hook up all - * the event listeners it is interested it. The provider should also take ownership of the passed in `WebviewPanel`. + * the event listeners it is interested it. The provider can also hold onto the `WebviewPanel` to use later, + * for example in a command. See [`WebviewPanel`](#WebviewPanel) for additional details. * - * @param document Resource being resolved. - * @param webviewPanel Webview to resolve. The provider should take ownership of this webview. + * @param document Document for the resource to resolve. + * @param webviewPanel Webview to resolve. * * @return Thenable indicating that the webview editor has been resolved. */ @@ -1600,7 +1656,7 @@ declare module 'vscode' { /** * The maximum number or the ending cursor of timeline items that should be returned. */ - limit?: number | string; + limit?: number | { cursor: string }; } export interface TimelineProvider { @@ -1660,6 +1716,11 @@ declare module 'vscode' { * @return The uri of the resource. */ asExtensionUri(relativePath: string): Uri; + + /** + * + */ + readonly extensionUri: Uri; } export interface Extension { @@ -1670,6 +1731,11 @@ declare module 'vscode' { * @return The uri of the resource. */ asExtensionUri(relativePath: string): Uri; + + /** + * + */ + readonly extensionUri: Uri; } //#endregion @@ -1728,4 +1794,34 @@ declare module 'vscode' { } //#endregion + + //#region https://github.com/microsoft/vscode/issues/90517 + + export interface FileSystemError { + /** + * A code that identifies this error. + * + * Possible values are names of errors, like [`FileNotFound`](#FileSystemError.FileNotFound), + * or `undefined` for an unspecified error. + */ + readonly code?: string; + } + + //#endregion + + + //#region https://github.com/microsoft/vscode/issues/90208 + + export namespace Uri { + + /** + * + * @param base + * @param pathFragments + * @returns A new uri + */ + export function joinPaths(base: Uri, ...pathFragments: string[]): Uri; + } + + //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index c3cad12a5d..cbd470a170 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -89,7 +89,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { const disposables = new DisposableStore(); - const webview = this._webviewService.createWebview('' + handle, { + const webview = this._webviewService.createWebviewElement('' + handle, { enableFindWidget: false, }, { allowScripts: options.enableScripts, diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index f9bbd9a0b4..ca21cfdb98 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -75,7 +75,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb } runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise { - return Promise.resolve(this._proxy.$runInTerminal(args)); + return this._proxy.$runInTerminal(args); } // RPC methods (MainThreadDebugServiceShape) diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index 66603f5ad7..1e5cc5b82c 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -5,7 +5,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { values } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor, isCodeEditor, isDiffEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; @@ -22,7 +21,7 @@ import { MainThreadTextEditors } from 'vs/workbench/api/browser/mainThreadEditor import { ExtHostContext, ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, IExtHostContext, IModelAddedData, ITextEditorAddData, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { EditorViewColumn, editorGroupToViewColumn } from 'vs/workbench/api/common/shared/editor'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; -import { IEditor as IWorkbenchEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -110,8 +109,8 @@ class DocumentAndEditorState { static compute(before: DocumentAndEditorState | undefined, after: DocumentAndEditorState): DocumentAndEditorStateDelta { if (!before) { return new DocumentAndEditorStateDelta( - [], values(after.documents), - [], values(after.textEditors), + [], [...after.documents.values()], + [], [...after.textEditors.values()], undefined, after.activeEditor ); } @@ -291,11 +290,11 @@ class MainThreadDocumentAndEditorStateComputer { } private _getActiveEditorFromEditorPart(): IEditor | undefined { - let result = this._editorService.activeTextEditorWidget; - if (isDiffEditor(result)) { - result = result.getModifiedEditor(); + let activeTextEditorControl = this._editorService.activeTextEditorControl; + if (isDiffEditor(activeTextEditorControl)) { + activeTextEditorControl = activeTextEditorControl.getModifiedEditor(); } - return result; + return activeTextEditorControl; } } @@ -436,17 +435,17 @@ export class MainThreadDocumentsAndEditors { } private _findEditorPosition(editor: MainThreadTextEditor): EditorViewColumn | undefined { - for (const workbenchEditor of this._editorService.visibleControls) { - if (editor.matches(workbenchEditor)) { - return editorGroupToViewColumn(this._editorGroupService, workbenchEditor.group); + for (const editorPane of this._editorService.visibleEditorPanes) { + if (editor.matches(editorPane)) { + return editorGroupToViewColumn(this._editorGroupService, editorPane.group); } } return undefined; } - findTextEditorIdFor(inputEditor: IWorkbenchEditor): string | undefined { + findTextEditorIdFor(editorPane: IEditorPane): string | undefined { for (const [id, editor] of this._textEditors) { - if (editor.matches(inputEditor)) { + if (editor.matches(editorPane)) { return id; } } diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index 657a31291d..c0b29bc055 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -14,7 +14,7 @@ import { ISingleEditOperation, ITextModel, ITextModelUpdateOptions, IIdentifiedS import { IModelService } from 'vs/editor/common/services/modelService'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, IUndoStopOptions, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol'; -import { IEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; import { withNullAsUndefined } from 'vs/base/common/types'; import { equals } from 'vs/base/common/arrays'; @@ -413,7 +413,7 @@ export class MainThreadTextEditor { return false; } - public matches(editor: IEditor): boolean { + public matches(editor: IEditorPane): boolean { if (!editor) { return false; } diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index 1ca0fbf4d9..551c6995b4 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -15,7 +15,7 @@ import { ISelection } from 'vs/editor/common/core/selection'; import { IDecorationOptions, IDecorationRenderOptions, ILineChange } from 'vs/editor/common/editorCommon'; import { ISingleEditOperation } from 'vs/editor/common/model'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IEditorOptions, ITextEditorOptions, IResourceInput, EditorActivation } from 'vs/platform/editor/common/editor'; +import { IEditorOptions, ITextEditorOptions, IResourceEditorInput, EditorActivation } from 'vs/platform/editor/common/editor'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors'; @@ -101,10 +101,10 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { private _getTextEditorPositionData(): ITextEditorPositionData { const result: ITextEditorPositionData = Object.create(null); - for (let workbenchEditor of this._editorService.visibleControls) { - const id = this._documentsAndEditors.findTextEditorIdFor(workbenchEditor); + for (let editorPane of this._editorService.visibleEditorPanes) { + const id = this._documentsAndEditors.findTextEditorIdFor(editorPane); if (id) { - result[id] = editorGroupToViewColumn(this._editorGroupService, workbenchEditor.group); + result[id] = editorGroupToViewColumn(this._editorGroupService, editorPane.group); } } return result; @@ -124,7 +124,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { activation: options.preserveFocus ? EditorActivation.RESTORE : undefined }; - const input: IResourceInput = { + const input: IResourceEditorInput = { resource: uri, options: editorOptions }; @@ -151,10 +151,10 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { async $tryHideEditor(id: string): Promise { const mainThreadEditor = this._documentsAndEditors.getEditor(id); if (mainThreadEditor) { - const editors = this._editorService.visibleControls; - for (let editor of editors) { - if (mainThreadEditor.matches(editor)) { - return editor.group.closeEditor(editor.input); + const editorPanes = this._editorService.visibleEditorPanes; + for (let editorPane of editorPanes) { + if (mainThreadEditor.matches(editorPane)) { + return editorPane.group.closeEditor(editorPane.input); } } } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 22898f0f97..589e716e55 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable } from 'vs/base/common/lifecycle'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { ITextModel, ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import * as search from 'vs/workbench/contrib/search/common/search'; @@ -367,8 +367,21 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- semantic tokens - $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void { - this._registrations.set(handle, modes.DocumentSemanticTokensProviderRegistry.register(selector, new MainThreadDocumentSemanticTokensProvider(this._proxy, handle, legend))); + $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend, eventHandle: number | undefined): void { + let event: Event | undefined = undefined; + if (typeof eventHandle === 'number') { + const emitter = new Emitter(); + this._registrations.set(eventHandle, emitter); + event = emitter.event; + } + this._registrations.set(handle, modes.DocumentSemanticTokensProviderRegistry.register(selector, new MainThreadDocumentSemanticTokensProvider(this._proxy, handle, legend, event))); + } + + $emitDocumentSemanticTokensEvent(eventHandle: number): void { + const obj = this._registrations.get(eventHandle); + if (obj instanceof Emitter) { + obj.fire(undefined); + } } $registerDocumentRangeSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void { @@ -662,6 +675,7 @@ export class MainThreadDocumentSemanticTokensProvider implements modes.DocumentS private readonly _proxy: ExtHostLanguageFeaturesShape, private readonly _handle: number, private readonly _legend: modes.SemanticTokensLegend, + public readonly onDidChange: Event | undefined, ) { } diff --git a/src/vs/workbench/api/browser/mainThreadStatusBar.ts b/src/vs/workbench/api/browser/mainThreadStatusBar.ts index 9a5a5cb919..27be742368 100644 --- a/src/vs/workbench/api/browser/mainThreadStatusBar.ts +++ b/src/vs/workbench/api/browser/mainThreadStatusBar.ts @@ -8,6 +8,7 @@ import { MainThreadStatusBarShape, MainContext, IExtHostContext } from '../commo import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { dispose } from 'vs/base/common/lifecycle'; +import { Command } from 'vs/editor/common/modes'; @extHostNamedCustomer(MainContext.MainThreadStatusBar) export class MainThreadStatusBar implements MainThreadStatusBarShape { @@ -24,7 +25,7 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape { this.entries.clear(); } - $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: string | undefined, color: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined): void { + $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined): void { const entry: IStatusbarEntry = { text, tooltip, command, color }; if (typeof priority === 'undefined') { diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 00fbba0cb6..00323c4281 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -33,6 +33,7 @@ import { RunOptionsDTO } from 'vs/workbench/api/common/shared/tasks'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; namespace TaskExecutionDTO { export function from(value: TaskExecution): TaskExecutionDTO { @@ -604,7 +605,7 @@ export class MainThreadTask implements MainThreadTaskShape { return URI.parse(`${info.scheme}://${info.authority}${path}`); }, context: this._extHostContext, - resolveVariables: (workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet): Promise => { + resolveVariables: (workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet, target: ConfigurationTarget): Promise => { const vars: string[] = []; toResolve.variables.forEach(item => vars.push(item)); return Promise.resolve(this._proxy.$resolveVariables(workspaceFolder.uri, { process: toResolve.process, variables: vars })).then(values => { @@ -613,7 +614,7 @@ export class MainThreadTask implements MainThreadTaskShape { partiallyResolvedVars.push(entry.value); }); return new Promise((resolve, reject) => { - this._configurationResolverService.resolveWithInteraction(workspaceFolder, partiallyResolvedVars, 'tasks').then(resolvedVars => { + this._configurationResolverService.resolveWithInteraction(workspaceFolder, partiallyResolvedVars, 'tasks', undefined, target).then(resolvedVars => { const result: ResolvedVariables = { process: undefined, variables: new Map() diff --git a/src/vs/workbench/api/browser/mainThreadTheming.ts b/src/vs/workbench/api/browser/mainThreadTheming.ts index f8d4daf4db..5d193cd241 100644 --- a/src/vs/workbench/api/browser/mainThreadTheming.ts +++ b/src/vs/workbench/api/browser/mainThreadTheming.ts @@ -22,8 +22,8 @@ export class MainThreadTheming implements MainThreadThemingShape { this._themeService = themeService; this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTheming); - this._themeChangeListener = this._themeService.onThemeChange(e => { - this._proxy.$onColorThemeChange(this._themeService.getTheme().type); + this._themeChangeListener = this._themeService.onDidColorThemeChange(e => { + this._proxy.$onColorThemeChange(this._themeService.getColorTheme().type); }); } diff --git a/src/vs/workbench/api/browser/mainThreadTimeline.ts b/src/vs/workbench/api/browser/mainThreadTimeline.ts index 4a5fc32161..269c84feff 100644 --- a/src/vs/workbench/api/browser/mainThreadTimeline.ts +++ b/src/vs/workbench/api/browser/mainThreadTimeline.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { MainContext, MainThreadTimelineShape, IExtHostContext, ExtHostTimelineShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline'; +import { TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, ITimelineService, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; @extHostNamedCustomer(MainContext.MainThreadTimeline) export class MainThreadTimeline implements MainThreadTimelineShape { @@ -39,7 +39,7 @@ export class MainThreadTimeline implements MainThreadTimelineShape { this._timelineService.registerTimelineProvider({ ...provider, onDidChange: onDidChange.event, - provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }) { + provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: InternalTimelineOptions) { return proxy.$getTimeline(provider.id, uri, options, token, internalOptions); }, dispose() { diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index 95b5681849..343a854df5 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -6,7 +6,7 @@ import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IRemoteExplorerService, MakeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { ITunnelProvider, ITunnelService, TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver'; @@ -51,6 +51,10 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun this.remoteExplorerService.registerCandidateFinder(() => this._proxy.$findCandidatePorts()); } + async $tunnelServiceReady(): Promise { + return this.remoteExplorerService.restore(); + } + async $setTunnelProvider(): Promise { const tunnelProvider: ITunnelProvider = { forwardPort: (tunnelOptions: TunnelOptions) => { @@ -60,9 +64,12 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun return { tunnelRemotePort: tunnel.remoteAddress.port, tunnelRemoteHost: tunnel.remoteAddress.host, - localAddress: tunnel.localAddress, - dispose: () => { - this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }); + localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : MakeAddress(tunnel.localAddress.host, tunnel.localAddress.port), + tunnelLocalPort: typeof tunnel.localAddress !== 'string' ? tunnel.localAddress.port : undefined, + dispose: (silent: boolean) => { + if (!silent) { + this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }); + } } }; }); diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index cade69a1ac..0f1adcf76e 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -3,32 +3,38 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createCancelablePromise } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, IDisposable, IReference, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; +import { basename } from 'vs/base/common/path'; import { isWeb } from 'vs/base/common/platform'; -import { startsWith } from 'vs/base/common/strings'; +import { escape } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILabelService } from 'vs/platform/label/common/label'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; -import { IEditorInput } from 'vs/workbench/common/editor'; +import { IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { CustomEditorInput, ModelType } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; +import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; +import { CustomTextEditorModel } from 'vs/workbench/contrib/customEditor/common/customTextEditorModel'; +import { WebviewExtensionDescription, WebviewIcons } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { extHostNamedCustomer } from '../common/extHostCustomers'; /** @@ -74,12 +80,17 @@ class WebviewViewTypeTransformer { } public toExternal(viewType: string): string | undefined { - return startsWith(viewType, this.prefix) + return viewType.startsWith(this.prefix) ? viewType.substr(this.prefix.length) : undefined; } } +const enum ModelType { + Custom, + Text, +} + const webviewPanelViewType = new WebviewViewTypeTransformer('mainThreadWebview-'); @extHostNamedCustomer(extHostProtocol.MainContext.MainThreadWebviews) @@ -97,7 +108,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma private readonly _webviewInputs = new WebviewInputStore(); private readonly _revivers = new Map(); private readonly _editorProviders = new Map(); - private readonly _customEditorModels = new Map(); + private readonly _webviewFromDiffEditorHandles = new Set(); constructor( context: extHostProtocol.IExtHostContext, @@ -109,13 +120,24 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma @IProductService private readonly _productService: IProductService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, - @IFileService private readonly _fileService: IFileService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(); this._proxy = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviews); - this._register(_editorService.onDidActiveEditorChange(this.updateWebviewViewStates, this)); - this._register(_editorService.onDidVisibleEditorsChange(this.updateWebviewViewStates, this)); + + this._register(_editorService.onDidActiveEditorChange(() => { + const activeInput = this._editorService.activeEditor; + if (activeInput instanceof DiffEditorInput && activeInput.master instanceof WebviewInput && activeInput.details instanceof WebviewInput) { + this.registerWebviewFromDiffEditorListeners(activeInput); + } + + this.updateWebviewViewStates(activeInput); + })); + + this._register(_editorService.onDidVisibleEditorsChange(() => { + this.updateWebviewViewStates(this._editorService.activeEditor); + })); // This reviver's only job is to activate webview panel extensions // This should trigger the real reviver to be registered from the extension host side. @@ -264,7 +286,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma return this.registerEditorProvider(ModelType.Custom, extensionData, viewType, options); } - public registerEditorProvider( + private registerEditorProvider( modelType: ModelType, extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, @@ -287,16 +309,13 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma webviewInput.webview.options = options; webviewInput.webview.extension = extension; - webviewInput.modelType = modelType; const resource = webviewInput.resource; - if (modelType === ModelType.Custom) { - const model = await this.retainCustomEditorModel(webviewInput, resource, viewType); - webviewInput.onDisposeWebview(() => { - this.releaseCustomEditorModel(model); - }); - } + const modelRef = await this.getOrCreateCustomEditorModel(modelType, webviewInput, resource, viewType); + webviewInput.webview.onDispose(() => { + modelRef.dispose(); + }); try { await this._proxy.$resolveWebviewEditor( @@ -328,71 +347,27 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._customEditorService.models.disposeAllModelsForView(viewType); } - private async retainCustomEditorModel(webviewInput: WebviewInput, resource: URI, viewType: string) { - const model = await this._customEditorService.models.resolve(webviewInput.resource, webviewInput.viewType); - - const key = viewType + resource.toString(); - const existingEntry = this._customEditorModels.get(key); - if (existingEntry) { - ++existingEntry.referenceCount; - // no need to hook up listeners again - return model; - } - this._customEditorModels.set(key, { referenceCount: 1 }); - const { editable } = await this._proxy.$createWebviewCustomEditorDocument(resource, viewType); - - if (editable) { - model.onUndo(() => { - this._proxy.$undo(resource, viewType); - }); - - model.onRedo(() => { - this._proxy.$redo(resource, viewType); - }); - - model.onWillSave(e => { - e.waitUntil(this._proxy.$onSave(resource.toJSON(), viewType)); - }); + private async getOrCreateCustomEditorModel( + modelType: ModelType, + webviewInput: WebviewInput, + resource: URI, + viewType: string, + ): Promise> { + const existingModel = this._customEditorService.models.tryRetain(webviewInput.resource, webviewInput.viewType); + if (existingModel) { + return existingModel; } - // Save as should always be implemented even if the model is readonly - model.onWillSaveAs(e => { - if (editable) { - e.waitUntil(this._proxy.$onSaveAs(e.resource.toJSON(), viewType, e.targetResource.toJSON())); - } else { - // Since the editor is readonly, just copy the file over - e.waitUntil(this._fileService.copy(e.resource, e.targetResource, false /* overwrite */)); - } - }); + const model = modelType === ModelType.Text + ? CustomTextEditorModel.create(this._instantiationService, viewType, resource) + : MainThreadCustomEditorModel.create(this._instantiationService, this._proxy, viewType, resource); - model.onBackup(() => { - return createCancelablePromise(token => - this._proxy.$backup(model.resource.toJSON(), viewType, token)); - }); - - return model; + return this._customEditorService.models.add(resource, viewType, model); } - private async releaseCustomEditorModel(model: ICustomEditorModel) { - const key = model.viewType + model.resource; - const entry = this._customEditorModels.get(key); - if (!entry) { - throw new Error('Model not found'); - } - - --entry.referenceCount; - if (entry.referenceCount <= 0) { - this._proxy.$disposeWebviewCustomEditorDocument(model.resource, model.viewType); - this._customEditorService.models.disposeModel(model); - this._customEditorModels.delete(key); - } - } - - - - public $onDidChangeCustomDocumentState(resource: UriComponents, viewType: string, state: { dirty: boolean }) { - const model = this._customEditorService.models.get(URI.revive(resource), viewType); - if (!model) { + public async $onDidChangeCustomDocumentState(resource: UriComponents, viewType: string, state: { dirty: boolean }) { + const model = await this._customEditorService.models.get(URI.revive(resource), viewType); + if (!model || !(model instanceof MainThreadCustomEditorModel)) { throw new Error('Could not find model for webview editor'); } model.setDirty(state.dirty); @@ -408,19 +383,39 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma input.onDispose(() => { disposables.dispose(); }); - input.onDisposeWebview(() => { + input.webview.onDispose(() => { this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => { this._webviewInputs.delete(handle); }); }); } - private updateWebviewViewStates() { + private registerWebviewFromDiffEditorListeners(diffEditorInput: DiffEditorInput): void { + const master = diffEditorInput.master as WebviewInput; + const details = diffEditorInput.details as WebviewInput; + + if (this._webviewFromDiffEditorHandles.has(master.id) || this._webviewFromDiffEditorHandles.has(details.id)) { + return; + } + + this._webviewFromDiffEditorHandles.add(master.id); + this._webviewFromDiffEditorHandles.add(details.id); + + const disposables = new DisposableStore(); + disposables.add(master.webview.onDidFocus(() => this.updateWebviewViewStates(master))); + disposables.add(details.webview.onDidFocus(() => this.updateWebviewViewStates(details))); + disposables.add(diffEditorInput.onDispose(() => { + this._webviewFromDiffEditorHandles.delete(master.id); + this._webviewFromDiffEditorHandles.delete(details.id); + dispose(disposables); + })); + } + + private updateWebviewViewStates(activeEditorInput: IEditorInput | undefined) { if (!this._webviewInputs.size) { return; } - const activeInput = this._editorService.activeControl && this._editorService.activeControl.input; const viewStates: extHostProtocol.WebviewPanelViewStateData = {}; const updateViewStatesForInput = (group: IEditorGroup, topLevelInput: IEditorInput, editorInput: IEditorInput) => { @@ -434,7 +429,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma if (handle) { viewStates[handle] = { visible: topLevelInput === group.activeEditor, - active: topLevelInput === activeInput, + active: editorInput === activeEditorInput, position: editorGroupToViewColumn(this._editorGroupService, group.id), }; } @@ -476,7 +471,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma private getWebviewInput(handle: extHostProtocol.WebviewPanelHandle): WebviewInput { const webview = this.tryGetWebviewInput(handle); if (!webview) { - throw new Error('Unknown webview handle:' + handle); + throw new Error(`Unknown webview handle:${handle}`); } return webview; } @@ -492,7 +487,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma - ${localize('errorMessage', "An error occurred while restoring view:{0}", viewType)} + ${localize('errorMessage', "An error occurred while restoring view:{0}", escape(viewType))} `; } } @@ -511,13 +506,179 @@ function reviveWebviewOptions(options: modes.IWebviewOptions): WebviewInputOptio function reviveWebviewIcon( value: { light: UriComponents, dark: UriComponents; } | undefined -): { light: URI, dark: URI; } | undefined { - if (!value) { - return undefined; +): WebviewIcons | undefined { + return value + ? { light: URI.revive(value.light), dark: URI.revive(value.dark) } + : undefined; +} + +namespace HotExitState { + export const enum Type { + Allowed, + NotAllowed, + Pending, } - return { - light: URI.revive(value.light), - dark: URI.revive(value.dark) - }; + export const Allowed = Object.freeze({ type: Type.Allowed } as const); + export const NotAllowed = Object.freeze({ type: Type.NotAllowed } as const); + + export class Pending { + readonly type = Type.Pending; + + constructor( + public readonly operation: CancelablePromise, + ) { } + } + + export type State = typeof Allowed | typeof NotAllowed | Pending; +} + +class MainThreadCustomEditorModel extends Disposable implements ICustomEditorModel, IWorkingCopy { + + private _hotExitState: HotExitState.State = HotExitState.Allowed; + private _dirty = false; + + public static async create( + instantiationService: IInstantiationService, + proxy: extHostProtocol.ExtHostWebviewsShape, + viewType: string, + resource: URI + ) { + const { editable } = await proxy.$createWebviewCustomEditorDocument(resource, viewType); + return instantiationService.createInstance(MainThreadCustomEditorModel, proxy, viewType, resource, editable); + } + + constructor( + private readonly _proxy: extHostProtocol.ExtHostWebviewsShape, + private readonly _viewType: string, + private readonly _resource: URI, + private readonly _editable: boolean, + @IWorkingCopyService workingCopyService: IWorkingCopyService, + @ILabelService private readonly _labelService: ILabelService, + @IFileService private readonly _fileService: IFileService, + ) { + super(); + this._register(workingCopyService.registerWorkingCopy(this)); + } + + dispose() { + this._proxy.$disposeWebviewCustomEditorDocument(this.resource, this._viewType); + super.dispose(); + } + + //#region IWorkingCopy + + public get resource() { return this._resource; } + + public get name() { + return basename(this._labelService.getUriLabel(this._resource)); + } + + public get capabilities(): WorkingCopyCapabilities { + return 0; + } + + public isDirty(): boolean { + return this._dirty; + } + + private readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); + readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; + + private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); + readonly onDidChangeContent: Event = this._onDidChangeContent.event; + + //#endregion + + public get viewType() { + return this._viewType; + } + + public setDirty(dirty: boolean): void { + this._onDidChangeContent.fire(); + + if (this._dirty !== dirty) { + this._dirty = dirty; + this._onDidChangeDirty.fire(); + } + } + + public async revert(_options?: IRevertOptions) { + if (this._editable) { + this._proxy.$revert(this.resource, this.viewType); + } + } + + public undo() { + if (this._editable) { + this._proxy.$undo(this.resource, this.viewType); + } + } + + public redo() { + if (this._editable) { + this._proxy.$redo(this.resource, this.viewType); + } + } + + public async save(_options?: ISaveOptions): Promise { + if (!this._editable) { + return false; + } + await createCancelablePromise(token => this._proxy.$onSave(this.resource, this.viewType, token)); + this.setDirty(false); + return true; + } + + public async saveAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise { + if (this._editable) { + await this._proxy.$onSaveAs(this.resource, this.viewType, targetResource); + this.setDirty(false); + return true; + } else { + // Since the editor is readonly, just copy the file over + await this._fileService.copy(resource, targetResource, false /* overwrite */); + return true; + } + } + + public async backup(): Promise { + const backupData: IWorkingCopyBackup = { + meta: { + viewType: this.viewType, + } + }; + + if (!this._editable) { + return backupData; + } + + if (this._hotExitState.type === HotExitState.Type.Pending) { + this._hotExitState.operation.cancel(); + } + + const pendingState = new HotExitState.Pending( + createCancelablePromise(token => + this._proxy.$backup(this.resource.toJSON(), this.viewType, token))); + this._hotExitState = pendingState; + + try { + await pendingState.operation; + // Make sure state has not changed in the meantime + if (this._hotExitState === pendingState) { + this._hotExitState = HotExitState.Allowed; + } + } catch (e) { + // Make sure state has not changed in the meantime + if (this._hotExitState === pendingState) { + this._hotExitState = HotExitState.NotAllowed; + } + } + + if (this._hotExitState === HotExitState.Allowed) { + return backupData; + } + + throw new Error('Cannot back up in this state'); + } } diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index b09c9c1f43..60bdc5bcde 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -12,7 +12,7 @@ import { isNative } from 'vs/base/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search'; -import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, WorkbenchState, IWorkspace, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -138,7 +138,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { } const query = this._queryBuilder.file( - includeFolder ? [includeFolder] : workspace.folders.map(f => f.uri), + includeFolder ? [toWorkspaceFolder(includeFolder)] : workspace.folders, { maxResults: withNullAsUndefined(maxResults), disregardExcludeSettings: (excludePatternOrDisregardExcludes === false) || undefined, @@ -190,7 +190,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { $checkExists(folders: UriComponents[], includes: string[], token: CancellationToken): Promise { const queryBuilder = this._instantiationService.createInstance(QueryBuilder); - const query = queryBuilder.file(folders.map(folder => URI.revive(folder)), { + const query = queryBuilder.file(folders.map(folder => toWorkspaceFolder(URI.revive(folder))), { _reason: 'checkExists', includePattern: includes.join(', '), expandPatterns: true, diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 2cdb9ecc29..57a90a5974 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -9,7 +9,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation } from 'vs/workbench/common/views'; -import { CustomTreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/customView'; +import { TreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/treeView'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce, } from 'vs/base/common/arrays'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -396,7 +396,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { const viewDescriptor = { id: item.id, name: item.name, - ctorDescriptor: new SyncDescriptor(CustomTreeViewPane), + ctorDescriptor: new SyncDescriptor(TreeViewPane), when: ContextKeyExpr.deserialize(item.when), canToggleVisibility: true, canMoveView: true, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index cd57b2d401..6883355204 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -146,7 +146,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostClipboard = new ExtHostClipboard(rpcProtocol); const extHostMessageService = new ExtHostMessageService(rpcProtocol, extHostLogService); const extHostDialogs = new ExtHostDialogs(rpcProtocol); - const extHostStatusBar = new ExtHostStatusBar(rpcProtocol); + const extHostStatusBar = new ExtHostStatusBar(rpcProtocol, extHostCommands.converter); const extHostLanguages = new ExtHostLanguages(rpcProtocol, extHostDocuments); // Register API-ish commands @@ -188,12 +188,21 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable { return extHostAuthentication.registerAuthenticationProvider(provider); }, - get providers() { - return extHostAuthentication.providers(extension); - }, get onDidChangeAuthenticationProviders(): Event { return extHostAuthentication.onDidChangeAuthenticationProviders; - } + }, + get providerIds(): string[] { + return extHostAuthentication.providerIds; + }, + getSessions(providerId: string, scopes: string[]): Thenable { + return extHostAuthentication.getSessions(extension, providerId, scopes); + }, + login(providerId: string, scopes: string[]): Thenable { + return extHostAuthentication.login(extension, providerId, scopes); + }, + get onDidChangeSessions(): Event { + return extHostAuthentication.onDidChangeSessions; + }, }; // namespace: commands @@ -769,11 +778,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostTunnelService.onDidChangeTunnels(listener, thisArg, disposables); - }, - onDidTunnelsChange: (listener, thisArg?, disposables?) => { - checkProposedApiEnabled(extension); - return extHostTunnelService.onDidChangeTunnels(listener, thisArg, disposables); - }, registerTimelineProvider: (scheme: string | string[], provider: vscode.TimelineProvider) => { checkProposedApiEnabled(extension); @@ -1037,6 +1041,7 @@ class Extension implements vscode.Extension { private _identifier: ExtensionIdentifier; readonly id: string; + readonly extensionUri: URI; readonly extensionPath: string; readonly packageJSON: IExtensionDescription; readonly extensionKind: vscode.ExtensionKind; @@ -1046,6 +1051,7 @@ class Extension implements vscode.Extension { this._originExtensionId = originExtensionId; this._identifier = description.identifier; this.id = description.identifier.value; + this.extensionUri = description.extensionLocation; this.extensionPath = path.normalize(originalFSPath(description.extensionLocation)); this.packageJSON = description; this.extensionKind = kind; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4ff6a6eba7..d63de5db49 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -49,7 +49,7 @@ import { SaveReason } from 'vs/workbench/common/editor'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; -import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; +import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { Dto } from 'vs/base/common/types'; @@ -371,7 +371,8 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; $registerNavigateTypeSupport(handle: number): void; $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportsResolveInitialValues: boolean): void; - $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void; + $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend, eventHandle: number | undefined): void; + $emitDocumentSemanticTokensEvent(eventHandle: number): void; $registerDocumentRangeSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: modes.SemanticTokensLegend): void; $registerSuggestSupport(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void; $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void; @@ -536,7 +537,7 @@ export interface MainThreadQuickOpenShape extends IDisposable { } export interface MainThreadStatusBarShape extends IDisposable { - $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: string | undefined, color: string | ThemeColor | undefined, alignment: statusbar.StatusbarAlignment, priority: number | undefined): void; + $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: ICommandDto | undefined, color: string | ThemeColor | undefined, alignment: statusbar.StatusbarAlignment, priority: number | undefined): void; $dispose(id: number): void; } @@ -621,7 +622,7 @@ export interface ExtHostWebviewsShape { $undo(resource: UriComponents, viewType: string): void; $redo(resource: UriComponents, viewType: string): void; $revert(resource: UriComponents, viewType: string): void; - $onSave(resource: UriComponents, viewType: string): Promise; + $onSave(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise; $onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise; $backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise; @@ -804,6 +805,7 @@ export interface MainThreadTunnelServiceShape extends IDisposable { $registerCandidateFinder(): Promise; $setTunnelProvider(): Promise; $setCandidateFilter(): Promise; + $tunnelServiceReady(): Promise; } export interface MainThreadTimelineShape extends IDisposable { @@ -1472,7 +1474,7 @@ export interface ExtHostTunnelServiceShape { } export interface ExtHostTimelineShape { - $getTimeline(source: string, uri: UriComponents, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise; + $getTimeline(source: string, uri: UriComponents, options: TimelineOptions, token: CancellationToken, internalOptions?: InternalTimelineOptions): Promise; } // --- proxy identifiers diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index f6870098df..f82260f038 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -174,7 +174,7 @@ const newCommands: ApiCommand[] = [ // -- selection range new ApiCommand( 'vscode.executeSelectionRangeProvider', '_executeSelectionRangeProvider', 'Execute selection range provider.', - [ApiCommandArgument.Uri, new ApiCommandArgument('position', 'A positions in a text document', v => Array.isArray(v) && v.every(v => types.Position.isPosition(v)), v => v.map(typeConverters.Position.from))], + [ApiCommandArgument.Uri, new ApiCommandArgument('position', 'A positions in a text document', v => Array.isArray(v) && v.every(v => types.Position.isPosition(v)), v => v.map(typeConverters.Position.from))], new ApiCommandResult('A promise that resolves to an array of ranges.', result => { return result.map(ranges => { let node: types.SelectionRange | undefined; diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index 88c658d26e..2d7179f46b 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -10,61 +10,6 @@ import { IMainContext, MainContext, MainThreadAuthenticationShape, ExtHostAuthen import { Disposable } from 'vs/workbench/api/common/extHostTypes'; import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -export class AuthenticationProviderWrapper implements vscode.AuthenticationProvider { - readonly onDidChangeSessions: vscode.Event; - - constructor(private _requestingExtension: IExtensionDescription, - private _provider: vscode.AuthenticationProvider, - private _proxy: MainThreadAuthenticationShape) { - - this.onDidChangeSessions = this._provider.onDidChangeSessions; - } - - get id(): string { - return this._provider.id; - } - - get displayName(): string { - return this._provider.displayName; - } - - async getSessions(): Promise> { - return (await this._provider.getSessions()).map(session => { - return { - id: session.id, - accountName: session.accountName, - scopes: session.scopes, - getAccessToken: async () => { - const isAllowed = await this._proxy.$getSessionsPrompt( - this._provider.id, - this.displayName, - ExtensionIdentifier.toKey(this._requestingExtension.identifier), - this._requestingExtension.displayName || this._requestingExtension.name); - - if (!isAllowed) { - throw new Error('User did not consent to token access.'); - } - - return session.getAccessToken(); - } - }; - }); - } - - async login(scopes: string[]): Promise { - const isAllowed = await this._proxy.$loginPrompt(this._provider.id, this.displayName, ExtensionIdentifier.toKey(this._requestingExtension.identifier), this._requestingExtension.displayName || this._requestingExtension.name); - if (!isAllowed) { - throw new Error('User did not consent to login.'); - } - - return this._provider.login(scopes); - } - - logout(sessionId: string): Thenable { - return this._provider.logout(sessionId); - } -} - export class ExtHostAuthentication implements ExtHostAuthenticationShape { private _proxy: MainThreadAuthenticationShape; private _authenticationProviders: Map = new Map(); @@ -72,14 +17,65 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { private _onDidChangeAuthenticationProviders = new Emitter(); readonly onDidChangeAuthenticationProviders: Event = this._onDidChangeAuthenticationProviders.event; + private _onDidChangeSessions = new Emitter(); + readonly onDidChangeSessions: Event = this._onDidChangeSessions.event; + constructor(mainContext: IMainContext) { this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication); } - providers(requestingExtension: IExtensionDescription): vscode.AuthenticationProvider[] { - let providers: vscode.AuthenticationProvider[] = []; - this._authenticationProviders.forEach(provider => providers.push(new AuthenticationProviderWrapper(requestingExtension, provider, this._proxy))); - return providers; + get providerIds(): string[] { + const ids: string[] = []; + this._authenticationProviders.forEach(provider => { + ids.push(provider.id); + }); + + return ids; + } + + async getSessions(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise { + const provider = this._authenticationProviders.get(providerId); + if (!provider) { + throw new Error(`No authentication provider with id '${providerId}' is currently registered.`); + } + + const orderedScopes = scopes.sort().join(' '); + return (await provider.getSessions()) + .filter(session => session.scopes.sort().join(' ') === orderedScopes) + .map(session => { + return { + id: session.id, + accountName: session.accountName, + scopes: session.scopes, + getAccessToken: async () => { + const isAllowed = await this._proxy.$getSessionsPrompt( + provider.id, + provider.displayName, + ExtensionIdentifier.toKey(requestingExtension.identifier), + requestingExtension.displayName || requestingExtension.name); + + if (!isAllowed) { + throw new Error('User did not consent to token access.'); + } + + return session.getAccessToken(); + } + }; + }); + } + + async login(requestingExtension: IExtensionDescription, providerId: string, scopes: string[]): Promise { + const provider = this._authenticationProviders.get(providerId); + if (!provider) { + throw new Error(`No authentication provider with id '${providerId}' is currently registered.`); + } + + const isAllowed = await this._proxy.$loginPrompt(provider.id, provider.displayName, ExtensionIdentifier.toKey(requestingExtension.identifier), requestingExtension.displayName || requestingExtension.name); + if (!isAllowed) { + throw new Error('User did not consent to login.'); + } + + return provider.login(scopes); } registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable { @@ -91,6 +87,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { const listener = provider.onDidChangeSessions(_ => { this._proxy.$onDidChangeSessions(provider.id); + this._onDidChangeSessions.fire([provider.id]); }); this._proxy.$registerAuthenticationProvider(provider.id, provider.displayName); diff --git a/src/vs/workbench/api/common/extHostDiagnostics.ts b/src/vs/workbench/api/common/extHostDiagnostics.ts index 3213606e21..4195d5a061 100644 --- a/src/vs/workbench/api/common/extHostDiagnostics.ts +++ b/src/vs/workbench/api/common/extHostDiagnostics.ts @@ -12,7 +12,6 @@ import { DiagnosticSeverity } from './extHostTypes'; import * as converter from './extHostTypeConverters'; import { mergeSort } from 'vs/base/common/arrays'; import { Event, Emitter } from 'vs/base/common/event'; -import { keys } from 'vs/base/common/map'; import { ILogService } from 'vs/platform/log/common/log'; export class DiagnosticCollection implements vscode.DiagnosticCollection { @@ -36,7 +35,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { dispose(): void { if (!this._isDisposed) { - this._onDidChangeDiagnostics.fire(keys(this._data)); + this._onDidChangeDiagnostics.fire([...this._data.keys()]); if (this._proxy) { this._proxy.$clear(this._owner); } @@ -169,7 +168,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { clear(): void { this._checkDisposed(); - this._onDidChangeDiagnostics.fire(keys(this._data)); + this._onDidChangeDiagnostics.fire([...this._data.keys()]); this._data.clear(); if (this._proxy) { this._proxy.$clear(this._owner); diff --git a/src/vs/workbench/api/common/extHostExtensionActivator.ts b/src/vs/workbench/api/common/extHostExtensionActivator.ts index fc885c0ed2..b35db25206 100644 --- a/src/vs/workbench/api/common/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/common/extHostExtensionActivator.ts @@ -252,7 +252,15 @@ export class ExtensionsActivator { return; } - const currentExtension = this._registry.getExtensionDescription(currentActivation.id)!; + const currentExtension = this._registry.getExtensionDescription(currentActivation.id); + if (!currentExtension) { + // Error condition 0: unknown extension + this._host.onExtensionActivationError(currentActivation.id, new MissingDependencyError(currentActivation.id.value)); + const error = new Error(`Unknown dependency '${currentActivation.id.value}'`); + this._activatedExtensions.set(ExtensionIdentifier.toKey(currentActivation.id), new FailedExtension(error)); + return; + } + const depIds = (typeof currentExtension.extensionDependencies === 'undefined' ? [] : currentExtension.extensionDependencies); let currentExtensionGetsGreenLight = true; diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 9651408cc0..0a7adc9bdd 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -366,6 +366,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio globalState, workspaceState, subscriptions: [], + get extensionUri() { return extensionDescription.extensionLocation; }, get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, get storagePath() { return that._storagePath.workspaceValue(extensionDescription); }, get globalStoragePath() { return that._storagePath.globalValue(extensionDescription); }, diff --git a/src/vs/workbench/api/common/extHostFileSystem.ts b/src/vs/workbench/api/common/extHostFileSystem.ts index dfbf69141b..aa99d24d64 100644 --- a/src/vs/workbench/api/common/extHostFileSystem.ts +++ b/src/vs/workbench/api/common/extHostFileSystem.ts @@ -148,7 +148,16 @@ class ConsumerFileSystem implements vscode.FileSystem { } // file system error - throw new FileSystemError(err.message, err.name as files.FileSystemProviderErrorCode); + switch (err.name) { + case files.FileSystemProviderErrorCode.FileExists: throw FileSystemError.FileExists(err.message); + case files.FileSystemProviderErrorCode.FileNotFound: throw FileSystemError.FileNotFound(err.message); + case files.FileSystemProviderErrorCode.FileNotADirectory: throw FileSystemError.FileNotADirectory(err.message); + case files.FileSystemProviderErrorCode.FileIsADirectory: throw FileSystemError.FileIsADirectory(err.message); + case files.FileSystemProviderErrorCode.NoPermissions: throw FileSystemError.NoPermissions(err.message); + case files.FileSystemProviderErrorCode.Unavailable: throw FileSystemError.Unavailable(err.message); + + default: throw new FileSystemError(err.message, err.name as files.FileSystemProviderErrorCode); + } } } diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 8b5cf49a53..7f9cb2f39e 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1702,9 +1702,19 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF //#region semantic coloring registerDocumentSemanticTokensProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentSemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable { - const handle = this._addNewAdapter(new DocumentSemanticTokensAdapter(this._documents, provider), extension); - this._proxy.$registerDocumentSemanticTokensProvider(handle, this._transformDocumentSelector(selector), legend); - return this._createDisposable(handle); + const handle = this._nextHandle(); + const eventHandle = (typeof provider.onDidChangeSemanticTokens === 'function' ? this._nextHandle() : undefined); + + this._adapter.set(handle, new AdapterData(new DocumentSemanticTokensAdapter(this._documents, provider), extension)); + this._proxy.$registerDocumentSemanticTokensProvider(handle, this._transformDocumentSelector(selector), legend, eventHandle); + let result = this._createDisposable(handle); + + if (eventHandle) { + const subscription = provider.onDidChangeSemanticTokens!(_ => this._proxy.$emitDocumentSemanticTokensEvent(eventHandle)); + result = Disposable.from(result, subscription); + } + + return result; } $provideDocumentSemanticTokens(handle: number, resource: UriComponents, previousResultId: number, token: CancellationToken): Promise { diff --git a/src/vs/workbench/api/common/extHostStatusBar.ts b/src/vs/workbench/api/common/extHostStatusBar.ts index 5bb6f2f8e5..74a4e53a85 100644 --- a/src/vs/workbench/api/common/extHostStatusBar.ts +++ b/src/vs/workbench/api/common/extHostStatusBar.ts @@ -5,11 +5,13 @@ import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; import { StatusBarAlignment as ExtHostStatusBarAlignment, Disposable, ThemeColor } from './extHostTypes'; -import { StatusBarItem, StatusBarAlignment } from 'vscode'; -import { MainContext, MainThreadStatusBarShape, IMainContext } from './extHost.protocol'; +import type * as vscode from 'vscode'; +import { MainContext, MainThreadStatusBarShape, IMainContext, ICommandDto } from './extHost.protocol'; import { localize } from 'vs/nls'; +import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; +import { DisposableStore } from 'vs/base/common/lifecycle'; -export class ExtHostStatusBarEntry implements StatusBarItem { +export class ExtHostStatusBarEntry implements vscode.StatusBarItem { private static ID_GEN = 0; private _id: number; @@ -24,14 +26,20 @@ export class ExtHostStatusBarEntry implements StatusBarItem { private _text: string = ''; private _tooltip?: string; private _color?: string | ThemeColor; - private _command?: string; + private readonly _internalCommandRegistration = new DisposableStore(); + private _command?: { + readonly fromApi: string | vscode.Command, + readonly internal: ICommandDto, + }; private _timeoutHandle: any; private _proxy: MainThreadStatusBarShape; + private _commands: CommandsConverter; - constructor(proxy: MainThreadStatusBarShape, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) { + constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) { this._id = ExtHostStatusBarEntry.ID_GEN++; this._proxy = proxy; + this._commands = commands; this._statusId = id; this._statusName = name; this._alignment = alignment; @@ -42,7 +50,7 @@ export class ExtHostStatusBarEntry implements StatusBarItem { return this._id; } - public get alignment(): StatusBarAlignment { + public get alignment(): vscode.StatusBarAlignment { return this._alignment; } @@ -62,8 +70,8 @@ export class ExtHostStatusBarEntry implements StatusBarItem { return this._color; } - public get command(): string | undefined { - return this._command; + public get command(): string | vscode.Command | undefined { + return this._command?.fromApi; } public set text(text: string) { @@ -81,8 +89,25 @@ export class ExtHostStatusBarEntry implements StatusBarItem { this.update(); } - public set command(command: string | undefined) { - this._command = command; + public set command(command: string | vscode.Command | undefined) { + if (this._command?.fromApi === command) { + return; + } + + this._internalCommandRegistration.clear(); + if (typeof command === 'string') { + this._command = { + fromApi: command, + internal: this._commands.toInternal({ title: '', command }, this._internalCommandRegistration), + }; + } else if (command) { + this._command = { + fromApi: command, + internal: this._commands.toInternal(command, this._internalCommandRegistration), + }; + } else { + this._command = undefined; + } this.update(); } @@ -109,7 +134,7 @@ export class ExtHostStatusBarEntry implements StatusBarItem { this._timeoutHandle = undefined; // Set to status bar - this._proxy.$setEntry(this.id, this._statusId, this._statusName, this.text, this.tooltip, this.command, this.color, + this._proxy.$setEntry(this.id, this._statusId, this._statusName, this.text, this.tooltip, this._command?.internal, this.color, this._alignment === ExtHostStatusBarAlignment.Left ? MainThreadStatusBarAlignment.LEFT : MainThreadStatusBarAlignment.RIGHT, this._priority); }, 0); @@ -123,7 +148,7 @@ export class ExtHostStatusBarEntry implements StatusBarItem { class StatusBarMessage { - private _item: StatusBarItem; + private _item: vscode.StatusBarItem; private _messages: { message: string }[] = []; constructor(statusBar: ExtHostStatusBar) { @@ -161,16 +186,18 @@ class StatusBarMessage { export class ExtHostStatusBar { - private _proxy: MainThreadStatusBarShape; + private readonly _proxy: MainThreadStatusBarShape; + private readonly _commands: CommandsConverter; private _statusMessage: StatusBarMessage; - constructor(mainContext: IMainContext) { + constructor(mainContext: IMainContext, commands: CommandsConverter) { this._proxy = mainContext.getProxy(MainContext.MainThreadStatusBar); + this._commands = commands; this._statusMessage = new StatusBarMessage(this); } - createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number): StatusBarItem { - return new ExtHostStatusBarEntry(this._proxy, id, name, alignment, priority); + createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number): vscode.StatusBarItem { + return new ExtHostStatusBarEntry(this._proxy, this._commands, id, name, alignment, priority); } setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable): Disposable { diff --git a/src/vs/workbench/api/common/extHostTimeline.ts b/src/vs/workbench/api/common/extHostTimeline.ts index 6480c766cd..ee2b553f55 100644 --- a/src/vs/workbench/api/common/extHostTimeline.ts +++ b/src/vs/workbench/api/common/extHostTimeline.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode'; import { UriComponents, URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ExtHostTimelineShape, MainThreadTimelineShape, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; -import { Timeline, TimelineItem, TimelineOptions, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline'; +import { Timeline, TimelineItem, TimelineOptions, TimelineProvider, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { CancellationToken } from 'vs/base/common/cancellation'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; @@ -16,21 +16,19 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export interface IExtHostTimeline extends ExtHostTimelineShape { readonly _serviceBrand: undefined; - $getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise; + $getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: InternalTimelineOptions): Promise; } export const IExtHostTimeline = createDecorator('IExtHostTimeline'); export class ExtHostTimeline implements IExtHostTimeline { - private static handlePool = 0; - _serviceBrand: undefined; private _proxy: MainThreadTimelineShape; private _providers = new Map(); - private _itemsBySourceByUriMap = new Map>>(); + private _itemsBySourceAndUriMap = new Map>>(); constructor( mainContext: IMainContext, @@ -42,7 +40,7 @@ export class ExtHostTimeline implements IExtHostTimeline { processArgument: arg => { if (arg && arg.$mid === 11) { const uri = arg.uri === undefined ? undefined : URI.revive(arg.uri); - return this._itemsBySourceByUriMap.get(getUriKey(uri))?.get(arg.source)?.get(arg.handle); + return this._itemsBySourceAndUriMap.get(arg.source)?.get(getUriKey(uri))?.get(arg.handle); } return arg; @@ -50,7 +48,7 @@ export class ExtHostTimeline implements IExtHostTimeline { }); } - async $getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise { + async $getTimeline(id: string, uri: UriComponents, options: vscode.TimelineOptions, token: vscode.CancellationToken, internalOptions?: InternalTimelineOptions): Promise { const provider = this._providers.get(id); return provider?.provideTimeline(URI.revive(uri), options, token, internalOptions); } @@ -62,26 +60,21 @@ export class ExtHostTimeline implements IExtHostTimeline { let disposable: IDisposable | undefined; if (provider.onDidChange) { - disposable = provider.onDidChange(this.emitTimelineChangeEvent(provider.id), this); + disposable = provider.onDidChange(e => this._proxy.$emitTimelineChangeEvent({ ...e, id: provider.id }), this); } - const itemsBySourceByUriMap = this._itemsBySourceByUriMap; + const itemsBySourceAndUriMap = this._itemsBySourceAndUriMap; return this.registerTimelineProviderCore({ ...provider, scheme: scheme, onDidChange: undefined, - async provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }) { - // For now, only allow the caching of a single Uri - if (internalOptions?.cacheResults) { - if (options.cursor === undefined) { - timelineDisposables.clear(); - } - - if (!itemsBySourceByUriMap.has(getUriKey(uri))) { - itemsBySourceByUriMap.clear(); - } - } else { + async provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: InternalTimelineOptions) { + if (internalOptions?.resetCache) { timelineDisposables.clear(); + + // For now, only allow the caching of a single Uri + // itemsBySourceAndUriMap.get(provider.id)?.get(getUriKey(uri))?.clear(); + itemsBySourceAndUriMap.get(provider.id)?.clear(); } const result = await provider.provideTimeline(uri, options, token); @@ -91,8 +84,9 @@ export class ExtHostTimeline implements IExtHostTimeline { return undefined; } - // TODO: Determine if we should cache dependent on who calls us (internal vs external) - const convertItem = convertTimelineItem(uri, internalOptions?.cacheResults ?? false); + // TODO: Should we bother converting all the data if we aren't caching? Meaning it is being requested by an extension? + + const convertItem = convertTimelineItem(uri, internalOptions); return { ...result, source: provider.id, @@ -100,6 +94,10 @@ export class ExtHostTimeline implements IExtHostTimeline { }; }, dispose() { + for (const sourceMap of itemsBySourceAndUriMap.values()) { + sourceMap.get(provider.id)?.clear(); + } + disposable?.dispose(); timelineDisposables.dispose(); } @@ -107,29 +105,28 @@ export class ExtHostTimeline implements IExtHostTimeline { } private convertTimelineItem(source: string, commandConverter: CommandsConverter, disposables: DisposableStore) { - return (uri: URI, cacheResults: boolean) => { - let itemsMap: Map | undefined; - if (cacheResults) { - const uriKey = getUriKey(uri); - - let sourceMap = this._itemsBySourceByUriMap.get(uriKey); - if (sourceMap === undefined) { - sourceMap = new Map(); - this._itemsBySourceByUriMap.set(uriKey, sourceMap); + return (uri: URI, options?: InternalTimelineOptions) => { + let items: Map | undefined; + if (options?.cacheResults) { + let itemsByUri = this._itemsBySourceAndUriMap.get(source); + if (itemsByUri === undefined) { + itemsByUri = new Map(); + this._itemsBySourceAndUriMap.set(source, itemsByUri); } - itemsMap = sourceMap.get(source); - if (itemsMap === undefined) { - itemsMap = new Map(); - sourceMap.set(source, itemsMap); + const uriKey = getUriKey(uri); + items = itemsByUri.get(uriKey); + if (items === undefined) { + items = new Map(); + itemsByUri.set(uriKey, items); } } return (item: vscode.TimelineItem): TimelineItem => { const { iconPath, ...props } = item; - const handle = `${source}|${item.id ?? `${item.timestamp}-${ExtHostTimeline.handlePool++}`}`; - itemsMap?.set(handle, item); + const handle = `${source}|${item.id ?? item.timestamp}`; + items?.set(handle, item); let icon; let iconDark; @@ -161,22 +158,6 @@ export class ExtHostTimeline implements IExtHostTimeline { }; } - private emitTimelineChangeEvent(id: string) { - return (e: vscode.TimelineChangeEvent) => { - // Clear caches - if (e?.uri === undefined) { - for (const sourceMap of this._itemsBySourceByUriMap.values()) { - sourceMap.get(id)?.clear(); - } - } - else { - this._itemsBySourceByUriMap.get(getUriKey(e.uri))?.clear(); - } - - this._proxy.$emitTimelineChangeEvent({ ...e, id: id }); - }; - } - private registerTimelineProviderCore(provider: TimelineProvider): IDisposable { // console.log(`ExtHostTimeline#registerTimelineProvider: id=${provider.id}`); @@ -193,7 +174,7 @@ export class ExtHostTimeline implements IExtHostTimeline { this._providers.set(provider.id, provider); return toDisposable(() => { - for (const sourceMap of this._itemsBySourceByUriMap.values()) { + for (const sourceMap of this._itemsBySourceAndUriMap.values()) { sourceMap.get(provider.id)?.clear(); } diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index 561a7836f1..2cbdde8ac6 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -3,16 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostTunnelServiceShape, MainContext, MainThreadTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import * as vscode from 'vscode'; import { RemoteTunnel, TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export interface TunnelDto { remoteAddress: { port: number, host: string }; - localAddress: string; + localAddress: { port: number, host: string } | string; } export namespace TunnelDto { @@ -42,6 +43,13 @@ export const IExtHostTunnelService = createDecorator('IEx export class ExtHostTunnelService implements IExtHostTunnelService { _serviceBrand: undefined; onDidChangeTunnels: vscode.Event = (new Emitter()).event; + private readonly _proxy: MainThreadTunnelServiceShape; + + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + ) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService); + } async openTunnel(forward: TunnelOptions): Promise { return undefined; @@ -55,7 +63,10 @@ export class ExtHostTunnelService implements IExtHostTunnelService { async $filterCandidates(candidates: { host: string, port: number, detail: string }[]): Promise { return candidates.map(() => true); } - async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise { return { dispose: () => { } }; } + async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise { + await this._proxy.$tunnelServiceReady(); + return { dispose: () => { } }; + } $forwardPort(tunnelOptions: TunnelOptions): Promise | undefined { return undefined; } async $closeTunnel(remote: { host: string, port: number }): Promise { } async $onDidTunnelsChange(): Promise { } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 72b5f1c760..93bd4a714b 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -7,7 +7,6 @@ import { coalesce, equals } from 'vs/base/common/arrays'; import { illegalArgument } from 'vs/base/common/errors'; import { IRelativePattern } from 'vs/base/common/glob'; import { isMarkdownString } from 'vs/base/common/htmlContent'; -import { values } from 'vs/base/common/map'; import { startsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; @@ -44,16 +43,16 @@ export class Disposable { }); } - private _callOnDispose?: () => any; + #callOnDispose?: () => any; constructor(callOnDispose: () => any) { - this._callOnDispose = callOnDispose; + this.#callOnDispose = callOnDispose; } dispose(): any { - if (typeof this._callOnDispose === 'function') { - this._callOnDispose(); - this._callOnDispose = undefined; + if (typeof this.#callOnDispose === 'function') { + this.#callOnDispose(); + this.#callOnDispose = undefined; } } } @@ -661,7 +660,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { textEdit[1].push(candidate.edit); } } - return values(textEdits); + return [...textEdits.values()]; } allEntries(): ReadonlyArray { @@ -2334,9 +2333,13 @@ export class FileSystemError extends Error { return new FileSystemError(messageOrUri, FileSystemProviderErrorCode.Unavailable, FileSystemError.Unavailable); } + readonly code?: string; + constructor(uriOrMessage?: string | URI, code: FileSystemProviderErrorCode = FileSystemProviderErrorCode.Unknown, terminator?: Function) { super(URI.isUri(uriOrMessage) ? uriOrMessage.toString(true) : uriOrMessage); + this.code = terminator?.name; + // mark the error as file system provider error so that // we can extract the error code on the receiving side markAsFileSystemProviderError(this, code); diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index fded359e27..0af7fc3ec2 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -104,19 +104,20 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa private _title: string; private _iconPath?: IconPath; - private readonly _options: vscode.WebviewPanelOptions; - private readonly _webview: ExtHostWebview; - private _viewColumn: vscode.ViewColumn | undefined; - private _visible: boolean = true; - private _active: boolean = true; + readonly #options: vscode.WebviewPanelOptions; + readonly #webview: ExtHostWebview; - _isDisposed: boolean = false; + #viewColumn: vscode.ViewColumn | undefined = undefined; + #visible: boolean = true; + #active: boolean = true; - readonly _onDisposeEmitter = this._register(new Emitter()); - public readonly onDidDispose: Event = this._onDisposeEmitter.event; + #isDisposed: boolean = false; - readonly _onDidChangeViewStateEmitter = this._register(new Emitter()); - public readonly onDidChangeViewState: Event = this._onDidChangeViewStateEmitter.event; + readonly #onDidDispose = this._register(new Emitter()); + public readonly onDidDispose = this.#onDidDispose.event; + + readonly #onDidChangeViewState = this._register(new Emitter()); + public readonly onDidChangeViewState = this.#onDidChangeViewState.event; constructor( handle: WebviewPanelHandle, @@ -131,27 +132,28 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa this._handle = handle; this._proxy = proxy; this._viewType = viewType; - this._options = editorOptions; - this._viewColumn = viewColumn; + this.#options = editorOptions; + this.#viewColumn = viewColumn; this._title = title; - this._webview = webview; + this.#webview = webview; } public dispose() { - if (this._isDisposed) { + if (this.#isDisposed) { return; } - this._isDisposed = true; - this._onDisposeEmitter.fire(); + + this.#isDisposed = true; + this.#onDidDispose.fire(); this._proxy.$disposeWebview(this._handle); - this._webview.dispose(); + this.#webview.dispose(); super.dispose(); } get webview() { this.assertNotDisposed(); - return this._webview; + return this.#webview; } get viewType(): string { @@ -187,42 +189,40 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa } get options() { - return this._options; + return this.#options; } get viewColumn(): vscode.ViewColumn | undefined { this.assertNotDisposed(); - if (typeof this._viewColumn === 'number' && this._viewColumn < 0) { + if (typeof this.#viewColumn === 'number' && this.#viewColumn < 0) { // We are using a symbolic view column // Return undefined instead to indicate that the real view column is currently unknown but will be resolved. return undefined; } - return this._viewColumn; - } - - _setViewColumn(value: vscode.ViewColumn) { - this.assertNotDisposed(); - this._viewColumn = value; + return this.#viewColumn; } public get active(): boolean { this.assertNotDisposed(); - return this._active; - } - - _setActive(value: boolean) { - this.assertNotDisposed(); - this._active = value; + return this.#active; } public get visible(): boolean { this.assertNotDisposed(); - return this._visible; + return this.#visible; } - _setVisible(value: boolean) { - this.assertNotDisposed(); - this._visible = value; + _updateViewState(newState: { active: boolean; visible: boolean; viewColumn: vscode.ViewColumn; }) { + if (this.#isDisposed) { + return; + } + + if (this.active !== newState.active || this.visible !== newState.visible || this.viewColumn !== newState.viewColumn) { + this.#active = newState.active; + this.#visible = newState.visible; + this.#viewColumn = newState.viewColumn; + this.#onDidChangeViewState.fire({ webviewPanel: this }); + } } public postMessage(message: any): Promise { @@ -239,7 +239,7 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa } private assertNotDisposed() { - if (this._isDisposed) { + if (this.#isDisposed) { throw new Error('Webview is disposed'); } } @@ -303,23 +303,26 @@ class CustomDocument extends Disposable implements vscode.CustomDocument { }); } - /** @internal*/ _revert() { + /** @internal*/ async _revert() { const editing = this.getEditingCapability(); if (this.#currentEditIndex === this.#savePoint) { return true; } + + let undoneEdits: EditType[] = []; + let appliedEdits: EditType[] = []; if (this.#currentEditIndex >= this.#savePoint) { - const editsToUndo = this.#edits.slice(this.#savePoint, this.#currentEditIndex); - editing.undoEdits(editsToUndo.reverse()); + undoneEdits = this.#edits.slice(this.#savePoint, this.#currentEditIndex).reverse(); } else if (this.#currentEditIndex < this.#savePoint) { - const editsToRedo = this.#edits.slice(this.#currentEditIndex, this.#savePoint); - editing.applyEdits(editsToRedo); + appliedEdits = this.#edits.slice(this.#currentEditIndex, this.#savePoint); } this.#currentEditIndex = this.#savePoint; this.spliceEdits(); + await editing.revert({ undoneEdits, appliedEdits }); + this.updateState(); return true; } @@ -350,8 +353,8 @@ class CustomDocument extends Disposable implements vscode.CustomDocument { this.updateState(); } - /** @internal*/ _save() { - return this.getEditingCapability().save(); + /** @internal*/ _save(cancellation: CancellationToken) { + return this.getEditingCapability().save(cancellation); } /** @internal*/ _saveAs(target: vscode.Uri) { @@ -585,18 +588,16 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { for (const handle of handles) { const panel = this.getWebviewPanel(handle); - if (!panel || panel._isDisposed) { + if (!panel) { continue; } const newState = newStates[handle]; - const viewColumn = typeConverters.ViewColumn.to(newState.position); - if (panel.active !== newState.active || panel.visible !== newState.visible || panel.viewColumn !== viewColumn) { - panel._setActive(newState.active); - panel._setVisible(newState.visible); - panel._setViewColumn(viewColumn); - panel._onDidChangeViewStateEmitter.fire({ webviewPanel: panel }); - } + panel._updateViewState({ + active: newState.active, + visible: newState.visible, + viewColumn: typeConverters.ViewColumn.to(newState.position), + }); } } @@ -659,7 +660,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } const revivedResource = URI.revive(resource); - const document = this.getDocument(viewType, revivedResource); + const document = this.getCustomDocument(viewType, revivedResource); this._documents.delete(document); document.dispose(); } @@ -686,12 +687,11 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { switch (entry.type) { case WebviewEditorType.Custom: { - const document = this.getDocument(viewType, revivedResource); + const document = this.getCustomDocument(viewType, revivedResource); return entry.provider.resolveCustomEditor(document, revivedPanel); } case WebviewEditorType.Text: { - await this._extHostDocuments.ensureDocumentData(revivedResource); const document = this._extHostDocuments.getDocument(revivedResource); return entry.provider.resolveCustomTextEditor(document, revivedPanel); } @@ -703,32 +703,32 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } async $undo(resourceComponents: UriComponents, viewType: string): Promise { - const document = this.getDocument(viewType, resourceComponents); + const document = this.getCustomDocument(viewType, resourceComponents); document._undo(); } async $redo(resourceComponents: UriComponents, viewType: string): Promise { - const document = this.getDocument(viewType, resourceComponents); + const document = this.getCustomDocument(viewType, resourceComponents); document._redo(); } async $revert(resourceComponents: UriComponents, viewType: string): Promise { - const document = this.getDocument(viewType, resourceComponents); + const document = this.getCustomDocument(viewType, resourceComponents); document._revert(); } - async $onSave(resourceComponents: UriComponents, viewType: string): Promise { - const document = this.getDocument(viewType, resourceComponents); - document._save(); + async $onSave(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise { + const document = this.getCustomDocument(viewType, resourceComponents); + document._save(cancellation); } async $onSaveAs(resourceComponents: UriComponents, viewType: string, targetResource: UriComponents): Promise { - const document = this.getDocument(viewType, resourceComponents); + const document = this.getCustomDocument(viewType, resourceComponents); return document._saveAs(URI.revive(targetResource)); } async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise { - const document = this.getDocument(viewType, resourceComponents); + const document = this.getCustomDocument(viewType, resourceComponents); return document._backup(cancellation); } @@ -736,7 +736,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { return this._webviewPanels.get(handle); } - private getDocument(viewType: string, resource: UriComponents): CustomDocument { + private getCustomDocument(viewType: string, resource: UriComponents): CustomDocument { const document = this._documents.get(viewType, URI.revive(resource)); if (!document) { throw new Error('No webview editor custom document found'); diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index cc5d6519d7..687a496413 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -473,7 +473,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined, disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined, disregardGlobalIgnoreFiles: typeof options.useGlobalIgnoreFiles === 'boolean' ? !options.useGlobalIgnoreFiles : undefined, - disregardExcludeSettings: options.exclude === null, + disregardExcludeSettings: typeof options.useDefaultExcludes === 'boolean' ? !options.useDefaultExcludes : true, fileEncoding: options.encoding, maxResults: options.maxResults, previewOptions, diff --git a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts index 9925ab160e..8c97ae245a 100644 --- a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts +++ b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts @@ -7,9 +7,10 @@ import * as nls from 'vs/nls'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import * as strings from 'vs/base/common/strings'; import * as resources from 'vs/base/common/resources'; +import { isString } from 'vs/base/common/types'; interface IJSONValidationExtensionPoint { - fileMatch: string; + fileMatch: string | string[]; url: string; } @@ -25,8 +26,11 @@ const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint { - if (typeof extension.fileMatch !== 'string') { - collector.error(nls.localize('invalid.fileMatch', "'configuration.jsonValidation.fileMatch' must be defined")); + if (!isString(extension.fileMatch) && !(Array.isArray(extension.fileMatch) && extension.fileMatch.every(isString))) { + collector.error(nls.localize('invalid.fileMatch', "'configuration.jsonValidation.fileMatch' must be defined as a string or an array of strings.")); return; } let uri = extension.url; - if (typeof extension.url !== 'string') { + if (!isString(uri)) { collector.error(nls.localize('invalid.url', "'configuration.jsonValidation.url' must be a URL or relative path")); return; } diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 4706f105b5..48d8d6a606 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -113,7 +113,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { } else if (args.kind === 'external') { - runInExternalTerminal(args, await this._configurationService.getConfigProvider()); + return runInExternalTerminal(args, await this._configurationService.getConfigProvider()); } return super.$runInTerminal(args); } diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index 60035f7b0f..d81b4378d0 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -23,8 +23,8 @@ class ExtensionTunnel implements vscode.Tunnel { onDidDispose: Event = this._onDispose.event; constructor( - public readonly remoteAddress: { port: number; host: string; }, - public readonly localAddress: string, + public readonly remoteAddress: { port: number, host: string }, + public readonly localAddress: { port: number, host: string } | string, private readonly _dispose: () => void) { } dispose(): void { @@ -52,6 +52,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe this.registerCandidateFinder(); } } + async openTunnel(forward: TunnelOptions): Promise { const tunnel = await this._proxy.$openTunnel(forward); if (tunnel) { @@ -91,6 +92,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe } else { this._forwardPortProvider = undefined; } + await this._proxy.$tunnelServiceReady(); return toDisposable(() => { this._forwardPortProvider = undefined; }); diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index 908a4e33ff..7b29e04253 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -17,7 +17,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Context } from 'vs/platform/contextkey/browser/contextKeyService'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { timeout } from 'vs/base/common/async'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; @@ -101,7 +101,7 @@ class ToggleScreencastModeAction extends Action { id: string, label: string, @IKeybindingService private readonly keybindingService: IKeybindingService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @ILayoutService private readonly layoutService: ILayoutService, @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, label); @@ -116,7 +116,7 @@ class ToggleScreencastModeAction extends Action { const disposables = new DisposableStore(); - const container = this.layoutService.getWorkbenchElement(); + const container = this.layoutService.container; const mouseMarker = append(container, $('.screencast-mouse')); disposables.add(toDisposable(() => mouseMarker.remove())); diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 8c26fb41a3..b3dda06d12 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -18,7 +18,7 @@ import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { isWindows, isLinux, isWeb } from 'vs/base/common/platform'; -import { IsMacNativeContext } from 'vs/workbench/browser/contextkeys'; +import { IsMacNativeContext } from 'vs/platform/contextkey/common/contextkeys'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { InEditorZenModeContext, IsCenteredLayoutContext, EditorAreaVisibleContext } from 'vs/workbench/common/editor'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -50,10 +50,8 @@ export class CloseSidebarAction extends Action { this.enabled = !!this.layoutService; } - run(): Promise { + async run(): Promise { this.layoutService.setSideBarHidden(true); - - return Promise.resolve(); } } @@ -79,7 +77,7 @@ export class ToggleActivityBarVisibilityAction extends Action { this.enabled = !!this.layoutService; } - run(): Promise { + run(): Promise { const visibility = this.layoutService.isVisible(Parts.ACTIVITYBAR_PART); const newVisibilityValue = !visibility; @@ -115,10 +113,8 @@ class ToggleCenteredLayout extends Action { this.enabled = !!this.layoutService; } - run(): Promise { + async run(): Promise { this.layoutService.centerEditorLayout(!this.layoutService.isEditorLayoutCentered()); - - return Promise.resolve(); } } @@ -165,11 +161,9 @@ export class ToggleEditorLayoutAction extends Action { this.enabled = this.editorGroupService.count > 1; } - run(): Promise { + async run(): Promise { const newOrientation = (this.editorGroupService.orientation === GroupOrientation.VERTICAL) ? GroupOrientation.HORIZONTAL : GroupOrientation.VERTICAL; this.editorGroupService.setGroupOrientation(newOrientation); - - return Promise.resolve(); } } @@ -205,7 +199,7 @@ export class ToggleSidebarPositionAction extends Action { this.enabled = !!this.layoutService && !!this.configurationService; } - run(): Promise { + run(): Promise { const position = this.layoutService.getSideBarPosition(); const newPositionValue = (position === Position.LEFT) ? 'right' : 'left'; @@ -255,10 +249,8 @@ export class ToggleEditorVisibilityAction extends Action { this.enabled = !!this.layoutService; } - run(): Promise { + async run(): Promise { this.layoutService.toggleMaximizedPanel(); - - return Promise.resolve(); } } @@ -289,11 +281,9 @@ export class ToggleSidebarVisibilityAction extends Action { this.enabled = !!this.layoutService; } - run(): Promise { + async run(): Promise { const hideSidebar = this.layoutService.isVisible(Parts.SIDEBAR_PART); this.layoutService.setSideBarHidden(hideSidebar); - - return Promise.resolve(); } } @@ -336,7 +326,7 @@ export class ToggleStatusbarVisibilityAction extends Action { this.enabled = !!this.layoutService; } - run(): Promise { + run(): Promise { const visibility = this.layoutService.isVisible(Parts.STATUSBAR_PART); const newVisibilityValue = !visibility; @@ -373,7 +363,7 @@ class ToggleTabsVisibilityAction extends Action { super(id, label); } - run(): Promise { + run(): Promise { const visibility = this.configurationService.getValue(ToggleTabsVisibilityAction.tabsVisibleKey); const newVisibilityValue = !visibility; @@ -403,10 +393,8 @@ class ToggleZenMode extends Action { this.enabled = !!this.layoutService; } - run(): Promise { + async run(): Promise { this.layoutService.toggleZenMode(); - - return Promise.resolve(); } } @@ -466,9 +454,7 @@ export class ToggleMenuBarAction extends Action { newVisibilityValue = (isWeb && currentVisibilityValue === 'hidden') ? 'compact' : 'default'; } - this.configurationService.updateValue(ToggleMenuBarAction.menuBarVisibilityKey, newVisibilityValue, ConfigurationTarget.USER); - - return Promise.resolve(); + return this.configurationService.updateValue(ToggleMenuBarAction.menuBarVisibilityKey, newVisibilityValue, ConfigurationTarget.USER); } } @@ -501,7 +487,7 @@ export class ResetViewLocationsAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); viewContainerRegistry.all.forEach(viewContainer => { const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); @@ -515,8 +501,6 @@ export class ResetViewLocationsAction extends Action { } }); }); - - return Promise.resolve(); } } @@ -541,20 +525,20 @@ export class MoveFocusedViewAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); const focusedViewId = FocusedViewContext.getValue(this.contextKeyService); if (focusedViewId === undefined || focusedViewId.trim() === '') { this.notificationService.error(nls.localize('moveFocusedView.error.noFocusedView', "There is no view currently focused.")); - return Promise.resolve(); + return; } const viewDescriptor = this.viewDescriptorService.getViewDescriptor(focusedViewId); if (!viewDescriptor || !viewDescriptor.canMoveView) { this.notificationService.error(nls.localize('moveFocusedView.error.nonMovableView', "The currently focused view is not movable.")); - return Promise.resolve(); + return; } const quickPick = this.quickInputService.createQuickPick(); @@ -608,8 +592,6 @@ export class MoveFocusedViewAction extends Action { }); quickPick.show(); - - return Promise.resolve(); } } diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index 1be2891d59..0ecf173b4b 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -625,18 +625,18 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }, handler: (accessor) => { const focused = accessor.get(IListService).lastFocusedList; + const fakeKeyboardEvent = getSelectionKeyboardEvent('keydown', false); // List if (focused instanceof List || focused instanceof PagedList) { const list = focused; - list.setSelection(list.getFocus()); - list.open(list.getFocus()); + list.setSelection(list.getFocus(), fakeKeyboardEvent); + list.open(list.getFocus(), fakeKeyboardEvent); } // ObjectTree else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; - const fakeKeyboardEvent = getSelectionKeyboardEvent('keydown', false); const focus = list.getFocus(); if (focus.length > 0) { diff --git a/src/vs/workbench/browser/actions/navigationActions.ts b/src/vs/workbench/browser/actions/navigationActions.ts index a7e549aa33..c3a31563c8 100644 --- a/src/vs/workbench/browser/actions/navigationActions.ts +++ b/src/vs/workbench/browser/actions/navigationActions.ts @@ -30,7 +30,7 @@ abstract class BaseNavigationAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const isEditorFocus = this.layoutService.hasFocus(Parts.EDITOR_PART); const isPanelFocus = this.layoutService.hasFocus(Parts.PANEL_PART); const isSidebarFocus = this.layoutService.hasFocus(Parts.SIDEBAR_PART); @@ -39,7 +39,7 @@ abstract class BaseNavigationAction extends Action { if (isEditorFocus) { const didNavigate = this.navigateAcrossEditorGroup(this.toGroupDirection(this.direction)); if (didNavigate) { - return Promise.resolve(true); + return true; } neighborPart = this.layoutService.getVisibleNeighborPart(Parts.EDITOR_PART, this.direction); @@ -54,7 +54,7 @@ abstract class BaseNavigationAction extends Action { } if (neighborPart === Parts.EDITOR_PART) { - return Promise.resolve(this.navigateToEditorGroup(this.direction === Direction.Right ? GroupLocation.FIRST : GroupLocation.LAST)); + return this.navigateToEditorGroup(this.direction === Direction.Right ? GroupLocation.FIRST : GroupLocation.LAST); } if (neighborPart === Parts.SIDEBAR_PART) { @@ -65,7 +65,7 @@ abstract class BaseNavigationAction extends Action { return this.navigateToPanel(); } - return Promise.resolve(false); + return false; } private async navigateToPanel(): Promise { @@ -90,12 +90,12 @@ abstract class BaseNavigationAction extends Action { private async navigateToSidebar(): Promise { if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) { - return Promise.resolve(false); + return false; } const activeViewlet = this.viewletService.getActiveViewlet(); if (!activeViewlet) { - return Promise.resolve(false); + return false; } const activeViewletId = activeViewlet.getId(); diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index d6b5fa19e3..407381d7eb 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -12,7 +12,8 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IsFullscreenContext, IsDevelopmentContext, IsMacNativeContext } from 'vs/workbench/browser/contextkeys'; +import { IsFullscreenContext } from 'vs/workbench/browser/contextkeys'; +import { IsMacNativeContext, IsDevelopmentContext } from 'vs/platform/contextkey/common/contextkeys'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IQuickInputButton, IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 0d56d6bfbf..8c3cfb4368 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -37,7 +37,7 @@ export class OpenFileAction extends Action { super(id, label); } - run(event?: any, data?: ITelemetryData): Promise { + run(event?: unknown, data?: ITelemetryData): Promise { return this.dialogService.pickFileAndOpen({ forceNewWindow: false, telemetryExtraData: data }); } } @@ -55,7 +55,7 @@ export class OpenFolderAction extends Action { super(id, label); } - run(event?: any, data?: ITelemetryData): Promise { + run(event?: unknown, data?: ITelemetryData): Promise { return this.dialogService.pickFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data }); } } @@ -73,7 +73,7 @@ export class OpenFileFolderAction extends Action { super(id, label); } - run(event?: any, data?: ITelemetryData): Promise { + run(event?: unknown, data?: ITelemetryData): Promise { return this.dialogService.pickFileFolderAndOpen({ forceNewWindow: false, telemetryExtraData: data }); } } @@ -91,7 +91,7 @@ export class OpenWorkspaceAction extends Action { super(id, label); } - run(event?: any, data?: ITelemetryData): Promise { + run(event?: unknown, data?: ITelemetryData): Promise { return this.dialogService.pickWorkspaceAndOpen({ telemetryExtraData: data }); } } @@ -139,12 +139,11 @@ export class OpenWorkspaceConfigFileAction extends Action { this.enabled = !!this.workspaceContextService.getWorkspace().configuration; } - run(): Promise { + async run(): Promise { const configuration = this.workspaceContextService.getWorkspace().configuration; if (configuration) { - return this.editorService.openEditor({ resource: configuration }); + await this.editorService.openEditor({ resource: configuration }); } - return Promise.resolve(); } } @@ -161,7 +160,7 @@ export class AddRootFolderAction extends Action { super(id, label); } - run(): Promise { + run(): Promise { return this.commandService.executeCommand(ADD_ROOT_FOLDER_COMMAND_ID); } } @@ -181,7 +180,7 @@ export class GlobalRemoveRootFolderAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const state = this.contextService.getWorkbenchState(); // Workspace / Folder @@ -191,8 +190,6 @@ export class GlobalRemoveRootFolderAction extends Action { await this.workspaceEditingService.removeFolders([folder.uri]); } } - - return true; } } @@ -211,7 +208,7 @@ export class SaveWorkspaceAsAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const configPathUri = await this.workspaceEditingService.pickNewWorkspacePath(); if (configPathUri && hasWorkspaceFileExtension(configPathUri)) { switch (this.contextService.getWorkbenchState()) { @@ -243,7 +240,7 @@ export class DuplicateWorkspaceInNewWindowAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const folders = this.workspaceContextService.getWorkspace().folders; const remoteAuthority = this.environmentService.configuration.remoteAuthority; diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 45bd4ee811..bed352468f 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -6,8 +6,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; -import { IWindowsConfiguration } from 'vs/platform/windows/common/windows'; +import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext } from 'vs/platform/contextkey/common/contextkeys'; import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsReadonlyContext, EditorAreaVisibleContext, DirtyWorkingCopiesContext } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -18,27 +17,15 @@ import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/ import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform'; import { PanelPositionContext } from 'vs/workbench/common/panel'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -export const IsMacContext = new RawContextKey('isMac', isMacintosh); -export const IsLinuxContext = new RawContextKey('isLinux', isLinux); -export const IsWindowsContext = new RawContextKey('isWindows', isWindows); - -export const IsWebContext = new RawContextKey('isWeb', isWeb); -export const IsMacNativeContext = new RawContextKey('isMacNative', isMacintosh && !isWeb); - export const Deprecated_RemoteAuthorityContext = new RawContextKey('remoteAuthority', ''); export const RemoteNameContext = new RawContextKey('remoteName', ''); export const RemoteConnectionState = new RawContextKey<'' | 'initializing' | 'disconnected' | 'connected'>('remoteConnectionState', ''); -export const HasMacNativeTabsContext = new RawContextKey('hasMacNativeTabs', false); - -export const IsDevelopmentContext = new RawContextKey('isDevelopment', false); - export const WorkbenchStateContext = new RawContextKey('workbenchState', undefined); export const WorkspaceFolderCountContext = new RawContextKey('workspaceFolderCount', 0); @@ -98,10 +85,6 @@ export class WorkbenchContextKeysHandler extends Disposable { RemoteNameContext.bindTo(this.contextKeyService).set(getRemoteName(this.environmentService.configuration.remoteAuthority) || ''); - // macOS Native Tabs - const windowConfig = this.configurationService.getValue(); - HasMacNativeTabsContext.bindTo(this.contextKeyService).set(windowConfig?.window?.nativeTabs); - // Development IsDevelopmentContext.bindTo(this.contextKeyService).set(!this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment); @@ -193,13 +176,13 @@ export class WorkbenchContextKeysHandler extends Disposable { private updateEditorContextKeys(): void { const activeGroup = this.editorGroupService.activeGroup; - const activeControl = this.editorService.activeControl; - const visibleEditors = this.editorService.visibleControls; + const activeEditorPane = this.editorService.activeEditorPane; + const visibleEditorPanes = this.editorService.visibleEditorPanes; - this.textCompareEditorActiveContext.set(activeControl?.getId() === TEXT_DIFF_EDITOR_ID); - this.textCompareEditorVisibleContext.set(visibleEditors.some(control => control.getId() === TEXT_DIFF_EDITOR_ID)); + this.textCompareEditorActiveContext.set(activeEditorPane?.getId() === TEXT_DIFF_EDITOR_ID); + this.textCompareEditorVisibleContext.set(visibleEditorPanes.some(editorPane => editorPane.getId() === TEXT_DIFF_EDITOR_ID)); - if (visibleEditors.length > 0) { + if (visibleEditorPanes.length > 0) { this.editorsVisibleContext.set(true); } else { this.editorsVisibleContext.reset(); @@ -221,9 +204,9 @@ export class WorkbenchContextKeysHandler extends Disposable { this.activeEditorGroupIndex.set(activeGroup.index + 1); // not zero-indexed this.activeEditorGroupLast.set(activeGroup.index === groupCount - 1); - if (activeControl) { - this.activeEditorContext.set(activeControl.getId()); - this.activeEditorIsReadonly.set(activeControl.input.isReadonly()); + if (activeEditorPane) { + this.activeEditorContext.set(activeEditorPane.getId()); + this.activeEditorIsReadonly.set(activeEditorPane.input.isReadonly()); } else { this.activeEditorContext.reset(); this.activeEditorIsReadonly.reset(); diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 0688cca89a..2be4b43dc9 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -20,7 +20,7 @@ import { isWindows, isWeb } from 'vs/base/common/platform'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor'; -import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; import { Disposable } from 'vs/base/common/lifecycle'; import { addDisposableListener, EventType, asDomUri } from 'vs/base/browser/dom'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -196,7 +196,7 @@ export class ResourcesDropHandler { this.workspacesService.addRecentlyOpened(recentFiles); } - const editors: IResourceEditor[] = untitledOrFileResources.map(untitledOrFileResource => ({ + const editors: IResourceEditorInputType[] = untitledOrFileResources.map(untitledOrFileResource => ({ resource: untitledOrFileResource.resource, encoding: (untitledOrFileResource as IDraggedEditor).encoding, mode: (untitledOrFileResource as IDraggedEditor).mode, @@ -239,14 +239,14 @@ export class ResourcesDropHandler { // Untitled: always ensure that we open a new untitled editor for each file we drop if (droppedDirtyEditor.resource.scheme === Schemas.untitled) { - const untitledEditorResource = this.editorService.createInput({ mode: droppedDirtyEditor.mode, encoding: droppedDirtyEditor.encoding, forceUntitled: true }).resource; + const untitledEditorResource = this.editorService.createEditorInput({ mode: droppedDirtyEditor.mode, encoding: droppedDirtyEditor.encoding, forceUntitled: true }).resource; if (untitledEditorResource) { droppedDirtyEditor.resource = untitledEditorResource; } } // File: ensure the file is not dirty or opened already - else if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.editorService.isOpen(this.editorService.createInput({ resource: droppedDirtyEditor.resource, forceFile: true }))) { + else if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.editorService.isOpen({ resource: droppedDirtyEditor.resource })) { return false; } @@ -348,12 +348,12 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: // Try to find editor view state from the visible editors that match given resource let viewState: IEditorViewState | undefined = undefined; - const textEditorWidgets = editorService.visibleTextEditorWidgets; - for (const textEditorWidget of textEditorWidgets) { - if (isCodeEditor(textEditorWidget)) { - const model = textEditorWidget.getModel(); + const textEditorControls = editorService.visibleTextEditorControls; + for (const textEditorControl of textEditorControls) { + if (isCodeEditor(textEditorControl)) { + const model = textEditorControl.getModel(); if (model?.uri?.toString() === file.resource.toString()) { - viewState = withNullAsUndefined(textEditorWidget.saveViewState()); + viewState = withNullAsUndefined(textEditorControl.saveViewState()); break; } } diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 9640c07c38..41affd6454 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -6,7 +6,6 @@ import { URI } from 'vs/base/common/uri'; import { dirname, isEqual, basenameOrAuthority } from 'vs/base/common/resources'; import { IconLabel, IIconLabelValueOptions, IIconLabelCreationOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -82,9 +81,9 @@ export class ResourceLabels extends Disposable { constructor( container: IResourceLabelsContainer, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IExtensionService private readonly extensionService: IExtensionService, @IConfigurationService private readonly configurationService: IConfigurationService, @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService, @IDecorationsService private readonly decorationsService: IDecorationsService, @IThemeService private readonly themeService: IThemeService, @ILabelService private readonly labelService: ILabelService, @@ -103,7 +102,7 @@ export class ResourceLabels extends Disposable { })); // notify when extensions are registered with potentially new languages - this._register(this.extensionService.onDidRegisterExtensions(() => this._widgets.forEach(widget => widget.notifyExtensionsRegistered()))); + this._register(this.modeService.onLanguagesMaybeChanged(() => this._widgets.forEach(widget => widget.notifyExtensionsRegistered()))); // notify when model mode changes this._register(this.modelService.onModelModeChanged(e => { @@ -127,7 +126,7 @@ export class ResourceLabels extends Disposable { this._register(this.decorationsService.onDidChangeDecorations(e => this._widgets.forEach(widget => widget.notifyFileDecorationsChanges(e)))); // notify when theme changes - this._register(this.themeService.onThemeChange(() => this._widgets.forEach(widget => widget.notifyThemeChange()))); + this._register(this.themeService.onDidColorThemeChange(() => this._widgets.forEach(widget => widget.notifyThemeChange()))); // notify when files.associations changes this._register(this.configurationService.onDidChangeConfiguration(e => { @@ -137,14 +136,14 @@ export class ResourceLabels extends Disposable { })); // notify when label formatters change - this._register(this.labelService.onDidChangeFormatters(() => { - this._widgets.forEach(widget => widget.notifyFormattersChange()); + this._register(this.labelService.onDidChangeFormatters(e => { + this._widgets.forEach(widget => widget.notifyFormattersChange(e.scheme)); })); // notify when untitled labels change - this.textFileService.untitled.onDidChangeLabel(model => { + this._register(this.textFileService.untitled.onDidChangeLabel(model => { this._widgets.forEach(widget => widget.notifyUntitledLabelChange(model.resource)); - }); + })); } get(index: number): IResourceLabel { @@ -206,15 +205,15 @@ export class ResourceLabel extends ResourceLabels { container: HTMLElement, options: IIconLabelCreationOptions | undefined, @IInstantiationService instantiationService: IInstantiationService, - @IExtensionService extensionService: IExtensionService, @IConfigurationService configurationService: IConfigurationService, @IModelService modelService: IModelService, + @IModeService modeService: IModeService, @IDecorationsService decorationsService: IDecorationsService, @IThemeService themeService: IThemeService, @ILabelService labelService: ILabelService, @ITextFileService textFileService: ITextFileService ) { - super(DEFAULT_LABELS_CONTAINER, instantiationService, extensionService, configurationService, modelService, decorationsService, themeService, labelService, textFileService); + super(DEFAULT_LABELS_CONTAINER, instantiationService, configurationService, modelService, modeService, decorationsService, themeService, labelService, textFileService); this._label = this._register(this.create(container, options)); } @@ -311,8 +310,10 @@ class ResourceLabelWidget extends IconLabel { this.render(true); } - notifyFormattersChange(): void { - this.render(false); + notifyFormattersChange(scheme: string): void { + if (this.label?.resource?.scheme === scheme) { + this.render(false); + } } notifyUntitledLabelChange(resource: URI): void { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 470f1ed230..0d5c7a83b8 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; -import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, Dimension, toggleClass, position, size } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, Dimension, toggleClass, position, size, IDimension } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -27,10 +27,9 @@ import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility } from 'vs/pl import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditor } from 'vs/editor/common/editorCommon'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { SerializableGrid, ISerializableView, ISerializedGrid, Orientation, ISerializedNode, ISerializedLeafNode, Direction, IViewSize } from 'vs/base/browser/ui/grid/grid'; -import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; import { IStatusbarService } from 'vs/workbench/services/statusbar/common/statusbar'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; @@ -118,6 +117,19 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private _container: HTMLElement = document.createElement('div'); get container(): HTMLElement { return this._container; } + get offset() { + return { + top: (() => { + let offset = 0; + if (this.isVisible(Parts.TITLEBAR_PART)) { + offset = this.getPart(Parts.TITLEBAR_PART).maximumHeight; + } + + return offset; + })() + }; + } + private parts: Map = new Map(); private workbenchGrid!: SerializableGrid; @@ -173,7 +185,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi centered: false, restoreCentered: false, restoreEditors: false, - editorsToOpen: [] as Promise | IResourceEditor[] + editorsToOpen: [] as Promise | IResourceEditorInputType[] }, panel: { @@ -274,7 +286,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Theme changes - this._register(this.themeService.onThemeChange(theme => this.updateStyles())); + this._register(this.themeService.onDidColorThemeChange(theme => this.updateStyles())); // Window focus changes this._register(this.hostService.onDidChangeFocus(e => this.onWindowFocusChanged(e))); @@ -403,7 +415,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return; } - const theme = this.themeService.getTheme(); + const theme = this.themeService.getColorTheme(); const activeBorder = theme.getColor(WINDOW_ACTIVE_BORDER); const inactiveBorder = theme.getColor(WINDOW_INACTIVE_BORDER); @@ -514,7 +526,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } - private resolveEditorsToOpen(fileService: IFileService): Promise | IResourceEditor[] { + private resolveEditorsToOpen(fileService: IFileService): Promise | IResourceEditorInputType[] { const configuration = this.environmentService.configuration; const hasInitialFilesToOpen = this.hasInitialFilesToOpen(); @@ -650,17 +662,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return true; // any other part cannot be hidden } - getDimension(part: Parts): Dimension | undefined { - return this.getPart(part).dimension; + focus(): void { + this.editorGroupService.activeGroup.focus(); } - getTitleBarOffset(): number { - let offset = 0; - if (this.isVisible(Parts.TITLEBAR_PART)) { - offset = this.getPart(Parts.TITLEBAR_PART).maximumHeight; - } - - return offset; + getDimension(part: Parts): Dimension | undefined { + return this.getPart(part).dimension; } getMaximumEditorDimensions(): Dimension { @@ -685,10 +692,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return this.parent; } - getWorkbenchElement(): HTMLElement { - return this.container; - } - toggleZenMode(skipLayout?: boolean, restoring = false): void { this.state.zenMode.active = !this.state.zenMode.active; this.state.zenMode.transitionDisposables.clear(); @@ -707,22 +710,22 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi editor.updateOptions({ lineNumbers }); }; - const editorWidgetSet = this.state.zenMode.editorWidgetSet; + const editorControlSet = this.state.zenMode.editorWidgetSet; if (!lineNumbers) { // Reset line numbers on all editors visible and non-visible - for (const editor of editorWidgetSet) { + for (const editor of editorControlSet) { setEditorLineNumbers(editor); } - editorWidgetSet.clear(); + editorControlSet.clear(); } else { - this.editorService.visibleTextEditorWidgets.forEach(editor => { - if (!editorWidgetSet.has(editor)) { - editorWidgetSet.add(editor); - this.state.zenMode.transitionDisposables.add(editor.onDidDispose(() => { - editorWidgetSet.delete(editor); + this.editorService.visibleTextEditorControls.forEach(editorControl => { + if (!editorControlSet.has(editorControl)) { + editorControlSet.add(editorControl); + this.state.zenMode.transitionDisposables.add(editorControl.onDidDispose(() => { + editorControlSet.delete(editorControl); })); } - setEditorLineNumbers(editor); + setEditorLineNumbers(editorControl); }); } }; @@ -806,7 +809,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Status bar and activity bar visibility come from settings -> update their visibility. this.doUpdateLayoutConfiguration(true); - this.editorGroupService.activeGroup.focus(); + this.focus(); if (this.state.zenMode.setNotificationsFilter) { this.notificationService.setFilter(NotificationsFilter.OFF); } @@ -1090,7 +1093,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (this.hasFocus(Parts.PANEL_PART) && activePanel) { activePanel.focus(); } else { - this.editorGroupService.activeGroup.focus(); + this.focus(); } } diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index a1148223f7..ea4041b969 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -23,9 +23,9 @@ .linux:lang(ja) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; } .linux:lang(ko) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } -.mac { --monaco-monospace-font: "SF Mono", Monaco, Menlo, Inconsolata, "Courier New", monospace; } -.windows { --monaco-monospace-font: Consolas, Inconsolata, "Courier New", monospace; } -.linux { --monaco-monospace-font: "Droid Sans Mono", Inconsolata, "Courier New", monospace, "Droid Sans Fallback"; } +.mac { --monaco-monospace-font: "SF Mono", Monaco, Menlo, Courier, monospace; } +.windows { --monaco-monospace-font: Consolas, "Courier New", monospace; } +.linux { --monaco-monospace-font: "Ubuntu Mono", "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace; } /* Global Styles */ diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index 7d4f48d664..4d2f4f2f4d 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -101,7 +101,7 @@ export abstract class TogglePanelAction extends Action { super(id, label, cssClass); } - async run(): Promise { + async run(): Promise { if (this.isPanelFocused()) { this.layoutService.setPanelHidden(true); } else { diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index 6ea2a75eb7..0222784e2e 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -5,10 +5,9 @@ import 'vs/css!./media/part'; import { Component } from 'vs/workbench/common/component'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; -import { Dimension, size } from 'vs/base/browser/dom'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; +import { Dimension, size, IDimension } from 'vs/base/browser/dom'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { ISerializableView, IViewSize } from 'vs/base/browser/ui/grid/grid'; import { Event, Emitter } from 'vs/base/common/event'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -53,7 +52,7 @@ export abstract class Part extends Component implements ISerializableView { layoutService.registerPart(this); } - protected onThemeChange(theme: ITheme): void { + protected onThemeChange(theme: IColorTheme): void { // only call if our create() method has been called if (this.parent) { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 8d6d2136ad..39fc5eead3 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -17,7 +17,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; -import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; @@ -74,15 +74,15 @@ export class ViewletActivityAction extends ActivityAction { this.activity = activity; } - async run(event: any): Promise { + async run(event: unknown): Promise { if (event instanceof MouseEvent && event.button === 2) { - return false; // do not run on right click + return; // do not run on right click } // prevent accident trigger on a doubleclick (to help nervous people) const now = Date.now(); if (now > this.lastRun /* https://github.com/Microsoft/vscode/issues/25830 */ && now - this.lastRun < ViewletActivityAction.preventDoubleClickDelay) { - return true; + return; } this.lastRun = now; @@ -93,7 +93,7 @@ export class ViewletActivityAction extends ActivityAction { if (sideBarVisible && activeViewlet?.getId() === this.activity.id) { this.logAction('hide'); this.layoutService.setSideBarHidden(true); - return true; + return; } this.logAction('show'); @@ -120,17 +120,17 @@ export class ToggleViewletAction extends Action { super(_viewlet.id, _viewlet.name); } - run(): Promise { + async run(): Promise { const sideBarVisible = this.layoutService.isVisible(Parts.SIDEBAR_PART); const activeViewlet = this.viewletService.getActiveViewlet(); // Hide sidebar if selected viewlet already visible if (sideBarVisible && activeViewlet?.getId() === this._viewlet.id) { this.layoutService.setSideBarHidden(true); - return Promise.resolve(); + return; } - return this.viewletService.openViewlet(this._viewlet.id, true); + await this.viewletService.openViewlet(this._viewlet.id, true); } } @@ -138,7 +138,7 @@ export class GlobalActivityActionViewItem extends ActivityActionViewItem { constructor( action: ActivityAction, - colors: (theme: ITheme) => ICompositeBarColors, + colors: (theme: IColorTheme) => ICompositeBarColors, @IThemeService themeService: IThemeService, @IMenuService private readonly menuService: IMenuService, @IContextMenuService protected contextMenuService: IContextMenuService, @@ -226,7 +226,7 @@ class SwitchSideBarViewAction extends Action { super(id, name); } - run(offset: number): Promise { + async run(offset: number): Promise { const pinnedViewletIds = this.activityBarService.getPinnedViewletIds(); const activeViewlet = this.viewletService.getActiveViewlet(); @@ -240,7 +240,8 @@ class SwitchSideBarViewAction extends Action { break; } } - return this.viewletService.openViewlet(targetViewletId, true); + + await this.viewletService.openViewlet(targetViewletId, true); } } @@ -258,7 +259,7 @@ export class PreviousSideBarViewAction extends SwitchSideBarViewAction { super(id, name, viewletService, activityBarService); } - run(): Promise { + run(): Promise { return super.run(-1); } } @@ -277,7 +278,7 @@ export class NextSideBarViewAction extends SwitchSideBarViewAction { super(id, name, viewletService, activityBarService); } - run(): Promise { + run(): Promise { return super.run(1); } } @@ -286,7 +287,7 @@ const registry = Registry.as(ActionExtensions.Workbenc registry.registerWorkbenchAction(SyncActionDescriptor.create(PreviousSideBarViewAction, PreviousSideBarViewAction.ID, PreviousSideBarViewAction.LABEL), 'View: Previous Side Bar View', nls.localize('view', "View")); registry.registerWorkbenchAction(SyncActionDescriptor.create(NextSideBarViewAction, NextSideBarViewAction.ID, NextSideBarViewAction.LABEL), 'View: Next Side Bar View', nls.localize('view', "View")); -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const activeForegroundColor = theme.getColor(ACTIVITY_BAR_FOREGROUND); if (activeForegroundColor) { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 78cdd0d161..ee5d31b6cc 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -16,7 +16,7 @@ import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from 'vs/ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction } from 'vs/workbench/browser/actions/layoutActions'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar'; @@ -134,7 +134,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { () => this.getPinnedViewletIds() ), compositeSize: 50, - colors: (theme: ITheme) => this.getActivitybarItemColors(theme), + colors: (theme: IColorTheme) => this.getActivitybarItemColors(theme), overflowActionSize: ActivitybarPart.ACTION_HEIGHT })); @@ -339,7 +339,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { container.style.borderLeftColor = !isPositionLeft ? borderColor : ''; } - private getActivitybarItemColors(theme: ITheme): ICompositeBarColors { + private getActivitybarItemColors(theme: IColorTheme): ICompositeBarColors { return { activeForegroundColor: theme.getColor(ACTIVITY_BAR_FOREGROUND), inactiveForegroundColor: theme.getColor(ACTIVITY_BAR_INACTIVE_FOREGROUND), @@ -354,7 +354,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private createGlobalActivityActionBar(container: HTMLElement): void { this.globalActivityActionBar = this._register(new ActionBar(container, { - actionViewItemProvider: action => this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, (theme: ITheme) => this.getActivitybarItemColors(theme)), + actionViewItemProvider: action => this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme)), orientation: ActionsOrientation.VERTICAL, ariaLabel: nls.localize('manage', "Manage"), animated: false diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 93b997a453..74d07dbd70 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -18,13 +18,14 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { Widget } from 'vs/base/browser/ui/widget'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { LocalSelectionTransfer, DragAndDropObserver } from 'vs/workbench/browser/dnd'; -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { Emitter } from 'vs/base/common/event'; import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { Registry } from 'vs/platform/registry/common/platform'; import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; import { ICompositeDragAndDrop, CompositeDragAndDropData } from 'vs/base/parts/composite/browser/compositeDnd'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; +import { IComposite } from 'vs/workbench/common/composite'; export interface ICompositeBarItem { id: string; @@ -80,7 +81,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { if (targetCompositeId) { const destinationContainer = viewContainerRegistry.get(targetCompositeId); if (destinationContainer && !destinationContainer.rejectAddedViews) { - if (this.targetContainerLocation === ViewContainerLocation.Sidebar) { + if (this.targetContainerLocation === ViewContainerLocation.Sidebar || this.targetContainerLocation === ViewContainerLocation.Panel) { this.viewDescriptorService.moveViewsToContainer([viewDescriptor], destinationContainer); this.openComposite(targetCompositeId, true).then(composite => { if (composite) { @@ -187,7 +188,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { export interface ICompositeBarOptions { readonly icon: boolean; readonly orientation: ActionsOrientation; - readonly colors: (theme: ITheme) => ICompositeBarColors; + readonly colors: (theme: IColorTheme) => ICompositeBarColors; readonly compositeSize: number; readonly overflowActionSize: number; readonly dndHandler: ICompositeDragAndDrop; @@ -197,7 +198,7 @@ export interface ICompositeBarOptions { getOnCompositeClickAction: (compositeId: string) => Action; getContextMenuActions: () => Action[]; getContextMenuActionsForComposite: (compositeId: string) => Action[]; - openComposite: (compositeId: string) => Promise; + openComposite: (compositeId: string) => Promise; getDefaultCompositeId: () => string; hidePart: () => void; } @@ -476,7 +477,7 @@ export class CompositeBar extends Widget implements ICompositeBar { } private updateFromDragging(element: HTMLElement, isDragging: boolean): void { - const theme = this.themeService.getTheme(); + const theme = this.themeService.getColorTheme(); const dragBackground = this.options.colors(theme).dragAndDropBackground; element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : ''; diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 29882993da..c7ca75eb87 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -10,7 +10,7 @@ import { BaseActionViewItem, IBaseActionViewItemOptions, Separator } from 'vs/ba import { ICommandService } from 'vs/platform/commands/common/commands'; import { dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -124,7 +124,7 @@ export interface ICompositeBarColors { export interface IActivityActionViewItemOptions extends IBaseActionViewItemOptions { icon?: boolean; - colors: (theme: ITheme) => ICompositeBarColors; + colors: (theme: IColorTheme) => ICompositeBarColors; } export class ActivityActionViewItem extends BaseActionViewItem { @@ -144,7 +144,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { ) { super(null, action, options); - this._register(this.themeService.onThemeChange(this.onThemeChange, this)); + this._register(this.themeService.onDidColorThemeChange(this.onThemeChange, this)); this._register(action.onDidChangeActivity(this.updateActivity, this)); this._register(action.onDidChangeBadge(this.updateBadge, this)); } @@ -154,7 +154,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { } protected updateStyles(): void { - const theme = this.themeService.getTheme(); + const theme = this.themeService.getColorTheme(); const colors = this.options.colors(theme); if (this.label) { @@ -233,7 +233,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { this.updateStyles(); } - private onThemeChange(theme: ITheme): void { + private onThemeChange(theme: IColorTheme): void { this.updateStyles(); } @@ -364,10 +364,8 @@ export class CompositeOverflowActivityAction extends ActivityAction { }); } - run(event: any): Promise { + async run(): Promise { this.showMenu(); - - return Promise.resolve(true); } } @@ -380,7 +378,7 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI private getActiveCompositeId: () => string | undefined, private getBadge: (compositeId: string) => IBadge, private getCompositeOpenAction: (compositeId: string) => Action, - colors: (theme: ITheme) => ICompositeBarColors, + colors: (theme: IColorTheme) => ICompositeBarColors, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IThemeService themeService: IThemeService ) { @@ -442,7 +440,7 @@ class ManageExtensionAction extends Action { super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension")); } - run(id: string): Promise { + run(id: string): Promise { return this.commandService.executeCommand('_extensions.manage', id); } } @@ -467,7 +465,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { private toggleCompositePinnedAction: Action, private compositeContextMenuActionsProvider: (compositeId: string) => ReadonlyArray, private contextMenuActionsProvider: () => ReadonlyArray, - colors: (theme: ITheme) => ICompositeBarColors, + colors: (theme: IColorTheme) => ICompositeBarColors, icon: boolean, private dndHandler: ICompositeDragAndDrop, private compositeBar: ICompositeBar, @@ -640,7 +638,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { } private updateFromDragging(element: HTMLElement, isDragging: boolean): void { - const theme = this.themeService.getTheme(); + const theme = this.themeService.getColorTheme(); const dragBackground = this.options.colors(theme).dragAndDropBackground; element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : ''; @@ -733,7 +731,7 @@ export class ToggleCompositePinnedAction extends Action { this.checked = !!this.activity && this.compositeBar.isPinned(this.activity.id); } - run(context: string): Promise { + async run(context: string): Promise { const id = this.activity ? this.activity.id : context; if (this.compositeBar.isPinned(id)) { @@ -741,7 +739,5 @@ export class ToggleCompositePinnedAction extends Action { } else { this.compositeBar.pin(id); } - - return Promise.resolve(true); } } diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index e972355616..4cc61fcead 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -169,7 +169,7 @@ export abstract class CompositePart extends Part { // Instantiate composite from registry otherwise const compositeDescriptor = this.registry.getComposite(id); if (compositeDescriptor) { - const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), compositeDescriptor.id, !!isActive); + const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), compositeDescriptor.id, !!isActive, undefined); const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection( [IEditorProgressService, compositeProgressIndicator] // provide the editor progress service for any editors instantiated within the composite )); diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index 631c1361c9..85480af904 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Panel } from 'vs/workbench/browser/panel'; -import { EditorInput, EditorOptions, IEditor, GroupIdentifier, IEditorMemento } from 'vs/workbench/common/editor'; +import { Composite } from 'vs/workbench/browser/composite'; +import { EditorInput, EditorOptions, IEditorPane, GroupIdentifier, IEditorMemento } from 'vs/workbench/common/editor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -33,9 +33,9 @@ import { indexOfPath } from 'vs/base/common/extpath'; * * This class is only intended to be subclassed and not instantiated. */ -export abstract class BaseEditor extends Panel implements IEditor { +export abstract class BaseEditor extends Composite implements IEditorPane { - private static readonly EDITOR_MEMENTOS: Map> = new Map>(); + private static readonly EDITOR_MEMENTOS = new Map>(); readonly minimumWidth = DEFAULT_EDITOR_MIN_DIMENSIONS.width; readonly maximumWidth = DEFAULT_EDITOR_MAX_DIMENSIONS.width; @@ -45,9 +45,12 @@ export abstract class BaseEditor extends Panel implements IEditor { readonly onDidSizeConstraintsChange = Event.None; protected _input: EditorInput | undefined; + get input(): EditorInput | undefined { return this._input; } + protected _options: EditorOptions | undefined; private _group?: IEditorGroup; + get group(): IEditorGroup | undefined { return this._group; } constructor( id: string, @@ -58,18 +61,6 @@ export abstract class BaseEditor extends Panel implements IEditor { super(id, telemetryService, themeService, storageService); } - get input(): EditorInput | undefined { - return this._input; - } - - get options(): EditorOptions | undefined { - return this._options; - } - - get group(): IEditorGroup | undefined { - return this._group; - } - /** * Note: Clients should not call this method, the workbench calls this * method. Calling it otherwise may result in unexpected behavior. @@ -179,17 +170,13 @@ export class EditorMemento implements IEditorMemento { private cleanedUp = false; constructor( - private _id: string, + public readonly id: string, private key: string, private memento: MementoObject, private limit: number, private editorGroupService: IEditorGroupsService ) { } - get id(): string { - return this._id; - } - saveEditorState(group: IEditorGroup, resource: URI, state: T): void; saveEditorState(group: IEditorGroup, editor: EditorInput, state: T): void; saveEditorState(group: IEditorGroup, resourceOrEditor: URI | EditorInput, state: T): void { diff --git a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts index 4321305812..ea1ddff2da 100644 --- a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts @@ -29,8 +29,8 @@ export class BinaryResourceDiffEditor extends SideBySideEditor { } getMetadata(): string | undefined { - const master = this.masterEditor; - const details = this.detailsEditor; + const master = this.masterEditorPane; + const details = this.detailsEditorPane; if (master instanceof BaseBinaryResourceEditor && details instanceof BaseBinaryResourceEditor) { return nls.localize('metadataDiff', "{0} ↔ {1}", details.getMetadata(), master.getMetadata()); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index f5c0169ec9..a69f99a3f6 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -290,10 +290,10 @@ export class BreadcrumbsControl { } private _getActiveCodeEditor(): ICodeEditor | undefined { - if (!this._editorGroup.activeControl) { + if (!this._editorGroup.activeEditorPane) { return undefined; } - let control = this._editorGroup.activeControl.getControl(); + let control = this._editorGroup.activeEditorPane.getControl(); let editor: ICodeEditor | undefined; if (isCodeEditor(control)) { editor = control as ICodeEditor; @@ -715,8 +715,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } widget.setFocused(undefined); widget.setSelection(undefined); - if (groups.activeGroup.activeControl) { - groups.activeGroup.activeControl.focus(); + if (groups.activeGroup.activeEditorPane) { + groups.activeGroup.activeEditorPane.focus(); } } }); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 21eacce38d..d3b9fd6a87 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -24,10 +24,11 @@ import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platf import { ResourceLabels, IResourceLabel, DEFAULT_LABELS_CONTAINER } from 'vs/workbench/browser/labels'; import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; -import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; + import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; import { OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItemComparator, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineDataSource, OutlineSortOrder, OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; import { IIdentityProvider, IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list'; +import { IFileIconTheme, IThemeService } from 'vs/platform/theme/common/themeService'; export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker { return element instanceof FileElement @@ -69,7 +70,7 @@ export abstract class BreadcrumbsPicker { constructor( parent: HTMLElement, @IInstantiationService protected readonly _instantiationService: IInstantiationService, - @IWorkbenchThemeService protected readonly _themeService: IWorkbenchThemeService, + @IThemeService protected readonly _themeService: IThemeService, @IConfigurationService protected readonly _configurationService: IConfigurationService, ) { this._domNode = document.createElement('div'); @@ -86,7 +87,7 @@ export abstract class BreadcrumbsPicker { show(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): void { - const theme = this._themeService.getTheme(); + const theme = this._themeService.getColorTheme(); const color = theme.getColor(breadcrumbsPickerBackground); this._arrow = document.createElement('div'); @@ -97,7 +98,7 @@ export abstract class BreadcrumbsPicker { this._treeContainer = document.createElement('div'); this._treeContainer.style.background = color ? color.toString() : ''; this._treeContainer.style.paddingTop = '2px'; - this._treeContainer.style.boxShadow = `0px 5px 8px ${this._themeService.getTheme().getColor(widgetShadow)}`; + this._treeContainer.style.boxShadow = `0px 5px 8px ${this._themeService.getColorTheme().getColor(widgetShadow)}`; this._domNode.appendChild(this._treeContainer); this._layoutInfo = { maxHeight, width, arrowSize, arrowOffset, inputHeight: 0 }; @@ -351,7 +352,7 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { constructor( parent: HTMLElement, @IInstantiationService instantiationService: IInstantiationService, - @IWorkbenchThemeService themeService: IWorkbenchThemeService, + @IThemeService themeService: IThemeService, @IConfigurationService configService: IConfigurationService, @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, ) { @@ -433,7 +434,7 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { constructor( parent: HTMLElement, @IInstantiationService instantiationService: IInstantiationService, - @IWorkbenchThemeService themeService: IWorkbenchThemeService, + @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, ) { super(parent, instantiationService, themeService, configurationService); diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 1051e9f838..1a8bc3867b 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -42,7 +42,7 @@ import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommand import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { getQuickNavigateHandler, inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickopen'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { isMacintosh } from 'vs/base/common/platform'; import { AllEditorsByAppearancePicker, ActiveGroupEditorsByMostRecentlyUsedPicker, AllEditorsByMostRecentlyUsedPicker } from 'vs/workbench/browser/parts/editor/editorPicker'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; @@ -56,6 +56,8 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura import { EditorAutoSave } from 'vs/workbench/browser/parts/editor/editorAutoSave'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; +import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess, AllEditorsByAppearanceQuickAccess, AllEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; // Register String Editor Registry.as(EditorExtensions.Editors).registerEditor( @@ -163,7 +165,7 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory { const mode = deserialized.modeId; const encoding = deserialized.encoding; - return accessor.get(IEditorService).createInput({ resource, mode, encoding, forceUntitled: true }) as UntitledTextEditorInput; + return accessor.get(IEditorService).createEditorInput({ resource, mode, encoding, forceUntitled: true }) as UntitledTextEditorInput; }); } } @@ -273,13 +275,13 @@ export class QuickOpenActionContributor extends ActionBarContributor { super(); } - hasActions(context: any): boolean { + hasActions(context: unknown): boolean { const entry = this.getEntry(context); return !!entry; } - getActions(context: any): ReadonlyArray { + getActions(context: unknown): ReadonlyArray { const actions: Action[] = []; const entry = this.getEntry(context); @@ -359,6 +361,33 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen ) ); +// Register Editor Quick Access +const quickAccessRegistry = Registry.as(QuickAccessExtensions.Quickaccess); + +quickAccessRegistry.registerQuickAccessProvider({ + ctor: ActiveGroupEditorsByMostRecentlyUsedQuickAccess, + prefix: ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX, + contextKey: editorPickerContextKey, + placeholder: nls.localize('editorQuickAccessPlaceholder', "Type the name of an editor to open it."), + helpEntries: [{ description: nls.localize('activeGroupEditorsByMostRecentlyUsedQuickAccess', "Show Editors in Active Group by Most Recently Used."), needsEditor: false }] +}); + +quickAccessRegistry.registerQuickAccessProvider({ + ctor: AllEditorsByAppearanceQuickAccess, + prefix: AllEditorsByAppearanceQuickAccess.PREFIX, + contextKey: editorPickerContextKey, + placeholder: nls.localize('editorQuickAccessPlaceholder', "Type the name of an editor to open it."), + helpEntries: [{ description: nls.localize('allEditorsByAppearanceQuickAccess', "Show All Opened Editors By Appearance"), needsEditor: false }] +}); + +quickAccessRegistry.registerQuickAccessProvider({ + ctor: AllEditorsByMostRecentlyUsedQuickAccess, + prefix: AllEditorsByMostRecentlyUsedQuickAccess.PREFIX, + contextKey: editorPickerContextKey, + placeholder: nls.localize('editorQuickAccessPlaceholder', "Type the name of an editor to open it."), + helpEntries: [{ description: nls.localize('allEditorsByMostRecentlyUsedQuickAccess', "Show All Opened Editors By Most Recently Used"), needsEditor: false }] +}); + // Register Editor Actions const category = nls.localize('view', "View"); registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenNextEditor, OpenNextEditor.ID, OpenNextEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET] } }), 'View: Open Next Editor', category); @@ -511,7 +540,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands. interface IEditorToolItem { id: string; title: string; icon?: { dark?: URI; light?: URI; } | ThemeIcon; } -function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpr | undefined, order: number, alternative?: IEditorToolItem, precondition?: ContextKeyExpr | undefined): void { +function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpression | undefined, order: number, alternative?: IEditorToolItem, precondition?: ContextKeyExpression | undefined): void { const item: IMenuItem = { command: { id: primary.id, diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 565633a654..d42e5a247e 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditor, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorInput } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditorPane, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorInput } from 'vs/workbench/common/editor'; import { EditorGroup } from 'vs/workbench/common/editor/editorGroup'; import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -14,7 +14,7 @@ import { IConfigurationChangeEvent } from 'vs/platform/configuration/common/conf import { ISerializableView } from 'vs/base/browser/ui/grid/grid'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; -import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; export const EDITOR_TITLE_HEIGHT = 35; @@ -64,6 +64,10 @@ export function getEditorPartOptions(config: IWorkbenchEditorConfiguration): IEd } export interface IEditorOpeningEvent extends IEditorIdentifier { + + /** + * The options used when opening the editor. + */ options?: IEditorOptions; /** @@ -73,7 +77,7 @@ export interface IEditorOpeningEvent extends IEditorIdentifier { * to return a promise that resolves to `undefined` to prevent the opening * alltogether. */ - prevent(callback: () => undefined | Promise): void; + prevent(callback: () => undefined | Promise): void; } export interface IEditorGroupsAccessor { @@ -126,7 +130,7 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito } export function getActiveTextEditorOptions(group: IEditorGroup, expectedActiveEditor?: IEditorInput, presetOptions?: EditorOptions): EditorOptions { - const activeGroupCodeEditor = group.activeControl ? getCodeEditor(group.activeControl.getControl()) : undefined; + const activeGroupCodeEditor = group.activeEditorPane ? getCodeEditor(group.activeEditorPane.getControl()) : undefined; if (activeGroupCodeEditor) { if (!expectedActiveEditor || expectedActiveEditor.matches(group.activeEditor)) { return TextEditorOptions.fromEditor(activeGroupCodeEditor, presetOptions); @@ -160,5 +164,5 @@ export interface EditorServiceImpl extends IEditorService { /** * Override to return a typed `EditorInput`. */ - createInput(input: IResourceEditor): EditorInput; + createEditorInput(input: IResourceEditorInputType): EditorInput; } diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 1e05a85500..ff72f343b7 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -11,7 +11,7 @@ import { QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenMo import { EditorQuickOpenEntry, EditorQuickOpenEntryGroup, IEditorQuickOpenEntry, QuickOpenAction } from 'vs/workbench/browser/quickopen'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -37,7 +37,7 @@ export class ExecuteCommandAction extends Action { super(id, label); } - run(): Promise { + run(): Promise { return this.commandService.executeCommand(this.commandId, this.commandArgs); } } @@ -71,7 +71,7 @@ export class BaseSplitEditorAction extends Action { })); } - async run(context?: IEditorIdentifier): Promise { + async run(context?: IEditorIdentifier): Promise { splitEditor(this.editorGroupService, this.direction, context); } } @@ -181,7 +181,7 @@ export class JoinTwoGroupsAction extends Action { super(id, label); } - async run(context?: IEditorIdentifier): Promise { + async run(context?: IEditorIdentifier): Promise { let sourceGroup: IEditorGroup | undefined; if (context && typeof context.groupId === 'number') { sourceGroup = this.editorGroupService.getGroup(context.groupId); @@ -216,7 +216,7 @@ export class JoinAllGroupsAction extends Action { super(id, label); } - async run(context?: IEditorIdentifier): Promise { + async run(): Promise { mergeAllGroups(this.editorGroupService); } } @@ -234,7 +234,7 @@ export class NavigateBetweenGroupsAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const nextGroup = this.editorGroupService.findGroup({ location: GroupLocation.NEXT }, this.editorGroupService.activeGroup, true); nextGroup.focus(); } @@ -253,7 +253,7 @@ export class FocusActiveGroupAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { this.editorGroupService.activeGroup.focus(); } } @@ -269,7 +269,7 @@ export abstract class BaseFocusGroupAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const group = this.editorGroupService.findGroup(this.scope, this.editorGroupService.activeGroup, true); if (group) { group.focus(); @@ -409,25 +409,26 @@ export class OpenToSideFromQuickOpenAction extends Action { this.class = (preferredDirection === GroupDirection.RIGHT) ? 'codicon-split-horizontal' : 'codicon-split-vertical'; } - async run(context: any): Promise { + async run(context: unknown): Promise { const entry = toEditorQuickOpenEntry(context); if (entry) { const input = entry.getInput(); if (input) { if (input instanceof EditorInput) { - return this.editorService.openEditor(input, entry.getOptions(), SIDE_GROUP); + await this.editorService.openEditor(input, entry.getOptions(), SIDE_GROUP); + return; } - const resourceInput = input as IResourceInput; - resourceInput.options = mixin(resourceInput.options, entry.getOptions()); + const resourceEditorInput = input as IResourceEditorInput; + resourceEditorInput.options = mixin(resourceEditorInput.options, entry.getOptions()); - return this.editorService.openEditor(resourceInput, SIDE_GROUP); + await this.editorService.openEditor(resourceEditorInput, SIDE_GROUP); } } } } -export function toEditorQuickOpenEntry(element: any): IEditorQuickOpenEntry | null { +export function toEditorQuickOpenEntry(element: unknown): IEditorQuickOpenEntry | null { // QuickOpenEntryGroup if (element instanceof QuickOpenEntryGroup) { @@ -458,7 +459,7 @@ export class CloseEditorAction extends Action { super(id, label, 'codicon-close'); } - run(context?: IEditorCommandsContext): Promise { + run(context?: IEditorCommandsContext): Promise { return this.commandService.executeCommand(CLOSE_EDITOR_COMMAND_ID, undefined, context); } } @@ -476,7 +477,7 @@ export class CloseOneEditorAction extends Action { super(id, label, 'codicon-close'); } - async run(context?: IEditorCommandsContext): Promise { + async run(context?: IEditorCommandsContext): Promise { let group: IEditorGroup | undefined; let editorIndex: number | undefined; if (context) { @@ -519,11 +520,11 @@ export class RevertAndCloseEditorAction extends Action { super(id, label); } - async run(): Promise { - const activeControl = this.editorService.activeControl; - if (activeControl) { - const editor = activeControl.input; - const group = activeControl.group; + async run(): Promise { + const activeEditorPane = this.editorService.activeEditorPane; + if (activeEditorPane) { + const editor = activeEditorPane.input; + const group = activeEditorPane.group; // first try a normal revert where the contents of the editor are restored try { @@ -555,7 +556,7 @@ export class CloseLeftEditorsInGroupAction extends Action { super(id, label); } - async run(context?: IEditorIdentifier): Promise { + async run(context?: IEditorIdentifier): Promise { const { group, editor } = getTarget(this.editorService, this.editorGroupService, context); if (group && editor) { return group.closeEditors({ direction: CloseDirection.LEFT, except: editor }); @@ -600,7 +601,7 @@ export abstract class BaseCloseAllAction extends Action { return groupsToClose; } - async run(): Promise { + async run(): Promise { // Just close all if there are no dirty editors if (!this.workingCopyService.hasDirty) { @@ -653,7 +654,7 @@ export abstract class BaseCloseAllAction extends Action { } } - protected abstract doCloseAll(): Promise; + protected abstract doCloseAll(): Promise; } export class CloseAllEditorsAction extends BaseCloseAllAction { @@ -672,8 +673,8 @@ export class CloseAllEditorsAction extends BaseCloseAllAction { super(id, label, 'codicon-close-all', workingCopyService, fileDialogService, editorGroupService, editorService); } - protected doCloseAll(): Promise { - return Promise.all(this.groupsToClose.map(g => g.closeAllEditors())); + protected async doCloseAll(): Promise { + await Promise.all(this.groupsToClose.map(g => g.closeAllEditors())); } } @@ -693,7 +694,7 @@ export class CloseAllEditorGroupsAction extends BaseCloseAllAction { super(id, label, undefined, workingCopyService, fileDialogService, editorGroupService, editorService); } - protected async doCloseAll(): Promise { + protected async doCloseAll(): Promise { await Promise.all(this.groupsToClose.map(group => group.closeAllEditors())); this.groupsToClose.forEach(group => this.editorGroupService.removeGroup(group)); @@ -713,9 +714,9 @@ export class CloseEditorsInOtherGroupsAction extends Action { super(id, label); } - run(context?: IEditorIdentifier): Promise { + async run(context?: IEditorIdentifier): Promise { const groupToSkip = context ? this.editorGroupService.getGroup(context.groupId) : this.editorGroupService.activeGroup; - return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(async g => { + await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(async g => { if (groupToSkip && g.id === groupToSkip.id) { return; } @@ -739,10 +740,10 @@ export class CloseEditorInAllGroupsAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const activeEditor = this.editorService.activeEditor; if (activeEditor) { - return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => g.closeEditor(activeEditor))); + await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => g.closeEditor(activeEditor))); } } } @@ -758,7 +759,7 @@ export class BaseMoveGroupAction extends Action { super(id, label); } - async run(context?: IEditorIdentifier): Promise { + async run(context?: IEditorIdentifier): Promise { let sourceGroup: IEditorGroup | undefined; if (context && typeof context.groupId === 'number') { sourceGroup = this.editorGroupService.getGroup(context.groupId); @@ -867,7 +868,7 @@ export class MinimizeOtherGroupsAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS); } } @@ -881,7 +882,7 @@ export class ResetGroupSizesAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { this.editorGroupService.arrangeGroups(GroupsArrangement.EVEN); } } @@ -895,7 +896,7 @@ export class ToggleGroupSizesAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { this.editorGroupService.arrangeGroups(GroupsArrangement.TOGGLE); } } @@ -915,7 +916,7 @@ export class MaximizeGroupAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { if (this.editorService.activeEditor) { this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS); this.layoutService.setSideBarHidden(true); @@ -934,7 +935,7 @@ export abstract class BaseNavigateEditorAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const result = this.navigate(); if (!result) { return; @@ -947,7 +948,7 @@ export abstract class BaseNavigateEditorAction extends Action { const group = this.editorGroupService.getGroup(groupId); if (group) { - return group.openEditor(editor); + await group.openEditor(editor); } } @@ -1123,7 +1124,7 @@ export class NavigateForwardAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { this.historyService.forward(); } } @@ -1137,7 +1138,7 @@ export class NavigateBackwardsAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { this.historyService.back(); } } @@ -1151,7 +1152,7 @@ export class NavigateToLastEditLocationAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { this.historyService.openLastEditLocation(); } } @@ -1165,7 +1166,7 @@ export class NavigateLastAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { this.historyService.last(); } } @@ -1183,7 +1184,7 @@ export class ReopenClosedEditorAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { this.historyService.reopenLastClosedEditor(); } } @@ -1202,7 +1203,7 @@ export class ClearRecentFilesAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { // Clear global recently opened this.workspacesService.clearRecentlyOpened(); @@ -1266,7 +1267,7 @@ export class BaseQuickOpenEditorAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const keybindings = this.keybindingService.lookupKeybindings(this.id); this.quickOpenService.show(this.prefix, { quickNavigateConfiguration: { keybindings } }); @@ -1347,7 +1348,7 @@ export class QuickOpenPreviousEditorFromHistoryAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const keybindings = this.keybindingService.lookupKeybindings(this.id); this.quickOpenService.show(undefined, { quickNavigateConfiguration: { keybindings } }); @@ -1367,7 +1368,7 @@ export class OpenNextRecentlyUsedEditorAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { this.historyService.openNextRecentlyUsedEditor(); } } @@ -1385,7 +1386,7 @@ export class OpenPreviousRecentlyUsedEditorAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { this.historyService.openPreviouslyUsedEditor(); } } @@ -1404,7 +1405,7 @@ export class OpenNextRecentlyUsedEditorInGroupAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { this.historyService.openNextRecentlyUsedEditor(this.editorGroupsService.activeGroup.id); } } @@ -1423,7 +1424,7 @@ export class OpenPreviousRecentlyUsedEditorInGroupAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { this.historyService.openPreviouslyUsedEditor(this.editorGroupsService.activeGroup.id); } } @@ -1441,7 +1442,7 @@ export class ClearEditorHistoryAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { // Editor history this.historyService.clear(); @@ -1711,7 +1712,7 @@ export class BaseCreateEditorGroupAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { this.editorGroupService.addGroup(this.editorGroupService.activeGroup, this.direction, { activate: true }); } } diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts index 0ae77e6e26..da99ef5091 100644 --- a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts +++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -78,9 +78,9 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution this.lastActiveEditorControlDisposable.clear(); // Listen to focus changes on control for auto save - const activeEditorControl = this.editorService.activeControl; - if (activeEditor && activeEditorControl) { - this.lastActiveEditorControlDisposable.add(activeEditorControl.onDidBlur(() => { + const activeEditorPane = this.editorService.activeEditorPane; + if (activeEditor && activeEditorPane) { + this.lastActiveEditorControlDisposable.add(activeEditorPane.onDidBlur(() => { this.maybeTriggerAutoSave(SaveReason.FOCUS_CHANGE, { groupId: activeGroup.id, editor: activeEditor }); })); } diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index e00c16897a..5f0d56fb5d 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -7,8 +7,8 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditor, IEditorInput } from 'vs/workbench/common/editor'; -import { IEditorService, IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; +import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditorInput, IVisibleEditorPane } from 'vs/workbench/common/editor'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; @@ -84,7 +84,7 @@ function registerActiveEditorMoveCommand(): void { weight: KeybindingWeight.WorkbenchContrib, when: EditorContextKeys.editorTextFocus, primary: 0, - handler: (accessor, args: any) => moveActiveEditor(args, accessor), + handler: (accessor, args) => moveActiveEditor(args, accessor), description: { description: nls.localize('editorCommand.activeEditorMove.description', "Move the active editor by tabs or groups"), args: [ @@ -120,18 +120,18 @@ function moveActiveEditor(args: ActiveEditorMoveArguments = Object.create(null), args.by = args.by || 'tab'; args.value = typeof args.value === 'number' ? args.value : 1; - const activeControl = accessor.get(IEditorService).activeControl; - if (activeControl) { + const activeEditorPane = accessor.get(IEditorService).activeEditorPane; + if (activeEditorPane) { switch (args.by) { case 'tab': - return moveActiveTab(args, activeControl, accessor); + return moveActiveTab(args, activeEditorPane, accessor); case 'group': - return moveActiveEditorToGroup(args, activeControl, accessor); + return moveActiveEditorToGroup(args, activeEditorPane, accessor); } } } -function moveActiveTab(args: ActiveEditorMoveArguments, control: IVisibleEditor, accessor: ServicesAccessor): void { +function moveActiveTab(args: ActiveEditorMoveArguments, control: IVisibleEditorPane, accessor: ServicesAccessor): void { const group = control.group; let index = group.getIndexOfEditor(control.input); switch (args.to) { @@ -159,7 +159,7 @@ function moveActiveTab(args: ActiveEditorMoveArguments, control: IVisibleEditor, group.moveEditor(control.input, group, { index }); } -function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, control: IVisibleEditor, accessor: ServicesAccessor): void { +function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, control: IVisibleEditorPane, accessor: ServicesAccessor): void { const editorGroupService = accessor.get(IEditorGroupsService); const configurationService = accessor.get(IConfigurationService); @@ -261,7 +261,7 @@ function registerDiffEditorCommands(): void { function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void { const editorService = accessor.get(IEditorService); - const candidates = [editorService.activeControl, ...editorService.visibleControls].filter(e => e instanceof TextDiffEditor); + const candidates = [editorService.activeEditorPane, ...editorService.visibleEditorPanes].filter(e => e instanceof TextDiffEditor); if (candidates.length > 0) { const navigator = (candidates[0]).getDiffNavigator(); @@ -317,9 +317,9 @@ function registerDiffEditorCommands(): void { function registerOpenEditorAtIndexCommands(): void { const openEditorAtIndex: ICommandHandler = (accessor: ServicesAccessor, editorIndex: number): void => { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl; - if (activeControl) { - const editor = activeControl.group.getEditorByIndex(editorIndex); + const activeEditorPane = editorService.activeEditorPane; + if (activeEditorPane) { + const editor = activeEditorPane.group.getEditorByIndex(editorIndex); if (editor) { editorService.openEditor(editor); } @@ -491,13 +491,11 @@ function registerCloseEditorCommands() { contexts.push({ groupId: activeGroup.id }); // active group as fallback } - return Promise.all(distinct(contexts.map(c => c.groupId)).map(groupId => { + return Promise.all(distinct(contexts.map(c => c.groupId)).map(async groupId => { const group = editorGroupService.getGroup(groupId); if (group) { return group.closeEditors({ savedOnly: true }); } - - return Promise.resolve(); })); } }); @@ -516,13 +514,11 @@ function registerCloseEditorCommands() { distinctGroupIds.push(editorGroupService.activeGroup.id); } - return Promise.all(distinctGroupIds.map(groupId => { + return Promise.all(distinctGroupIds.map(async groupId => { const group = editorGroupService.getGroup(groupId); if (group) { return group.closeAllEditors(); } - - return Promise.resolve(); })); } }); @@ -544,7 +540,7 @@ function registerCloseEditorCommands() { const groupIds = distinct(contexts.map(context => context.groupId)); - return Promise.all(groupIds.map(groupId => { + return Promise.all(groupIds.map(async groupId => { const group = editorGroupService.getGroup(groupId); if (group) { const editors = coalesce(contexts @@ -553,8 +549,6 @@ function registerCloseEditorCommands() { return group.closeEditors(editors); } - - return Promise.resolve(); })); } }); @@ -599,7 +593,7 @@ function registerCloseEditorCommands() { const groupIds = distinct(contexts.map(context => context.groupId)); - return Promise.all(groupIds.map(groupId => { + return Promise.all(groupIds.map(async groupId => { const group = editorGroupService.getGroup(groupId); if (group) { const editors = contexts @@ -613,8 +607,6 @@ function registerCloseEditorCommands() { return group.closeEditors(editorsToClose); } - - return Promise.resolve(); })); } }); @@ -624,7 +616,7 @@ function registerCloseEditorCommands() { weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: undefined, - handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + handler: async (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { const editorGroupService = accessor.get(IEditorGroupsService); const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context)); @@ -635,8 +627,6 @@ function registerCloseEditorCommands() { return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor }); } - - return Promise.resolve(false); } }); @@ -645,15 +635,13 @@ function registerCloseEditorCommands() { weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter), - handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + handler: async (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { const editorGroupService = accessor.get(IEditorGroupsService); const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context)); if (group && editor) { return group.pinEditor(editor); } - - return Promise.resolve(false); } }); @@ -708,21 +696,19 @@ function getCommandsContext(resourceOrContext: URI | IEditorCommandsContext, con return undefined; } -function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor?: IEditorInput, control?: IEditor } { +function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor?: IEditorInput } { // Resolve from context let group = context && typeof context.groupId === 'number' ? editorGroupService.getGroup(context.groupId) : undefined; let editor = group && context && typeof context.editorIndex === 'number' ? types.withNullAsUndefined(group.getEditorByIndex(context.editorIndex)) : undefined; - let control = group ? group.activeControl : undefined; // Fallback to active group as needed if (!group) { group = editorGroupService.activeGroup; editor = group.activeEditor; - control = group.activeControl; } - return { group, editor, control }; + return { group, editor }; } export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsContext | undefined, listService: IListService, editorGroupService: IEditorGroupsService): IEditorCommandsContext[] { diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index e5990d478f..04d3bcfea4 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IVisibleEditorPane } from 'vs/workbench/common/editor'; import { Dimension, show, hide, addClass } from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; import { IEditorRegistry, Extensions as EditorExtensions, IEditorDescriptor } from 'vs/workbench/browser/editor'; @@ -14,20 +14,19 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IEditorProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress'; import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; import { Emitter } from 'vs/base/common/event'; -import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; import { assertIsDefined } from 'vs/base/common/types'; export interface IOpenEditorResult { - readonly control: BaseEditor; + readonly editorPane: BaseEditor; readonly editorChanged: boolean; } export class EditorControl extends Disposable { - get minimumWidth() { return this._activeControl ? this._activeControl.minimumWidth : DEFAULT_EDITOR_MIN_DIMENSIONS.width; } - get minimumHeight() { return this._activeControl ? this._activeControl.minimumHeight : DEFAULT_EDITOR_MIN_DIMENSIONS.height; } - get maximumWidth() { return this._activeControl ? this._activeControl.maximumWidth : DEFAULT_EDITOR_MAX_DIMENSIONS.width; } - get maximumHeight() { return this._activeControl ? this._activeControl.maximumHeight : DEFAULT_EDITOR_MAX_DIMENSIONS.height; } + get minimumWidth() { return this._activeEditorPane ? this._activeEditorPane.minimumWidth : DEFAULT_EDITOR_MIN_DIMENSIONS.width; } + get minimumHeight() { return this._activeEditorPane ? this._activeEditorPane.minimumHeight : DEFAULT_EDITOR_MIN_DIMENSIONS.height; } + get maximumWidth() { return this._activeEditorPane ? this._activeEditorPane.maximumWidth : DEFAULT_EDITOR_MAX_DIMENSIONS.width; } + get maximumHeight() { return this._activeEditorPane ? this._activeEditorPane.maximumHeight : DEFAULT_EDITOR_MAX_DIMENSIONS.height; } private readonly _onDidFocus = this._register(new Emitter()); readonly onDidFocus = this._onDidFocus.event; @@ -35,10 +34,10 @@ export class EditorControl extends Disposable { private _onDidSizeConstraintsChange = this._register(new Emitter<{ width: number; height: number; } | undefined>()); readonly onDidSizeConstraintsChange = this._onDidSizeConstraintsChange.event; - private _activeControl: BaseEditor | null = null; - private controls: BaseEditor[] = []; + private _activeEditorPane: BaseEditor | null = null; + private readonly editorPanes: BaseEditor[] = []; - private readonly activeControlDisposables = this._register(new DisposableStore()); + private readonly activeEditorPaneDisposables = this._register(new DisposableStore()); private dimension: Dimension | undefined; private editorOperation: LongRunningOperation; @@ -54,119 +53,119 @@ export class EditorControl extends Disposable { this.editorOperation = this._register(new LongRunningOperation(editorProgressService)); } - get activeControl(): IVisibleEditor | null { - return this._activeControl as IVisibleEditor | null; + get activeEditorPane(): IVisibleEditorPane | null { + return this._activeEditorPane as IVisibleEditorPane | null; } async openEditor(editor: EditorInput, options?: EditorOptions): Promise { - // Editor control + // Editor pane const descriptor = Registry.as(EditorExtensions.Editors).getEditor(editor); if (!descriptor) { - throw new Error('No editor descriptor found'); + throw new Error(`No editor descriptor found for input id ${editor.getTypeId()}`); } - const control = this.doShowEditorControl(descriptor); + const editorPane = this.doShowEditorPane(descriptor); // Set input - const editorChanged = await this.doSetInput(control, editor, options); - return { control, editorChanged }; + const editorChanged = await this.doSetInput(editorPane, editor, options); + return { editorPane, editorChanged }; } - private doShowEditorControl(descriptor: IEditorDescriptor): BaseEditor { + private doShowEditorPane(descriptor: IEditorDescriptor): BaseEditor { - // Return early if the currently active editor control can handle the input - if (this._activeControl && descriptor.describes(this._activeControl)) { - return this._activeControl; + // Return early if the currently active editor pane can handle the input + if (this._activeEditorPane && descriptor.describes(this._activeEditorPane)) { + return this._activeEditorPane; } // Hide active one first - this.doHideActiveEditorControl(); + this.doHideActiveEditorPane(); - // Create editor - const control = this.doCreateEditorControl(descriptor); + // Create editor pane + const editorPane = this.doCreateEditorPane(descriptor); // Set editor as active - this.doSetActiveControl(control); + this.doSetActiveEditorPane(editorPane); // Show editor - const container = assertIsDefined(control.getContainer()); + const container = assertIsDefined(editorPane.getContainer()); this.parent.appendChild(container); show(container); // Indicate to editor that it is now visible - control.setVisible(true, this.groupView); + editorPane.setVisible(true, this.groupView); // Layout if (this.dimension) { - control.layout(this.dimension); + editorPane.layout(this.dimension); } - return control; + return editorPane; } - private doCreateEditorControl(descriptor: IEditorDescriptor): BaseEditor { + private doCreateEditorPane(descriptor: IEditorDescriptor): BaseEditor { // Instantiate editor - const control = this.doInstantiateEditorControl(descriptor); + const editorPane = this.doInstantiateEditorPane(descriptor); // Create editor container as needed - if (!control.getContainer()) { - const controlInstanceContainer = document.createElement('div'); - addClass(controlInstanceContainer, 'editor-instance'); - controlInstanceContainer.setAttribute('data-editor-id', descriptor.getId()); + if (!editorPane.getContainer()) { + const editorPaneContainer = document.createElement('div'); + addClass(editorPaneContainer, 'editor-instance'); + editorPaneContainer.setAttribute('data-editor-id', descriptor.getId()); - control.create(controlInstanceContainer); + editorPane.create(editorPaneContainer); } - return control; + return editorPane; } - private doInstantiateEditorControl(descriptor: IEditorDescriptor): BaseEditor { + private doInstantiateEditorPane(descriptor: IEditorDescriptor): BaseEditor { // Return early if already instantiated - const existingControl = this.controls.filter(control => descriptor.describes(control))[0]; - if (existingControl) { - return existingControl; + const existingEditorPane = this.editorPanes.filter(editorPane => descriptor.describes(editorPane))[0]; + if (existingEditorPane) { + return existingEditorPane; } // Otherwise instantiate new - const control = this._register(descriptor.instantiate(this.instantiationService)); - this.controls.push(control); + const editorPane = this._register(descriptor.instantiate(this.instantiationService)); + this.editorPanes.push(editorPane); - return control; + return editorPane; } - private doSetActiveControl(control: BaseEditor | null) { - this._activeControl = control; + private doSetActiveEditorPane(editorPane: BaseEditor | null) { + this._activeEditorPane = editorPane; - // Clear out previous active control listeners - this.activeControlDisposables.clear(); + // Clear out previous active editor pane listeners + this.activeEditorPaneDisposables.clear(); - // Listen to control changes - if (control) { - this.activeControlDisposables.add(control.onDidSizeConstraintsChange(e => this._onDidSizeConstraintsChange.fire(e))); - this.activeControlDisposables.add(control.onDidFocus(() => this._onDidFocus.fire())); + // Listen to editor pane changes + if (editorPane) { + this.activeEditorPaneDisposables.add(editorPane.onDidSizeConstraintsChange(e => this._onDidSizeConstraintsChange.fire(e))); + this.activeEditorPaneDisposables.add(editorPane.onDidFocus(() => this._onDidFocus.fire())); } // Indicate that size constraints could have changed due to new editor this._onDidSizeConstraintsChange.fire(undefined); } - private async doSetInput(control: BaseEditor, editor: EditorInput, options: EditorOptions | undefined): Promise { + private async doSetInput(editorPane: BaseEditor, editor: EditorInput, options: EditorOptions | undefined): Promise { // If the input did not change, return early and only apply the options // unless the options instruct us to force open it even if it is the same const forceReload = options?.forceReload; - const inputMatches = control.input && control.input.matches(editor); + const inputMatches = editorPane.input && editorPane.input.matches(editor); if (inputMatches && !forceReload) { // Forward options - control.setOptions(options); + editorPane.setOptions(options); // Still focus as needed const focus = !options || !options.preserveFocus; if (focus) { - control.focus(); + editorPane.focus(); } return false; @@ -176,16 +175,16 @@ export class EditorControl extends Disposable { // be more relaxed about progress showing by increasing the delay a little bit to reduce flicker. const operation = this.editorOperation.start(this.layoutService.isRestored() ? 800 : 3200); - // Call into editor control + // Call into editor pane const editorWillChange = !inputMatches; try { - await control.setInput(editor, options, operation.token); + await editorPane.setInput(editor, options, operation.token); // Focus (unless prevented or another operation is running) if (operation.isCurrent()) { const focus = !options || !options.preserveFocus; if (focus) { - control.focus(); + editorPane.focus(); } } @@ -195,47 +194,47 @@ export class EditorControl extends Disposable { } } - private doHideActiveEditorControl(): void { - if (!this._activeControl) { + private doHideActiveEditorPane(): void { + if (!this._activeEditorPane) { return; } // Stop any running operation this.editorOperation.stop(); - // Remove control from parent and hide - const controlInstanceContainer = this._activeControl.getContainer(); - if (controlInstanceContainer) { - this.parent.removeChild(controlInstanceContainer); - hide(controlInstanceContainer); - this._activeControl.onHide(); + // Remove editor pane from parent and hide + const editorPaneContainer = this._activeEditorPane.getContainer(); + if (editorPaneContainer) { + this.parent.removeChild(editorPaneContainer); + hide(editorPaneContainer); + this._activeEditorPane.onHide(); } - // Indicate to editor control - this._activeControl.clearInput(); - this._activeControl.setVisible(false, this.groupView); + // Indicate to editor pane + this._activeEditorPane.clearInput(); + this._activeEditorPane.setVisible(false, this.groupView); - // Clear active control - this.doSetActiveControl(null); + // Clear active editor pane + this.doSetActiveEditorPane(null); } closeEditor(editor: EditorInput): void { - if (this._activeControl && editor.matches(this._activeControl.input)) { - this.doHideActiveEditorControl(); + if (this._activeEditorPane && editor.matches(this._activeEditorPane.input)) { + this.doHideActiveEditorPane(); } } setVisible(visible: boolean): void { - if (this._activeControl) { - this._activeControl.setVisible(visible, this.groupView); + if (this._activeEditorPane) { + this._activeEditorPane.setVisible(visible, this.groupView); } } layout(dimension: Dimension): void { this.dimension = dimension; - if (this._activeControl && this.dimension) { - this._activeControl.layout(this.dimension); + if (this._activeEditorPane && this.dimension) { + this._activeEditorPane.layout(this.dimension); } } } diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index f3e9bcf35b..fa39835349 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -7,8 +7,8 @@ import 'vs/css!./media/editordroptarget'; import { LocalSelectionTransfer, DraggedEditorIdentifier, ResourcesDropHandler, DraggedEditorGroupIdentifier, DragAndDropObserver, containsDragType } from 'vs/workbench/browser/dnd'; import { addDisposableListener, EventType, EventHelper, isAncestor, toggleClass, addClass, removeClass } from 'vs/base/browser/dom'; import { IEditorGroupsAccessor, EDITOR_TITLE_HEIGHT, IEditorGroupView, getActiveTextEditorOptions } from 'vs/workbench/browser/parts/editor/editor'; -import { EDITOR_DRAG_AND_DROP_BACKGROUND, Themable } from 'vs/workbench/common/theme'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { EDITOR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; +import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IEditorIdentifier, EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; @@ -308,7 +308,7 @@ class DropOverlay extends Themable { } // Open as untitled file with the provided contents - const untitledEditor = this.editorService.createInput({ + const untitledEditor = this.editorService.createEditorInput({ resource: proposedFilePath, forceUntitled: true, contents: VSBuffer.wrap(new Uint8Array(event.target.result)).toString() diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 2fc28adf62..ccd97a6975 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane } from 'vs/workbench/common/editor'; import { Event, Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom'; @@ -14,9 +14,9 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService'; import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { Themable, EDITOR_GROUP_HEADER_TABS_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER } from 'vs/workbench/common/theme'; +import { EDITOR_GROUP_HEADER_TABS_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER } from 'vs/workbench/common/theme'; import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl'; import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl'; @@ -25,7 +25,7 @@ import { EditorProgressIndicator } from 'vs/workbench/services/progress/browser/ import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose, MutableDisposable } from 'vs/base/common/lifecycle'; -import { Severity, INotificationService, INotificationActions } from 'vs/platform/notification/common/notification'; +import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { RunOnceWorker } from 'vs/base/common/async'; @@ -42,7 +42,7 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { isErrorWithActions, IErrorWithActions } from 'vs/base/common/errorsWithActions'; -import { IVisibleEditor, IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { hash } from 'vs/base/common/hash'; import { guessMimeTypes } from 'vs/base/common/mime'; @@ -255,7 +255,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { if (this.isEmpty) { EventHelper.stop(e); - this.openEditor(this.editorService.createInput({ forceUntitled: true }), EditorOptions.create({ pinned: true })); + this.openEditor(this.editorService.createEditorInput({ forceUntitled: true }), EditorOptions.create({ pinned: true })); } })); @@ -733,8 +733,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._group.count; } - get activeControl(): IVisibleEditor | undefined { - return this.editorControl ? withNullAsUndefined(this.editorControl.activeControl) : undefined; + get activeEditorPane(): IVisibleEditorPane | undefined { + return this.editorControl ? withNullAsUndefined(this.editorControl.activeEditorPane) : undefined; } get activeEditor(): EditorInput | null { @@ -771,9 +771,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { focus(): void { - // Pass focus to widgets - if (this.activeControl) { - this.activeControl.focus(); + // Pass focus to editor panes + if (this.activeEditorPane) { + this.activeEditorPane.focus(); } else { this.element.focus(); } @@ -803,7 +803,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region openEditor() - async openEditor(editor: EditorInput, options?: EditorOptions): Promise { + async openEditor(editor: EditorInput, options?: EditorOptions): Promise { // Guard against invalid inputs if (!editor) { @@ -822,7 +822,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return withUndefinedAsNull(await this.doOpenEditor(editor, options)); } - private doOpenEditor(editor: EditorInput, options?: EditorOptions): Promise { + private doOpenEditor(editor: EditorInput, options?: EditorOptions): Promise { // Guard against invalid inputs. Disposed inputs // should never open because they emit no events @@ -895,10 +895,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return showEditorResult; } - private async doShowEditor(editor: EditorInput, active: boolean, options?: EditorOptions): Promise { + private async doShowEditor(editor: EditorInput, active: boolean, options?: EditorOptions): Promise { // Show in editor control if the active editor changed - let openEditorPromise: Promise; + let openEditorPromise: Promise; if (active) { openEditorPromise = (async () => { try { @@ -909,7 +909,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_ACTIVE, editor }); } - return result.control; + return result.editorPane; } catch (error) { // Handle errors but do not bubble them up @@ -980,7 +980,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Otherwise, show a background notification. else { - const actions: INotificationActions = { primary: [] }; + const actions = { primary: [] as readonly IAction[] }; if (Array.isArray(errorActions)) { actions.primary = errorActions; } @@ -1015,7 +1015,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region openEditors() - async openEditors(editors: { editor: EditorInput, options?: EditorOptions }[]): Promise { + async openEditors(editors: { editor: EditorInput, options?: EditorOptions }[]): Promise { if (!editors.length) { return null; } @@ -1041,7 +1041,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Opening many editors at once can put any editor to be // the active one depending on options. As such, we simply // return the active control after this operation. - return this.editorControl.activeControl; + return this.editorControl.activeEditorPane; } //#endregion @@ -1075,7 +1075,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Update model and make sure to continue to use the editor we get from // the model. It is possible that the editor was already opened and we // want to ensure that we use the existing instance in that case. - const editor = this.group.getEditorByIndex(currentIndex)!; + const editor = this._group.getEditorByIndex(currentIndex); + if (!editor) { + return; + } // Update model this._group.moveEditor(editor, moveToIndex); @@ -1278,7 +1281,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return false; // editor must be dirty and not saving } - if (editor instanceof SideBySideEditorInput && this.isOpened(editor.master)) { + if (editor instanceof SideBySideEditorInput && this._group.contains(editor.master)) { return false; // master-side of editor is still opened somewhere else } @@ -1329,25 +1332,24 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Otherwise, handle accordingly switch (res) { case ConfirmResult.SAVE: - const result = await editor.save(this.id, { reason: SaveReason.EXPLICIT }); + await editor.save(this.id, { reason: SaveReason.EXPLICIT }); - return !result; + return editor.isDirty(); // veto if still dirty case ConfirmResult.DONT_SAVE: - try { // first try a normal revert where the contents of the editor are restored - const result = await editor.revert(this.id); + await editor.revert(this.id); - return !result; + return editor.isDirty(); // veto if still dirty } catch (error) { // if that fails, since we are about to close the editor, we accept that // the editor cannot be reverted and instead do a soft revert that just // enables us to close the editor. With this, a user can always close a // dirty editor even when reverting fails. - const result = await editor.revert(this.id, { soft: true }); + await editor.revert(this.id, { soft: true }); - return !result; + return editor.isDirty(); // veto if still dirty } case ConfirmResult.CANCEL: return true; // veto @@ -1623,7 +1625,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } class EditorOpeningEvent implements IEditorOpeningEvent { - private override: (() => Promise) | undefined = undefined; + private override: (() => Promise) | undefined = undefined; constructor( private _group: GroupIdentifier, @@ -1644,11 +1646,11 @@ class EditorOpeningEvent implements IEditorOpeningEvent { return this._options; } - prevent(callback: () => Promise): void { + prevent(callback: () => Promise): void { this.override = callback; } - isPrevented(): (() => Promise) | undefined { + isPrevented(): (() => Promise) | undefined { return this.override; } } diff --git a/src/vs/workbench/browser/parts/editor/editorPicker.ts b/src/vs/workbench/browser/parts/editor/editorPicker.ts index 33aa158924..7a3652118a 100644 --- a/src/vs/workbench/browser/parts/editor/editorPicker.ts +++ b/src/vs/workbench/browser/parts/editor/editorPicker.ts @@ -16,7 +16,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IEditorGroupsService, IEditorGroup, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { toResource, SideBySideEditor, IEditorInput, EditorsOrder } from 'vs/workbench/common/editor'; -import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; +import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/common/fuzzyScorer'; import { CancellationToken } from 'vs/base/common/cancellation'; export class EditorPickerEntry extends QuickOpenEntryGroup { diff --git a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts new file mode 100644 index 0000000000..60cc28662a --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts @@ -0,0 +1,188 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource } from 'vs/platform/quickinput/common/quickInput'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/common/quickAccess'; +import { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; +import { prepareQuery, scoreItem, compareItemsByScore } from 'vs/base/common/fuzzyScorer'; + +interface IEditorQuickPickItem extends IQuickPickItemWithResource, IEditorIdentifier, IPickerQuickAccessItem { } + +export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessProvider { + + constructor( + prefix: string, + @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService, + @IEditorService protected readonly editorService: IEditorService, + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService + ) { + super(prefix); + } + + protected getPicks(filter: string): Array { + const query = prepareQuery(filter); + const scorerCache = Object.create(null); + const filteredEditorEntries = this.doGetEditorPickItems().filter(entry => { + if (!query.value) { + return true; + } + + // Score on label and description + const itemScore = scoreItem(entry, query, true, quickPickItemScorerAccessor, scorerCache); + if (!itemScore.score) { + return false; + } + + // Apply highlights + entry.highlights = { label: itemScore.labelMatch, description: itemScore.descriptionMatch }; + + return true; + }); + + // Sorting + if (query.value) { + const groups = this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).map(group => group.id); + filteredEditorEntries.sort((entryA, entryB) => { + if (entryA.groupId !== entryB.groupId) { + return groups.indexOf(entryA.groupId) - groups.indexOf(entryB.groupId); // older groups first + } + + return compareItemsByScore(entryA, entryB, query, true, quickPickItemScorerAccessor, scorerCache); + }); + } + + // Grouping (for more than one group) + const filteredEditorEntriesWithSeparators: Array = []; + if (this.editorGroupService.count > 1) { + let lastGroupId: number | undefined = undefined; + for (const entry of filteredEditorEntries) { + if (typeof lastGroupId !== 'number' || lastGroupId !== entry.groupId) { + const group = this.editorGroupService.getGroup(entry.groupId); + if (group) { + filteredEditorEntriesWithSeparators.push({ type: 'separator', label: group.label }); + } + lastGroupId = entry.groupId; + } + + filteredEditorEntriesWithSeparators.push(entry); + } + } else { + filteredEditorEntriesWithSeparators.push(...filteredEditorEntries); + } + + return filteredEditorEntriesWithSeparators; + } + + private doGetEditorPickItems(): Array { + return this.doGetEditors().map(({ editor, groupId }) => { + const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); + + return { + editor, + groupId, + resource, + label: editor.isDirty() && !editor.isSaving() ? `$(circle-filled) ${editor.getName()}` : editor.getName(), + ariaLabel: localize('entryAriaLabel', "{0}, editor picker", editor.getName()), + description: editor.getDescription(), + iconClasses: getIconClasses(this.modelService, this.modeService, resource), + italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor), + accept: () => this.editorGroupService.getGroup(groupId)?.openEditor(editor) + }; + }); + } + + protected abstract doGetEditors(): IEditorIdentifier[]; +} + +//#region Active Editor Group Editors by Most Recently Used + +export class ActiveGroupEditorsByMostRecentlyUsedQuickAccess extends BaseEditorQuickAccessProvider { + + static PREFIX = 'edt active '; + + constructor( + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService + ) { + super(ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX, editorGroupService, editorService, modelService, modeService); + } + + protected doGetEditors(): IEditorIdentifier[] { + const group = this.editorGroupService.activeGroup; + + return group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map(editor => ({ editor, groupId: group.id })); + } +} + +//#endregion + + +//#region All Editors by Appearance + +export class AllEditorsByAppearanceQuickAccess extends BaseEditorQuickAccessProvider { + + static PREFIX = 'edt '; + + constructor( + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService + ) { + super(AllEditorsByAppearanceQuickAccess.PREFIX, editorGroupService, editorService, modelService, modeService); + } + + protected doGetEditors(): IEditorIdentifier[] { + const entries: IEditorIdentifier[] = []; + + for (const group of this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)) { + for (const editor of group.getEditors(EditorsOrder.SEQUENTIAL)) { + entries.push({ editor, groupId: group.id }); + } + } + + return entries; + } +} + +//#endregion + + +//#region All Editors by Most Recently Used + +export class AllEditorsByMostRecentlyUsedQuickAccess extends BaseEditorQuickAccessProvider { + + static PREFIX = 'edt mru '; + + constructor( + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService + ) { + super(AllEditorsByMostRecentlyUsedQuickAccess.PREFIX, editorGroupService, editorService, modelService, modeService); + } + + protected doGetEditors(): IEditorIdentifier[] { + const entries: IEditorIdentifier[] = []; + + for (const editor of this.editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) { + entries.push(editor); + } + + return entries; + } +} + +//#endregion diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 50f238cc9d..6383e84f8f 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; import { Language } from 'vs/base/common/platform'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; -import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor'; +import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditorPane, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor'; import { Disposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorAction } from 'vs/editor/common/editorCommon'; import { EndOfLineSequence } from 'vs/editor/common/model'; @@ -366,8 +366,8 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } private async showIndentationPicker(): Promise { - const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget); - if (!activeTextEditorWidget) { + const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); + if (!activeTextEditorControl) { return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); } @@ -376,19 +376,19 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } const picks: QuickPickInput[] = [ - activeTextEditorWidget.getAction(IndentUsingSpaces.ID), - activeTextEditorWidget.getAction(IndentUsingTabs.ID), - activeTextEditorWidget.getAction(DetectIndentation.ID), - activeTextEditorWidget.getAction(IndentationToSpacesAction.ID), - activeTextEditorWidget.getAction(IndentationToTabsAction.ID), - activeTextEditorWidget.getAction(TrimTrailingWhitespaceAction.ID) + activeTextEditorControl.getAction(IndentUsingSpaces.ID), + activeTextEditorControl.getAction(IndentUsingTabs.ID), + activeTextEditorControl.getAction(DetectIndentation.ID), + activeTextEditorControl.getAction(IndentationToSpacesAction.ID), + activeTextEditorControl.getAction(IndentationToTabsAction.ID), + activeTextEditorControl.getAction(TrimTrailingWhitespaceAction.ID) ].map((a: IEditorAction) => { return { id: a.id, label: a.label, detail: (Language.isDefaultVariant() || a.label === a.alias) ? undefined : a.alias, run: () => { - activeTextEditorWidget.focus(); + activeTextEditorControl.focus(); a.run(); } }; @@ -611,8 +611,8 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private updateStatusBar(): void { const activeInput = this.editorService.activeEditor; - const activeControl = this.editorService.activeControl; - const activeCodeEditor = activeControl ? withNullAsUndefined(getCodeEditor(activeControl.getControl())) : undefined; + const activeEditorPane = this.editorService.activeEditorPane; + const activeCodeEditor = activeEditorPane ? withNullAsUndefined(getCodeEditor(activeEditorPane.getControl())) : undefined; // Update all states this.onColumnSelectionModeChange(activeCodeEditor); @@ -620,9 +620,9 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.onSelectionChange(activeCodeEditor); this.onModeChange(activeCodeEditor, activeInput); this.onEOLChange(activeCodeEditor); - this.onEncodingChange(activeControl, activeCodeEditor); + this.onEncodingChange(activeEditorPane, activeCodeEditor); this.onIndentationChange(activeCodeEditor); - this.onMetadataChange(activeControl); + this.onMetadataChange(activeEditorPane); this.currentProblemStatus.update(activeCodeEditor); // Dispose old active editor listeners @@ -675,25 +675,25 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } // Handle binary editors - else if (activeControl instanceof BaseBinaryResourceEditor || activeControl instanceof BinaryResourceDiffEditor) { + else if (activeEditorPane instanceof BaseBinaryResourceEditor || activeEditorPane instanceof BinaryResourceDiffEditor) { const binaryEditors: BaseBinaryResourceEditor[] = []; - if (activeControl instanceof BinaryResourceDiffEditor) { - const details = activeControl.getDetailsEditor(); + if (activeEditorPane instanceof BinaryResourceDiffEditor) { + const details = activeEditorPane.getDetailsEditorPane(); if (details instanceof BaseBinaryResourceEditor) { binaryEditors.push(details); } - const master = activeControl.getMasterEditor(); + const master = activeEditorPane.getMasterEditorPane(); if (master instanceof BaseBinaryResourceEditor) { binaryEditors.push(master); } } else { - binaryEditors.push(activeControl); + binaryEditors.push(activeEditorPane); } binaryEditors.forEach(editor => { this.activeEditorListeners.add(editor.onMetadataChanged(metadata => { - this.onMetadataChange(activeControl); + this.onMetadataChange(activeEditorPane); })); this.activeEditorListeners.add(editor.onDidOpenInPlace(() => { @@ -736,7 +736,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.updateState(update); } - private onMetadataChange(editor: IBaseEditor | undefined): void { + private onMetadataChange(editor: IEditorPane | undefined): void { const update: StateDelta = { type: 'metadata', metadata: undefined }; if (editor instanceof BaseBinaryResourceEditor || editor instanceof BinaryResourceDiffEditor) { @@ -835,7 +835,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.updateState(info); } - private onEncodingChange(editor: IBaseEditor | undefined, editorWidget: ICodeEditor | undefined): void { + private onEncodingChange(editor: IEditorPane | undefined, editorWidget: ICodeEditor | undefined): void { if (editor && !this.isActiveEditor(editor)) { return; } @@ -862,13 +862,13 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } private onResourceEncodingChange(resource: URI): void { - const activeControl = this.editorService.activeControl; - if (activeControl) { - const activeResource = toResource(activeControl.input, { supportSideBySide: SideBySideEditor.MASTER }); + const activeEditorPane = this.editorService.activeEditorPane; + if (activeEditorPane) { + const activeResource = toResource(activeEditorPane.input, { supportSideBySide: SideBySideEditor.MASTER }); if (activeResource && isEqual(activeResource, resource)) { - const activeCodeEditor = withNullAsUndefined(getCodeEditor(activeControl.getControl())); + const activeCodeEditor = withNullAsUndefined(getCodeEditor(activeEditorPane.getControl())); - return this.onEncodingChange(activeControl, activeCodeEditor); // only update if the encoding changed for the active resource + return this.onEncodingChange(activeEditorPane, activeCodeEditor); // only update if the encoding changed for the active resource } } } @@ -879,10 +879,10 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.updateState(info); } - private isActiveEditor(control: IBaseEditor): boolean { - const activeControl = this.editorService.activeControl; + private isActiveEditor(control: IEditorPane): boolean { + const activeEditorPane = this.editorService.activeEditorPane; - return !!activeControl && activeControl === control; + return !!activeEditorPane && activeEditorPane === control; } } @@ -1049,13 +1049,14 @@ export class ChangeModeAction extends Action { super(actionId, actionLabel); } - async run(): Promise { - const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget); - if (!activeTextEditorWidget) { - return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + async run(): Promise { + const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); + if (!activeTextEditorControl) { + await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + return; } - const textModel = activeTextEditorWidget.getModel(); + const textModel = activeTextEditorControl.getModel(); const resource = this.editorService.activeEditor ? toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null; let hasLanguageSupport = !!resource; @@ -1139,7 +1140,7 @@ export class ChangeModeAction extends Action { // User decided to configure settings for current language if (pick === configureModeSettings) { - this.preferencesService.configureSettingsForLanguage(withUndefinedAsNull(modeId)); + this.preferencesService.openGlobalSettings(true, { editSetting: `[${withUndefinedAsNull(modeId)}]` }); return; } @@ -1249,17 +1250,19 @@ export class ChangeEOLAction extends Action { super(actionId, actionLabel); } - async run(): Promise { - const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget); - if (!activeTextEditorWidget) { - return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + async run(): Promise { + const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); + if (!activeTextEditorControl) { + await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + return; } if (this.editorService.activeEditor?.isReadonly()) { - return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]); + await this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]); + return; } - let textModel = activeTextEditorWidget.getModel(); + let textModel = activeTextEditorControl.getModel(); const EOLOptions: IChangeEOLEntry[] = [ { label: nlsEOLLF, eol: EndOfLineSequence.LF }, @@ -1270,7 +1273,7 @@ export class ChangeEOLAction extends Action { const eol = await this.quickInputService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] }); if (eol) { - const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorWidget); + const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorControl); if (activeCodeEditor?.hasModel() && !this.editorService.activeEditor?.isReadonly()) { textModel = activeCodeEditor.getModel(); textModel.pushStackElement(); @@ -1298,19 +1301,22 @@ export class ChangeEncodingAction extends Action { super(actionId, actionLabel); } - async run(): Promise { - if (!getCodeEditor(this.editorService.activeTextEditorWidget)) { - return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + async run(): Promise { + if (!getCodeEditor(this.editorService.activeTextEditorControl)) { + await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + return; } - const activeControl = this.editorService.activeControl; - if (!activeControl) { - return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + const activeEditorPane = this.editorService.activeEditorPane; + if (!activeEditorPane) { + await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + return; } - const encodingSupport: IEncodingSupport | null = toEditorWithEncodingSupport(activeControl.input); + const encodingSupport: IEncodingSupport | null = toEditorWithEncodingSupport(activeEditorPane.input); if (!encodingSupport) { - return this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]); + await this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]); + return; } const saveWithEncodingPick: IQuickPickItem = { label: nls.localize('saveWithEncoding', "Save with Encoding") }; @@ -1331,7 +1337,7 @@ export class ChangeEncodingAction extends Action { let action: IQuickPickItem; if (encodingSupport instanceof UntitledTextEditorInput) { action = saveWithEncodingPick; - } else if (activeControl.input.isReadonly()) { + } else if (activeEditorPane.input.isReadonly()) { action = reopenWithEncodingPick; } else { action = await this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); @@ -1343,9 +1349,9 @@ export class ChangeEncodingAction extends Action { await timeout(50); // quick open is sensitive to being opened so soon after another - const resource = toResource(activeControl.input, { supportSideBySide: SideBySideEditor.MASTER }); + const resource = toResource(activeEditorPane.input, { supportSideBySide: SideBySideEditor.MASTER }); if (!resource || (!this.fileService.canHandleResource(resource) && resource.scheme !== Schemas.untitled)) { - return null; // encoding detection only possible for resources the file service can handle or that are untitled + return; // encoding detection only possible for resources the file service can handle or that are untitled } let guessedEncoding: string | undefined = undefined; @@ -1406,11 +1412,11 @@ export class ChangeEncodingAction extends Action { return; } - if (!this.editorService.activeControl) { + if (!this.editorService.activeEditorPane) { return; } - const activeEncodingSupport = toEditorWithEncodingSupport(this.editorService.activeControl.input); + const activeEncodingSupport = toEditorWithEncodingSupport(this.editorService.activeEditorPane.input); if (typeof encoding.id !== 'undefined' && activeEncodingSupport && activeEncodingSupport.getEncoding() !== encoding.id) { activeEncodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding } diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index 5a5bdbeb29..d186b003c7 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -3,15 +3,16 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEditorInput, IEditorInputFactoryRegistry, IEditorIdentifier, GroupIdentifier, Extensions, IEditorPartOptionsChangeEvent, EditorsOrder } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditorInputFactoryRegistry, IEditorIdentifier, GroupIdentifier, Extensions, IEditorPartOptionsChangeEvent, EditorsOrder, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; import { Event, Emitter } from 'vs/base/common/event'; import { IEditorGroupsService, IEditorGroup, GroupChangeKind, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { coalesce } from 'vs/base/common/arrays'; -import { LinkedMap, Touch } from 'vs/base/common/map'; +import { LinkedMap, Touch, ResourceMap } from 'vs/base/common/map'; import { equals } from 'vs/base/common/objects'; +import { URI } from 'vs/base/common/uri'; interface ISerializedEditorsList { entries: ISerializedEditorIdentifier[]; @@ -37,9 +38,10 @@ export class EditorsObserver extends Disposable { private readonly keyMap = new Map>(); private readonly mostRecentEditorsMap = new LinkedMap(); + private readonly editorResourcesMap = new ResourceMap(); - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChange = this._onDidChange.event; + private readonly _onDidMostRecentlyActiveEditorsChange = this._register(new Emitter()); + readonly onDidMostRecentlyActiveEditorsChange = this._onDidMostRecentlyActiveEditorsChange.event; get count(): number { return this.mostRecentEditorsMap.size; @@ -49,6 +51,10 @@ export class EditorsObserver extends Disposable { return this.mostRecentEditorsMap.values(); } + hasEditor(resource: URI): boolean { + return this.editorResourcesMap.has(resource); + } + constructor( @IEditorGroupsService private editorGroupsService: IEditorGroupsService, @IStorageService private readonly storageService: IStorageService @@ -72,12 +78,12 @@ export class EditorsObserver extends Disposable { // of the new group into our list in LRU order const groupEditorsMru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); for (let i = groupEditorsMru.length - 1; i >= 0; i--) { - this.addMostRecentEditor(group, groupEditorsMru[i], false /* is not active */); + this.addMostRecentEditor(group, groupEditorsMru[i], false /* is not active */, true /* is new */); } // Make sure that active editor is put as first if group is active if (this.editorGroupsService.activeGroup === group && group.activeEditor) { - this.addMostRecentEditor(group, group.activeEditor, true /* is active */); + this.addMostRecentEditor(group, group.activeEditor, true /* is active */, false /* already added before */); } // Group Listeners @@ -92,7 +98,7 @@ export class EditorsObserver extends Disposable { // Group gets active: put active editor as most recent case GroupChangeKind.GROUP_ACTIVE: { if (this.editorGroupsService.activeGroup === group && group.activeEditor) { - this.addMostRecentEditor(group, group.activeEditor, true /* is active */); + this.addMostRecentEditor(group, group.activeEditor, true /* is active */, false /* editor already opened */); } break; @@ -102,7 +108,7 @@ export class EditorsObserver extends Disposable { // if group is active, otherwise second most recent case GroupChangeKind.EDITOR_ACTIVE: { if (e.editor) { - this.addMostRecentEditor(group, e.editor, this.editorGroupsService.activeGroup === group); + this.addMostRecentEditor(group, e.editor, this.editorGroupsService.activeGroup === group, false /* editor already opened */); } break; @@ -114,7 +120,7 @@ export class EditorsObserver extends Disposable { // start to close oldest ones if needed. case GroupChangeKind.EDITOR_OPEN: { if (e.editor) { - this.addMostRecentEditor(group, e.editor, false /* is not active */); + this.addMostRecentEditor(group, e.editor, false /* is not active */, true /* is new */); this.ensureOpenedEditorsLimit({ groupId: group.id, editor: e.editor }, group.id); } @@ -148,7 +154,7 @@ export class EditorsObserver extends Disposable { } } - private addMostRecentEditor(group: IEditorGroup, editor: IEditorInput, isActive: boolean): void { + private addMostRecentEditor(group: IEditorGroup, editor: IEditorInput, isActive: boolean, isNew: boolean): void { const key = this.ensureKey(group, editor); const mostRecentEditor = this.mostRecentEditorsMap.first; @@ -169,11 +175,39 @@ export class EditorsObserver extends Disposable { this.mostRecentEditorsMap.set(mostRecentEditor, mostRecentEditor, Touch.AsOld /* make first */); } + // Update in resource map if this is a new editor + if (isNew) { + this.updateEditorResourcesMap(editor, true); + } + // Event - this._onDidChange.fire(); + this._onDidMostRecentlyActiveEditorsChange.fire(); + } + + private updateEditorResourcesMap(editor: IEditorInput, add: boolean): void { + const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); + if (!resource) { + return; // require a resource + } + + if (add) { + this.editorResourcesMap.set(resource, (this.editorResourcesMap.get(resource) ?? 0) + 1); + } else { + const counter = this.editorResourcesMap.get(resource) ?? 0; + if (counter > 1) { + this.editorResourcesMap.set(resource, counter - 1); + } else { + this.editorResourcesMap.delete(resource); + } + } } private removeMostRecentEditor(group: IEditorGroup, editor: IEditorInput): void { + + // Update in resource map + this.updateEditorResourcesMap(editor, false); + + // Update in MRU list const key = this.findKey(group, editor); if (key) { @@ -187,7 +221,7 @@ export class EditorsObserver extends Disposable { } // Event - this._onDidChange.fire(); + this._onDidMostRecentlyActiveEditorsChange.fire(); } } @@ -361,7 +395,7 @@ export class EditorsObserver extends Disposable { const group = groups[i]; const groupEditorsMru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); for (let i = groupEditorsMru.length - 1; i >= 0; i--) { - this.addMostRecentEditor(group, groupEditorsMru[i], true /* enforce as active to preserve order */); + this.addMostRecentEditor(group, groupEditorsMru[i], true /* enforce as active to preserve order */, true /* is new */); } } } @@ -392,6 +426,9 @@ export class EditorsObserver extends Disposable { // Make sure key is registered as well const editorIdentifier = this.ensureKey(group, editor); mapValues.push([editorIdentifier, editorIdentifier]); + + // Update in resource map + this.updateEditorResourcesMap(editor, true); } // Fill map with deserialized values diff --git a/src/vs/workbench/browser/parts/editor/rangeDecorations.ts b/src/vs/workbench/browser/parts/editor/rangeDecorations.ts index 2b66f578a1..93d55ce1ef 100644 --- a/src/vs/workbench/browser/parts/editor/rangeDecorations.ts +++ b/src/vs/workbench/browser/parts/editor/rangeDecorations.ts @@ -10,7 +10,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IRange } from 'vs/editor/common/core/range'; import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { TrackedRangeStickiness, IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; export interface IRangeHighlightDecoration { @@ -41,9 +41,9 @@ export class RangeHighlightDecorations extends Disposable { this.rangeHighlightDecorationId = null; } - highlightRange(range: IRangeHighlightDecoration, editor?: ICodeEditor) { + highlightRange(range: IRangeHighlightDecoration, editor?: any) { editor = editor ? editor : this.getEditor(range); - if (editor) { + if (isCodeEditor(editor)) { this.doHighlightRange(editor, range); } } @@ -63,7 +63,7 @@ export class RangeHighlightDecorations extends Disposable { const resource = activeEditor && activeEditor.resource; if (resource) { if (resource.toString() === resourceRange.resource.toString()) { - return this.editorService.activeTextEditorWidget as ICodeEditor; + return this.editorService.activeTextEditorControl as ICodeEditor; } } diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index 2ae4a231f0..63c78c1b45 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -5,7 +5,7 @@ import * as DOM from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorInput, EditorOptions, SideBySideEditorInput, IEditorControl, IEditor } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, SideBySideEditorInput, IEditorControl, IEditorPane } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -24,15 +24,15 @@ export class SideBySideEditor extends BaseEditor { static readonly ID: string = 'workbench.editor.sidebysideEditor'; static MASTER: SideBySideEditor | undefined; - get minimumMasterWidth() { return this.masterEditor ? this.masterEditor.minimumWidth : 0; } - get maximumMasterWidth() { return this.masterEditor ? this.masterEditor.maximumWidth : Number.POSITIVE_INFINITY; } - get minimumMasterHeight() { return this.masterEditor ? this.masterEditor.minimumHeight : 0; } - get maximumMasterHeight() { return this.masterEditor ? this.masterEditor.maximumHeight : Number.POSITIVE_INFINITY; } + get minimumMasterWidth() { return this.masterEditorPane ? this.masterEditorPane.minimumWidth : 0; } + get maximumMasterWidth() { return this.masterEditorPane ? this.masterEditorPane.maximumWidth : Number.POSITIVE_INFINITY; } + get minimumMasterHeight() { return this.masterEditorPane ? this.masterEditorPane.minimumHeight : 0; } + get maximumMasterHeight() { return this.masterEditorPane ? this.masterEditorPane.maximumHeight : Number.POSITIVE_INFINITY; } - get minimumDetailsWidth() { return this.detailsEditor ? this.detailsEditor.minimumWidth : 0; } - get maximumDetailsWidth() { return this.detailsEditor ? this.detailsEditor.maximumWidth : Number.POSITIVE_INFINITY; } - get minimumDetailsHeight() { return this.detailsEditor ? this.detailsEditor.minimumHeight : 0; } - get maximumDetailsHeight() { return this.detailsEditor ? this.detailsEditor.maximumHeight : Number.POSITIVE_INFINITY; } + get minimumDetailsWidth() { return this.detailsEditorPane ? this.detailsEditorPane.minimumWidth : 0; } + get maximumDetailsWidth() { return this.detailsEditorPane ? this.detailsEditorPane.maximumWidth : Number.POSITIVE_INFINITY; } + get minimumDetailsHeight() { return this.detailsEditorPane ? this.detailsEditorPane.minimumHeight : 0; } + get maximumDetailsHeight() { return this.detailsEditorPane ? this.detailsEditorPane.maximumHeight : Number.POSITIVE_INFINITY; } // these setters need to exist because this extends from BaseEditor set minimumWidth(value: number) { /* noop */ } @@ -45,8 +45,8 @@ export class SideBySideEditor extends BaseEditor { get minimumHeight() { return this.minimumMasterHeight + this.minimumDetailsHeight; } get maximumHeight() { return this.maximumMasterHeight + this.maximumDetailsHeight; } - protected masterEditor?: BaseEditor; - protected detailsEditor?: BaseEditor; + protected masterEditorPane?: BaseEditor; + protected detailsEditorPane?: BaseEditor; private masterEditorContainer: HTMLElement | undefined; private detailsEditorContainer: HTMLElement | undefined; @@ -77,7 +77,7 @@ export class SideBySideEditor extends BaseEditor { this.detailsEditorContainer = DOM.$('.details-editor-container'); this.splitview.addView({ element: this.detailsEditorContainer, - layout: size => this.detailsEditor && this.detailsEditor.layout(new DOM.Dimension(size, this.dimension.height)), + layout: size => this.detailsEditorPane && this.detailsEditorPane.layout(new DOM.Dimension(size, this.dimension.height)), minimumSize: 220, maximumSize: Number.POSITIVE_INFINITY, onDidChange: Event.None @@ -86,7 +86,7 @@ export class SideBySideEditor extends BaseEditor { this.masterEditorContainer = DOM.$('.master-editor-container'); this.splitview.addView({ element: this.masterEditorContainer, - layout: size => this.masterEditor && this.masterEditor.layout(new DOM.Dimension(size, this.dimension.height)), + layout: size => this.masterEditorPane && this.masterEditorPane.layout(new DOM.Dimension(size, this.dimension.height)), minimumSize: 220, maximumSize: Number.POSITIVE_INFINITY, onDidChange: Event.None @@ -103,30 +103,30 @@ export class SideBySideEditor extends BaseEditor { } setOptions(options: EditorOptions | undefined): void { - if (this.masterEditor) { - this.masterEditor.setOptions(options); + if (this.masterEditorPane) { + this.masterEditorPane.setOptions(options); } } protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - if (this.masterEditor) { - this.masterEditor.setVisible(visible, group); + if (this.masterEditorPane) { + this.masterEditorPane.setVisible(visible, group); } - if (this.detailsEditor) { - this.detailsEditor.setVisible(visible, group); + if (this.detailsEditorPane) { + this.detailsEditorPane.setVisible(visible, group); } super.setEditorVisible(visible, group); } clearInput(): void { - if (this.masterEditor) { - this.masterEditor.clearInput(); + if (this.masterEditorPane) { + this.masterEditorPane.clearInput(); } - if (this.detailsEditor) { - this.detailsEditor.clearInput(); + if (this.detailsEditorPane) { + this.detailsEditorPane.clearInput(); } this.disposeEditors(); @@ -135,8 +135,8 @@ export class SideBySideEditor extends BaseEditor { } focus(): void { - if (this.masterEditor) { - this.masterEditor.focus(); + if (this.masterEditorPane) { + this.masterEditorPane.focus(); } } @@ -148,19 +148,19 @@ export class SideBySideEditor extends BaseEditor { } getControl(): IEditorControl | undefined { - if (this.masterEditor) { - return this.masterEditor.getControl(); + if (this.masterEditorPane) { + return this.masterEditorPane.getControl(); } return undefined; } - getMasterEditor(): IEditor | undefined { - return this.masterEditor; + getMasterEditorPane(): IEditorPane | undefined { + return this.masterEditorPane; } - getDetailsEditor(): IEditor | undefined { - return this.detailsEditor; + getDetailsEditorPane(): IEditorPane | undefined { + return this.detailsEditorPane; } private async updateInput(oldInput: SideBySideEditorInput, newInput: SideBySideEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { @@ -172,13 +172,13 @@ export class SideBySideEditor extends BaseEditor { return this.setNewInput(newInput, options, token); } - if (!this.detailsEditor || !this.masterEditor) { + if (!this.detailsEditorPane || !this.masterEditorPane) { return; } await Promise.all([ - this.detailsEditor.setInput(newInput.details, undefined, token), - this.masterEditor.setInput(newInput.master, options, token) + this.detailsEditorPane.setInput(newInput.details, undefined, token), + this.masterEditorPane.setInput(newInput.master, options, token) ]); } @@ -203,8 +203,8 @@ export class SideBySideEditor extends BaseEditor { } private async onEditorsCreated(details: BaseEditor, master: BaseEditor, detailsInput: EditorInput, masterInput: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { - this.detailsEditor = details; - this.masterEditor = master; + this.detailsEditorPane = details; + this.masterEditorPane = master; this._onDidSizeConstraintsChange.input = Event.any( Event.map(details.onDidSizeConstraintsChange, () => undefined), @@ -214,8 +214,8 @@ export class SideBySideEditor extends BaseEditor { this.onDidCreateEditors.fire(undefined); await Promise.all([ - this.detailsEditor.setInput(detailsInput, undefined, token), - this.masterEditor.setInput(masterInput, options, token)] + this.detailsEditorPane.setInput(detailsInput, undefined, token), + this.masterEditorPane.setInput(masterInput, options, token)] ); } @@ -228,14 +228,14 @@ export class SideBySideEditor extends BaseEditor { } private disposeEditors(): void { - if (this.detailsEditor) { - this.detailsEditor.dispose(); - this.detailsEditor = undefined; + if (this.detailsEditorPane) { + this.detailsEditorPane.dispose(); + this.detailsEditorPane = undefined; } - if (this.masterEditor) { - this.masterEditor.dispose(); - this.masterEditor = undefined; + if (this.masterEditorPane) { + this.masterEditorPane.dispose(); + this.masterEditorPane = undefined; } if (this.detailsEditorContainer) { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index dd9025763e..f2706b9270 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/tabstitlecontrol'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { shorten } from 'vs/base/common/labels'; import { toResource, GroupIdentifier, IEditorInput, Verbosity, EditorCommandsContextActionRunner, IEditorPartOptions, SideBySideEditor } from 'vs/workbench/common/editor'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -24,7 +24,7 @@ import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDispo import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getOrSet } from 'vs/base/common/map'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, editorBackground, breadcrumbsBackground } from 'vs/platform/theme/common/colorRegistry'; import { ResourcesDropHandler, fillResourceDataTransfers, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd'; @@ -40,9 +40,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; import { IFileService } from 'vs/platform/files/common/files'; import { withNullAsUndefined, assertAllDefined, assertIsDefined } from 'vs/base/common/types'; -import { ILabelService } from 'vs/platform/label/common/label'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { basenameOrAuthority } from 'vs/base/common/resources'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; +import { IPath, win32, posix } from 'vs/base/common/path'; // {{SQL CARBON EDIT}} -- Display the editor's tab color import * as QueryConstants from 'sql/platform/query/common/constants'; @@ -74,6 +76,8 @@ export class TabsTitleControl extends TitleControl { private readonly layoutScheduled = this._register(new MutableDisposable()); private blockRevealActiveTab: boolean | undefined; + private path: IPath = isWindows ? win32 : posix; + constructor( parent: HTMLElement, accessor: IEditorGroupsAccessor, @@ -90,13 +94,18 @@ export class TabsTitleControl extends TitleControl { @IExtensionService extensionService: IExtensionService, @IConfigurationService configurationService: IConfigurationService, @IFileService fileService: IFileService, - @ILabelService labelService: ILabelService, - @IEditorService private readonly editorService: EditorServiceImpl + @IEditorService private readonly editorService: EditorServiceImpl, + @IRemotePathService private readonly remotePathService: IRemotePathService ) { - super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService, fileService, labelService); + super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService, fileService); this.tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER)); this.closeOneEditorAction = this._register(this.instantiationService.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL)); + + // Resolve the correct path library for the OS we are on + // If we are connected to remote, this accounts for the + // remote OS. + (async () => this.path = await this.remotePathService.path)(); } protected create(parent: HTMLElement): void { @@ -198,7 +207,7 @@ export class TabsTitleControl extends TitleControl { EventHelper.stop(e); - this.group.openEditor(this.editorService.createInput({ + this.group.openEditor(this.editorService.createEditorInput({ forceUntitled: true, options: { pinned: true, // untitled is always pinned @@ -397,10 +406,16 @@ export class TabsTitleControl extends TitleControl { this.layout(this.dimension); } + private updateEditorLabelAggregator = this._register(new RunOnceScheduler(() => this.updateEditorLabels(), 0)); + updateEditorLabel(editor: IEditorInput): void { // Update all labels to account for changes to tab labels - this.updateEditorLabels(); + // Since this method may be called a lot of times from + // individual editors, we collect all those requests and + // then run the update once because we have to update + // all opened tabs in the group at once. + this.updateEditorLabelAggregator.schedule(); } updateEditorLabels(): void { @@ -881,7 +896,7 @@ export class TabsTitleControl extends TitleControl { } // Shorten descriptions - const shortenedDescriptions = shorten(descriptions); + const shortenedDescriptions = shorten(descriptions, this.path.sep); descriptions.forEach((description, i) => { for (const label of mapDescriptionToDuplicates.get(description) || []) { label.description = shortenedDescriptions[i]; @@ -1250,7 +1265,7 @@ export class TabsTitleControl extends TitleControl { let sqlEditor = editor as any; let tabColorMode = WorkbenchUtils.getSqlConfigValue(this.configurationService, 'tabColorMode'); if (tabColorMode === QueryConstants.tabColorModeOff || (tabColorMode !== QueryConstants.tabColorModeBorder && tabColorMode !== QueryConstants.tabColorModeFill) - || this.themeService.getTheme().type === HIGH_CONTRAST || !sqlEditor.tabColor) { + || this.themeService.getColorTheme().type === HIGH_CONTRAST || !sqlEditor.tabColor) { tabContainer.style.borderTopColor = ''; tabContainer.style.borderTopWidth = ''; tabContainer.style.borderTopStyle = ''; @@ -1274,7 +1289,7 @@ export class TabsTitleControl extends TitleControl { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Add border between tabs and breadcrumbs in high contrast mode. if (theme.type === HIGH_CONTRAST) { const borderColor = (theme.getColor(TAB_BORDER) || theme.getColor(contrastBorder)); diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 1732dc209f..54f522b664 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -9,7 +9,7 @@ import { isFunction, isObject, isArray, assertIsDefined } from 'vs/base/common/t import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; -import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, ITextDiffEditor, IEditorMemento } from 'vs/workbench/common/editor'; +import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, ITextDiffEditorPane, IEditorMemento } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; @@ -34,7 +34,7 @@ import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/edit /** * The text editor that leverages the diff text editor for the editing experience. */ -export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { +export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPane { static readonly ID = TEXT_DIFF_EDITOR_ID; @@ -168,12 +168,12 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { const binaryDiffInput = new DiffEditorInput(input.getName(), input.getDescription(), originalInput, modifiedInput, true); // Forward binary flag to input if supported - const fileInputFactory = Registry.as(EditorInputExtensions.EditorInputFactories).getFileInputFactory(); - if (fileInputFactory.isFileInput(originalInput)) { + const fileEditorInputFactory = Registry.as(EditorInputExtensions.EditorInputFactories).getFileEditorInputFactory(); + if (fileEditorInputFactory.isFileEditorInput(originalInput)) { originalInput.setForceOpenAsBinary(); } - if (fileInputFactory.isFileInput(modifiedInput)) { + if (fileEditorInputFactory.isFileEditorInput(modifiedInput)) { modifiedInput.setForceOpenAsBinary(); } diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 9168bb92b6..5c1c4e0c52 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -10,7 +10,7 @@ import { Event } from 'vs/base/common/event'; import { isObject, assertIsDefined, withNullAsUndefined, isFunction } from 'vs/base/common/types'; import { Dimension } from 'vs/base/browser/dom'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { EditorInput, EditorOptions, IEditorMemento, ITextEditor, TextEditorOptions } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IEditorMemento, ITextEditorPane, TextEditorOptions } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IEditorViewState, IEditor, ScrollType } from 'vs/editor/common/editorCommon'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -33,7 +33,7 @@ export interface IEditorConfiguration { * The base class of editors that leverage the text editor for the editing experience. This class is only intended to * be subclassed and not instantiated. */ -export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { +export abstract class BaseTextEditor extends BaseEditor implements ITextEditorPane { static readonly TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState'; diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 1efb60744e..53c707cc91 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -26,7 +26,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; -import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService'; import { prepareActions } from 'vs/workbench/browser/actions'; import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillResourceDataTransfers, LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; @@ -35,12 +35,10 @@ import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/bro import { EDITOR_TITLE_HEIGHT, IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions, SideBySideEditor, EditorPinnedContext } from 'vs/workbench/common/editor'; import { ResourceContextKey } from 'vs/workbench/common/resources'; -import { Themable } from 'vs/workbench/common/theme'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IFileService } from 'vs/platform/files/common/files'; import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types'; -import { ILabelService } from 'vs/platform/label/common/label'; import { isFirefox } from 'vs/base/browser/browser'; export interface IToolbarActions { @@ -83,8 +81,7 @@ export abstract class TitleControl extends Themable { @IThemeService themeService: IThemeService, @IExtensionService private readonly extensionService: IExtensionService, @IConfigurationService protected configurationService: IConfigurationService, - @IFileService private readonly fileService: IFileService, - @ILabelService private readonly labelService: ILabelService + @IFileService private readonly fileService: IFileService ) { super(themeService); @@ -98,8 +95,9 @@ export abstract class TitleControl extends Themable { } private registerListeners(): void { + + // Update actions toolbar when extension register that may contribute them this._register(this.extensionService.onDidRegisterExtensions(() => this.updateEditorActionsToolbar())); - this._register(this.labelService.onDidChangeFormatters(() => this.updateEditorLabels())); } protected abstract create(parent: HTMLElement): void; @@ -160,12 +158,12 @@ export abstract class TitleControl extends Themable { } private actionViewItemProvider(action: IAction): IActionViewItem | undefined { - const activeControl = this.group.activeControl; + const activeEditorPane = this.group.activeEditorPane; // Check Active Editor let actionViewItem: IActionViewItem | undefined = undefined; - if (activeControl instanceof BaseEditor) { - actionViewItem = activeControl.getActionViewItem(action); + if (activeEditorPane instanceof BaseEditor) { + actionViewItem = activeEditorPane.getActionViewItem(action); } // Check extensions @@ -227,9 +225,9 @@ export abstract class TitleControl extends Themable { this.editorPinnedContext.set(this.group.activeEditor ? this.group.isPinned(this.group.activeEditor) : false); // Editor actions require the editor control to be there, so we retrieve it via service - const activeControl = this.group.activeControl; - if (activeControl instanceof BaseEditor) { - const codeEditor = getCodeEditor(activeControl.getControl()); + const activeEditorPane = this.group.activeEditorPane; + if (activeEditorPane instanceof BaseEditor) { + const codeEditor = getCodeEditor(activeEditorPane.getControl()); const scopedContextKeyService = codeEditor?.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService; const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService); this.editorToolBarMenuDisposables.add(titleBarMenu); @@ -390,7 +388,7 @@ export abstract class TitleControl extends Themable { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Drag Feedback const dragImageBackground = theme.getColor(listActiveSelectionBackground); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts index 81ceb61825..7530e0964c 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts @@ -26,7 +26,7 @@ export class ClearNotificationAction extends Action { super(id, label, 'codicon-close'); } - async run(notification: INotificationViewItem): Promise { + async run(notification: INotificationViewItem): Promise { this.commandService.executeCommand(CLEAR_NOTIFICATION, notification); } } @@ -44,7 +44,7 @@ export class ClearAllNotificationsAction extends Action { super(id, label, 'codicon-clear-all'); } - async run(notification: INotificationViewItem): Promise { + async run(): Promise { this.commandService.executeCommand(CLEAR_ALL_NOTIFICATIONS); } } @@ -62,7 +62,7 @@ export class HideNotificationsCenterAction extends Action { super(id, label, 'codicon-chevron-down'); } - async run(notification: INotificationViewItem): Promise { + async run(): Promise { this.commandService.executeCommand(HIDE_NOTIFICATIONS_CENTER); } } @@ -80,7 +80,7 @@ export class ExpandNotificationAction extends Action { super(id, label, 'codicon-chevron-up'); } - async run(notification: INotificationViewItem): Promise { + async run(notification: INotificationViewItem): Promise { this.commandService.executeCommand(EXPAND_NOTIFICATION, notification); } } @@ -98,7 +98,7 @@ export class CollapseNotificationAction extends Action { super(id, label, 'codicon-chevron-down'); } - async run(notification: INotificationViewItem): Promise { + async run(notification: INotificationViewItem): Promise { this.commandService.executeCommand(COLLAPSE_NOTIFICATION, notification); } } @@ -130,7 +130,7 @@ export class CopyNotificationMessageAction extends Action { super(id, label); } - run(notification: INotificationViewItem): Promise { + run(notification: INotificationViewItem): Promise { return this.clipboardService.writeText(notification.message.raw); } } @@ -144,7 +144,7 @@ export class NotificationActionRunner extends ActionRunner { super(); } - protected async runAction(action: IAction, context: INotificationViewItem): Promise { + protected async runAction(action: IAction, context: INotificationViewItem): Promise { this.telemetryService.publicLog2('workbenchActionExecuted', { id: action.id, from: 'message' }); // Run and make sure to notify on any error again diff --git a/src/vs/workbench/browser/parts/notifications/notificationsAlerts.ts b/src/vs/workbench/browser/parts/notifications/notificationsAlerts.ts index ec134651db..fc4c761038 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsAlerts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsAlerts.ts @@ -5,7 +5,7 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { localize } from 'vs/nls'; -import { INotificationViewItem, INotificationsModel, NotificationChangeType, INotificationChangeEvent, NotificationViewItemLabelKind } from 'vs/workbench/common/notifications'; +import { INotificationViewItem, INotificationsModel, NotificationChangeType, INotificationChangeEvent, NotificationViewItemContentChangeKind } from 'vs/workbench/common/notifications'; import { Disposable } from 'vs/base/common/lifecycle'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Severity } from 'vs/platform/notification/common/notification'; @@ -45,9 +45,9 @@ export class NotificationsAlerts extends Disposable { private triggerAriaAlert(notifiation: INotificationViewItem): void { - // Trigger the alert again whenever the label changes - const listener = notifiation.onDidChangeLabel(e => { - if (e.kind === NotificationViewItemLabelKind.MESSAGE) { + // Trigger the alert again whenever the message changes + const listener = notifiation.onDidChangeContent(e => { + if (e.kind === NotificationViewItemContentChangeKind.MESSAGE) { this.doTriggerAriaAlert(notifiation); } }); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 401abae921..0f557e005e 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -5,9 +5,9 @@ import 'vs/css!./media/notificationsCenter'; import 'vs/css!./media/notificationsActions'; -import { Themable, NOTIFICATIONS_BORDER, NOTIFICATIONS_CENTER_HEADER_FOREGROUND, NOTIFICATIONS_CENTER_HEADER_BACKGROUND, NOTIFICATIONS_CENTER_BORDER } from 'vs/workbench/common/theme'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { INotificationsModel, INotificationChangeEvent, NotificationChangeType } from 'vs/workbench/common/notifications'; +import { NOTIFICATIONS_BORDER, NOTIFICATIONS_CENTER_HEADER_FOREGROUND, NOTIFICATIONS_CENTER_HEADER_BACKGROUND, NOTIFICATIONS_CENTER_BORDER } from 'vs/workbench/common/theme'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, Themable } from 'vs/platform/theme/common/themeService'; +import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, NotificationViewItemContentChangeKind } from 'vs/workbench/common/notifications'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { Emitter } from 'vs/base/common/event'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -177,7 +177,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente let focusEditor = false; - // Update notifications list based on event + // Update notifications list based on event kind const [notificationsList, notificationsCenterContainer] = assertAllDefined(this.notificationsList, this.notificationsCenterContainer); switch (e.kind) { case NotificationChangeType.ADD: @@ -185,6 +185,22 @@ export class NotificationsCenter extends Themable implements INotificationsCente e.item.updateVisibility(true); break; case NotificationChangeType.CHANGE: + // Handle content changes + // - actions: re-draw to properly show them + // - message: update notification height unless collapsed + switch (e.detail) { + case NotificationViewItemContentChangeKind.ACTIONS: + notificationsList.updateNotificationsList(e.index, 1, [e.item]); + break; + case NotificationViewItemContentChangeKind.MESSAGE: + if (e.item.expanded) { + notificationsList.updateNotificationHeight(e.item); + } + break; + } + break; + case NotificationChangeType.EXPAND_COLLAPSE: + // Re-draw entire item when expansion changes to reveal or hide details notificationsList.updateNotificationsList(e.index, 1, [e.item]); break; case NotificationChangeType.REMOVE: @@ -300,7 +316,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const notificationBorderColor = theme.getColor(NOTIFICATIONS_BORDER); if (notificationBorderColor) { collector.addRule(`.monaco-workbench > .notifications-center .notifications-list-container .monaco-list-row[data-last-element="false"] > .notification-list-item { border-bottom: 1px solid ${notificationBorderColor}; }`); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index 0fe24649fb..82c2d07616 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -107,7 +107,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace }, - handler: (accessor, args?: any) => { + handler: (accessor, args?) => { const notification = getNotificationFromContext(accessor.get(IListService), args); if (notification && !notification.hasProgress) { notification.close(); @@ -121,7 +121,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl weight: KeybindingWeight.WorkbenchContrib, when: NotificationFocusedContext, primary: KeyCode.RightArrow, - handler: (accessor, args?: any) => { + handler: (accessor, args?) => { const notification = getNotificationFromContext(accessor.get(IListService), args); if (notification) { notification.expand(); @@ -135,7 +135,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl weight: KeybindingWeight.WorkbenchContrib, when: NotificationFocusedContext, primary: KeyCode.LeftArrow, - handler: (accessor, args?: any) => { + handler: (accessor, args?) => { const notification = getNotificationFromContext(accessor.get(IListService), args); if (notification) { notification.collapse(); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index e4759038c2..4e78bc91d0 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -8,8 +8,8 @@ import { addClass, isAncestor, trackFocus } from 'vs/base/browser/dom'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IListOptions } from 'vs/base/browser/ui/list/listWidget'; -import { Themable, NOTIFICATIONS_LINKS, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND, NOTIFICATIONS_ERROR_ICON_FOREGROUND, NOTIFICATIONS_WARNING_ICON_FOREGROUND, NOTIFICATIONS_INFO_ICON_FOREGROUND } from 'vs/workbench/common/theme'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { NOTIFICATIONS_LINKS, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND, NOTIFICATIONS_ERROR_ICON_FOREGROUND, NOTIFICATIONS_WARNING_ICON_FOREGROUND, NOTIFICATIONS_INFO_ICON_FOREGROUND } from 'vs/workbench/common/theme'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, Themable } from 'vs/platform/theme/common/themeService'; import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { INotificationViewItem } from 'vs/workbench/common/notifications'; import { NotificationsListDelegate, NotificationRenderer } from 'vs/workbench/browser/parts/notifications/notificationsViewer'; @@ -21,6 +21,7 @@ import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; export class NotificationsList extends Themable { private listContainer: HTMLElement | undefined; private list: WorkbenchList | undefined; + private listDelegate: NotificationsListDelegate | undefined; private viewModel: INotificationViewItem[]; private isVisible: boolean | undefined; @@ -73,11 +74,12 @@ export class NotificationsList extends Themable { const renderer = this.instantiationService.createInstance(NotificationRenderer, actionRunner); // List + const listDelegate = this.listDelegate = new NotificationsListDelegate(this.listContainer); const list = this.list = >this._register(this.instantiationService.createInstance( WorkbenchList, 'NotificationsList', this.listContainer, - new NotificationsListDelegate(this.listContainer), + listDelegate, [renderer], { ...this.options, @@ -123,7 +125,7 @@ export class NotificationsList extends Themable { // Only allow for focus in notifications, as the // selection is too strong over the contents of // the notification - this._register(list.onSelectionChange(e => { + this._register(list.onDidChangeSelection(e => { if (e.indexes.length > 0) { list.setSelection([]); } @@ -186,6 +188,17 @@ export class NotificationsList extends Themable { } } + updateNotificationHeight(item: INotificationViewItem): void { + const index = this.viewModel.indexOf(item); + if (index === -1) { + return; + } + + const [list, listDelegate] = assertAllDefined(this.list, this.listDelegate); + list.updateElementHeight(index, listDelegate.getHeight(item)); + list.layout(); + } + hide(): void { if (!this.isVisible || !this.list) { return; // already hidden @@ -250,7 +263,7 @@ export class NotificationsList extends Themable { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const linkColor = theme.getColor(NOTIFICATIONS_LINKS); if (linkColor) { collector.addRule(`.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-message a { color: ${linkColor}; }`); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index 44e9ec9466..bf0e4cb022 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/notificationsToasts'; -import { INotificationsModel, NotificationChangeType, INotificationChangeEvent, INotificationViewItem, NotificationViewItemLabelKind } from 'vs/workbench/common/notifications'; +import { INotificationsModel, NotificationChangeType, INotificationChangeEvent, INotificationViewItem, NotificationViewItemContentChangeKind } from 'vs/workbench/common/notifications'; import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { addClass, removeClass, isAncestor, addDisposableListener, EventType, Dimension } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { NotificationsList } from 'vs/workbench/browser/parts/notifications/notificationsList'; import { Event, Emitter } from 'vs/base/common/event'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; -import { Themable, NOTIFICATIONS_TOAST_BORDER, NOTIFICATIONS_BACKGROUND } from 'vs/workbench/common/theme'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { NOTIFICATIONS_TOAST_BORDER, NOTIFICATIONS_BACKGROUND } from 'vs/workbench/common/theme'; +import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { NotificationsToastsVisibleContext, INotificationsToastController } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; @@ -196,19 +196,24 @@ export class NotificationsToasts extends Themable implements INotificationsToast // the height computation takes the content of it into account! this.layoutContainer(maxDimensions.height); - // Update when item height changes due to expansion + // Re-draw entire item when expansion changes to reveal or hide details itemDisposables.add(item.onDidChangeExpansion(() => { notificationList.updateNotificationsList(0, 1, [item]); })); - // Update when item height potentially changes due to label changes - itemDisposables.add(item.onDidChangeLabel(e => { - if (!item.expanded) { - return; // dynamic height only applies to expanded notifications - } - - if (e.kind === NotificationViewItemLabelKind.ACTIONS || e.kind === NotificationViewItemLabelKind.MESSAGE) { - notificationList.updateNotificationsList(0, 1, [item]); + // Handle content changes + // - actions: re-draw to properly show them + // - message: update notification height unless collapsed + itemDisposables.add(item.onDidChangeContent(e => { + switch (e.kind) { + case NotificationViewItemContentChangeKind.ACTIONS: + notificationList.updateNotificationsList(0, 1, [item]); + break; + case NotificationViewItemContentChangeKind.MESSAGE: + if (item.expanded) { + notificationList.updateNotificationHeight(item); + } + break; } })); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 743a3f3271..7528b6eb3b 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -17,7 +17,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { dispose, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; -import { INotificationViewItem, NotificationViewItem, NotificationViewItemLabelKind, INotificationMessage, ChoiceAction } from 'vs/workbench/common/notifications'; +import { INotificationViewItem, NotificationViewItem, NotificationViewItemContentChangeKind, INotificationMessage, ChoiceAction } from 'vs/workbench/common/notifications'; import { ClearNotificationAction, ExpandNotificationAction, CollapseNotificationAction, ConfigureNotificationAction } from 'vs/workbench/browser/parts/notifications/notificationsActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; @@ -46,14 +46,13 @@ export class NotificationsListDelegate implements IListVirtualDelegate { + // Label Change Events that we can handle directly + // (changes to actions require an entire redraw of + // the notification because it has an impact on + // epxansion state) + this.inputDisposables.add(notification.onDidChangeContent(event => { switch (event.kind) { - case NotificationViewItemLabelKind.SEVERITY: + case NotificationViewItemContentChangeKind.SEVERITY: this.renderSeverity(notification); break; - case NotificationViewItemLabelKind.PROGRESS: + case NotificationViewItemContentChangeKind.PROGRESS: this.renderProgress(notification); break; - case NotificationViewItemLabelKind.MESSAGE: + case NotificationViewItemContentChangeKind.MESSAGE: this.renderMessage(notification); break; } diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 13d51e8ed8..e186ed9aee 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -12,19 +12,19 @@ z-index: initial; } -.monaco-workbench .part.panel .title { +.monaco-workbench .part.panel .composite.title { height: 35px; display: flex; flex-direction: row; justify-content: space-between; } -.monaco-workbench .part.panel.bottom .title { +.monaco-workbench .part.panel.bottom .composite.title { border-top-width: 1px; border-top-style: solid; } -.monaco-workbench.noeditorarea .part.panel.bottom .title { +.monaco-workbench.noeditorarea .part.panel.bottom .composite.title { border-top-width: 0; /* no border when editor area is hiden */ } @@ -46,13 +46,13 @@ border-right-width: 0; /* no border when editor area is hiden */ } -.monaco-workbench .part.panel > .title > .title-actions .monaco-action-bar .action-item .action-label { +.monaco-workbench .part.panel > .composite.tit > .title-actions .monaco-action-bar .action-item .action-label { outline-offset: -2px; } /** Panel Switcher */ -.monaco-workbench .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.codicon-more { +.monaco-workbench .part.panel > .composite.tit > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.codicon-more { display: flex; align-items: center; justify-content: center; @@ -63,19 +63,19 @@ } .monaco-workbench .part.panel > .composite.title > .composite-bar-excess { - width: 100%; + width: 100px; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar { +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar { line-height: 27px; /* matches panel titles in settings */ height: 35px; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:first-child { +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item:first-child { padding-left: 12px; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item { +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item { text-transform: uppercase; padding-left: 10px; padding-right: 10px; @@ -85,24 +85,24 @@ display: flex; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label{ +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item .action-label{ margin-right: 0; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:last-child { +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item:last-child { padding-right: 10px; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item.checked .action-label { +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.checked .action-label { border-bottom: 1px solid; margin-right: 0; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .badge { +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .badge { margin-left: 8px; } -.monaco-workbench .part.panel > .title > .panel-switcher-container > .monaco-action-bar .badge .badge-content { +.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .badge .badge-content { padding: 3px 5px; border-radius: 11px; font-size: 11px; diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 73cfb24577..b9af164ac1 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -17,7 +17,7 @@ import { ActivityAction, ToggleCompositePinnedAction, ICompositeBar } from 'vs/w import { IActivity } from 'vs/workbench/common/activity'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/panel'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; export class ClosePanelAction extends Action { @@ -32,9 +32,8 @@ export class ClosePanelAction extends Action { super(id, name, 'codicon-close'); } - run(): Promise { + async run(): Promise { this.layoutService.setPanelHidden(true); - return Promise.resolve(); } } @@ -51,9 +50,8 @@ export class TogglePanelAction extends Action { super(id, name, layoutService.isVisible(Parts.PANEL_PART) ? 'panel expanded' : 'panel'); } - run(): Promise { + async run(): Promise { this.layoutService.setPanelHidden(this.layoutService.isVisible(Parts.PANEL_PART)); - return Promise.resolve(); } } @@ -71,12 +69,11 @@ class FocusPanelAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { // Show panel if (!this.layoutService.isVisible(Parts.PANEL_PART)) { this.layoutService.setPanelHidden(false); - return Promise.resolve(); } // Focus into active panel @@ -84,8 +81,6 @@ class FocusPanelAction extends Action { if (panel) { panel.focus(); } - - return Promise.resolve(); } } @@ -115,13 +110,12 @@ export class ToggleMaximizedPanelAction extends Action { })); } - run(): Promise { + async run(): Promise { if (!this.layoutService.isVisible(Parts.PANEL_PART)) { this.layoutService.setPanelHidden(false); } this.layoutService.toggleMaximizedPanel(); - return Promise.resolve(); } } @@ -133,7 +127,7 @@ const PositionPanelActionId = { interface PanelActionConfig { id: string; - when: ContextKeyExpr; + when: ContextKeyExpression; alias: string; label: string; value: T; @@ -166,10 +160,9 @@ export class SetPanelPositionAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const position = positionByActionId.get(this.id); this.layoutService.setPanelPosition(position === undefined ? Position.BOTTOM : position); - return Promise.resolve(); } } @@ -182,7 +175,7 @@ export class PanelActivityAction extends ActivityAction { super(activity); } - async run(event: any): Promise { + async run(): Promise { await this.panelService.openPanel(this.activity.id, true); this.activate(); } @@ -224,7 +217,7 @@ export class SwitchPanelViewAction extends Action { super(id, name); } - async run(offset: number): Promise { + async run(offset: number): Promise { const pinnedPanels = this.panelService.getPinnedPanels(); const activePanel = this.panelService.getActivePanel(); if (!activePanel) { @@ -256,7 +249,7 @@ export class PreviousPanelViewAction extends SwitchPanelViewAction { super(id, name, panelService); } - run(): Promise { + run(): Promise { return super.run(-1); } } @@ -274,7 +267,7 @@ export class NextPanelViewAction extends SwitchPanelViewAction { super(id, name, panelService); } - run(): Promise { + run(): Promise { return super.run(1); } } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 895010fd42..9dad515c24 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -19,7 +19,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ClosePanelAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction, PlaceHolderPanelActivityAction, PlaceHolderToggleCompositePinnedAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND, PANEL_INPUT_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry'; import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar'; @@ -150,7 +150,7 @@ export class PanelPart extends CompositePart implements IPanelService { ), compositeSize: 0, overflowActionSize: 44, - colors: (theme: ITheme) => ({ + colors: (theme: IColorTheme) => ({ activeBackgroundColor: theme.getColor(PANEL_BACKGROUND), // Background color for overflow action inactiveBackgroundColor: theme.getColor(PANEL_BACKGROUND), // Background color for overflow action activeBorderBottomColor: theme.getColor(PANEL_ACTIVE_TITLE_BORDER), @@ -633,7 +633,7 @@ export class PanelPart extends CompositePart implements IPanelService { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Panel Background: since panels can host editors, we apply a background rule if the panel background // color is different from the editor background color. This is a bit of a hack though. The better way diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts index e0da651de7..88ab6dad4c 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts @@ -99,3 +99,28 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ secondary: undefined } }); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.quickPickManyToggle', + weight: KeybindingWeight.WorkbenchContrib, + when: inQuickOpenContext, + primary: 0, + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + quickInputService.toggle(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.quickInputBack', + weight: KeybindingWeight.WorkbenchContrib + 50, + when: inQuickOpenContext, + primary: 0, + win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, + mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS }, + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + quickInputService.back(); + } +}); diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 953e32b96e..29955909f9 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -18,7 +18,7 @@ import { QuickOpenWidget, HideReason } from 'vs/base/parts/quickopen/browser/qui import { ContributableActionProvider } from 'vs/workbench/browser/actions'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IModeService } from 'vs/editor/common/services/modeService'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -34,11 +34,11 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { QUICK_INPUT_BACKGROUND, QUICK_INPUT_FOREGROUND } from 'vs/workbench/common/theme'; +import { quickInputBackground, quickInputForeground } from 'vs/platform/theme/common/colorRegistry'; import { attachQuickOpenStyler } from 'vs/platform/theme/common/styler'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; -import { scoreItem, ScorerCache, compareItemsByScore, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; +import { scoreItem, ScorerCache, compareItemsByScore, prepareQuery } from 'vs/base/common/fuzzyScorer'; import { WorkbenchTree } from 'vs/platform/list/browser/listService'; import { Schemas } from 'vs/base/common/network'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -175,7 +175,7 @@ export class QuickOpenController extends Component implements IQuickOpenService // Create upon first open if (!this.quickOpenWidget) { const quickOpenWidget: QuickOpenWidget = this.quickOpenWidget = this._register(new QuickOpenWidget( - this.layoutService.getWorkbenchElement(), + this.layoutService.container, { onOk: () => this.onOk(), onCancel: () => { /* ignore */ }, @@ -188,7 +188,7 @@ export class QuickOpenController extends Component implements IQuickOpenService keyboardSupport: false, treeCreator: (container, config, opts) => this.instantiationService.createInstance(WorkbenchTree, container, config, opts) })); - this._register(attachQuickOpenStyler(this.quickOpenWidget, this.themeService, { background: QUICK_INPUT_BACKGROUND, foreground: QUICK_INPUT_FOREGROUND })); + this._register(attachQuickOpenStyler(this.quickOpenWidget, this.themeService, { background: quickInputBackground, foreground: quickInputForeground })); const quickOpenContainer = this.quickOpenWidget.create(); addClass(quickOpenContainer, 'show-file-icons'); @@ -238,10 +238,8 @@ export class QuickOpenController extends Component implements IQuickOpenService } private positionQuickOpenWidget(): void { - const titlebarOffset = this.layoutService.getTitleBarOffset(); - if (this.quickOpenWidget) { - this.quickOpenWidget.getElement().style.top = `${titlebarOffset}px`; + this.quickOpenWidget.getElement().style.top = `${this.layoutService.offset?.top ?? 0}px`; } } @@ -676,7 +674,7 @@ class EditorHistoryHandler { if (input instanceof EditorInput) { resource = resourceForEditorHistory(input, this.fileService); } else { - resource = (input as IResourceInput).resource; + resource = (input as IResourceEditorInput).resource; } return !!resource; @@ -709,8 +707,8 @@ class EditorHistoryItemAccessorClass extends QuickOpenItemAccessorClass { super(); } - getItemDescription(entry: QuickOpenEntry): string | null { - return this.allowMatchOnDescription ? types.withUndefinedAsNull(entry.getDescription()) : null; + getItemDescription(entry: QuickOpenEntry): string | undefined { + return this.allowMatchOnDescription ? entry.getDescription() : undefined; } } @@ -722,14 +720,14 @@ export class EditorHistoryEntryGroup extends QuickOpenEntryGroup { } export class EditorHistoryEntry extends EditorQuickOpenEntry { - private input: IEditorInput | IResourceInput; + private input: IEditorInput | IResourceEditorInput; private resource: URI | undefined; private label: string; private description?: string; private icon: string; constructor( - input: IEditorInput | IResourceInput, + input: IEditorInput | IResourceEditorInput, @IEditorService editorService: IEditorService, @IModeService private readonly modeService: IModeService, @IModelService private readonly modelService: IModelService, @@ -749,15 +747,15 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { this.description = input.getDescription(); this.icon = this.getDirtyIndicatorForEditor(input); } else { - const resourceInput = input as IResourceInput; - this.resource = resourceInput.resource; - this.label = resources.basenameOrAuthority(resourceInput.resource); + const resourceEditorInput = input as IResourceEditorInput; + this.resource = resourceEditorInput.resource; + this.label = resources.basenameOrAuthority(resourceEditorInput.resource); this.description = labelService.getUriLabel(resources.dirname(this.resource), { relative: true }); - this.icon = this.getDirtyIndicatorForEditor(resourceInput); + this.icon = this.getDirtyIndicatorForEditor(resourceEditorInput); } } - private getDirtyIndicatorForEditor(input: EditorInput | IResourceInput): string { + private getDirtyIndicatorForEditor(input: EditorInput | IResourceEditorInput): string { let signalDirty = false; if (input instanceof EditorInput) { signalDirty = input.isDirty() && !input.isSaving(); @@ -794,7 +792,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { return this.resource; } - getInput(): IEditorInput | IResourceInput { + getInput(): IEditorInput | IResourceEditorInput { return this.input; } @@ -806,7 +804,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { if (this.input instanceof EditorInput) { this.editorService.openEditor(this.input, { pinned }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } else { - this.editorService.openEditor({ resource: (this.input as IResourceInput).resource, options: { pinned } }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + this.editorService.openEditor({ resource: (this.input as IResourceEditorInput).resource, options: { pinned } }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } return true; @@ -847,7 +845,7 @@ export class RemoveFromEditorHistoryAction extends Action { async run(): Promise { interface IHistoryPickEntry extends IQuickPickItem { - input: IEditorInput | IResourceInput; + input: IEditorInput | IResourceEditorInput; } const history = this.historyService.getHistory(); diff --git a/src/vs/workbench/browser/parts/quickopen/quickopen.ts b/src/vs/workbench/browser/parts/quickopen/quickopen.ts index 2b0ee96e15..a48cbd02c7 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickopen.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickopen.ts @@ -8,9 +8,13 @@ import { Action } from 'vs/base/common/actions'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; const inQuickOpenKey = 'inQuickOpen'; export const InQuickOpenContextKey = new RawContextKey(inQuickOpenKey, false); @@ -60,14 +64,12 @@ export class BaseQuickOpenNavigateAction extends Action { super(id, label); } - run(event?: any): Promise { + async run(): Promise { const keys = this.keybindingService.lookupKeybindings(this.id); const quickNavigate = this.quickNavigate ? { keybindings: keys } : undefined; this.quickOpenService.navigate(this.next, quickNavigate); this.quickInputService.navigate(this.next, quickNavigate); - - return Promise.resolve(true); } } @@ -148,3 +150,56 @@ export class QuickOpenSelectPreviousAction extends BaseQuickOpenNavigateAction { super(id, label, false, false, quickOpenService, quickInputService, keybindingService); } } + +// TODO@Ben delete eventually when quick open is implemented using quick input +export class LegacyQuickInputQuickOpenController extends Disposable { + + private readonly inQuickOpenWidgets: Record = Object.create(null); + private readonly inQuickOpenContext = InQuickOpenContextKey.bindTo(this.contextKeyService); + + constructor( + @IQuickOpenService private readonly quickOpenService: IQuickOpenService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IQuickInputService private readonly quickInputService: IQuickInputService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.quickOpenService.onShow(() => this.inQuickOpen('quickOpen', true))); + this._register(this.quickOpenService.onHide(() => this.inQuickOpen('quickOpen', false))); + + this._register(this.quickOpenService.onShow(() => this.quickInputService.hide(true))); + + this._register(this.quickInputService.onShow(() => { + this.quickOpenService.close(); + this.inQuickOpen('quickInput', true); + })); + + this._register(this.quickInputService.onHide(() => { + this.inQuickOpen('quickInput', false); + })); + } + + private inQuickOpen(widget: 'quickInput' | 'quickOpen', open: boolean) { + if (open) { + this.inQuickOpenWidgets[widget] = true; + } else { + delete this.inQuickOpenWidgets[widget]; + } + + if (Object.keys(this.inQuickOpenWidgets).length) { + if (!this.inQuickOpenContext.get()) { + this.inQuickOpenContext.set(true); + } + } else { + if (this.inQuickOpenContext.get()) { + this.inQuickOpenContext.reset(); + } + } + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LegacyQuickInputQuickOpenController, LifecyclePhase.Ready); diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 0a9ec3030a..cb25aef3df 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -328,11 +328,12 @@ class FocusSideBarAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { // Show side bar if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) { - return Promise.resolve(this.layoutService.setSideBarHidden(false)); + this.layoutService.setSideBarHidden(false); + return; } // Focus into active viewlet @@ -340,8 +341,6 @@ class FocusSideBarAction extends Action { if (viewlet) { viewlet.focus(); } - - return Promise.resolve(true); } } diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 51b7e08a86..0b97995b85 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -16,7 +16,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Action, IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, ThemeColor } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, ThemeColor } from 'vs/platform/theme/common/themeService'; import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER } from 'vs/workbench/common/theme'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -34,6 +34,7 @@ import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { values } from 'vs/base/common/map'; import { assertIsDefined } from 'vs/base/common/types'; import { Emitter, Event } from 'vs/base/common/event'; +import { Command } from 'vs/editor/common/modes'; interface IPendingStatusbarEntry { id: string; @@ -303,14 +304,12 @@ class ToggleStatusbarEntryVisibilityAction extends Action { this.checked = !model.isHidden(id); } - run(): Promise { + async run(): Promise { if (this.model.isHidden(this.id)) { this.model.show(this.id); } else { this.model.hide(this.id); } - - return Promise.resolve(true); } } @@ -320,10 +319,8 @@ class HideStatusbarEntryAction extends Action { super(id, nls.localize('hide', "Hide '{0}'", name), undefined, true); } - run(): Promise { + async run(): Promise { this.model.hide(this.id); - - return Promise.resolve(true); } } @@ -706,7 +703,7 @@ class StatusbarEntryItem extends Disposable { const command = entry.command; if (command) { - this.commandListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command, entry.arguments)); + this.commandListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command)); removeClass(this.labelContainer, 'disabled'); } else { @@ -742,13 +739,14 @@ class StatusbarEntryItem extends Disposable { this.entry = entry; } - private async executeCommand(id: string, args?: unknown[]): Promise { - args = args || []; + private async executeCommand(command: string | Command): Promise { + const id = typeof command === 'string' ? command : command.id; + const args = typeof command === 'string' ? [] : command.arguments ?? []; // Maintain old behaviour of always focusing the editor here - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - activeTextEditorWidget.focus(); + const activeTextEditorControl = this.editorService.activeTextEditorControl; + if (activeTextEditorControl) { + activeTextEditorControl.focus(); } this.telemetryService.publicLog2('workbenchActionExecuted', { id, from: 'status bar' }); @@ -770,9 +768,9 @@ class StatusbarEntryItem extends Disposable { if (color) { if (isThemeColor(color)) { - colorResult = (this.themeService.getTheme().getColor(color.id) || Color.transparent).toString(); + colorResult = (this.themeService.getColorTheme().getColor(color.id) || Color.transparent).toString(); - const listener = this.themeService.onThemeChange(theme => { + const listener = this.themeService.onDidColorThemeChange(theme => { const colorValue = (theme.getColor(color.id) || Color.transparent).toString(); if (isBackground) { @@ -808,7 +806,7 @@ class StatusbarEntryItem extends Disposable { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND); if (statusBarItemHoverBackground) { collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:hover { background-color: ${statusBarItemHoverBackground}; }`); diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index b0a1d271fe..2cab0f4025 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { IMenuService, MenuId, IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions'; -import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction, Action } from 'vs/base/common/actions'; @@ -35,14 +35,6 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { isFullscreen } from 'vs/base/browser/browser'; import { IHostService } from 'vs/workbench/services/host/browser/host'; - -// TODO@sbatten https://github.com/microsoft/vscode/issues/81360 -// eslint-disable-next-line code-layering, code-import-patterns -import { IElectronService } from 'vs/platform/electron/node/electron'; -import { optional } from 'vs/platform/instantiation/common/instantiation'; -// TODO@sbatten -// eslint-disable-next-line code-layering, code-import-patterns -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; export abstract class MenubarControl extends Disposable { @@ -294,9 +286,7 @@ export class CustomMenubarControl extends MenubarControl { @IThemeService private readonly themeService: IThemeService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IHostService protected readonly hostService: IHostService, - @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, - @optional(IElectronService) private readonly electronService: IElectronService, - @optional(IElectronEnvironmentService) private readonly electronEnvironmentService: IElectronEnvironmentService + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService ) { super( @@ -324,7 +314,7 @@ export class CustomMenubarControl extends MenubarControl { this.registerListeners(); - registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const menubarActiveWindowFgColor = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND); if (menubarActiveWindowFgColor) { collector.addRule(` @@ -650,14 +640,6 @@ export class CustomMenubarControl extends MenubarControl { protected registerListeners(): void { super.registerListeners(); - // Listen for maximize/unmaximize - if (!isWeb) { - this._register(Event.any( - Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), _ => true), - Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), _ => false) - )(e => this.updateMenubar())); - } - this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, () => { if (this.menubar && !(isIOS && BrowserFeatures.pointerEvents)) { this.menubar.blur(); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 85bc33bdbf..78a722ee63 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -19,22 +19,21 @@ import * as nls from 'vs/nls'; import { toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { Color } from 'vs/base/common/color'; import { trim } from 'vs/base/common/strings'; -import { EventType, EventHelper, Dimension, isAncestor, hide, show, removeClass, addClass, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame, removeNode } from 'vs/base/browser/dom'; +import { EventType, EventHelper, Dimension, isAncestor, removeClass, addClass, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame, removeNode } from 'vs/base/browser/dom'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; -import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { template } from 'vs/base/common/labels'; import { ILabelService } from 'vs/platform/label/common/label'; import { Emitter } from 'vs/base/common/event'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -42,10 +41,6 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IProductService } from 'vs/platform/product/common/productService'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -// TODO@sbatten https://github.com/microsoft/vscode/issues/81360 -// eslint-disable-next-line code-layering, code-import-patterns -import { IElectronService } from 'vs/platform/electron/node/electron'; - export class TitlebarPart extends Part implements ITitleService { private static readonly NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]"); @@ -68,15 +63,10 @@ export class TitlebarPart extends Part implements ITitleService { _serviceBrand: undefined; - private title!: HTMLElement; - private dragRegion: HTMLElement | undefined; - private windowControls: HTMLElement | undefined; - private maxRestoreControl: HTMLElement | undefined; - private appIcon: HTMLElement | undefined; - private customMenubar: CustomMenubarControl | undefined; - private menubar?: HTMLElement; - private resizer: HTMLElement | undefined; - private lastLayoutDimensions: Dimension | undefined; + protected title!: HTMLElement; + protected customMenubar: CustomMenubarControl | undefined; + protected menubar?: HTMLElement; + protected lastLayoutDimensions: Dimension | undefined; private titleBarStyle: 'native' | 'custom'; private pendingTitle: string | undefined; @@ -86,15 +76,15 @@ export class TitlebarPart extends Part implements ITitleService { private readonly properties: ITitleProperties = { isPure: true, isAdmin: false }; private readonly activeEditorListeners = this._register(new DisposableStore()); - private titleUpdater: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0)); + private readonly titleUpdater = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0)); private contextMenu: IMenu; constructor( @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IConfigurationService protected readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @@ -105,7 +95,6 @@ export class TitlebarPart extends Part implements ITitleService { @IContextKeyService contextKeyService: IContextKeyService, @IHostService private readonly hostService: IHostService, @IProductService private readonly productService: IProductService, - @optional(IElectronService) private electronService: IElectronService ) { super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); @@ -136,7 +125,7 @@ export class TitlebarPart extends Part implements ITitleService { this.updateStyles(); } - private onConfigurationChanged(event: IConfigurationChangeEvent): void { + protected onConfigurationChanged(event: IConfigurationChangeEvent): void { if (event.affectsConfiguration('window.title')) { this.titleUpdater.schedule(); } @@ -150,41 +139,16 @@ export class TitlebarPart extends Part implements ITitleService { } } } - - if (event.affectsConfiguration('window.doubleClickIconToClose')) { - if (this.appIcon) { - this.onUpdateAppIconDragBehavior(); - } - } } - private onMenubarVisibilityChanged(visible: boolean) { + protected onMenubarVisibilityChanged(visible: boolean) { if (isWeb || isWindows || isLinux) { - // Hide title when toggling menu bar - if (!isWeb && this.currentMenubarVisibility === 'toggle' && visible) { - // Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor - if (this.dragRegion) { - hide(this.dragRegion); - setTimeout(() => show(this.dragRegion!), 50); - } - } - this.adjustTitleMarginToCenter(); this._onMenubarVisibilityChange.fire(visible); } } - private onMenubarFocusChanged(focused: boolean) { - if (!isWeb && (isWindows || isLinux) && this.currentMenubarVisibility !== 'compact' && this.dragRegion) { - if (focused) { - hide(this.dragRegion); - } else { - show(this.dragRegion); - } - } - } - private onActiveEditorChange(): void { // Dispose old listeners @@ -352,7 +316,7 @@ export class TitlebarPart extends Part implements ITitleService { } } - private installMenubar(): void { + protected installMenubar(): void { // If the menubar is already installed, skip if (this.menubar) { return; @@ -367,27 +331,11 @@ export class TitlebarPart extends Part implements ITitleService { this.customMenubar.create(this.menubar); this._register(this.customMenubar.onVisibilityChange(e => this.onMenubarVisibilityChanged(e))); - this._register(this.customMenubar.onFocusStateChange(e => this.onMenubarFocusChanged(e))); } createContentArea(parent: HTMLElement): HTMLElement { this.element = parent; - // Draggable region that we can manipulate for #52522 - if (!isWeb) { - this.dragRegion = append(this.element, $('div.titlebar-drag-region')); - } - - // App Icon (Native Windows/Linux) - if (!isMacintosh && !isWeb) { - this.appIcon = append(this.element, $('div.window-appicon')); - this.onUpdateAppIconDragBehavior(); - - this._register(addDisposableListener(this.appIcon, EventType.DBLCLICK, (e => { - this.electronService.closeWindow(); - }))); - } - // Menubar: install a custom menu bar depending on configuration // and when not in activity bar if (this.titleBarStyle !== 'native' @@ -415,40 +363,6 @@ export class TitlebarPart extends Part implements ITitleService { })); }); - // Window Controls (Native Windows/Linux) - if (!isMacintosh && !isWeb) { - this.windowControls = append(this.element, $('div.window-controls-container')); - - // Minimize - const minimizeIcon = append(this.windowControls, $('div.window-icon.window-minimize.codicon.codicon-chrome-minimize')); - this._register(addDisposableListener(minimizeIcon, EventType.CLICK, e => { - this.electronService.minimizeWindow(); - })); - - // Restore - this.maxRestoreControl = append(this.windowControls, $('div.window-icon.window-max-restore.codicon')); - this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, async e => { - const maximized = await this.electronService.isMaximized(); - if (maximized) { - return this.electronService.unmaximizeWindow(); - } - - return this.electronService.maximizeWindow(); - })); - - // Close - const closeIcon = append(this.windowControls, $('div.window-icon.window-close.codicon.codicon-chrome-close')); - this._register(addDisposableListener(closeIcon, EventType.CLICK, e => { - this.electronService.closeWindow(); - })); - - // Resizer - this.resizer = append(this.element, $('div.resizer')); - - this._register(this.layoutService.onMaximizeChange(maximized => this.onDidChangeMaximized(maximized))); - this.onDidChangeMaximized(this.layoutService.isWindowMaximized()); - } - // Since the title area is used to drag the window, we do not want to steal focus from the // currently active element. So we restore focus after a timeout back to where it was. this._register(addDisposableListener(this.element, EventType.MOUSE_DOWN, e => { @@ -469,28 +383,6 @@ export class TitlebarPart extends Part implements ITitleService { return this.element; } - private onDidChangeMaximized(maximized: boolean) { - if (this.maxRestoreControl) { - if (maximized) { - removeClass(this.maxRestoreControl, 'codicon-chrome-maximize'); - addClass(this.maxRestoreControl, 'codicon-chrome-restore'); - } else { - removeClass(this.maxRestoreControl, 'codicon-chrome-restore'); - addClass(this.maxRestoreControl, 'codicon-chrome-maximize'); - } - } - - if (this.resizer) { - if (maximized) { - hide(this.resizer); - } else { - show(this.resizer); - } - } - - this.adjustTitleMarginToCenter(); - } - updateStyles(): void { super.updateStyles(); @@ -524,15 +416,6 @@ export class TitlebarPart extends Part implements ITitleService { } } - private onUpdateAppIconDragBehavior() { - const setting = this.configurationService.getValue('window.doubleClickIconToClose'); - if (setting && this.appIcon) { - (this.appIcon.style as any)['-webkit-app-region'] = 'no-drag'; - } else if (this.appIcon) { - (this.appIcon.style as any)['-webkit-app-region'] = 'drag'; - } - } - private onContextMenu(e: MouseEvent): void { // Find target anchor @@ -551,10 +434,10 @@ export class TitlebarPart extends Part implements ITitleService { }); } - private adjustTitleMarginToCenter(): void { + protected adjustTitleMarginToCenter(): void { if (this.customMenubar && this.menubar) { - const leftMarker = (this.appIcon ? this.appIcon.clientWidth : 0) + this.menubar.clientWidth + 10; - const rightMarker = this.element.clientWidth - (this.windowControls ? this.windowControls.clientWidth : 0) - 10; + const leftMarker = this.menubar.clientWidth + 10; + const rightMarker = this.element.clientWidth - 10; // Not enough space to center the titlebar within window, // Center between menu and window controls @@ -572,7 +455,7 @@ export class TitlebarPart extends Part implements ITitleService { this.title.style.transform = 'translate(-50%, 0)'; } - private get currentMenubarVisibility(): MenuBarVisibility { + protected get currentMenubarVisibility(): MenuBarVisibility { return getMenuBarVisibility(this.configurationService, this.environmentService); } @@ -583,26 +466,8 @@ export class TitlebarPart extends Part implements ITitleService { // Only prevent zooming behavior on macOS or when the menubar is not visible if ((!isWeb && isMacintosh) || this.currentMenubarVisibility === 'hidden') { this.title.style.zoom = `${1 / getZoomFactor()}`; - if (!isWeb && (isWindows || isLinux)) { - if (this.appIcon) { - this.appIcon.style.zoom = `${1 / getZoomFactor()}`; - } - - if (this.windowControls) { - this.windowControls.style.zoom = `${1 / getZoomFactor()}`; - } - } } else { this.title.style.zoom = null; - if (!isWeb && (isWindows || isLinux)) { - if (this.appIcon) { - this.appIcon.style.zoom = null; - } - - if (this.windowControls) { - this.windowControls.style.zoom = null; - } - } } runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter()); @@ -627,7 +492,7 @@ export class TitlebarPart extends Part implements ITitleService { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND); if (titlebarActiveFg) { collector.addRule(` @@ -646,5 +511,3 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } }); - -registerSingleton(ITitleService, TitlebarPart); diff --git a/src/vs/workbench/browser/parts/views/media/paneviewlet.css b/src/vs/workbench/browser/parts/views/media/paneviewlet.css index b631c68e48..9ced3bbf37 100644 --- a/src/vs/workbench/browser/parts/views/media/paneviewlet.css +++ b/src/vs/workbench/browser/parts/views/media/paneviewlet.css @@ -7,6 +7,10 @@ border-top: none !important; /* less clutter: do not show any border for first views in a pane */ } +.monaco-pane-view .pane > .pane-header { + position: relative; +} + .monaco-pane-view .pane > .pane-header > .actions.show { display: initial; } @@ -19,3 +23,15 @@ -webkit-margin-before: 0; -webkit-margin-after: 0; } + +.monaco-pane-view .pane > .pane-header h3.title:first-child { + margin-left: 7px; +} + +.monaco-pane-view .pane > .pane-header .monaco-progress-container { + position: absolute; + left: 0; + bottom: 0; + z-index: 5; + height: 2px; +} diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index 084e5158ab..1cf41c8eaa 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -141,6 +141,10 @@ margin-top: 3px; } +.customview-tree .monaco-list .monaco-list-row.selected .custom-view-tree-node-item > .custom-view-tree-node-item-icon.codicon { + color: currentColor !important; +} + .customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-resourceLabel .monaco-icon-label-container > .monaco-icon-name-container { flex: 1; } diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/treeView.ts similarity index 82% rename from src/vs/workbench/browser/parts/views/customView.ts rename to src/vs/workbench/browser/parts/views/treeView.ts index 3ba9d3e41d..ca64933c77 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -7,19 +7,18 @@ import 'vs/css!./media/views'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IAction, IActionViewItem, ActionRunner, Action } from 'vs/base/common/actions'; +import { IAction, ActionRunner } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, MenuItemAction, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, ContextKeyExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ITreeItemLabel, Extensions, IViewDescriptorService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import * as DOM from 'vs/base/browser/dom'; import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; @@ -28,7 +27,7 @@ import { URI } from 'vs/base/common/uri'; import { dirname, basename } from 'vs/base/common/resources'; import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService'; import { FileKind } from 'vs/platform/files/common/files'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { localize } from 'vs/nls'; import { timeout } from 'vs/base/common/async'; @@ -45,13 +44,12 @@ import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -export class CustomTreeViewPane extends ViewPane { +export class TreeViewPane extends ViewPane { private treeView: ITreeView; constructor( options: IViewletViewOptions, - @INotificationService private readonly notificationService: INotificationService, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @@ -62,13 +60,14 @@ export class CustomTreeViewPane extends ViewPane { @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super({ ...(options as IViewPaneOptions), titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id)); this.treeView = treeView; this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this)); this._register(this.treeView.onDidChangeTitle((newTitle) => this.updateTitle(newTitle))); this._register(toDisposable(() => this.treeView.setVisibility(false))); this._register(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility())); + this._register(this.treeView.onDidChangeWelcomeState(() => this._onDidChangeViewWelcomeState.fire())); this.updateTreeVisibility(); } @@ -80,27 +79,19 @@ export class CustomTreeViewPane extends ViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); - if (this.treeView instanceof CustomTreeView) { + if (this.treeView instanceof TreeView) { this.treeView.show(container); } } + shouldShowWelcome(): boolean { + return ((this.treeView.dataProvider === undefined) || !!this.treeView.dataProvider.isTreeEmpty) && (this.treeView.message === undefined); + } + layoutBody(height: number, width: number): void { this.treeView.layout(height, width); } - getActions(): IAction[] { - return [...super.getActions(), ...this.treeView.getPrimaryActions()]; - } - - getSecondaryActions(): IAction[] { - return [...super.getSecondaryActions(), ...this.treeView.getSecondaryActions()]; - } - - getActionViewItem(action: IAction): IActionViewItem | undefined { - return action instanceof MenuItemAction ? new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService) : undefined; - } - getOptimalWidth(): number { return this.treeView.getOptimalWidth(); } @@ -120,15 +111,18 @@ class Root implements ITreeItem { const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data."); -class CustomTree extends WorkbenchAsyncDataTree { } +class Tree extends WorkbenchAsyncDataTree { } -export class CustomTreeView extends Disposable implements ITreeView { +export class TreeView extends Disposable implements ITreeView { private isVisible: boolean = false; - private activated: boolean = false; private _hasIconForParentNode = false; private _hasIconForLeafNode = false; - private _showCollapseAllAction = false; + + private readonly collapseAllContextKey: RawContextKey; + private readonly collapseAllContext: IContextKey; + private readonly refreshContextKey: RawContextKey; + private readonly refreshContext: IContextKey; private focused: boolean = false; private domNode!: HTMLElement; @@ -136,7 +130,7 @@ export class CustomTreeView extends Disposable implements ITreeView { private _messageValue: string | undefined; private _canSelectMany: boolean = false; private messageElement!: HTMLDivElement; - private tree: CustomTree | undefined; + private tree: Tree | undefined; private treeLabels: ResourceLabels | undefined; private root: ITreeItem; @@ -157,29 +151,37 @@ export class CustomTreeView extends Disposable implements ITreeView { private readonly _onDidChangeActions: Emitter = this._register(new Emitter()); readonly onDidChangeActions: Event = this._onDidChangeActions.event; + private readonly _onDidChangeWelcomeState: Emitter = this._register(new Emitter()); + readonly onDidChangeWelcomeState: Event = this._onDidChangeWelcomeState.event; + private readonly _onDidChangeTitle: Emitter = this._register(new Emitter()); readonly onDidChangeTitle: Event = this._onDidChangeTitle.event; private readonly _onDidCompleteRefresh: Emitter = this._register(new Emitter()); constructor( - private id: string, + protected readonly id: string, private _title: string, - @IExtensionService private readonly extensionService: IExtensionService, - @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, + @IThemeService private readonly themeService: IThemeService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ICommandService private readonly commandService: ICommandService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IProgressService private readonly progressService: IProgressService, + @IProgressService protected readonly progressService: IProgressService, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IKeybindingService private readonly keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, - @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(); this.root = new Root(); + this.collapseAllContextKey = new RawContextKey(`treeView.${this.id}.enableCollapseAll`, false); + this.collapseAllContext = this.collapseAllContextKey.bindTo(contextKeyService); + this.refreshContextKey = new RawContextKey(`treeView.${this.id}.enableRefresh`, false); + this.refreshContext = this.refreshContextKey.bindTo(contextKeyService); + this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); - this._register(this.themeService.onThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); + this._register(this.themeService.onDidColorThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('explorer.decorations')) { this.doRefresh([this.root]); /** soft refresh **/ @@ -190,6 +192,7 @@ export class CustomTreeView extends Disposable implements ITreeView { this.tree?.updateOptions({ overrideStyles: { listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND } }); } })); + this.registerActions(); this.create(); } @@ -214,21 +217,43 @@ export class CustomTreeView extends Disposable implements ITreeView { if (dataProvider) { this._dataProvider = new class implements ITreeViewDataProvider { + private _isEmpty: boolean = true; + private _onDidChangeEmpty: Emitter = new Emitter(); + public onDidChangeEmpty: Event = this._onDidChangeEmpty.event; + + get isTreeEmpty(): boolean { + return this._isEmpty; + } + async getChildren(node: ITreeItem): Promise { + let children: ITreeItem[]; if (node && node.children) { - return Promise.resolve(node.children); + children = node.children; + } else { + children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node)); + node.children = children; + } + if (node instanceof Root) { + const oldEmpty = this._isEmpty; + this._isEmpty = children.length === 0; + if (oldEmpty !== this._isEmpty) { + this._onDidChangeEmpty.fire(); + } } - const children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node)); - node.children = children; return children; } }; + if (this._dataProvider.onDidChangeEmpty) { + this._register(this._dataProvider.onDidChangeEmpty(() => this._onDidChangeWelcomeState.fire())); + } this.updateMessage(); this.refresh(); } else { this._dataProvider = undefined; this.updateMessage(); } + + this._onDidChangeWelcomeState.fire(); } private _message: string | undefined; @@ -239,6 +264,7 @@ export class CustomTreeView extends Disposable implements ITreeView { set message(message: string | undefined) { this._message = message; this.updateMessage(); + this._onDidChangeWelcomeState.fire(); } get title(): string { @@ -271,26 +297,61 @@ export class CustomTreeView extends Disposable implements ITreeView { } get showCollapseAllAction(): boolean { - return this._showCollapseAllAction; + return !!this.collapseAllContext.get(); } set showCollapseAllAction(showCollapseAllAction: boolean) { - if (this._showCollapseAllAction !== !!showCollapseAllAction) { - this._showCollapseAllAction = !!showCollapseAllAction; - this._onDidChangeActions.fire(); - } + this.collapseAllContext.set(showCollapseAllAction); } - getPrimaryActions(): IAction[] { - if (this.showCollapseAllAction) { - return [new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action codicon-collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve())]; - } else { - return []; - } + get showRefreshAction(): boolean { + return !!this.refreshContext.get(); } - getSecondaryActions(): IAction[] { - return []; + set showRefreshAction(showRefreshAction: boolean) { + this.refreshContext.set(showRefreshAction); + } + + private registerActions() { + const that = this; + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.treeView.${that.id}.refresh`, + title: localize('refresh', "Refresh"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.refreshContextKey), + group: 'navigation', + order: Number.MAX_SAFE_INTEGER - 1, + }, + icon: { id: 'codicon/refresh' } + }); + } + async run(): Promise { + return that.refresh(); + } + })); + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.treeView.${that.id}.collapseAll`, + title: localize('collapseAll', "Collapse All"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.collapseAllContextKey), + group: 'navigation', + order: Number.MAX_SAFE_INTEGER, + }, + icon: { id: 'codicon/collapse-all' } + }); + } + async run(): Promise { + if (that.tree) { + return new CollapseAllAction(that.tree, true).run(); + } + } + })); } setVisibility(isVisible: boolean): void { @@ -300,9 +361,6 @@ export class CustomTreeView extends Disposable implements ITreeView { } this.isVisible = isVisible; - if (this.isVisible) { - this.activate(); - } if (this.tree) { if (this.isVisible) { @@ -360,9 +418,9 @@ export class CustomTreeView extends Disposable implements ITreeView { const aligner = new Aligner(this.themeService); const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner); - this.tree = this._register(this.instantiationService.createInstance(CustomTree, 'CustomView', this.treeContainer, new CustomTreeDelegate(), [renderer], + this.tree = this._register(this.instantiationService.createInstance(Tree, this.id, this.treeContainer, new TreeViewDelegate(), [renderer], dataSource, { - identityProvider: new CustomViewIdentityProvider(), + identityProvider: new TreeViewIdentityProvider(), accessibilityProvider: { getAriaLabel(element: ITreeItem): string { return element.tooltip ? element.tooltip : element.label ? element.label.label : ''; @@ -404,9 +462,9 @@ export class CustomTreeView extends Disposable implements ITreeView { })); this.tree.setInput(this.root).then(() => this.updateContentAreas()); - const customTreeNavigator = new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false }); - this._register(customTreeNavigator); - this._register(customTreeNavigator.onDidOpenResource(e => { + const treeNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false }); + this._register(treeNavigator); + this._register(treeNavigator.onDidOpenResource(e => { if (!e.browserEvent) { return; } @@ -457,7 +515,7 @@ export class CustomTreeView extends Disposable implements ITreeView { }); } - private updateMessage(): void { + protected updateMessage(): void { if (this._message) { this.showMessage(this._message); } else if (!this.dataProvider) { @@ -574,17 +632,6 @@ export class CustomTreeView extends Disposable implements ITreeView { return Promise.resolve(); } - private activate() { - if (!this.activated) { - this.progressService.withProgress({ location: this.viewContainer.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`)) - .then(() => timeout(2000)) - .then(() => { - this.updateMessage(); - }); - this.activated = true; - } - } - private refreshing: boolean = false; private async doRefresh(elements: ITreeItem[]): Promise { const tree = this.tree; @@ -613,13 +660,13 @@ export class CustomTreeView extends Disposable implements ITreeView { } } -class CustomViewIdentityProvider implements IIdentityProvider { +class TreeViewIdentityProvider implements IIdentityProvider { getId(element: ITreeItem): { toString(): string; } { return element.handle; } } -class CustomTreeDelegate implements IListVirtualDelegate { +class TreeViewDelegate implements IListVirtualDelegate { getHeight(element: ITreeItem): number { return TreeRenderer.ITEM_HEIGHT; @@ -697,7 +744,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer | undefined; - constructor(private themeService: IWorkbenchThemeService) { + constructor(private themeService: IThemeService) { super(); } @@ -854,7 +901,7 @@ class Aligner extends Disposable { } private hasIcon(node: ITreeItem): boolean { - const icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark; + const icon = this.themeService.getColorTheme().type === LIGHT ? node.icon : node.iconDark; if (icon) { return true; } @@ -881,7 +928,7 @@ class MultipleSelectionActionRunner extends ActionRunner { })); } - runAction(action: IAction, context: TreeViewItemHandleArg): Promise { + runAction(action: IAction, context: TreeViewItemHandleArg): Promise { const selection = this.getSelectedResources(); let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined; let actionInSelected: boolean = false; @@ -938,3 +985,44 @@ class TreeMenus extends Disposable implements IDisposable { return result; } } + +export class CustomTreeView extends TreeView { + + private activated: boolean = false; + + constructor( + id: string, + title: string, + @IThemeService themeService: IThemeService, + @IInstantiationService instantiationService: IInstantiationService, + @ICommandService commandService: ICommandService, + @IConfigurationService configurationService: IConfigurationService, + @IProgressService progressService: IProgressService, + @IContextMenuService contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, + @INotificationService notificationService: INotificationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IExtensionService private readonly extensionService: IExtensionService, + ) { + super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService, contextKeyService); + } + + setVisibility(isVisible: boolean): void { + super.setVisibility(isVisible); + if (this.visible) { + this.activate(); + } + } + + private activate() { + if (!this.activated) { + this.progressService.withProgress({ location: this.viewContainer.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`)) + .then(() => timeout(2000)) + .then(() => { + this.updateMessage(); + }); + this.activated = true; + } + } +} diff --git a/src/vs/workbench/browser/parts/views/viewMenuActions.ts b/src/vs/workbench/browser/parts/views/viewMenuActions.ts index 5fc6479dab..4818f442ec 100644 --- a/src/vs/workbench/browser/parts/views/viewMenuActions.ts +++ b/src/vs/workbench/browser/parts/views/viewMenuActions.ts @@ -36,7 +36,7 @@ export class ViewMenuActions extends Disposable { const updateActions = () => { this.primaryActions = []; this.secondaryActions = []; - this.titleActionsDisposable.value = createAndFillInActionBarActions(menu, undefined, { primary: this.primaryActions, secondary: this.secondaryActions }); + this.titleActionsDisposable.value = createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, { primary: this.primaryActions, secondary: this.secondaryActions }); this._onDidChangeTitle.fire(); }; this._register(menu.onDidChange(updateActions)); @@ -45,7 +45,7 @@ export class ViewMenuActions extends Disposable { const contextMenu = this._register(this.menuService.createMenu(contextMenuId, scopedContextKeyService)); const updateContextMenuActions = () => { this.contextMenuActions = []; - this.titleActionsDisposable.value = createAndFillInActionBarActions(contextMenu, undefined, { primary: [], secondary: this.contextMenuActions }); + this.titleActionsDisposable.value = createAndFillInActionBarActions(contextMenu, { shouldForwardArgs: true }, { primary: [], secondary: this.contextMenuActions }); }; this._register(contextMenu.onDidChange(updateContextMenuActions)); updateContextMenuActions(); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 1bf4c43b93..36775b1d02 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/paneviewlet'; import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; -import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler } from 'vs/platform/theme/common/styler'; +import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass } from 'vs/base/browser/dom'; import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -43,6 +43,10 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Button } from 'vs/base/browser/ui/button/button'; import { Link } from 'vs/platform/opener/browser/link'; import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; +import { Orientation } from 'vs/base/browser/ui/sash/sash'; +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; +import { IProgressIndicator } from 'vs/platform/progress/common/progress'; export interface IPaneColors extends IColorMapping { dropBackground?: ColorIdentifier; @@ -53,7 +57,6 @@ export interface IPaneColors extends IColorMapping { export interface IViewPaneOptions extends IPaneOptions { id: string; - title: string; showActionsAlways?: boolean; titleMenuId?: MenuId; } @@ -181,6 +184,8 @@ export abstract class ViewPane extends Pane implements IView { title: string; private readonly menuActions: ViewMenuActions; + private progressBar!: ProgressBar; + private progressIndicator!: IProgressIndicator; private toolbar?: ToolBar; private readonly showActionsAlways: boolean = false; @@ -205,12 +210,14 @@ export abstract class ViewPane extends Pane implements IView { @IThemeService protected themeService: IThemeService, @ITelemetryService protected telemetryService: ITelemetryService, ) { - super(options); + super({ ...options, ...{ orientation: viewDescriptorService.getViewLocation(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } }); this.id = options.id; this.title = options.title; this.showActionsAlways = !!options.showActionsAlways; this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService); + this._preventCollapse = this.viewDescriptorService.getViewLocation(this.id) === ViewContainerLocation.Panel; + this._expanded = this._preventCollapse || this._expanded; this.menuActions = this._register(instantiationService.createInstance(ViewMenuActions, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext)); this._register(this.menuActions.onDidChangeTitle(() => this.updateActions())); @@ -266,7 +273,9 @@ export abstract class ViewPane extends Pane implements IView { protected renderHeader(container: HTMLElement): void { this.headerContainer = container; - this.renderTwisties(container); + if (!this._preventCollapse) { + this.renderTwisties(container); + } this.renderHeaderTitle(container, this.title); @@ -285,6 +294,13 @@ export abstract class ViewPane extends Pane implements IView { const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig)); this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this)); this.updateActionsVisibility(); + + if (this.progressBar !== undefined) { + // Progress bar + this.progressBar = this._register(new ProgressBar(this.headerContainer)); + this._register(attachProgressBarStyler(this.progressBar, this.themeService)); + this.progressBar.hide(); + } } protected renderTwisties(container: HTMLElement): void { @@ -316,6 +332,24 @@ export abstract class ViewPane extends Pane implements IView { // noop } + getProgressIndicator() { + if (!this.headerContainer) { + return undefined; + } + + if (this.progressBar === undefined) { + // Progress bar + this.progressBar = this._register(new ProgressBar(this.headerContainer)); + this._register(attachProgressBarStyler(this.progressBar, this.themeService)); + this.progressBar.hide(); + } + + if (this.progressIndicator === undefined) { + this.progressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), this.id, this.isVisible(), { exclusiveProgressBar: true }); + } + return this.progressIndicator; + } + protected getProgressLocation(): string { return this.viewDescriptorService.getViewContainer(this.id)!.id; } @@ -354,15 +388,15 @@ export abstract class ViewPane extends Pane implements IView { } getActions(): IAction[] { - return this.menuActions ? this.menuActions.getPrimaryActions() : []; + return this.menuActions.getPrimaryActions(); } getSecondaryActions(): IAction[] { - return this.menuActions ? this.menuActions.getSecondaryActions() : []; + return this.menuActions.getSecondaryActions(); } getContextMenuActions(): IAction[] { - return this.menuActions ? this.menuActions.getContextMenuActions() : []; + return this.menuActions.getContextMenuActions(); } getActionViewItem(action: IAction): IActionViewItem | undefined { @@ -564,6 +598,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } create(parent: HTMLElement): void { + const options = this.options as IPaneViewOptions; + options.orientation = this.viewDescriptorService.getViewContainerLocation(this.viewContainer) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL; this.paneview = this._register(new PaneView(parent, this.options)); this._register(this.paneview.onDidDrop(({ from, to }) => this.movePane(from as ViewPane, to as ViewPane))); this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e)))); @@ -837,8 +873,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { { id: viewDescriptor.id, title: viewDescriptor.name, - expanded: !collapsed, - minimumBodySize: this.viewDescriptorService.getViewContainerLocation(this.viewContainer) === ViewContainerLocation.Panel ? 0 : 120 + expanded: !collapsed }); pane.render(); diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 4f9f1523b4..3ff9961bbe 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -17,7 +17,6 @@ import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/act import { localize } from 'vs/nls'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { values } from 'vs/base/common/map'; -import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { toggleClass, addClass } from 'vs/base/browser/dom'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; @@ -27,7 +26,7 @@ import { VIEW_ID as SEARCH_VIEW_ID } from 'vs/workbench/services/search/common/s import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { PaneCompositePanel, PanelRegistry, PanelDescriptor, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -35,6 +34,7 @@ import { Viewlet, ViewletDescriptor, ViewletRegistry, Extensions as ViewletExten import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { URI } from 'vs/base/common/uri'; +import { IProgressIndicator } from 'vs/platform/progress/common/progress'; export interface IViewState { visibleGlobal: boolean | undefined; @@ -466,6 +466,8 @@ export class ViewsService extends Disposable implements IViewsService { private readonly visibleViewContextKeys: Map>; + private readonly viewPaneContainers: Map; + constructor( @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IPanelService private readonly panelService: IPanelService, @@ -477,6 +479,7 @@ export class ViewsService extends Disposable implements IViewsService { this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); this.viewDisposable = new Map(); this.visibleViewContextKeys = new Map>(); + this.viewPaneContainers = new Map(); this._register(toDisposable(() => { this.viewDisposable.forEach(disposable => disposable.dispose()); @@ -485,13 +488,16 @@ export class ViewsService extends Disposable implements IViewsService { this.viewContainersRegistry.all.forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer, this.viewContainersRegistry.getViewContainerLocation(viewContainer))); this._register(this.viewContainersRegistry.onDidRegister(({ viewContainer, viewContainerLocation }) => this.onDidRegisterViewContainer(viewContainer, viewContainerLocation))); + + this._register(this.viewContainersRegistry.onDidDeregister(e => this.viewPaneContainers.delete(e.viewContainer.id))); } - registerViewPaneContainer(viewPaneContainer: ViewPaneContainer): ViewPaneContainer { + private registerViewPaneContainer(viewPaneContainer: ViewPaneContainer): void { this._register(viewPaneContainer.onDidAddViews(views => this.onViewsAdded(views))); this._register(viewPaneContainer.onDidChangeViewVisibility(view => this.onViewsVisibilityChanged(view, view.isBodyVisible()))); this._register(viewPaneContainer.onDidRemoveViews(views => this.onViewsRemoved(views))); - return viewPaneContainer; + + this.viewPaneContainers.set(viewPaneContainer.getId(), viewPaneContainer); } private onViewsAdded(added: IView[]): void { @@ -521,7 +527,8 @@ export class ViewsService extends Disposable implements IViewsService { return contextKey; } - private onDidRegisterViewContainer(viewContainer: ViewContainer, location: ViewContainerLocation): void { + private onDidRegisterViewContainer(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void { + this.registerViewletOrPanel(viewContainer, viewContainerLocation); const viewDescriptorCollection = this.viewDescriptorService.getViewDescriptors(viewContainer); this.onViewDescriptorsAdded(viewDescriptorCollection.allViewDescriptors, viewContainer); this._register(viewDescriptorCollection.onDidChangeViews(({ added, removed }) => { @@ -559,7 +566,7 @@ export class ViewsService extends Disposable implements IViewsService { } }); } - run(accessor: ServicesAccessor): any { + run(accessor: ServicesAccessor): void { accessor.get(IViewsService).openView(viewDescriptor.id, true); } })); @@ -589,7 +596,7 @@ export class ViewsService extends Disposable implements IViewsService { }], }); } - run(accessor: ServicesAccessor): any { + run(accessor: ServicesAccessor): void { accessor.get(IViewDescriptorService).moveViewToLocation(viewDescriptor, newLocation); accessor.get(IViewsService).openView(viewDescriptor.id, true); } @@ -699,9 +706,90 @@ export class ViewsService extends Disposable implements IViewsService { return null; } + + getProgressIndicator(id: string): IProgressIndicator | undefined { + const viewContainer = this.viewDescriptorService.getViewContainer(id); + if (viewContainer === null) { + return undefined; + } + + const view = this.viewPaneContainers.get(viewContainer.id)?.getView(id); + return view?.getProgressIndicator(); + } + + private registerViewletOrPanel(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void { + switch (viewContainerLocation) { + case ViewContainerLocation.Panel: + this.registerPanel(viewContainer); + break; + case ViewContainerLocation.Sidebar: + if (viewContainer.ctorDescriptor) { + this.registerViewlet(viewContainer); + } + break; + } + } + + private registerPanel(viewContainer: ViewContainer): void { + const that = this; + class PaneContainerPanel extends PaneCompositePanel { + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + ) { + // Use composite's instantiation service to get the editor progress service for any editors instantiated within the composite + const viewPaneContainer = (instantiationService as any).createInstance(viewContainer.ctorDescriptor!.ctor, ...(viewContainer.ctorDescriptor!.staticArguments || [])); + super(viewContainer.id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + that.registerViewPaneContainer(this.viewPaneContainer); + } + } + Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( + PaneContainerPanel, + viewContainer.id, + viewContainer.name, + isString(viewContainer.icon) ? viewContainer.icon : undefined, + viewContainer.order, + viewContainer.focusCommand?.id, + )); + } + + private registerViewlet(viewContainer: ViewContainer): void { + const that = this; + class PaneContainerViewlet extends Viewlet { + constructor( + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + ) { + // Use composite's instantiation service to get the editor progress service for any editors instantiated within the composite + const viewPaneContainer = (instantiationService as any).createInstance(viewContainer.ctorDescriptor!.ctor, ...(viewContainer.ctorDescriptor!.staticArguments || [])); + super(viewContainer.id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService); + that.registerViewPaneContainer(this.viewPaneContainer); + } + } + Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( + PaneContainerViewlet, + viewContainer.id, + viewContainer.name, + isString(viewContainer.icon) ? viewContainer.icon : undefined, + viewContainer.order, + viewContainer.icon instanceof URI ? viewContainer.icon : undefined + )); + } } -export function createFileIconThemableTreeContainerScope(container: HTMLElement, themeService: IWorkbenchThemeService): IDisposable { +export function createFileIconThemableTreeContainerScope(container: HTMLElement, themeService: IThemeService): IDisposable { addClass(container, 'file-icon-themable-tree'); addClass(container, 'show-file-icons'); @@ -715,79 +803,3 @@ export function createFileIconThemableTreeContainerScope(container: HTMLElement, } registerSingleton(IViewsService, ViewsService); - -// Viewlets & Panels -(function registerViewletsAndPanels(): void { - const registerPanel = (viewContainer: ViewContainer): void => { - class PaneContainerPanel extends PaneCompositePanel { - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IStorageService storageService: IStorageService, - @IInstantiationService instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IViewsService viewsService: ViewsService - ) { - // Use composite's instantiation service to get the editor progress service for any editors instantiated within the composite - const viewPaneContainer = viewsService.registerViewPaneContainer((instantiationService as any).createInstance(viewContainer.ctorDescriptor!.ctor, ...(viewContainer.ctorDescriptor!.staticArguments || []))); - super(viewContainer.id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); - } - } - Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( - PaneContainerPanel, - viewContainer.id, - viewContainer.name, - isString(viewContainer.icon) ? viewContainer.icon : undefined, - viewContainer.order, - viewContainer.focusCommand?.id, - )); - }; - - const registerViewlet = (viewContainer: ViewContainer): void => { - class PaneContainerViewlet extends Viewlet { - constructor( - @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @ITelemetryService telemetryService: ITelemetryService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IStorageService storageService: IStorageService, - @IInstantiationService instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService, - @IViewsService viewsService: ViewsService - ) { - // Use composite's instantiation service to get the editor progress service for any editors instantiated within the composite - const viewPaneContainer = viewsService.registerViewPaneContainer((instantiationService as any).createInstance(viewContainer.ctorDescriptor!.ctor, ...(viewContainer.ctorDescriptor!.staticArguments || []))); - super(viewContainer.id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService); - } - } - const viewletDescriptor = ViewletDescriptor.create( - PaneContainerViewlet, - viewContainer.id, - viewContainer.name, - isString(viewContainer.icon) ? viewContainer.icon : undefined, - viewContainer.order, - viewContainer.icon instanceof URI ? viewContainer.icon : undefined - ); - - Registry.as(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor); - }; - - const viewContainerRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); - viewContainerRegistry.getViewContainers(ViewContainerLocation.Panel).forEach(viewContainer => registerPanel(viewContainer)); - viewContainerRegistry.onDidRegister(({ viewContainer, viewContainerLocation }) => { - switch (viewContainerLocation) { - case ViewContainerLocation.Panel: - registerPanel(viewContainer); - return; - case ViewContainerLocation.Sidebar: - if (viewContainer.ctorDescriptor) { - registerViewlet(viewContainer); - } - return; - } - }); -})(); diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 8f9ec01537..1577a64da4 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -9,14 +9,13 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/views'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ViewPaneContainer, ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService'; -import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; import { Event } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -155,7 +154,7 @@ export class FileIconThemableWorkbenchTree extends WorkbenchTree { options: ITreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, - @IThemeService themeService: IWorkbenchThemeService, + @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, @IInstantiationService instantiationService: IInstantiationService ) { diff --git a/src/vs/workbench/browser/quickopen.ts b/src/vs/workbench/browser/quickopen.ts index d4240c3e7a..8008b630cf 100644 --- a/src/vs/workbench/browser/quickopen.ts +++ b/src/vs/workbench/browser/quickopen.ts @@ -13,7 +13,7 @@ import { Action } from 'vs/base/common/actions'; import { Mode, IEntryRunContext, IAutoFocus, IModel, IQuickNavigateConfiguration } from 'vs/base/parts/quickopen/common/quickOpen'; import { QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { EditorOptions, EditorInput, IEditorInput } from 'vs/workbench/common/editor'; -import { IResourceInput, IEditorOptions } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput, IEditorOptions } from 'vs/platform/editor/common/editor'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; @@ -233,7 +233,7 @@ export interface IEditorQuickOpenEntry { /** * The editor input used for this entry when opening. */ - getInput(): IResourceInput | IEditorInput | undefined; + getInput(): IResourceEditorInput | IEditorInput | undefined; /** * The editor options used for this entry when opening. @@ -254,7 +254,7 @@ export class EditorQuickOpenEntry extends QuickOpenEntry implements IEditorQuick return this._editorService; } - getInput(): IResourceInput | IEditorInput | undefined { + getInput(): IResourceEditorInput | IEditorInput | undefined { return undefined; } @@ -286,13 +286,13 @@ export class EditorQuickOpenEntry extends QuickOpenEntry implements IEditorQuick this.editorService.openEditor(input, withNullAsUndefined(opts), sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } else { - const resourceInput = input; + const resourceEditorInput = input; if (openOptions) { - resourceInput.options = assign(resourceInput.options || Object.create(null), openOptions); + resourceEditorInput.options = assign(resourceEditorInput.options || Object.create(null), openOptions); } - this.editorService.openEditor(resourceInput, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + this.editorService.openEditor(resourceEditorInput, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } } @@ -305,7 +305,7 @@ export class EditorQuickOpenEntry extends QuickOpenEntry implements IEditorQuick */ export class EditorQuickOpenEntryGroup extends QuickOpenEntryGroup implements IEditorQuickOpenEntry { - getInput(): IEditorInput | IResourceInput | undefined { + getInput(): IEditorInput | IResourceEditorInput | undefined { return undefined; } diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index fd858c9071..f01c05da08 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -5,14 +5,14 @@ import 'vs/css!./media/style'; -import { registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; import { WORKBENCH_BACKGROUND, TITLE_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; import { isWeb, isIOS } from 'vs/base/common/platform'; import { createMetaElement } from 'vs/base/browser/dom'; import { isSafari, isStandalone } from 'vs/base/browser/browser'; -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Icon defaults const iconForegroundColor = theme.getColor(iconForeground); diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index c1033f4163..e474ad7d71 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -165,17 +165,16 @@ export class ShowViewletAction extends Action { this.enabled = !!this.viewletService && !!this.editorGroupService; } - run(): Promise { + async run(): Promise { // Pass focus to viewlet if not open or focused if (this.otherViewletShowing() || !this.sidebarHasFocus()) { - return this.viewletService.openViewlet(this.viewletId, true); + await this.viewletService.openViewlet(this.viewletId, true); + return; } // Otherwise pass focus to editor group this.editorGroupService.activeGroup.focus(); - - return Promise.resolve(true); } private otherViewletShowing(): boolean { diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 1624e591b0..f024942545 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -33,7 +33,7 @@ import { NotificationsAlerts } from 'vs/workbench/browser/parts/notifications/no import { NotificationsStatus } from 'vs/workbench/browser/parts/notifications/notificationsStatus'; import { registerNotificationCommands } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { NotificationsToasts } from 'vs/workbench/browser/parts/notifications/notificationsToasts'; -import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { setARIAContainer } from 'vs/base/browser/ui/aria/aria'; import { readFontInfo, restoreFontInfo, serializeFontInfo } from 'vs/editor/browser/config/configuration'; @@ -416,7 +416,7 @@ export class Workbench extends Layout { await editorGroupService.whenRestored; // then see for editors to open as instructed - let editors: IResourceEditor[]; + let editors: IResourceEditorInputType[]; if (Array.isArray(this.state.editor.editorsToOpen)) { editors = this.state.editor.editorsToOpen; } else { diff --git a/src/vs/workbench/common/actions.ts b/src/vs/workbench/common/actions.ts index dd0cf76e5e..db3aba8ee6 100644 --- a/src/vs/workbench/common/actions.ts +++ b/src/vs/workbench/common/actions.ts @@ -11,7 +11,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; export const Extensions = { WorkbenchActions: 'workbench.contributions.actions' @@ -28,11 +28,11 @@ export interface IWorkbenchActionRegistry { Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionRegistry { - registerWorkbenchAction(descriptor: SyncActionDescriptor, alias: string, category?: string, when?: ContextKeyExpr): IDisposable { + registerWorkbenchAction(descriptor: SyncActionDescriptor, alias: string, category?: string, when?: ContextKeyExpression): IDisposable { return this.registerWorkbenchCommandFromAction(descriptor, alias, category, when); } - private registerWorkbenchCommandFromAction(descriptor: SyncActionDescriptor, alias: string, category?: string, when?: ContextKeyExpr): IDisposable { + private registerWorkbenchCommandFromAction(descriptor: SyncActionDescriptor, alias: string, category?: string, when?: ContextKeyExpression): IDisposable { const registrations = new DisposableStore(); // command @@ -98,7 +98,7 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR }; } - private async triggerAndDisposeAction(instantiationService: IInstantiationService, lifecycleService: ILifecycleService, descriptor: SyncActionDescriptor, args: any): Promise { + private async triggerAndDisposeAction(instantiationService: IInstantiationService, lifecycleService: ILifecycleService, descriptor: SyncActionDescriptor, args: unknown): Promise { // run action when workbench is created await lifecycleService.when(LifecyclePhase.Ready); @@ -115,7 +115,7 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR // otherwise run and dispose try { - const from = args?.from || 'keybinding'; + const from = (args as any)?.from || 'keybinding'; await actionInstance.run(undefined, { from }); } finally { actionInstance.dispose(); diff --git a/src/vs/workbench/common/component.ts b/src/vs/workbench/common/component.ts index b758771624..0989454037 100644 --- a/src/vs/workbench/common/component.ts +++ b/src/vs/workbench/common/component.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Memento, MementoObject } from 'vs/workbench/common/memento'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { Themable } from 'vs/workbench/common/theme'; +import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; export class Component extends Themable { @@ -42,4 +41,4 @@ export class Component extends Themable { protected saveState(): void { // Subclasses to implement for storing state } -} \ No newline at end of file +} diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index 7e29fb30d7..3fa0838b86 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -38,15 +38,17 @@ export interface IWorkbenchContributionsRegistry { } class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry { + private instantiationService: IInstantiationService | undefined; private lifecycleService: ILifecycleService | undefined; - private readonly toBeInstantiated: Map[]> = new Map[]>(); + private readonly toBeInstantiated = new Map[]>(); + + registerWorkbenchContribution(ctor: IConstructorSignature0, phase: LifecyclePhase = LifecyclePhase.Starting): void { - registerWorkbenchContribution(ctor: new (...services: Services) => IWorkbenchContribution, phase: LifecyclePhase = LifecyclePhase.Starting): void { // Instantiate directly if we are already matching the provided phase if (this.instantiationService && this.lifecycleService && this.lifecycleService.phase >= phase) { - this.instantiationService.createInstance(ctor); + this.instantiationService.createInstance(ctor); } // Otherwise keep contributions by lifecycle phase diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 6c604cb71f..b32d3ef3c3 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -9,22 +9,21 @@ import { assign } from 'vs/base/common/objects'; import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IEditor as ICodeEditor, IEditorViewState, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; -import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, IResourceInput, EditorActivation, EditorOpenContext, ITextEditorSelection, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; +import { IEditor, IEditorViewState, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; +import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceEditorInput, IResourceEditorInput, EditorActivation, EditorOpenContext, ITextEditorSelection, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; -import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITextModel } from 'vs/editor/common/model'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { ICompositeControl } from 'vs/workbench/common/composite'; +import { ICompositeControl, IComposite } from 'vs/workbench/common/composite'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { IPathData } from 'vs/platform/windows/common/windows'; import { coalesce, firstOrDefault } from 'vs/base/common/arrays'; import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; import { isEqual, dirname } from 'vs/base/common/resources'; -import { IPanel } from 'vs/workbench/common/panel'; import { IRange } from 'vs/editor/common/core/range'; import { createMemoizer } from 'vs/base/common/decorators'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -38,7 +37,7 @@ export const EditorsVisibleContext = new RawContextKey('editorIsOpen', export const EditorPinnedContext = new RawContextKey('editorPinned', false); export const EditorGroupActiveEditorDirtyContext = new RawContextKey('groupActiveEditorDirty', false); export const EditorGroupEditorsCountContext = new RawContextKey('groupEditorsCount', 0); -export const NoEditorsVisibleContext: ContextKeyExpr = EditorsVisibleContext.toNegated(); +export const NoEditorsVisibleContext = EditorsVisibleContext.toNegated(); export const TextCompareEditorVisibleContext = new RawContextKey('textCompareEditorVisible', false); export const TextCompareEditorActiveContext = new RawContextKey('textCompareEditorActive', false); export const ActiveEditorGroupEmptyContext = new RawContextKey('activeEditorGroupEmpty', false); @@ -61,22 +60,20 @@ export const TEXT_DIFF_EDITOR_ID = 'workbench.editors.textDiffEditor'; */ export const BINARY_DIFF_EDITOR_ID = 'workbench.editors.binaryResourceDiffEditor'; -export interface IEditor extends IPanel { +/** + * The editor pane is the container for workbench editors. + */ +export interface IEditorPane extends IComposite { /** * The assigned input of this editor. */ - input: IEditorInput | undefined; - - /** - * The assigned options of this editor. - */ - options: IEditorOptions | undefined; + readonly input: IEditorInput | undefined; /** * The assigned group this editor is showing in. */ - group: IEditorGroup | undefined; + readonly group: IEditorGroup | undefined; /** * The minimum width of this editor. @@ -104,7 +101,9 @@ export interface IEditor extends IPanel { readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; } | undefined>; /** - * Returns the underlying control of this editor. + * Returns the underlying control of this editor. Callers need to cast + * the control to a specific instance as needed, e.g. by using the + * `isCodeEditor` helper method to access the text code editor. */ getControl(): IEditorControl | undefined; @@ -114,12 +113,23 @@ export interface IEditor extends IPanel { isVisible(): boolean; } -export interface ITextEditor extends IEditor { +/** + * Overrides `IEditorPane` where `input` and `group` are known to be set. + */ +export interface IVisibleEditorPane extends IEditorPane { + readonly input: IEditorInput; + readonly group: IEditorGroup; +} + +/** + * The text editor pane is the container for workbench text editors. + */ +export interface ITextEditorPane extends IEditorPane { /** * Returns the underlying text editor widget of this editor. */ - getControl(): ICodeEditor | undefined; + getControl(): IEditor | undefined; /** * Returns the current view state of the text editor if any. @@ -127,13 +137,16 @@ export interface ITextEditor extends IEditor { getViewState(): IEditorViewState | undefined; } -export function isTextEditor(thing: IEditor | undefined): thing is ITextEditor { - const candidate = thing as ITextEditor | undefined; +export function isTextEditorPane(thing: IEditorPane | undefined): thing is ITextEditorPane { + const candidate = thing as ITextEditorPane | undefined; return typeof candidate?.getViewState === 'function'; } -export interface ITextDiffEditor extends IEditor { +/** + * The text editor pane is the container for workbench text diff editors. + */ +export interface ITextDiffEditorPane extends IEditorPane { /** * Returns the underlying text editor widget of this editor. @@ -141,44 +154,31 @@ export interface ITextDiffEditor extends IEditor { getControl(): IDiffEditor | undefined; } -export interface ITextSideBySideEditor extends IEditor { - - /** - * Returns the underlying text editor widget of the master side - * of this side-by-side editor. - */ - getMasterEditor(): ITextEditor; - - /** - * Returns the underlying text editor widget of the details side - * of this side-by-side editor. - */ - getDetailsEditor(): ITextEditor; -} - /** - * Marker interface for the base editor control + * Marker interface for the control inside an editor pane. Callers + * have to cast the control to work with it, e.g. via methods + * such as `isCodeEditor(control)`. */ export interface IEditorControl extends ICompositeControl { } -export interface IFileInputFactory { +export interface IFileEditorInputFactory { - createFileInput(resource: URI, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput; + createFileEditorInput(resource: URI, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput; - isFileInput(obj: unknown): obj is IFileEditorInput; + isFileEditorInput(obj: unknown): obj is IFileEditorInput; } export interface IEditorInputFactoryRegistry { /** - * Registers the file input factory to use for file inputs. + * Registers the file editor input factory to use for file inputs. */ - registerFileInputFactory(factory: IFileInputFactory): void; + registerFileEditorInputFactory(factory: IFileEditorInputFactory): void; /** - * Returns the file input factory to use for file inputs. + * Returns the file editor input factory to use for file inputs. */ - getFileInputFactory(): IFileInputFactory; + getFileEditorInputFactory(): IFileEditorInputFactory; /** * Registers a editor input factory for the given editor input to the registry. An editor input factory @@ -222,7 +222,7 @@ export interface IEditorInputFactory { deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | undefined; } -export interface IUntitledTextResourceInput extends IBaseResourceInput { +export interface IUntitledTextResourceEditorInput extends IBaseResourceEditorInput { /** * Optional resource. If the resource is not provided a new untitled file is created (e.g. Untitled-1). @@ -248,7 +248,7 @@ export interface IUntitledTextResourceInput extends IBaseResourceInput { readonly encoding?: string; } -export interface IResourceDiffInput extends IBaseResourceInput { +export interface IResourceDiffEditorInput extends IBaseResourceEditorInput { /** * The left hand side URI to open inside a diff editor. @@ -261,19 +261,6 @@ export interface IResourceDiffInput extends IBaseResourceInput { readonly rightResource: URI; } -export interface IResourceSideBySideInput extends IBaseResourceInput { - - /** - * The right hand side URI to open inside a side by side editor. - */ - readonly masterResource: URI; - - /** - * The left hand side URI to open inside a side by side editor. - */ - readonly detailResource: URI; -} - export const enum Verbosity { SHORT, MEDIUM, @@ -346,7 +333,7 @@ export interface IRevertOptions { } export interface IMoveResult { - editor: EditorInput | IResourceEditor; + editor: EditorInput | IResourceEditorInputType; options?: IEditorOptions; } @@ -449,7 +436,7 @@ export interface IEditorInput extends IDisposable { /** * Reverts this input from the provided group. */ - revert(group: GroupIdentifier, options?: IRevertOptions): Promise; + revert(group: GroupIdentifier, options?: IRevertOptions): Promise; /** * Called to determine how to handle a resource that is moved that matches @@ -557,9 +544,7 @@ export abstract class EditorInput extends Disposable implements IEditorInput { return this; } - async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { - return true; - } + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { } move(group: GroupIdentifier, target: URI): IMoveResult | undefined { return undefined; @@ -611,8 +596,20 @@ export abstract class TextResourceEditorInput extends EditorInput { protected registerListeners(): void { // Clear label memoizer on certain events that have impact - this._register(this.labelService.onDidChangeFormatters(() => TextResourceEditorInput.MEMOIZER.clear())); - this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(() => TextResourceEditorInput.MEMOIZER.clear())); + this._register(this.labelService.onDidChangeFormatters(e => this.onLabelEvent(e.scheme))); + this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onLabelEvent(e.scheme))); + this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onLabelEvent(e.scheme))); + } + + private onLabelEvent(scheme: string): void { + if (scheme === this.resource.scheme) { + + // Clear any cached labels from before + TextResourceEditorInput.MEMOIZER.clear(); + + // Trigger recompute of label + this._onDidChangeLabel.fire(); + } } getName(): string { @@ -687,10 +684,6 @@ export abstract class TextResourceEditorInput extends EditorInput { return false; // untitled is never readonly } - if (!this.fileService.canHandleResource(this.resource)) { - return true; // resources without file support are always readonly - } - return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); } @@ -729,14 +722,14 @@ export abstract class TextResourceEditorInput extends EditorInput { } if (!isEqual(target, this.resource)) { - return this.editorService.createInput({ resource: target }); + return this.editorService.createEditorInput({ resource: target }); } return this; } - revert(group: GroupIdentifier, options?: IRevertOptions): Promise { - return this.textFileService.revert(this.resource, options); + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + await this.textFileService.revert(this.resource, options); } } @@ -876,7 +869,7 @@ export class SideBySideEditorInput extends EditorInput { return this.master.saveAs(group, options); } - revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + revert(group: GroupIdentifier, options?: IRevertOptions): Promise { return this.master.revert(group, options); } @@ -1143,7 +1136,7 @@ export class TextEditorOptions extends EditorOptions implements ITextEditorOptio */ selectionRevealType: TextEditorSelectionRevealType | undefined; - static from(input?: IBaseResourceInput): TextEditorOptions | undefined { + static from(input?: IBaseResourceEditorInput): TextEditorOptions | undefined { if (!input || !input.options) { return undefined; } @@ -1197,7 +1190,7 @@ export class TextEditorOptions extends EditorOptions implements ITextEditorOptio /** * Create a TextEditorOptions inline to be used when the editor is opening. */ - static fromEditor(editor: ICodeEditor, settings?: IEditorOptions): TextEditorOptions { + static fromEditor(editor: IEditor, settings?: IEditorOptions): TextEditorOptions { const options = TextEditorOptions.create(settings); // View state @@ -1211,7 +1204,7 @@ export class TextEditorOptions extends EditorOptions implements ITextEditorOptio * * @return if something was applied */ - apply(editor: ICodeEditor, scrollType: ScrollType): boolean { + apply(editor: IEditor, scrollType: ScrollType): boolean { let gotApplied = false; // First try viewstate @@ -1380,7 +1373,7 @@ export interface IEditorMemento { class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { private instantiationService: IInstantiationService | undefined; - private fileInputFactory: IFileInputFactory | undefined; + private fileEditorInputFactory: IFileEditorInputFactory | undefined; private readonly editorInputFactoryConstructors: Map> = new Map(); private readonly editorInputFactoryInstances: Map = new Map(); @@ -1400,12 +1393,12 @@ class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { this.editorInputFactoryInstances.set(editorInputId, instance); } - registerFileInputFactory(factory: IFileInputFactory): void { - this.fileInputFactory = factory; + registerFileEditorInputFactory(factory: IFileEditorInputFactory): void { + this.fileEditorInputFactory = factory; } - getFileInputFactory(): IFileInputFactory { - return assertIsDefined(this.fileInputFactory); + getFileEditorInputFactory(): IFileEditorInputFactory { + return assertIsDefined(this.fileEditorInputFactory); } registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0): IDisposable { @@ -1433,7 +1426,7 @@ export const Extensions = { Registry.add(Extensions.EditorInputFactories, new EditorInputFactoryRegistry()); -export async function pathsToEditors(paths: IPathData[] | undefined, fileService: IFileService): Promise<(IResourceInput | IUntitledTextResourceInput)[]> { +export async function pathsToEditors(paths: IPathData[] | undefined, fileService: IFileService): Promise<(IResourceEditorInput | IUntitledTextResourceEditorInput)[]> { if (!paths || !paths.length) { return []; } @@ -1454,7 +1447,7 @@ export async function pathsToEditors(paths: IPathData[] | undefined, fileService pinned: true } : { pinned: true }; - let input: IResourceInput | IUntitledTextResourceInput; + let input: IResourceEditorInput | IUntitledTextResourceEditorInput; if (!exists) { input = { resource, options, forceUntitled: true }; } else { diff --git a/src/vs/workbench/common/editor/diffEditorModel.ts b/src/vs/workbench/common/editor/diffEditorModel.ts index 38d3cf72ca..b403d80aac 100644 --- a/src/vs/workbench/common/editor/diffEditorModel.ts +++ b/src/vs/workbench/common/editor/diffEditorModel.ts @@ -23,18 +23,10 @@ export class DiffEditorModel extends EditorModel { } get originalModel(): IEditorModel | null { - if (!this._originalModel) { - return null; - } - return this._originalModel; } get modifiedModel(): IEditorModel | null { - if (!this._modifiedModel) { - return null; - } - return this._modifiedModel; } diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index dc55247d63..1692de85e7 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -94,7 +94,7 @@ export class ResourceEditorInput extends TextResourceEditorInput implements IMod ref.dispose(); this.modelReference = undefined; - throw new Error(`Unexpected model for ResourceInput: ${this.resource}`); + throw new Error(`Unexpected model for ResourcEditorInput: ${this.resource}`); } this.cachedModel = model; diff --git a/src/vs/workbench/common/memento.ts b/src/vs/workbench/common/memento.ts index 7e3cb895ed..de98520e7d 100644 --- a/src/vs/workbench/common/memento.ts +++ b/src/vs/workbench/common/memento.ts @@ -87,4 +87,4 @@ class ScopedMemento { this.storageService.remove(this.id, this.scope); } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index c22007a54e..d40d4d0174 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -41,8 +41,26 @@ export interface INotificationsModel { } export const enum NotificationChangeType { + + /** + * A notification was added. + */ ADD, + + /** + * A notification changed. Check `detail` property + * on the event for additional information. + */ CHANGE, + + /** + * A notification expanded or collapsed. + */ + EXPAND_COLLAPSE, + + /** + * A notification was removed. + */ REMOVE } @@ -62,6 +80,12 @@ export interface INotificationChangeEvent { * The kind of notification change. */ kind: NotificationChangeType; + + /** + * Additional detail about the item change. Only applies to + * `NotificationChangeType.CHANGE`. + */ + detail?: NotificationViewItemContentChangeKind } export const enum StatusMessageChangeType { @@ -206,26 +230,19 @@ export class NotificationsModel extends Disposable implements INotificationsMode } // Item Events - const onItemChangeEvent = () => { + const fireNotificationChangeEvent = (kind: NotificationChangeType, detail?: NotificationViewItemContentChangeKind) => { const index = this._notifications.indexOf(item); if (index >= 0) { - this._onDidChangeNotification.fire({ item, index, kind: NotificationChangeType.CHANGE }); + this._onDidChangeNotification.fire({ item, index, kind, detail }); } }; - const itemExpansionChangeListener = item.onDidChangeExpansion(() => onItemChangeEvent()); - - const itemLabelChangeListener = item.onDidChangeLabel(e => { - // a label change in the area of actions or the message is a change that potentially has an impact - // on the size of the notification and as such we emit a change event so that viewers can redraw - if (e.kind === NotificationViewItemLabelKind.ACTIONS || e.kind === NotificationViewItemLabelKind.MESSAGE) { - onItemChangeEvent(); - } - }); + const itemExpansionChangeListener = item.onDidChangeExpansion(() => fireNotificationChangeEvent(NotificationChangeType.EXPAND_COLLAPSE)); + const itemContentChangeListener = item.onDidChangeContent(e => fireNotificationChangeEvent(NotificationChangeType.CHANGE, e.kind)); Event.once(item.onDidClose)(() => { itemExpansionChangeListener.dispose(); - itemLabelChangeListener.dispose(); + itemContentChangeListener.dispose(); const index = this._notifications.indexOf(item); if (index >= 0) { @@ -272,9 +289,9 @@ export interface INotificationViewItem { readonly hasProgress: boolean; readonly onDidChangeExpansion: Event; - readonly onDidClose: Event; readonly onDidChangeVisibility: Event; - readonly onDidChangeLabel: Event; + readonly onDidChangeContent: Event; + readonly onDidClose: Event; expand(): void; collapse(skipEvents?: boolean): void; @@ -295,15 +312,15 @@ export function isNotificationViewItem(obj: unknown): obj is INotificationViewIt return obj instanceof NotificationViewItem; } -export const enum NotificationViewItemLabelKind { +export const enum NotificationViewItemContentChangeKind { SEVERITY, MESSAGE, ACTIONS, PROGRESS } -export interface INotificationViewItemLabelChangeEvent { - kind: NotificationViewItemLabelKind; +export interface INotificationViewItemContentChangeEvent { + kind: NotificationViewItemContentChangeKind; } export interface INotificationViewItemProgressState { @@ -420,8 +437,8 @@ export class NotificationViewItem extends Disposable implements INotificationVie private readonly _onDidClose = this._register(new Emitter()); readonly onDidClose = this._onDidClose.event; - private readonly _onDidChangeLabel = this._register(new Emitter()); - readonly onDidChangeLabel = this._onDidChangeLabel.event; + private readonly _onDidChangeContent = this._register(new Emitter()); + readonly onDidChangeContent = this._onDidChangeContent.event; private readonly _onDidChangeVisibility = this._register(new Emitter()); readonly onDidChangeVisibility = this._onDidChangeVisibility.event; @@ -512,20 +529,16 @@ export class NotificationViewItem extends Disposable implements INotificationVie } private setActions(actions: INotificationActions = { primary: [], secondary: [] }): void { - if (!Array.isArray(actions.primary)) { - actions.primary = []; - } + this._actions = { + primary: Array.isArray(actions.primary) ? actions.primary : [], + secondary: Array.isArray(actions.secondary) ? actions.secondary : [] + }; - if (!Array.isArray(actions.secondary)) { - actions.secondary = []; - } - - this._actions = actions; - this._expanded = actions.primary.length > 0; + this._expanded = actions.primary && actions.primary.length > 0; } get canCollapse(): boolean { - return !this.hasPrompt; + return !this.hasActions; } get expanded(): boolean { @@ -541,11 +554,11 @@ export class NotificationViewItem extends Disposable implements INotificationVie return true; // explicitly sticky } - const hasPrompt = this.hasPrompt; + const hasActions = this.hasActions; if ( - (hasPrompt && this._severity === Severity.Error) || // notification errors with actions are sticky - (!hasPrompt && this._expanded) || // notifications that got expanded are sticky - (this._progress && !this._progress.state.done) // notifications with running progress are sticky + (hasActions && this._severity === Severity.Error) || // notification errors with actions are sticky + (!hasActions && this._expanded) || // notifications that got expanded are sticky + (this._progress && !this._progress.state.done) // notifications with running progress are sticky ) { return true; } @@ -557,7 +570,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie return !!this._silent; } - private get hasPrompt(): boolean { + private get hasActions(): boolean { if (!this._actions) { return false; } @@ -576,7 +589,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie get progress(): INotificationViewItemProgress { if (!this._progress) { this._progress = this._register(new NotificationViewItemProgress()); - this._register(this._progress.onDidChange(() => this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.PROGRESS }))); + this._register(this._progress.onDidChange(() => this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.PROGRESS }))); } return this._progress; @@ -596,7 +609,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie updateSeverity(severity: Severity): void { this._severity = severity; - this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.SEVERITY }); + this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.SEVERITY }); } updateMessage(input: NotificationMessage): void { @@ -606,13 +619,12 @@ export class NotificationViewItem extends Disposable implements INotificationVie } this._message = message; - this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.MESSAGE }); + this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.MESSAGE }); } updateActions(actions?: INotificationActions): void { this.setActions(actions); - - this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.ACTIONS }); + this._onDidChangeContent.fire({ kind: NotificationViewItemContentChangeKind.ACTIONS }); } updateVisibility(visible: boolean): void { diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 7ea68a9a89..a2ee4b104c 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -5,13 +5,12 @@ import * as nls from 'vs/nls'; import { registerColor, editorBackground, contrastBorder, transparent, editorWidgetBackground, textLinkForeground, lighten, darken, focusBorder, activeContrastBorder, editorWidgetForeground, editorErrorForeground, editorWarningForeground, editorInfoForeground } from 'vs/platform/theme/common/colorRegistry'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; // < --- Workbench (not customizable) --- > -export function WORKBENCH_BACKGROUND(theme: ITheme): Color { +export function WORKBENCH_BACKGROUND(theme: IColorTheme): Color { switch (theme.type) { case 'dark': return Color.fromHex('#252526'); @@ -461,20 +460,6 @@ export const SIDE_BAR_SECTION_HEADER_BORDER = registerColor('sideBarSectionHeade }, nls.localize('sideBarSectionHeaderBorder', "Side bar section header border color. The side bar is the container for views like explorer and search.")); -// < --- Quick Input -- > - -export const QUICK_INPUT_BACKGROUND = registerColor('quickInput.background', { - dark: SIDE_BAR_BACKGROUND, - light: SIDE_BAR_BACKGROUND, - hc: SIDE_BAR_BACKGROUND -}, nls.localize('quickInputBackground', "Quick Input background color. The Quick Input widget is the container for views like the color theme picker.")); - -export const QUICK_INPUT_FOREGROUND = registerColor('quickInput.foreground', { - dark: SIDE_BAR_FOREGROUND, - light: SIDE_BAR_FOREGROUND, - hc: SIDE_BAR_FOREGROUND -}, nls.localize('quickInputForeground', "Quick Input foreground color. The Quick Input widget is the container for views like the color theme picker.")); - // < --- Title Bar --- > export const TITLE_BAR_ACTIVE_FOREGROUND = registerColor('titleBar.activeForeground', { @@ -606,41 +591,3 @@ export const WINDOW_INACTIVE_BORDER = registerColor('window.inactiveBorder', { light: null, hc: contrastBorder }, nls.localize('windowInactiveBorder', "The color used for the border of the window when it is inactive. Only supported in the desktop client when using the custom title bar.")); - -/** - * Base class for all themable workbench components. - */ -export class Themable extends Disposable { - protected theme: ITheme; - - constructor( - protected themeService: IThemeService - ) { - super(); - - this.theme = themeService.getTheme(); - - // Hook up to theme changes - this._register(this.themeService.onThemeChange(theme => this.onThemeChange(theme))); - } - - protected onThemeChange(theme: ITheme): void { - this.theme = theme; - - this.updateStyles(); - } - - protected updateStyles(): void { - // Subclasses to override - } - - protected getColor(id: string, modify?: (color: Color, theme: ITheme) => Color): string | null { - let color = this.theme.getColor(id); - - if (color && modify) { - color = modify(color, this.theme); - } - - return color ? color.toString() : null; - } -} diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 4a20b0dd59..5dfb8c231a 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -6,7 +6,7 @@ import { Command } from 'vs/editor/common/modes'; import { UriComponents, URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; -import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -20,6 +20,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { flatten, mergeSort } from 'vs/base/common/arrays'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SetMap } from 'vs/base/common/collections'; +import { IProgressIndicator } from 'vs/platform/progress/common/progress'; export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; @@ -178,7 +179,7 @@ export interface IViewDescriptor { readonly ctorDescriptor: SyncDescriptor; - readonly when?: ContextKeyExpr; + readonly when?: ContextKeyExpression; readonly order?: number; @@ -220,13 +221,13 @@ export enum ViewContentPriority { export interface IViewContentDescriptor { readonly content: string; - readonly when?: ContextKeyExpr | 'default'; + readonly when?: ContextKeyExpression | 'default'; readonly priority?: ViewContentPriority; /** * ordered preconditions for each button in the content */ - readonly preconditions?: (ContextKeyExpr | undefined)[]; + readonly preconditions?: (ContextKeyExpression | undefined)[]; } export interface IViewsRegistry { @@ -262,7 +263,8 @@ function compareViewContentDescriptors(a: IViewContentDescriptor, b: IViewConten return aPriority - bPriority; } - return a.content < b.content ? -1 : 1; + // No priroity, keep views sorted in the order they got registered + return 0; } class ViewsRegistry extends Disposable implements IViewsRegistry { @@ -401,6 +403,7 @@ export interface IView { setExpanded(expanded: boolean): boolean; + getProgressIndicator(): IProgressIndicator | undefined; } export interface IViewsViewlet extends IViewlet { @@ -425,6 +428,7 @@ export interface IViewsService { closeView(id: string): void; + getProgressIndicator(id: string): IProgressIndicator | undefined; } /** @@ -487,6 +491,8 @@ export interface ITreeView extends IDisposable { readonly onDidChangeTitle: Event; + readonly onDidChangeWelcomeState: Event; + refresh(treeItems?: ITreeItem[]): Promise; setVisibility(visible: boolean): void; @@ -505,9 +511,6 @@ export interface ITreeView extends IDisposable { setFocus(item: ITreeItem): void; - getPrimaryActions(): IAction[]; - - getSecondaryActions(): IAction[]; } export interface IRevealOptions { @@ -573,7 +576,8 @@ export interface ITreeItem { } export interface ITreeViewDataProvider { - + readonly isTreeEmpty?: boolean; + onDidChangeEmpty?: Event; getChildren(element?: ITreeItem): Promise; } @@ -601,4 +605,3 @@ export interface IViewPaneContainer { getView(viewId: string): IView | undefined; saveState(): void; } - diff --git a/src/vs/workbench/contrib/backup/common/backupRestorer.ts b/src/vs/workbench/contrib/backup/common/backupRestorer.ts index e8d7d7f39b..46dca1f7a5 100644 --- a/src/vs/workbench/contrib/backup/common/backupRestorer.ts +++ b/src/vs/workbench/contrib/backup/common/backupRestorer.ts @@ -7,10 +7,10 @@ import { URI } from 'vs/base/common/uri'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { Schemas } from 'vs/base/common/network'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IUntitledTextResourceInput, IEditorInput } from 'vs/workbench/common/editor'; +import { IUntitledTextResourceEditorInput, IEditorInput } from 'vs/workbench/common/editor'; import { toLocalResource, isEqual } from 'vs/base/common/resources'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -86,7 +86,7 @@ export class BackupRestorer implements IWorkbenchContribution { await this.editorService.openEditors(inputs); } - private resolveInput(resource: URI, index: number, hasOpenedEditors: boolean): IResourceInput | IUntitledTextResourceInput { + private resolveInput(resource: URI, index: number, hasOpenedEditors: boolean): IResourceEditorInput | IUntitledTextResourceEditorInput { const options = { pinned: true, preserveFocus: true, inactive: index > 0 || hasOpenedEditors }; // this is a (weak) strategy to find out if the untitled input had diff --git a/src/vs/workbench/contrib/backup/common/backupTracker.ts b/src/vs/workbench/contrib/backup/common/backupTracker.ts index 7a59fd883f..f9dca583ff 100644 --- a/src/vs/workbench/contrib/backup/common/backupTracker.ts +++ b/src/vs/workbench/contrib/backup/common/backupTracker.ts @@ -114,10 +114,6 @@ export abstract class BackupTracker extends Disposable { return; // skip if auto save is enabled with a short delay } - if (typeof workingCopy.backup !== 'function') { - return; // skip if working copy does not support backups - } - // Clear any running backup operation dispose(this.pendingBackups.get(workingCopy)); this.pendingBackups.delete(workingCopy); @@ -131,7 +127,7 @@ export abstract class BackupTracker extends Disposable { this.pendingBackups.delete(workingCopy); // Backup if dirty - if (workingCopy.isDirty() && typeof workingCopy.backup === 'function') { + if (workingCopy.isDirty()) { this.logService.trace(`[backup tracker] running backup`, workingCopy.resource.toString()); const backup = await workingCopy.backup(); diff --git a/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts b/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts index 10baea47b8..85905f8b1e 100644 --- a/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts +++ b/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts @@ -161,12 +161,10 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont // Backup does not exist else { - if (typeof workingCopy.backup === 'function') { - const backup = await workingCopy.backup(); - await this.backupFileService.backup(workingCopy.resource, backup.content, contentVersion, backup.meta); + const backup = await workingCopy.backup(); + await this.backupFileService.backup(workingCopy.resource, backup.content, contentVersion, backup.meta); - backups.push(workingCopy); - } + backups.push(workingCopy); } })); } @@ -235,16 +233,13 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont const revertOptions = { soft: true }; // First revert through the editor service if we revert all - let result: boolean | undefined = undefined; if (workingCopies.length === this.workingCopyService.dirtyCount) { - result = await this.editorService.revertAll(revertOptions); + await this.editorService.revertAll(revertOptions); } // If we still have dirty working copies, revert those directly // unless the revert operation was not successful (e.g. cancelled) - if (result !== false) { - await Promise.all(workingCopies.map(workingCopy => workingCopy.isDirty() ? workingCopy.revert(revertOptions) : Promise.resolve(true))); - } + await Promise.all(workingCopies.map(workingCopy => workingCopy.isDirty() ? workingCopy.revert(revertOptions) : Promise.resolve())); } private noVeto(backupsToDiscard: IWorkingCopy[]): boolean | Promise { diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts index 02967986c4..79ce06c7af 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts @@ -18,7 +18,7 @@ import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorInput, IUntitledTextResourceInput } from 'vs/workbench/common/editor'; +import { EditorInput, IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; @@ -40,6 +40,11 @@ import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker' import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestFilesConfigurationService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backuprestorer'); const backupHome = path.join(userdataDir, 'Backups'); @@ -113,11 +118,23 @@ suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); }); - async function createTracker(): Promise<[TestServiceAccessor, EditorPart, BackupTracker, IInstantiationService]> { + async function createTracker(autoSaveEnabled = false): Promise<[TestServiceAccessor, EditorPart, BackupTracker, IInstantiationService]> { const backupFileService = new NodeTestBackupFileService(workspaceBackupPath); const instantiationService = workbenchInstantiationService(); instantiationService.stub(IBackupFileService, backupFileService); + const configurationService = new TestConfigurationService(); + if (autoSaveEnabled) { + configurationService.setUserConfiguration('files', { autoSave: 'afterDelay', autoSaveDelay: 1 }); + } + instantiationService.stub(IConfigurationService, configurationService); + + instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService( + instantiationService.createInstance(MockContextKeyService), + configurationService, + TestEnvironmentService + )); + const part = instantiationService.createInstance(EditorPart); part.create(document.createElement('div')); part.layout(400, 300); @@ -136,7 +153,7 @@ suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests return [accessor, part, tracker, instantiationService]; } - async function untitledBackupTest(untitled: IUntitledTextResourceInput = {}): Promise { + async function untitledBackupTest(untitled: IUntitledTextResourceEditorInput = {}): Promise { const [accessor, part, tracker] = await createTracker(); const untitledEditor = (await accessor.editorService.openEditor(untitled))?.input as UntitledTextEditorInput; @@ -198,7 +215,7 @@ suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests tracker.dispose(); }); - test('confirm onWillShutdown - no veto', async function () { + test('onWillShutdown - no veto if no dirty files', async function () { const [accessor, part, tracker] = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); @@ -207,18 +224,14 @@ suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); - const veto = event.value; - if (typeof veto === 'boolean') { - assert.ok(!veto); - } else { - assert.ok(!(await veto)); - } + const veto = await event.value; + assert.ok(!veto); part.dispose(); tracker.dispose(); }); - test('confirm onWillShutdown - veto if user cancels', async function () { + test('onWillShutdown - veto if user cancels (hot.exit: off)', async function () { const [accessor, part, tracker] = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); @@ -227,6 +240,7 @@ suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests const model = accessor.textFileService.files.get(resource); accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); + accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); await model?.load(); model?.textEditorModel?.setValue('foo'); @@ -234,13 +248,39 @@ suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); - assert.ok(event.value); + + const veto = await event.value; + assert.ok(veto); part.dispose(); tracker.dispose(); }); - test('confirm onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)', async function () { + test('onWillShutdown - no veto if auto save is on', async function () { + const [accessor, part, tracker] = await createTracker(true /* auto save enabled */); + + const resource = toResource.call(this, '/path/index.txt'); + await accessor.editorService.openEditor({ resource, options: { pinned: true } }); + + const model = accessor.textFileService.files.get(resource); + + await model?.load(); + model?.textEditorModel?.setValue('foo'); + assert.equal(accessor.workingCopyService.dirtyCount, 1); + + const event = new BeforeShutdownEventImpl(); + accessor.lifecycleService.fireWillShutdown(event); + + const veto = await event.value; + assert.ok(!veto); + + assert.equal(accessor.workingCopyService.dirtyCount, 0); + + part.dispose(); + tracker.dispose(); + }); + + test('onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)', async function () { const [accessor, part, tracker] = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); @@ -257,21 +297,15 @@ suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); - let veto = event.value; - if (typeof veto === 'boolean') { - assert.ok(accessor.backupFileService.discardedBackups.length > 0); - assert.ok(!veto); - } else { - veto = await veto; - assert.ok(accessor.backupFileService.discardedBackups.length > 0); - assert.ok(!veto); - } + const veto = await event.value; + assert.ok(!veto); + assert.ok(accessor.backupFileService.discardedBackups.length > 0); part.dispose(); tracker.dispose(); }); - test('confirm onWillShutdown - save (hot.exit: off)', async function () { + test('onWillShutdown - save (hot.exit: off)', async function () { const [accessor, part, tracker] = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); @@ -288,7 +322,7 @@ suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); - const veto = await (>event.value); + const veto = await event.value; assert.ok(!veto); assert.ok(!model?.isDirty()); @@ -431,7 +465,7 @@ suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests event.reason = shutdownReason; accessor.lifecycleService.fireWillShutdown(event); - const veto = await (>event.value); + const veto = await event.value; assert.equal(accessor.backupFileService.discardedBackups.length, 0); // When hot exit is set, backups should never be cleaned since the confirm result is cancel assert.equal(veto, shouldVeto); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 2b1cc251b6..77e465dd95 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./bulkEdit'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator, IOpenEvent } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree, IOpenEvent, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, BulkEditAriaProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement, BulkEditSorter } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; import { diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -143,7 +143,7 @@ export class BulkEditPane extends ViewPane { this._disposables.add(this._tree.onContextMenu(this._onContextMenu, this)); - const navigator = new TreeResourceNavigator(this._tree, { openOnFocus: true }); + const navigator = ResourceNavigator.createTreeResourceNavigator(this._tree, { openOnFocus: true }); this._disposables.add(navigator); this._disposables.add(navigator.onDidOpenResource(e => this._openElementAsEditor(e))); @@ -379,7 +379,7 @@ export class BulkEditPane extends ViewPane { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const diffInsertedColor = theme.getColor(diffInserted); if (diffInsertedColor) { diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index 9c4751e682..dd6f7b4ddb 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -18,7 +18,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { Emitter, Event } from 'vs/base/common/event'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { ConflictDetector } from 'vs/workbench/services/bulkEdit/browser/conflicts'; -import { values, ResourceMap } from 'vs/base/common/map'; +import { ResourceMap } from 'vs/base/common/map'; import { localize } from 'vs/nls'; export class CheckedStates { @@ -89,7 +89,7 @@ export class BulkFileOperation { readonly parent: BulkFileOperations ) { } - addEdit(index: number, type: BulkFileOperationType, edit: WorkspaceTextEdit | WorkspaceFileEdit, ) { + addEdit(index: number, type: BulkFileOperationType, edit: WorkspaceTextEdit | WorkspaceFileEdit) { this.type |= type; this.originalEdits.set(index, edit); if (WorkspaceTextEdit.is(edit)) { @@ -126,8 +126,8 @@ export class BulkCategory { constructor(readonly metadata: WorkspaceEditMetadata = BulkCategory._defaultMetadata) { } - get fileOperations(): BulkFileOperation[] { - return values(this.operationByResource); + get fileOperations(): IterableIterator { + return this.operationByResource.values(); } } @@ -260,6 +260,17 @@ export class BulkFileOperations { } } + // sort (once) categories atop which have unconfirmed edits + this.categories.sort((a, b) => { + if (a.metadata.needsConfirmation === b.metadata.needsConfirmation) { + return a.metadata.label.localeCompare(b.metadata.label); + } else if (a.metadata.needsConfirmation) { + return -1; + } else { + return 1; + } + }); + return this; } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 57a9a98b1a..bdee42a23a 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -27,6 +27,7 @@ import { WorkspaceFileEdit } from 'vs/editor/common/modes'; import { compare } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { Iterable } from 'vs/base/common/iterator'; // --- VIEW MODEL @@ -201,7 +202,7 @@ export class BulkEditDataSource implements IAsyncDataSource new FileElement(element, op)); + return [...Iterable.map(element.category.fileOperations, op => new FileElement(element, op))]; } // file: text edit @@ -258,19 +259,6 @@ export class BulkEditDataSource implements IAsyncDataSource { compare(a: BulkEditElement, b: BulkEditElement): number { - if (a instanceof CategoryElement && b instanceof CategoryElement) { - // - const aConfirm = BulkEditSorter._needsConfirmation(a.category); - const bConfirm = BulkEditSorter._needsConfirmation(b.category); - if (aConfirm === bConfirm) { - return a.category.metadata.label.localeCompare(b.category.metadata.label); - } else if (aConfirm) { - return -1; - } else { - return 1; - } - } - if (a instanceof FileElement && b instanceof FileElement) { return compare(a.edit.uri.toString(), b.edit.uri.toString()); } @@ -281,10 +269,6 @@ export class BulkEditSorter implements ITreeSorter { return 0; } - - private static _needsConfirmation(a: BulkCategory): boolean { - return a.fileOperations.some(ops => ops.needsConfirmation()); - } } // --- ACCESSI diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 098312888d..515fff1528 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -24,7 +24,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions, OverviewRulerLane } from 'vs/editor/common/model'; -import { registerThemingParticipant, themeColorFromId, IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, themeColorFromId, IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { IPosition } from 'vs/editor/common/core/position'; import { Action } from 'vs/base/common/actions'; import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -112,8 +112,8 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { super(editor, { showFrame: true, showArrow: true, isResizeable: true, isAccessible: true }); this.create(); this._peekViewService.addExclusiveWidget(editor, this); - this._applyTheme(themeService.getTheme()); - this._disposables.add(themeService.onThemeChange(this._applyTheme, this)); + this._applyTheme(themeService.getColorTheme()); + this._disposables.add(themeService.onDidColorThemeChange(this._applyTheme, this)); this._disposables.add(this._previewDisposable); } @@ -129,7 +129,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { return this._direction; } - private _applyTheme(theme: ITheme) { + private _applyTheme(theme: IColorTheme) { const borderColor = theme.getColor(peekView.peekViewBorder) || Color.transparent; this.style({ arrowColor: borderColor, diff --git a/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts b/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts index 3476d7d912..a113fef410 100644 --- a/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts +++ b/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts @@ -7,7 +7,6 @@ import { flatten } from 'vs/base/common/arrays'; import { Emitter } from 'vs/base/common/event'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { Disposable } from 'vs/base/common/lifecycle'; -import { values } from 'vs/base/common/map'; import { codeActionCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/codeAction'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import * as nls from 'vs/nls'; @@ -137,7 +136,7 @@ export class CodeActionsContribution extends Disposable implements IWorkbenchCon out.set(action.kind, action); } } - return values(out); + return Array.from(out.values()); }; return [ diff --git a/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts b/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts index ca9669fb83..320f889749 100644 --- a/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts +++ b/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts @@ -10,7 +10,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { DocumentationExtensionPoint } from './documentationExtensionPoint'; @@ -20,7 +20,7 @@ export class CodeActionDocumentationContribution extends Disposable implements I private contributions: { title: string; - when: ContextKeyExpr; + when: ContextKeyExpression; command: string; }[] = []; diff --git a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts index bf48cf5e07..da53ddb91f 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -9,6 +9,7 @@ import './diffEditorHelper'; import './inspectKeybindings'; import './largeFileOptimizations'; import './inspectEditorTokens/inspectEditorTokens'; +import './quickaccess/gotoLineQuickAccess'; import './saveParticipants'; import './toggleColumnSelection'; import './toggleMinimap'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index b11f8b23ce..c0820027d2 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -16,7 +16,7 @@ import { SimpleButton } from 'vs/editor/contrib/find/findWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput } from 'vs/platform/browser/contextScopedHistoryWidget'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); @@ -165,7 +165,7 @@ export abstract class SimpleFindWidget extends Widget { return this._focusTracker; } - public updateTheme(theme: ITheme): void { + public updateTheme(theme: IColorTheme): void { const inputStyles: IFindInputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground), diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index 65bf2db355..a5b8cbe8fb 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -501,7 +501,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { const range = new Range(line + 1, character + 1, line + 1, character + 1 + len); const definitions = {}; const colorMap = this._themeService.getColorTheme().tokenColorMap; - const theme = this._themeService.getTheme() as ColorThemeData; + const theme = this._themeService.getColorTheme() as ColorThemeData; const tokenStyle = theme.getTokenStyleMetadata(type, modifiers, true, definitions); let metadata: IDecodedMetadata | undefined = undefined; @@ -528,7 +528,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { if (definition === undefined) { return ''; } - const theme = this._themeService.getTheme() as ColorThemeData; + const theme = this._themeService.getColorTheme() as ColorThemeData; const isTokenStylingRule = (d: any): d is TokenStylingRule => !!d.value; if (Array.isArray(definition)) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts new file mode 100644 index 0000000000..ea55d2b77f --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IKeyMods } from 'vs/platform/quickinput/common/quickInput'; +import { IEditor } from 'vs/editor/common/editorCommon'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IRange } from 'vs/editor/common/core/range'; +import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLine'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; + +export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider { + + readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; + + constructor(@IEditorService private readonly editorService: IEditorService) { + super(); + } + + get activeTextEditorControl() { + return this.editorService.activeTextEditorControl; + } + + protected gotoLine(editor: IEditor, range: IRange, keyMods: IKeyMods): void { + + // Check for sideBySide use + if (keyMods.ctrlCmd && this.editorService.activeEditor) { + this.editorService.openEditor(this.editorService.activeEditor, { selection: range, pinned: keyMods.alt }, SIDE_GROUP); + } + + // Otherwise let parent handle it + else { + super.gotoLine(editor, range, keyMods); + } + } +} + +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: GotoLineQuickAccessProvider, + prefix: AbstractGotoLineQuickAccessProvider.PREFIX, + placeholder: localize('gotoLineQuickAccessPlaceholder', "Type the line number and optional column to go to (e.g. 42:5 for line 42 and column 5)."), + helpEntries: [{ description: localize('gotoLineQuickAccess', "Go to Line"), needsEditor: true }] +}); diff --git a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts index 236c222a5b..9344554f9a 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -55,7 +55,7 @@ class NotebookUpdateParticipant implements ITextFileSaveParticipant { // {{SQL C } } -class TrimWhitespaceParticipant implements ITextFileSaveParticipant { +export class TrimWhitespaceParticipant implements ITextFileSaveParticipant { constructor( @IConfigurationService private readonly configurationService: IConfigurationService, diff --git a/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts b/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts index 1490dad73a..1c003af7d3 100644 --- a/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts +++ b/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { FinalNewLineParticipant, TrimFinalNewLinesParticipant } from 'vs/workbench/contrib/codeEditor/browser/saveParticipants'; +import { FinalNewLineParticipant, TrimFinalNewLinesParticipant, TrimWhitespaceParticipant } from 'vs/workbench/contrib/codeEditor/browser/saveParticipants'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; @@ -16,7 +16,7 @@ import { IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/ser import { SaveReason } from 'vs/workbench/common/editor'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -suite('MainThreadSaveParticipant', function () { +suite('Save Participants', function () { let instantiationService: IInstantiationService; let accessor: TestServiceAccessor; @@ -151,4 +151,24 @@ suite('MainThreadSaveParticipant', function () { model.textEditorModel.redo(); assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}${eol}`); }); + + test('trim whitespace', async function () { + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel; + + await model.load(); + const configService = new TestConfigurationService(); + configService.setUserConfiguration('files', { 'trimTrailingWhitespace': true }); + const participant = new TrimWhitespaceParticipant(configService, undefined!); + const textContent = 'Test'; + let content = `${textContent} `; + model.textEditorModel.setValue(content); + + // save many times + for (let i = 0; i < 10; i++) { + await participant.participate(model, { reason: SaveReason.EXPLICIT }); + } + + // confirm trimming + assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}`); + }); }); diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 16ae10cfa7..68674a8d0a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -35,7 +35,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { contrastBorder, editorForeground, focusBorder, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, transparent } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions'; import { CommentGlyphWidget } from 'vs/workbench/contrib/comments/browser/commentGlyphWidget'; import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; @@ -144,13 +144,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this.create(); this._styleElement = dom.createStyleSheet(this.domNode); - this._globalToDispose.add(this.themeService.onThemeChange(this._applyTheme, this)); + this._globalToDispose.add(this.themeService.onDidColorThemeChange(this._applyTheme, this)); this._globalToDispose.add(this.editor.onDidChangeConfiguration(e => { if (e.hasChanged(EditorOption.fontInfo)) { - this._applyTheme(this.themeService.getTheme()); + this._applyTheme(this.themeService.getColorTheme()); } })); - this._applyTheme(this.themeService.getTheme()); + this._applyTheme(this.themeService.getColorTheme()); this._markdownRenderer = this._globalToDispose.add(new MarkdownRenderer(editor, this.modeService, this.openerService)); this._parentEditor = editor; @@ -758,7 +758,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget renderOptions: { after: { contentText: placeholder, - color: `${transparent(editorForeground, 0.4)(this.themeService.getTheme())}` + color: `${transparent(editorForeground, 0.4)(this.themeService.getColorTheme())}` } } }]; @@ -828,7 +828,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } } - private _applyTheme(theme: ITheme) { + private _applyTheme(theme: IColorTheme) { const borderColor = theme.getColor(peekViewBorder) || Color.transparent; this.style({ arrowColor: borderColor, diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index cfcc940800..d48a1cbcfc 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -739,21 +739,21 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }); export function getActiveEditor(accessor: ServicesAccessor): IActiveCodeEditor | null { - let activeTextEditorWidget = accessor.get(IEditorService).activeTextEditorWidget; + let activeTextEditorControl = accessor.get(IEditorService).activeTextEditorControl; - if (isDiffEditor(activeTextEditorWidget)) { - if (activeTextEditorWidget.getOriginalEditor().hasTextFocus()) { - activeTextEditorWidget = activeTextEditorWidget.getOriginalEditor(); + if (isDiffEditor(activeTextEditorControl)) { + if (activeTextEditorControl.getOriginalEditor().hasTextFocus()) { + activeTextEditorControl = activeTextEditorControl.getOriginalEditor(); } else { - activeTextEditorWidget = activeTextEditorWidget.getModifiedEditor(); + activeTextEditorControl = activeTextEditorControl.getModifiedEditor(); } } - if (!isCodeEditor(activeTextEditorWidget) || !activeTextEditorWidget.hasModel()) { + if (!isCodeEditor(activeTextEditorControl) || !activeTextEditorControl.hasModel()) { return null; } - return activeTextEditorWidget; + return activeTextEditorControl; } registerThemingParticipant((theme, collector) => { diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 43de2a6d9a..5b64ae3f28 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -10,7 +10,6 @@ import { IAction, Action } from 'vs/base/common/actions'; import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TreeResourceNavigator } from 'vs/platform/list/browser/listService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CommentNode, CommentsModel, ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; import { CommentController } from 'vs/workbench/contrib/comments/browser/commentsEditorContribution'; @@ -28,6 +27,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ResourceNavigator } from 'vs/platform/list/browser/listService'; export class CommentsPanel extends ViewPane { @@ -55,7 +55,7 @@ export class CommentsPanel extends ViewPane { @ICommentService private readonly commentService: ICommentService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), id: COMMENTS_VIEW_ID, ariaHeaderLabel: COMMENTS_VIEW_TITLE }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); } public renderBody(container: HTMLElement): void { @@ -75,7 +75,7 @@ export class CommentsPanel extends ViewPane { const styleElement = dom.createStyleSheet(container); this.applyStyles(styleElement); - this._register(this.themeService.onThemeChange(_ => this.applyStyles(styleElement))); + this._register(this.themeService.onDidColorThemeChange(_ => this.applyStyles(styleElement))); this._register(this.onDidChangeBodyVisibility(visible => { if (visible) { @@ -89,7 +89,7 @@ export class CommentsPanel extends ViewPane { private applyStyles(styleElement: HTMLStyleElement) { const content: string[] = []; - const theme = this.themeService.getTheme(); + const theme = this.themeService.getColorTheme(); const linkColor = theme.getColor(textLinkForeground); if (linkColor) { content.push(`.comments-panel .comments-panel-container a { color: ${linkColor}; }`); @@ -151,7 +151,7 @@ export class CommentsPanel extends ViewPane { this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); this.tree = this._register(this.instantiationService.createInstance(CommentsList, this.treeLabels, this.treeContainer)); - const commentsNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true })); + const commentsNavigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: true })); this._register(commentsNavigator.onDidOpenResource(e => { this.openFile(e.element, e.editorOptions.pinned, e.editorOptions.preserveFocus, e.sideBySide); })); @@ -173,7 +173,7 @@ export class CommentsPanel extends ViewPane { if (currentActiveResource && currentActiveResource.toString() === element.resource.toString()) { const threadToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].threadId : element.threadId; const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment.uniqueIdInThread : element.comment.uniqueIdInThread; - const control = this.editorService.activeTextEditorWidget; + const control = this.editorService.activeTextEditorControl; if (threadToReveal && isCodeEditor(control)) { const controller = CommentController.get(control); controller.revealCommentThread(threadToReveal, commentToReveal, false); diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index dd3d5714bb..124183066b 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -115,7 +115,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { public runCommand(accessor: ServicesAccessor): void { const editorService = accessor.get(IEditorService); - const activeInput = editorService.activeControl?.input; + const activeInput = editorService.activeEditorPane?.input; if (activeInput instanceof CustomEditorInput) { activeInput.undo(); } @@ -142,7 +142,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { public runCommand(accessor: ServicesAccessor): void { const editorService = accessor.get(IEditorService); - const activeInput = editorService.activeControl?.input; + const activeInput = editorService.activeEditorPane?.input; if (activeInput instanceof CustomEditorInput) { activeInput.redo(); } @@ -161,13 +161,13 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { public runCommand(accessor: ServicesAccessor): void { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl; - if (!activeControl) { + const activeEditorPane = editorService.activeEditorPane; + if (!activeEditorPane) { return; } - const activeGroup = activeControl.group; - const activeEditor = activeControl.input; + const activeGroup = activeEditorPane.group; + const activeEditor = activeEditorPane.input; const targetResource = activeEditor.resource; if (!targetResource) { diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index ae6b755bd2..4611489064 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -5,43 +5,37 @@ import { memoize } from 'vs/base/common/decorators'; import { Lazy } from 'vs/base/common/lazy'; +import { IReference } from 'vs/base/common/lifecycle'; import { basename } from 'vs/base/common/path'; import { isEqual } from 'vs/base/common/resources'; import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IEditorModel, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, Verbosity } from 'vs/workbench/common/editor'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { IWebviewService, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; - -export const enum ModelType { - Custom = 'custom', - Text = 'text', -} export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { - public static typeId = 'workbench.editors.webviewEditor'; private readonly _editorResource: URI; get resource() { return this._editorResource; } - private _model?: { readonly type: ModelType.Custom, readonly model: ICustomEditorModel } | { readonly type: ModelType.Text }; + private _modelRef?: IReference; constructor( resource: URI, viewType: string, id: string, - webview: Lazy, + webview: Lazy, @IWebviewService webviewService: IWebviewService, @IWebviewWorkbenchService webviewWorkbenchService: IWebviewWorkbenchService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -50,15 +44,11 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { @IFileDialogService private readonly fileDialogService: IFileDialogService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IEditorService private readonly editorService: IEditorService, - @ITextFileService private readonly textFileService: ITextFileService, - ) { super(id, viewType, '', webview, webviewService, webviewWorkbenchService); this._editorResource = resource; } - public modelType?: ModelType; - public getTypeId(): string { return CustomEditorInput.typeId; } @@ -110,20 +100,10 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } public isDirty(): boolean { - if (!this._model) { + if (!this._modelRef) { return false; } - - switch (this._model.type) { - case ModelType.Text: - return this.textFileService.isDirty(this.resource); - - case ModelType.Custom: - return this._model.model.isDirty(); - - default: - throw new Error('Unknown model type'); - } + return this._modelRef.object.isDirty(); } public isSaving(): boolean { @@ -139,105 +119,44 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } public async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - if (!this._model) { - return undefined; - } - - switch (this._model.type) { - case ModelType.Text: - { - const result = await this.textFileService.save(this.resource, options); - return result ? this : undefined; - } - case ModelType.Custom: - { - const result = await this._model.model.save(options); - return result ? this : undefined; - } - default: - throw new Error('Unknown model type'); - } + const modelRef = assertIsDefined(this._modelRef); + const result = await modelRef.object.save(options); + return result ? this : undefined; } public async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - if (!this._model) { - return undefined; - } + const modelRef = assertIsDefined(this._modelRef); - let dialogPath = this._editorResource; + const dialogPath = this._editorResource; const target = await this.fileDialogService.pickFileToSave(dialogPath, options?.availableFileSystems); if (!target) { return undefined; // save cancelled } - switch (this._model.type) { - case ModelType.Text: - if (!await this.textFileService.saveAs(this.resource, target, options)) { - return undefined; - } - break; - - case ModelType.Custom: - if (!await this._model.model.saveAs(this._editorResource, target, options)) { - return undefined; - } - break; - - default: - throw new Error('Unknown model type'); + if (!await modelRef.object.saveAs(this._editorResource, target, options)) { + return undefined; } - return this.handleMove(groupId, target) || this.editorService.createInput({ resource: target, forceFile: true }); + return this.handleMove(groupId, target) || this.editorService.createEditorInput({ resource: target, forceFile: true }); } - public async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { - if (!this._model) { - return false; - } - - switch (this._model.type) { - case ModelType.Text: - return this.textFileService.revert(this.resource, options); - - case ModelType.Custom: - return this._model.model.revert(options); - - default: - throw new Error('Unknown model type'); - } + public async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + return assertIsDefined(this._modelRef).object.revert(options); } - public async resolve(): Promise { - const editorModel = await super.resolve(); - if (!this._model) { - switch (this.modelType) { - case ModelType.Custom: - const model = await this.customEditorService.models.resolve(this.resource, this.viewType); - this._model = { type: ModelType.Custom, model }; - this._register(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + public async resolve(): Promise { + await super.resolve(); - break; + if (!this._modelRef) { + this._modelRef = this._register(assertIsDefined(await this.customEditorService.models.tryRetain(this.resource, this.viewType))); + this._register(this._modelRef.object.onDidChangeDirty(() => this._onDidChangeDirty.fire())); - case ModelType.Text: - this._model = { type: ModelType.Text, }; - this.textFileService.files.onDidChangeDirty(e => { - if (isEqual(this.resource, e.resource)) { - this._onDidChangeDirty.fire(); - } - }); - - break; - - default: - throw new Error('Unknown model type'); + if (this.isDirty()) { + this._onDidChangeDirty.fire(); } } - if (this.isDirty()) { - this._onDidChangeDirty.fire(); - } - - return editorModel; + return null; } public handleMove(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { @@ -256,40 +175,10 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } public undo(): void { - if (!this._model) { - return; - } - - switch (this._model.type) { - case ModelType.Custom: - this._model.model.undo(); - return; - - case ModelType.Text: - this.textFileService.files.get(this.resource)?.textEditorModel?.undo(); - return; - - default: - throw new Error('Unknown model type'); - } + assertIsDefined(this._modelRef).object.undo(); } public redo(): void { - if (!this._model) { - return; - } - - switch (this._model.type) { - case ModelType.Custom: - this._model.model.redo(); - return; - - case ModelType.Text: - this.textFileService.files.get(this.resource)?.textEditorModel?.redo(); - return; - - default: - throw new Error('Unknown model type'); - } + assertIsDefined(this._modelRef).object.redo(); } } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index bbb003b38a..dbcea2fe9a 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -26,7 +26,6 @@ export class CustomEditorInputFactory extends WebviewEditorInputFactory { const data = { ...this.toJson(input), editorResource: input.resource.toJSON(), - modelType: input.modelType }; try { @@ -55,9 +54,6 @@ export class CustomEditorInputFactory extends WebviewEditorInputFactory { if (typeof data.group === 'number') { customInput.updateGroup(data.group); } - if ((data as any).modelType) { - customInput.modelType = (data as any).modelType; - } return customInput; } } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 65fe1275c3..00415100ea 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -16,21 +16,19 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { FileOperation, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ILabelService } from 'vs/platform/label/common/label'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { EditorInput, EditorOptions, IEditor, IEditorInput } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IEditorInput, IEditorPane } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint'; -import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_CUSTOM_EDITORS, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, CustomEditorSelector, ICustomEditor, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CONTEXT_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, CustomEditorSelector, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager'; import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; -import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { CustomEditorInput } from './customEditorInput'; export const defaultEditorId = 'default'; @@ -96,7 +94,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ private readonly _editorInfoStore = this._register(new CustomEditorInfoStore()); - private readonly _models: CustomEditorModelManager; + private readonly _models = new CustomEditorModelManager(); private readonly _customEditorContextKey: IContextKey; private readonly _focusedCustomEditorIsEditable: IContextKey; @@ -104,7 +102,6 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ constructor( @IContextKeyService contextKeyService: IContextKeyService, - @IWorkingCopyService workingCopyService: IWorkingCopyService, @IFileService fileService: IFileService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, @@ -112,12 +109,9 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ @IInstantiationService private readonly instantiationService: IInstantiationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IWebviewService private readonly webviewService: IWebviewService, - @ILabelService labelService: ILabelService ) { super(); - this._models = new CustomEditorModelManager(workingCopyService, labelService); - this._customEditorContextKey = CONTEXT_CUSTOM_EDITORS.bindTo(contextKeyService); this._focusedCustomEditorIsEditable = CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE.bindTo(contextKeyService); this._webviewHasOwnEditFunctions = webviewHasOwnEditFunctionsContext.bindTo(contextKeyService); @@ -136,15 +130,6 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ public get models() { return this._models; } - public get activeCustomEditor(): ICustomEditor | undefined { - const activeInput = this.editorService.activeControl?.input; - if (!(activeInput instanceof CustomEditorInput)) { - return undefined; - } - const resource = activeInput.resource; - return { resource, viewType: activeInput.viewType }; - } - public getCustomEditor(viewType: string): CustomEditorInfo | undefined { return this._editorInfoStore.get(viewType); } @@ -165,7 +150,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ resource: URI, options?: ITextEditorOptions, group?: IEditorGroup, - ): Promise { + ): Promise { const customEditors = new CustomEditorInfoCollection([ defaultEditorInfo, ...this.getUserConfiguredCustomEditors(resource).allEditors, @@ -243,10 +228,10 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ viewType: string, options?: ITextEditorOptions, group?: IEditorGroup, - ): Promise { + ): Promise { if (viewType === defaultEditorId) { - const fileInput = this.editorService.createInput({ resource, forceFile: true }); - return this.openEditorForResource(resource, fileInput, { ...options, ignoreOverrides: true }, group); + const fileEditorInput = this.editorService.createEditorInput({ resource, forceFile: true }); + return this.openEditorForResource(resource, fileEditorInput, { ...options, ignoreOverrides: true }, group); } if (!this._editorInfoStore.get(viewType)) { @@ -264,12 +249,12 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ options?: { readonly customClasses: string; }, ): IEditorInput { if (viewType === defaultEditorId) { - return this.editorService.createInput({ resource, forceFile: true }); + return this.editorService.createEditorInput({ resource, forceFile: true }); } const id = generateUuid(); const webview = new Lazy(() => { - return this.webviewService.createWebviewEditorOverlay(id, { customClasses: options?.customClasses }, {}); + return this.webviewService.createWebviewOverlay(id, { customClasses: options?.customClasses }, {}); }); const input = this.instantiationService.createInstance(CustomEditorInput, resource, viewType, id, webview); if (group) { @@ -283,7 +268,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ input: IEditorInput, options?: IEditorOptions, group?: IEditorGroup - ): Promise { + ): Promise { const targetGroup = group || this.editorGroupService.activeGroup; // Try to replace existing editors for resource @@ -307,8 +292,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ } private updateContexts() { - const activeControl = this.editorService.activeControl; - const resource = activeControl?.input.resource; + const activeEditorPane = this.editorService.activeEditorPane; + const resource = activeEditorPane?.input.resource; if (!resource) { this._customEditorContextKey.reset(); this._focusedCustomEditorIsEditable.reset(); @@ -321,7 +306,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ ...this.getUserConfiguredCustomEditors(resource).allEditors, ]; this._customEditorContextKey.set(possibleEditors.map(x => x.id).join(',')); - this._focusedCustomEditorIsEditable.set(activeControl?.input instanceof CustomEditorInput); + this._focusedCustomEditorIsEditable.set(activeEditorPane?.input instanceof CustomEditorInput); this._webviewHasOwnEditFunctions.set(possibleEditors.length > 0); } @@ -341,6 +326,9 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ this.editorService.replaceEditors([{ editor: editor, replacement: replacement, + options: { + preserveFocus: true + } }], group); } } diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 0773b4fd12..f533eaf85d 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -3,8 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { distinct, find, mergeSort } from 'vs/base/common/arrays'; -import { CancelablePromise } from 'vs/base/common/async'; +import { distinct, mergeSort } from 'vs/base/common/arrays'; import { Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; import { basename } from 'vs/base/common/resources'; @@ -12,74 +11,50 @@ import { URI } from 'vs/base/common/uri'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IEditor, IRevertOptions, ISaveOptions, IEditorInput } from 'vs/workbench/common/editor'; +import { IEditorPane, IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IDisposable, IReference } from 'vs/base/common/lifecycle'; export const ICustomEditorService = createDecorator('customEditorService'); export const CONTEXT_CUSTOM_EDITORS = new RawContextKey('customEditors', ''); export const CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE = new RawContextKey('focusedCustomEditorIsEditable', false); -export interface ICustomEditor { - readonly resource: URI; - readonly viewType: string; -} - export interface ICustomEditorService { _serviceBrand: any; readonly models: ICustomEditorModelManager; - readonly activeCustomEditor: ICustomEditor | undefined; - getCustomEditor(viewType: string): CustomEditorInfo | undefined; getContributedCustomEditors(resource: URI): CustomEditorInfoCollection; getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection; createInput(resource: URI, viewType: string, group: IEditorGroup | undefined, options?: { readonly customClasses: string }): IEditorInput; - openWith(resource: URI, customEditorViewType: string, options?: ITextEditorOptions, group?: IEditorGroup): Promise; - promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise; + openWith(resource: URI, customEditorViewType: string, options?: ITextEditorOptions, group?: IEditorGroup): Promise; + promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise; } export interface ICustomEditorModelManager { - get(resource: URI, viewType: string): ICustomEditorModel | undefined; + get(resource: URI, viewType: string): Promise; - resolve(resource: URI, viewType: string): Promise; + tryRetain(resource: URI, viewType: string): Promise> | undefined; - disposeModel(model: ICustomEditorModel): void; + add(resource: URI, viewType: string, model: Promise): Promise>; disposeAllModelsForView(viewType: string): void; } -export interface CustomEditorSaveEvent { - readonly resource: URI; - readonly waitUntil: (until: Promise) => void; -} - -export interface CustomEditorSaveAsEvent { - readonly resource: URI; - readonly targetResource: URI; - readonly waitUntil: (until: Promise) => void; -} - -export interface ICustomEditorModel extends IWorkingCopy { +export interface ICustomEditorModel extends IDisposable { readonly viewType: string; + readonly resource: URI; - readonly onUndo: Event; - readonly onRedo: Event; - readonly onRevert: Event; + isDirty(): boolean; + readonly onDidChangeDirty: Event; - readonly onWillSave: Event; - readonly onWillSaveAs: Event; - - onBackup(f: () => CancelablePromise): void; - - setDirty(dirty: boolean): void; undo(): void; redo(): void; - revert(options?: IRevertOptions): Promise; + revert(options?: IRevertOptions): Promise; save(options?: ISaveOptions): Promise; saveAs(resource: URI, targetResource: URI, currentOptions?: ISaveOptions): Promise; @@ -145,7 +120,7 @@ export class CustomEditorInfoCollection { * other contributed editors. */ public get defaultEditor(): CustomEditorInfo | undefined { - return find(this.allEditors, editor => { + return this.allEditors.find(editor => { switch (editor.priority) { case CustomEditorPriority.default: case CustomEditorPriority.builtin: diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts deleted file mode 100644 index 2d3a2490ca..0000000000 --- a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts +++ /dev/null @@ -1,206 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CancelablePromise } from 'vs/base/common/async'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; -import { CustomEditorSaveAsEvent, CustomEditorSaveEvent, ICustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { basename } from 'vs/base/common/path'; - -namespace HotExitState { - export const enum Type { - NotSupported, - Allowed, - NotAllowed, - Pending, - } - - export const NotSupported = Object.freeze({ type: Type.NotSupported } as const); - export const Allowed = Object.freeze({ type: Type.Allowed } as const); - export const NotAllowed = Object.freeze({ type: Type.NotAllowed } as const); - - export class Pending { - readonly type = Type.Pending; - - constructor( - public readonly operation: CancelablePromise, - ) { } - } - - export type State = typeof NotSupported | typeof Allowed | typeof NotAllowed | Pending; -} - -export class CustomEditorModel extends Disposable implements ICustomEditorModel { - - private _hotExitState: HotExitState.State = HotExitState.NotSupported; - private _dirty = false; - - constructor( - public readonly viewType: string, - private readonly _resource: URI, - private readonly labelService: ILabelService, - ) { - super(); - } - - //#region IWorkingCopy - - public get resource() { - return this._resource; - } - - public get name() { - return basename(this.labelService.getUriLabel(this._resource)); - } - - public get capabilities(): WorkingCopyCapabilities { - return 0; - } - - public isDirty(): boolean { - return this._dirty; - } - - private readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); - readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; - - private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); - readonly onDidChangeContent: Event = this._onDidChangeContent.event; - - //#endregion - - private readonly _onUndo = this._register(new Emitter()); - public readonly onUndo = this._onUndo.event; - - private readonly _onRedo = this._register(new Emitter()); - public readonly onRedo = this._onRedo.event; - - private readonly _onRevert = this._register(new Emitter()); - public readonly onRevert = this._onRevert.event; - - private readonly _onWillSave = this._register(new Emitter()); - public readonly onWillSave = this._onWillSave.event; - - private readonly _onWillSaveAs = this._register(new Emitter()); - public readonly onWillSaveAs = this._onWillSaveAs.event; - - private _onBackup: undefined | (() => CancelablePromise); - - public onBackup(f: () => CancelablePromise) { - if (this._onBackup) { - throw new Error('Backup already implemented'); - } - this._onBackup = f; - - if (this._hotExitState === HotExitState.NotSupported) { - this._hotExitState = this.isDirty() ? HotExitState.NotAllowed : HotExitState.Allowed; - } - } - - public setDirty(dirty: boolean): void { - this._onDidChangeContent.fire(); - - if (this._dirty !== dirty) { - this._dirty = dirty; - this._onDidChangeDirty.fire(); - } - } - - public async revert(_options?: IRevertOptions) { - if (!this._dirty) { - return true; - } - - this._onRevert.fire(); - return true; - } - - public undo() { - this._onUndo.fire(); - } - - public redo() { - this._onRedo.fire(); - } - - public async save(_options?: ISaveOptions): Promise { - const untils: Promise[] = []; - const handler: CustomEditorSaveEvent = { - resource: this._resource, - waitUntil: (until: Promise) => untils.push(until) - }; - - try { - this._onWillSave.fire(handler); - await Promise.all(untils); - } catch { - return false; - } - - this.setDirty(false); - - return true; - } - - public async saveAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise { - const untils: Promise[] = []; - const handler: CustomEditorSaveAsEvent = { - resource, - targetResource, - waitUntil: (until: Promise) => untils.push(until) - }; - - try { - this._onWillSaveAs.fire(handler); - await Promise.all(untils); - } catch { - return false; - } - - this.setDirty(false); - - return true; - } - - public async backup(): Promise { - if (this._hotExitState === HotExitState.NotSupported) { - throw new Error('Not supported'); - } - - if (this._hotExitState.type === HotExitState.Type.Pending) { - this._hotExitState.operation.cancel(); - } - this._hotExitState = HotExitState.NotAllowed; - - const pendingState = new HotExitState.Pending(this._onBackup!()); - this._hotExitState = pendingState; - - try { - await pendingState.operation; - // Make sure state has not changed in the meantime - if (this._hotExitState === pendingState) { - this._hotExitState = HotExitState.Allowed; - } - } catch (e) { - // Make sure state has not changed in the meantime - if (this._hotExitState === pendingState) { - this._hotExitState = HotExitState.NotAllowed; - } - } - - if (this._hotExitState === HotExitState.Allowed) { - return { - meta: { - viewType: this.viewType, - } - }; - } - throw new Error('Cannot back up in this state'); - } -} diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts index 83c3f2dc4b..ce4f73e3c2 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts @@ -3,60 +3,66 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IReference } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ICustomEditorModel, ICustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { CustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditorModel'; -import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { ILabelService } from 'vs/platform/label/common/label'; +import { once } from 'vs/base/common/functional'; export class CustomEditorModelManager implements ICustomEditorModelManager { - private readonly _models = new Map(); - constructor( - @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, - @ILabelService private readonly _labelService: ILabelService - ) { } + private readonly _references = new Map, + counter: number + }>(); - - public get(resource: URI, viewType: string): ICustomEditorModel | undefined { - return this._models.get(this.key(resource, viewType))?.model; + public async get(resource: URI, viewType: string): Promise { + const key = this.key(resource, viewType); + const entry = this._references.get(key); + return entry?.model; } - public async resolve(resource: URI, viewType: string): Promise { - const existing = this.get(resource, viewType); - if (existing) { - return existing; + public tryRetain(resource: URI, viewType: string): Promise> | undefined { + const key = this.key(resource, viewType); + + const entry = this._references.get(key); + if (!entry) { + return undefined; } - const model = new CustomEditorModel(viewType, resource, this._labelService); - const disposables = new DisposableStore(); - disposables.add(this._workingCopyService.registerWorkingCopy(model)); - this._models.set(this.key(resource, viewType), { model, disposables }); - return model; - } + entry.counter++; - public disposeModel(model: ICustomEditorModel): void { - let foundKey: string | undefined; - this._models.forEach((value, key) => { - if (model === value.model) { - value.disposables.dispose(); - value.model.dispose(); - foundKey = key; - } + return entry.model.then(model => { + return { + object: model, + dispose: once(() => { + if (--entry!.counter <= 0) { + entry.model.then(x => x.dispose()); + this._references.delete(key); + } + }), + }; }); - if (typeof foundKey === 'string') { - this._models.delete(foundKey); + } + + public add(resource: URI, viewType: string, model: Promise): Promise> { + const key = this.key(resource, viewType); + const existing = this._references.get(key); + if (existing) { + throw new Error('Model already exists'); } - return; + + this._references.set(key, { viewType, model, counter: 0 }); + return this.tryRetain(resource, viewType)!; } public disposeAllModelsForView(viewType: string): void { - this._models.forEach((value) => { - if (value.model.viewType === viewType) { - this.disposeModel(value.model); + for (const [key, value] of this._references) { + if (value.viewType === viewType) { + value.model.then(x => x.dispose()); + this._references.delete(key); } - }); + } } private key(resource: URI, viewType: string): string { diff --git a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts new file mode 100644 index 0000000000..59fd6e1ddc --- /dev/null +++ b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, IReference } from 'vs/base/common/lifecycle'; +import { isEqual } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; +import { ICustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +export class CustomTextEditorModel extends Disposable implements ICustomEditorModel { + + public static async create( + instantiationService: IInstantiationService, + viewType: string, + resource: URI + ): Promise { + return instantiationService.invokeFunction(async accessor => { + const textModelResolverService = accessor.get(ITextModelService); + const textFileService = accessor.get(ITextFileService); + const model = await textModelResolverService.createModelReference(resource); + return new CustomTextEditorModel(viewType, resource, model, textFileService); + }); + } + + private constructor( + public readonly viewType: string, + private readonly _resource: URI, + model: IReference, + @ITextFileService private readonly textFileService: ITextFileService, + ) { + super(); + + this._register(model); + + this._register(this.textFileService.files.onDidChangeDirty(e => { + if (isEqual(this.resource, e.resource)) { + this._onDidChangeDirty.fire(); + this._onDidChangeContent.fire(); + } + })); + } + + public get resource() { + return this._resource; + } + + public isDirty(): boolean { + return this.textFileService.isDirty(this.resource); + } + + private readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); + readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; + + private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); + readonly onDidChangeContent: Event = this._onDidChangeContent.event; + + public async revert(options?: IRevertOptions) { + return this.textFileService.revert(this.resource, options); + } + + public undo() { + this.textFileService.files.get(this.resource)?.textEditorModel?.undo(); + } + + public redo() { + this.textFileService.files.get(this.resource)?.textEditorModel?.redo(); + } + + public async save(options?: ISaveOptions): Promise { + return !!await this.textFileService.save(this.resource, options); + } + + public async saveAs(resource: URI, targetResource: URI, options?: ISaveOptions): Promise { + return !!await this.textFileService.saveAs(resource, targetResource, options); + } +} diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index ef7d501926..cf591240a0 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -15,7 +15,7 @@ import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IDebugService, IBreakpoint, BreakpointWidgetContext as Context, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, DEBUG_SCHEME, CONTEXT_IN_BREAKPOINT_WIDGET, IBreakpointUpdateData, IBreakpointEditorContribution, BREAKPOINT_EDITOR_CONTRIBUTION_ID } from 'vs/workbench/contrib/debug/common/debug'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; @@ -56,7 +56,7 @@ function isCurlyBracketOpen(input: IActiveCodeEditor): boolean { return false; } -function createDecorations(theme: ITheme, placeHolder: string): IDecorationOptions[] { +function createDecorations(theme: IColorTheme, placeHolder: string): IDecorationOptions[] { const transparentForeground = transparent(editorForeground, 0.4)(theme); return [{ range: { @@ -225,11 +225,11 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.toDispose.push(model); const setDecorations = () => { const value = this.input.getModel().getValue(); - const decorations = !!value ? [] : createDecorations(this.themeService.getTheme(), this.placeholder); + const decorations = !!value ? [] : createDecorations(this.themeService.getColorTheme(), this.placeholder); this.input.setDecorations(DECORATION_KEY, decorations); }; this.input.getModel().onDidChangeContent(() => setDecorations()); - this.themeService.onThemeChange(() => setDecorations()); + this.themeService.onDidColorThemeChange(() => setDecorations()); this.toDispose.push(CompletionProviderRegistry.register({ scheme: DEBUG_SCHEME, hasAccessToAllModels: true }, { provideCompletionItems: (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken): Promise => { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 22fd758daa..f8d03c793f 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -18,7 +18,7 @@ import { Constants } from 'vs/base/common/uint'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IListVirtualDelegate, IListContextMenuEvent, IListRenderer } from 'vs/base/browser/ui/list/list'; -import { IEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -28,7 +28,7 @@ import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { ILabelService } from 'vs/platform/label/common/label'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Gesture } from 'vs/base/browser/touch'; @@ -74,7 +74,7 @@ export class BreakpointsView extends ViewPane { @IOpenerService openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.minimumBodySize = this.maximumBodySize = getExpandedBodySize(this.debugService.getModel()); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); @@ -617,7 +617,7 @@ class FunctionBreakpointInputRenderer implements IListRenderer { +export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolean, preserveFocus: boolean, debugService: IDebugService, editorService: IEditorService): Promise { if (breakpoint.uri.scheme === DEBUG_SCHEME && debugService.state === State.Inactive) { return Promise.resolve(undefined); } diff --git a/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts index 3e7ac354e7..142bd299f2 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts @@ -10,6 +10,7 @@ import { IDebugService, IStackFrame } from 'vs/workbench/contrib/debug/common/de import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; +import { Event } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -91,7 +92,7 @@ export class CallStackEditorContribution implements IEditorContribution { @IDebugService private readonly debugService: IDebugService, ) { const setDecorations = () => this.decorationIds = this.editor.deltaDecorations(this.decorationIds, this.createCallStackDecorations()); - this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(() => { + this.toDispose.push(Event.any(this.debugService.getViewModel().onDidFocusStackFrame, this.debugService.getModel().onDidChangeCallStack)(() => { setDecorations(); })); this.toDispose.push(this.editor.onDidChangeModel(e => { diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 2de025ea2e..ac7e36c687 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -18,13 +18,13 @@ import { IAction, Action } from 'vs/base/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { ILabelService } from 'vs/platform/label/common/label'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; -import { TreeResourceNavigator, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { ResourceNavigator, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import { Event } from 'vs/base/common/event'; @@ -104,7 +104,7 @@ export class CallStackView extends ViewPane { @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.callStackItemType = CONTEXT_CALLSTACK_ITEM_TYPE.bindTo(contextKeyService); this.contributedContextMenu = menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService); @@ -213,7 +213,7 @@ export class CallStackView extends ViewPane { this.tree.setInput(this.debugService.getModel()); - const callstackNavigator = new TreeResourceNavigator(this.tree); + const callstackNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree); this._register(callstackNavigator); this._register(callstackNavigator.onDidOpenResource(e => { if (this.ignoreSelectionChangedEvent) { diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 055f9f4d38..e9ede6d154 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -31,7 +31,7 @@ import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandler import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; import { isMacintosh } from 'vs/base/common/platform'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { URI } from 'vs/base/common/uri'; import { DebugQuickOpenHandler } from 'vs/workbench/contrib/debug/browser/debugQuickOpen'; import { DebugStatusContribution } from 'vs/workbench/contrib/debug/browser/debugStatus'; @@ -44,7 +44,7 @@ import { WatchExpressionsView } from 'vs/workbench/contrib/debug/browser/watchEx import { VariablesView } from 'vs/workbench/contrib/debug/browser/variablesView'; import { ClearReplAction, Repl } from 'vs/workbench/contrib/debug/browser/repl'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; -import { StartView } from 'vs/workbench/contrib/debug/browser/startView'; +import { WelcomeView } from 'vs/workbench/contrib/debug/browser/welcomeView'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { DebugViewPaneContainer, OpenDebugPanelAction } from 'vs/workbench/contrib/debug/browser/debugViewlet'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; @@ -52,10 +52,12 @@ import { CallStackEditorContribution } from 'vs/workbench/contrib/debug/browser/ import { BreakpointEditorContribution } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { StartDebugQuickAccessProvider } from 'vs/workbench/contrib/debug/browser/debugQuickAccess'; class OpenDebugViewletAction extends ShowViewletAction { public static readonly ID = VIEWLET_ID; - public static readonly LABEL = nls.localize('toggleDebugViewlet', "Show Debug"); + public static readonly LABEL = nls.localize('toggleDebugViewlet', "Show Run and Debug"); constructor( id: string, @@ -108,7 +110,7 @@ viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variab viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctorDescriptor: new SyncDescriptor(WatchExpressionsView), order: 20, weight: 10, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctorDescriptor: new SyncDescriptor(CallStackView), order: 30, weight: 30, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctorDescriptor: new SyncDescriptor(BreakpointsView), order: 40, weight: 20, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: ContextKeyExpr.or(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); -viewsRegistry.registerViews([{ id: StartView.ID, name: StartView.LABEL, ctorDescriptor: new SyncDescriptor(StartView), order: 10, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer); +viewsRegistry.registerViews([{ id: WelcomeView.ID, name: WelcomeView.LABEL, ctorDescriptor: new SyncDescriptor(WelcomeView), order: 10, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer); viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctorDescriptor: new SyncDescriptor(LoadedScriptsView), order: 35, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); registerCommands(); @@ -123,19 +125,20 @@ Registry.as(WorkbenchExtensions.Workbench).regi Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(StatusBarColorProvider, LifecyclePhase.Eventually); const debugCategory = nls.localize('debugCategory', "Debug"); +const runCategroy = nls.localize('runCategory', "Run"); registry.registerWorkbenchAction(SyncActionDescriptor.create(StartAction, StartAction.ID, StartAction.LABEL, { primary: KeyCode.F5 }, CONTEXT_IN_DEBUG_MODE.toNegated()), 'Debug: Start Debugging', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(ConfigureAction, ConfigureAction.ID, ConfigureAction.LABEL), 'Debug: Open launch.json', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL), 'Debug: Add Function Breakpoint', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL), 'Debug: Reapply All Breakpoints', debugCategory); -registry.registerWorkbenchAction(SyncActionDescriptor.create(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }), 'Debug: Run (Start Without Debugging)', debugCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }), 'Run: Start Without Debugging', runCategroy); registry.registerWorkbenchAction(SyncActionDescriptor.create(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL), 'Debug: Remove All Breakpoints', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL), 'Debug: Enable All Breakpoints', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL), 'Debug: Disable All Breakpoints', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(SelectAndStartAction, SelectAndStartAction.ID, SelectAndStartAction.LABEL), 'Debug: Select and Start Debugging', debugCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL), 'Debug: Clear Console', debugCategory); -const registerDebugCommandPaletteItem = (id: string, title: string, when?: ContextKeyExpr, precondition?: ContextKeyExpr) => { +const registerDebugCommandPaletteItem = (id: string, title: string, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { when, command: { @@ -172,6 +175,14 @@ registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, nls.localize('inlin ) ); +// Register Quick Access +Registry.as(QuickAccessExtensions.Quickaccess).registerQuickAccessProvider({ + ctor: StartDebugQuickAccessProvider, + prefix: StartDebugQuickAccessProvider.PREFIX, + placeholder: nls.localize('startDebugPlaceholder', "Type the name of a launch configuration to run."), + helpEntries: [{ description: nls.localize('startDebugHelp', "Start Debug Configurations"), needsEditor: false }] +}); + // register service registerSingleton(IDebugService, service.DebugService); @@ -290,7 +301,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi // Debug toolbar -const registerDebugToolBarItem = (id: string, title: string, order: number, icon: { light?: URI, dark?: URI } | ThemeIcon, when?: ContextKeyExpr, precondition?: ContextKeyExpr) => { +const registerDebugToolBarItem = (id: string, title: string, order: number, icon: { light?: URI, dark?: URI } | ThemeIcon, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => { MenuRegistry.appendMenuItem(MenuId.DebugToolBar, { group: 'navigation', when, @@ -316,7 +327,7 @@ registerDebugToolBarItem(STEP_BACK_ID, nls.localize('stepBackDebug', "Step Back" registerDebugToolBarItem(REVERSE_CONTINUE_ID, nls.localize('reverseContinue', "Reverse"), 60, { id: 'codicon/debug-reverse-continue' }, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); // Debug callstack context menu -const registerDebugCallstackItem = (id: string, title: string, order: number, when?: ContextKeyExpr, precondition?: ContextKeyExpr, group = 'navigation') => { +const registerDebugCallstackItem = (id: string, title: string, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation') => { MenuRegistry.appendMenuItem(MenuId.DebugCallStackContext, { group, when, @@ -558,7 +569,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { // Touch Bar if (isMacintosh) { - const registerTouchBarEntry = (id: string, title: string, order: number, when: ContextKeyExpr | undefined, iconUri: URI) => { + const registerTouchBarEntry = (id: string, title: string, order: number, when: ContextKeyExpression | undefined, iconUri: URI) => { MenuRegistry.appendMenuItem(MenuId.TouchBarContext, { command: { id, diff --git a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts index 184abf71a5..7fd99519e4 100644 --- a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts +++ b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts @@ -227,7 +227,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme * nothing. */ function setBasicColor(styleCode: number): void { - const theme = themeService.getTheme(); + const theme = themeService.getColorTheme(); let colorType: 'foreground' | 'background' | undefined; let colorIndex: number | undefined; diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index e7759579e8..6208ae54fc 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -160,7 +160,7 @@ export class StartAction extends AbstractDebugAction { export class RunAction extends StartAction { static readonly ID = 'workbench.action.debug.run'; - static LABEL = nls.localize('startWithoutDebugging', "Run (Start Without Debugging)"); + static LABEL = nls.localize('startWithoutDebugging', "Start Without Debugging"); protected isNoDebug(): boolean { return true; diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 74562983fd..e43c944f81 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -158,13 +158,13 @@ export function registerCommands(): void { const debugService = accessor.get(IDebugService); const stackFrame = debugService.getViewModel().focusedStackFrame; const editorService = accessor.get(IEditorService); - const activeEditor = editorService.activeTextEditorWidget; + const activeEditorControl = editorService.activeTextEditorControl; const notificationService = accessor.get(INotificationService); const quickInputService = accessor.get(IQuickInputService); - if (stackFrame && isCodeEditor(activeEditor) && activeEditor.hasModel()) { - const position = activeEditor.getPosition(); - const resource = activeEditor.getModel().uri; + if (stackFrame && isCodeEditor(activeEditorControl) && activeEditorControl.hasModel()) { + const position = activeEditorControl.getPosition(); + const resource = activeEditorControl.getModel().uri; const source = stackFrame.thread.session.getSourceForUri(resource); if (source) { const response = await stackFrame.thread.session.gotoTargets(source.raw, position.lineNumber, position.column); @@ -368,11 +368,11 @@ export function registerCommands(): void { handler: (accessor) => { const debugService = accessor.get(IDebugService); const editorService = accessor.get(IEditorService); - const widget = editorService.activeTextEditorWidget; - if (isCodeEditor(widget)) { - const model = widget.getModel(); + const control = editorService.activeTextEditorControl; + if (isCodeEditor(control)) { + const model = control.getModel(); if (model) { - const position = widget.getPosition(); + const position = control.getPosition(); if (position) { const bps = debugService.getModel().getBreakpoints({ uri: model.uri, lineNumber: position.lineNumber }); if (bps.length) { @@ -514,11 +514,11 @@ export function registerCommands(): void { const inlineBreakpointHandler = (accessor: ServicesAccessor) => { const debugService = accessor.get(IDebugService); const editorService = accessor.get(IEditorService); - const widget = editorService.activeTextEditorWidget; - if (isCodeEditor(widget)) { - const position = widget.getPosition(); - if (position && widget.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(widget.getModel())) { - const modelUri = widget.getModel().uri; + const control = editorService.activeTextEditorControl; + if (isCodeEditor(control)) { + const position = control.getPosition(); + if (position && control.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(control.getModel())) { + const modelUri = control.getModel().uri; const breakpointAlreadySet = debugService.getModel().getBreakpoints({ lineNumber: position.lineNumber, uri: modelUri }) .some(bp => (bp.sessionAgnosticData.column === position.column || (!bp.column && position.column <= 1))); diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 99e40a963e..f8e28c2e73 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -12,7 +12,7 @@ import { URI as uri } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ITextModel } from 'vs/editor/common/model'; -import { IEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -443,10 +443,10 @@ export class ConfigurationManager implements IConfigurationManager { return Promise.resolve(adapter); } - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; + const activeTextEditorControl = this.editorService.activeTextEditorControl; let candidates: Debugger[] | undefined; - if (isCodeEditor(activeTextEditorWidget)) { - const model = activeTextEditorWidget.getModel(); + if (isCodeEditor(activeTextEditorControl)) { + const model = activeTextEditorControl.getModel(); const language = model ? model.getLanguageIdentifier().language : undefined; const adapters = this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0); if (adapters.length === 1) { @@ -573,7 +573,7 @@ class Launch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch', { resource: this.workspace.uri }).workspaceFolderValue; } - async openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditor | null, created: boolean }> { + async openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null, created: boolean }> { const resource = this.uri; let created = false; let content = ''; @@ -653,7 +653,7 @@ class WorkspaceLaunch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch').workspaceValue; } - async openConfigFile(sideBySide: boolean, preserveFocus: boolean): Promise<{ editor: IEditor | null, created: boolean }> { + async openConfigFile(sideBySide: boolean, preserveFocus: boolean): Promise<{ editor: IEditorPane | null, created: boolean }> { const editor = await this.editorService.openEditor({ resource: this.contextService.getWorkspace().configuration!, @@ -696,8 +696,8 @@ class UserLaunch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch').userValue; } - async openConfigFile(_: boolean, preserveFocus: boolean): Promise<{ editor: IEditor | null, created: boolean }> { - const editor = await this.preferencesService.openGlobalSettings(false, { preserveFocus }); + async openConfigFile(_: boolean, preserveFocus: boolean): Promise<{ editor: IEditorPane | null, created: boolean }> { + const editor = await this.preferencesService.openGlobalSettings(true, { preserveFocus }); return ({ editor: withUndefinedAsNull(editor), created: false diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts new file mode 100644 index 0000000000..41461a5e53 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/common/quickAccess'; +import { localize } from 'vs/nls'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { matchesFuzzy } from 'vs/base/common/filters'; +import { StartAction } from 'vs/workbench/contrib/debug/browser/debugActions'; +import { withNullAsUndefined } from 'vs/base/common/types'; + +export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = 'debug '; + + constructor( + @IDebugService private readonly debugService: IDebugService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @ICommandService private readonly commandService: ICommandService, + @INotificationService private readonly notificationService: INotificationService + ) { + super(StartDebugQuickAccessProvider.PREFIX); + } + + protected getPicks(filter: string): (IQuickPickSeparator | IPickerQuickAccessItem)[] { + const picks: Array = []; + + const configManager = this.debugService.getConfigurationManager(); + + // Entries: configs + let lastGroup: string | undefined; + for (let config of configManager.getAllConfigurations()) { + const highlights = matchesFuzzy(filter, config.name, true); + if (highlights) { + + // Separator + if (lastGroup !== config.presentation?.group) { + picks.push({ type: 'separator' }); + lastGroup = config.presentation?.group; + } + + // Launch entry + picks.push({ + label: config.name, + ariaLabel: localize('entryAriaLabel', "{0}, debug", config.name), + description: this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? config.launch.name : '', + highlights: { label: highlights }, + accept: async () => { + if (StartAction.isEnabled(this.debugService)) { + this.debugService.getConfigurationManager().selectConfiguration(config.launch, config.name); + try { + await this.debugService.startDebugging(config.launch); + } catch (error) { + this.notificationService.error(error); + } + } + } + }); + } + } + + // Entries: launches + const visibleLaunches = configManager.getLaunches().filter(launch => !launch.hidden); + + // Separator + if (visibleLaunches.length > 0) { + picks.push({ type: 'separator' }); + } + + for (const launch of visibleLaunches) { + const label = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? + localize("addConfigTo", "Add Config ({0})...", launch.name) : + localize('addConfiguration', "Add Configuration..."); + + // Add Config entry + picks.push({ + label, + ariaLabel: localize('entryAriaLabel', "{0}, debug", label), + description: this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? launch.name : '', + highlights: { label: withNullAsUndefined(matchesFuzzy(filter, label, true)) }, + accept: () => this.commandService.executeCommand('debug.addConfiguration', launch.uri.toString()) + }); + } + + return picks; + } +} diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 9a12015a80..8e9df08be7 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -46,6 +46,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { TaskRunResult, DebugTaskRunner } from 'vs/workbench/contrib/debug/browser/debugTaskRunner'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IViewsService } from 'vs/workbench/common/views'; +import { generateUuid } from 'vs/base/common/uuid'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint'; @@ -73,7 +74,7 @@ export class DebugService implements IDebugService { private breakpointsToSendOnResourceSaved: Set; private initializing = false; private previousState: State | undefined; - private initCancellationToken: CancellationTokenSource | undefined; + private sessionCancellationTokens = new Map(); private activity: IDisposable | undefined; constructor( @@ -206,24 +207,33 @@ export class DebugService implements IDebugService { return this.initializing ? State.Initializing : State.Inactive; } - private startInitializingState() { + private startInitializingState(): void { if (!this.initializing) { this.initializing = true; this.onStateChange(); } } - private endInitializingState() { - if (this.initCancellationToken) { - this.initCancellationToken.cancel(); - this.initCancellationToken = undefined; - } + private endInitializingState(): void { if (this.initializing) { this.initializing = false; this.onStateChange(); } } + private cancelTokens(id: string | undefined): void { + if (id) { + const token = this.sessionCancellationTokens.get(id); + if (token) { + token.cancel(); + this.sessionCancellationTokens.delete(id); + } + } else { + this.sessionCancellationTokens.forEach(t => t.cancel()); + this.sessionCancellationTokens.clear(); + } + } + private onStateChange(): void { const state = this.state; if (this.previousState !== state) { @@ -380,8 +390,11 @@ export class DebugService implements IDebugService { } } - this.initCancellationToken = new CancellationTokenSource(); - const configByProviders = await this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!, this.initCancellationToken.token); + const initCancellationToken = new CancellationTokenSource(); + const sessionId = generateUuid(); + this.sessionCancellationTokens.set(sessionId, initCancellationToken); + + const configByProviders = await this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!, initCancellationToken.token); // a falsy config indicates an aborted launch if (configByProviders && configByProviders.type) { try { @@ -391,15 +404,15 @@ export class DebugService implements IDebugService { return false; } - if (!this.initCancellationToken) { + if (initCancellationToken.token.isCancellationRequested) { // User cancelled, silently return return false; } - const cfg = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, type, resolvedConfig, this.initCancellationToken.token); + const cfg = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, type, resolvedConfig, initCancellationToken.token); if (!cfg) { - if (launch && type && cfg === null && this.initCancellationToken) { // show launch.json only for "config" being "null". - await launch.openConfigFile(false, true, type, this.initCancellationToken.token); + if (launch && type && cfg === null && !initCancellationToken.token.isCancellationRequested) { // show launch.json only for "config" being "null". + await launch.openConfigFile(false, true, type, initCancellationToken.token); } return false; } @@ -423,7 +436,7 @@ export class DebugService implements IDebugService { const workspace = launch?.workspace || this.contextService.getWorkspace(); const taskResult = await this.taskRunner.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask, (msg, actions) => this.showError(msg, actions)); if (taskResult === TaskRunResult.Success) { - return this.doCreateSession(launch?.workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options); + return this.doCreateSession(sessionId, launch?.workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options); } return false; } catch (err) { @@ -432,16 +445,16 @@ export class DebugService implements IDebugService { } else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { await this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved and that you have a debug extension installed for that file type.")); } - if (launch && this.initCancellationToken) { - await launch.openConfigFile(false, true, undefined, this.initCancellationToken.token); + if (launch && !initCancellationToken.token.isCancellationRequested) { + await launch.openConfigFile(false, true, undefined, initCancellationToken.token); } return false; } } - if (launch && type && configByProviders === null && this.initCancellationToken) { // show launch.json only for "config" being "null". - await launch.openConfigFile(false, true, type, this.initCancellationToken.token); + if (launch && type && configByProviders === null && !initCancellationToken.token.isCancellationRequested) { // show launch.json only for "config" being "null". + await launch.openConfigFile(false, true, type, initCancellationToken.token); } return false; @@ -450,9 +463,9 @@ export class DebugService implements IDebugService { /** * instantiates the new session, initializes the session, registers session listeners and reports telemetry */ - private async doCreateSession(root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }, options?: IDebugSessionOptions): Promise { + private async doCreateSession(sessionId: string, root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }, options?: IDebugSessionOptions): Promise { - const session = this.instantiationService.createInstance(DebugSession, configuration, root, this.model, options); + const session = this.instantiationService.createInstance(DebugSession, sessionId, configuration, root, this.model, options); this.model.addSession(session); // register listeners as the very first thing! this.registerSessionListeners(session); @@ -566,6 +579,7 @@ export class DebugService implements IDebugService { } } this.endInitializingState(); + this.cancelTokens(session.getId()); this._onDidEndSession.fire(session); const focusedSession = this.viewModel.focusedSession; @@ -656,12 +670,13 @@ export class DebugService implements IDebugService { let resolved: IConfig | undefined | null = session.configuration; if (launch && needsToSubstitute && unresolved) { - this.initCancellationToken = new CancellationTokenSource(); - const resolvedByProviders = await this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, this.initCancellationToken.token); + const initCancellationToken = new CancellationTokenSource(); + this.sessionCancellationTokens.set(session.getId(), initCancellationToken); + const resolvedByProviders = await this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, initCancellationToken.token); if (resolvedByProviders) { resolved = await this.substituteVariables(launch, resolvedByProviders); - if (resolved && this.initCancellationToken) { - resolved = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, unresolved.type, resolved, this.initCancellationToken.token); + if (resolved && !initCancellationToken.token.isCancellationRequested) { + resolved = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, unresolved.type, resolved, initCancellationToken.token); } } else { resolved = resolvedByProviders; @@ -695,6 +710,7 @@ export class DebugService implements IDebugService { if (sessions.length === 0) { this.taskRunner.cancel(); this.endInitializingState(); + this.cancelTokens(undefined); } return Promise.all(sessions.map(s => s.terminate())); diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 8f05992da2..fb811f4b4a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -37,7 +37,6 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; export class DebugSession implements IDebugSession { - private id: string; private _subId: string | undefined; private raw: RawDebugSession | undefined; private initialized = false; @@ -62,6 +61,7 @@ export class DebugSession implements IDebugSession { private readonly _onDidChangeName = new Emitter(); constructor( + private id: string, private _configuration: { resolved: IConfig, unresolved: IConfig | undefined }, public root: IWorkspaceFolder | undefined, private model: DebugModel, @@ -78,7 +78,6 @@ export class DebugSession implements IDebugSession { @INotificationService private readonly notificationService: INotificationService, @ILifecycleService lifecycleService: ILifecycleService ) { - this.id = generateUuid(); this._options = options || {}; if (this.hasSeparateRepl()) { this.repl = new ReplModel(); diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index da0931e840..7d38585e70 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -18,8 +18,7 @@ import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/d import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { Themable } from 'vs/workbench/common/theme'; -import { registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { registerColor, contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -67,7 +66,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { super(themeService); this.$el = dom.$('div.debug-toolbar'); - this.$el.style.top = `${layoutService.getTitleBarOffset()}px`; + this.$el.style.top = `${layoutService.offset?.top ?? 0}px`; this.dragArea = dom.append(this.$el, dom.$('div.drag-area.codicon.codicon-gripper')); @@ -152,7 +151,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { // Prevent default to stop editor selecting text #8524 mouseMoveEvent.preventDefault(); // Reduce x by width of drag handle to reduce jarring #16604 - this.setCoordinates(mouseMoveEvent.posx - 14, mouseMoveEvent.posy - this.layoutService.getTitleBarOffset()); + this.setCoordinates(mouseMoveEvent.posx - 14, mouseMoveEvent.posy - (this.layoutService.offset?.top ?? 0)); }); const mouseUpListener = dom.addDisposableGenericMouseUpListner(window, (e: MouseEvent) => { @@ -198,7 +197,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } private setYCoordinate(y = this.yCoordinate): void { - const titlebarOffset = this.layoutService.getTitleBarOffset(); + const titlebarOffset = this.layoutService.offset?.top ?? 0; this.$el.style.top = `${titlebarOffset + y}px`; this.yCoordinate = y; } @@ -240,7 +239,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } if (!this.isBuilt) { this.isBuilt = true; - this.layoutService.getWorkbenchElement().appendChild(this.$el); + this.layoutService.container.appendChild(this.$el); } this.isVisible = true; diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 952f41822e..5b1042d3dc 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -32,8 +32,8 @@ import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryAc import { INotificationService } from 'vs/platform/notification/common/notification'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { StartView } from 'vs/workbench/contrib/debug/browser/startView'; import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { WelcomeView } from 'vs/workbench/contrib/debug/browser/welcomeView'; export class DebugViewPaneContainer extends ViewPaneContainer { @@ -92,7 +92,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { if (this.startDebugActionViewItem) { this.startDebugActionViewItem.focus(); } else { - this.focusView(StartView.ID); + this.focusView(WelcomeView.ID); } } diff --git a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts index 868ffe827d..ba444b6ce5 100644 --- a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts @@ -10,7 +10,7 @@ import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IExceptionInfo, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -35,8 +35,8 @@ export class ExceptionWidget extends ZoneWidget { this._backgroundColor = Color.white; - this._applyTheme(themeService.getTheme()); - this._disposables.add(themeService.onThemeChange(this._applyTheme.bind(this))); + this._applyTheme(themeService.getColorTheme()); + this._disposables.add(themeService.onDidColorThemeChange(this._applyTheme.bind(this))); this.create(); const onDidLayoutChangeScheduler = new RunOnceScheduler(() => this._doLayout(undefined, undefined), 50); @@ -44,7 +44,7 @@ export class ExceptionWidget extends ZoneWidget { this._disposables.add(onDidLayoutChangeScheduler); } - private _applyTheme(theme: ITheme): void { + private _applyTheme(theme: IColorTheme): void { this._backgroundColor = theme.getColor(debugExceptionWidgetBackground); const frameColor = theme.getColor(debugExceptionWidgetBorder); this.style({ diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 724871d9f4..870e1033bc 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { normalize, isAbsolute, posix } from 'vs/base/common/path'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -29,7 +29,7 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { TreeResourceNavigator, WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; +import { ResourceNavigator, WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; import { dispose } from 'vs/base/common/lifecycle'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; @@ -430,7 +430,7 @@ export class LoadedScriptsView extends ViewPane { @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService); } @@ -491,7 +491,7 @@ export class LoadedScriptsView extends ViewPane { }, 300); this._register(this.changeScheduler); - const loadedScriptsNavigator = new TreeResourceNavigator(this.tree); + const loadedScriptsNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree); this._register(loadedScriptsNavigator); this._register(loadedScriptsNavigator.onDidOpenResource(e => { if (e.element instanceof BaseTreeItem) { diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index c70090a612..78f1496103 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -153,6 +153,7 @@ .debug-pane .debug-call-stack .monaco-list-row .monaco-action-bar { display: none; + flex-shrink: 0; } .debug-pane .debug-call-stack .monaco-list-row:hover .monaco-action-bar { diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index 14873645d6..a914f8d07b 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -53,6 +53,10 @@ margin-right: 8px; cursor: pointer; text-decoration: underline; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 150px; } .repl .repl-tree .monaco-list-row { diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index e25e3da074..034e0d303f 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -270,7 +270,7 @@ export class RawDebugSession implements IDisposable { if (this.capabilities.supportsTerminateRequest) { if (!this.terminated) { this.terminated = true; - return this.send('terminate', { restart }, undefined, 500); + return this.send('terminate', { restart }, undefined, 1000); } return this.disconnect(restart); } @@ -481,7 +481,7 @@ export class RawDebugSession implements IDisposable { this.inShutdown = true; if (this.debugAdapter) { try { - await this.send('disconnect', { restart }, undefined, 500); + await this.send('disconnect', { restart }, undefined, 1000); } finally { this.stopAdapter(error); } @@ -601,9 +601,15 @@ export class RawDebugSession implements IDisposable { private send(command: string, args: any, token?: CancellationToken, timeout?: number): Promise { return new Promise((completeDispatch, errorDispatch) => { if (!this.debugAdapter) { - errorDispatch(new Error(nls.localize('noDebugAdapter', "No debug adapter found. Can not send '{0}'.", command))); + if (this.inShutdown) { + // We are in shutdown silently complete + completeDispatch(); + } else { + errorDispatch(new Error(nls.localize('noDebugAdapter', "No debug adapter found. Can not send '{0}'.", command))); + } return; } + let cancelationListener: IDisposable; const requestId = this.debugAdapter.sendRequest(command, args, (response: DebugProtocol.Response) => { if (cancelationListener) { diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 2ff5b70b59..f6773ac441 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -113,7 +113,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { @IOpenerService openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), id: REPL_VIEW_ID, ariaHeaderLabel: localize('debugConsole', "Debug Console") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50); codeEditorService.registerDecorationType(DECORATION_KEY, {}); @@ -190,7 +190,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { } this.updateActions(); })); - this._register(this.themeService.onThemeChange(() => { + this._register(this.themeService.onDidColorThemeChange(() => { this.refreshReplElements(false); if (this.isVisible()) { this.updateInputDecoration(); @@ -244,12 +244,12 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { return; } - const activeEditor = this.editorService.activeTextEditorWidget; - if (isCodeEditor(activeEditor)) { + const activeEditorControl = this.editorService.activeTextEditorControl; + if (isCodeEditor(activeEditorControl)) { this.modelChangeListener.dispose(); - this.modelChangeListener = activeEditor.onDidChangeModelLanguage(() => this.setMode()); - if (activeEditor.hasModel()) { - this.model.setMode(activeEditor.getModel().getLanguageIdentifier()); + this.modelChangeListener = activeEditorControl.onDidChangeModelLanguage(() => this.setMode()); + if (activeEditorControl.hasModel()) { + this.model.setMode(activeEditorControl.getModel().getLanguageIdentifier()); } } } @@ -421,6 +421,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { @memoize private get refreshScheduler(): RunOnceScheduler { + const autoExpanded = new Set(); return new RunOnceScheduler(async () => { if (!this.tree.getInput()) { return; @@ -431,11 +432,22 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { const session = this.tree.getInput(); if (session) { - const replElements = session.getReplElements(); - const lastElement = replElements.length ? replElements[replElements.length - 1] : undefined; - if (lastElement instanceof ReplGroup && lastElement.autoExpand) { - await this.tree.expand(lastElement); - } + // Automatically expand repl group elements when specified + const autoExpandElements = async (elements: IReplElement[]) => { + for (let element of elements) { + if (element instanceof ReplGroup) { + if (element.autoExpand && !autoExpanded.has(element.getId())) { + autoExpanded.add(element.getId()); + await this.tree.expand(element); + } + if (!this.tree.isCollapsed(element)) { + // Repl groups can have children which are repl groups thus we might need to expand those as well + await autoExpandElements(element.getChildren()); + } + } + } + }; + await autoExpandElements(session.getReplElements()); } if (lastElementVisible) { @@ -582,7 +594,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { const decorations: IDecorationOptions[] = []; if (this.isReadonly && this.replInput.hasTextFocus() && !this.replInput.getValue()) { - const transparentForeground = transparent(editorForeground, 0.4)(this.themeService.getTheme()); + const transparentForeground = transparent(editorForeground, 0.4)(this.themeService.getColorTheme()); decorations.push({ range: { startLineNumber: 0, diff --git a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts index bfb6d1c728..a2cf980fd0 100644 --- a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts +++ b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts @@ -3,14 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { localize } from 'vs/nls'; import { registerColor, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IDebugService, State, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_BACKGROUND, Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_BORDER } from 'vs/workbench/common/theme'; +import { STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_BORDER } from 'vs/workbench/common/theme'; import { addClass, removeClass, createStyleSheet } from 'vs/base/browser/dom'; import { assertIsDefined } from 'vs/base/common/types'; diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 3c3e74938b..c4da910364 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -17,7 +17,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { CopyValueAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeMouseEvent, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; @@ -61,7 +61,7 @@ export class VariablesView extends ViewPane { @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); // Use scheduler to prevent unnecessary flashing this.onFocusStackFrameScheduler = new RunOnceScheduler(async () => { diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index ddbea95bd1..a00ec6c466 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -18,7 +18,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; @@ -58,7 +58,7 @@ export class WatchExpressionsView extends ViewPane { @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => { this.needsRefresh = false; diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/welcomeView.ts similarity index 86% rename from src/vs/workbench/contrib/debug/browser/startView.ts rename to src/vs/workbench/contrib/debug/browser/welcomeView.ts index bdc14da43d..a425c030f8 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/welcomeView.ts @@ -13,7 +13,7 @@ import { localize } from 'vs/nls'; import { StartAction, ConfigureAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewDescriptorService, IViewsRegistry, Extensions } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -29,10 +29,10 @@ const debugStartLanguageKey = 'debugStartLanguage'; const CONTEXT_DEBUG_START_LANGUAGE = new RawContextKey(debugStartLanguageKey, undefined); const CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR = new RawContextKey('debuggerInterestedInActiveEditor', false); -export class StartView extends ViewPane { +export class WelcomeView extends ViewPane { - static ID = 'workbench.debug.startView'; - static LABEL = localize('start', "Start"); + static ID = 'workbench.debug.welcome'; + static LABEL = localize('run', "Run"); private debugStartLanguageContext: IContextKey; private debuggerInterestedContext: IContextKey; @@ -52,7 +52,7 @@ export class StartView extends ViewPane { @IStorageService storageSevice: IStorageService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.debugStartLanguageContext = CONTEXT_DEBUG_START_LANGUAGE.bindTo(contextKeyService); this.debuggerInterestedContext = CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR.bindTo(contextKeyService); @@ -60,9 +60,9 @@ export class StartView extends ViewPane { this.debugStartLanguageContext.set(lastSetLanguage); const setContextKey = () => { - const editor = this.editorService.activeTextEditorWidget; - if (isCodeEditor(editor)) { - const model = editor.getModel(); + const editorControl = this.editorService.activeTextEditorControl; + if (isCodeEditor(editorControl)) { + const model = editorControl.getModel(); const language = model ? model.getLanguageIdentifier().language : undefined; if (language && this.debugService.getConfigurationManager().isDebuggerInterestedInLanguage(language)) { this.debugStartLanguageContext.set(language); @@ -87,23 +87,23 @@ export class StartView extends ViewPane { } const viewsRegistry = Registry.as(Extensions.ViewsRegistry); -viewsRegistry.registerViewWelcomeContent(StartView.ID, { +viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { content: localize('openAFileWhichCanBeDebugged', "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID), when: CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR.toNegated() }); let debugKeybindingLabel = ''; -viewsRegistry.registerViewWelcomeContent(StartView.ID, { +viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { content: localize('runAndDebugAction', "[Run and Debug{0}](command:{1})", debugKeybindingLabel, StartAction.ID), preconditions: [CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR] }); -viewsRegistry.registerViewWelcomeContent(StartView.ID, { +viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { content: localize('customizeRunAndDebug', "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID), when: WorkbenchStateContext.notEqualsTo('empty') }); -viewsRegistry.registerViewWelcomeContent(StartView.ID, { +viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { content: localize('customizeRunAndDebugOpenFolder', "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID), when: WorkbenchStateContext.isEqualTo('empty') }); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 73812cd376..2cf79c3d79 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -11,7 +11,7 @@ import { IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { ITextModel as EditorIModel } from 'vs/editor/common/model'; -import { IEditor, ITextEditor } from 'vs/workbench/common/editor'; +import { IEditorPane, ITextEditorPane } from 'vs/workbench/common/editor'; import { Position, IPosition } from 'vs/editor/common/core/position'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { Range, IRange } from 'vs/editor/common/core/range'; @@ -316,7 +316,7 @@ export interface IStackFrame extends ITreeElement { forgetScopes(): void; restart(): Promise; toString(): string; - openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean): Promise; + openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean): Promise; equals(other: IStackFrame): boolean; } @@ -709,7 +709,7 @@ export interface ILaunch { /** * Opens the launch.json file. Creates if it does not exist. */ - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditor | null, created: boolean }>; + openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null, created: boolean }>; } // Debug service interfaces diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 37b586ec6a..4b18b16ca1 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -21,7 +21,7 @@ import { commonSuffixLength } from 'vs/base/common/strings'; import { posix } from 'vs/base/common/path'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { ITextEditor } from 'vs/workbench/common/editor'; +import { ITextEditorPane } from 'vs/workbench/common/editor'; import { mixin } from 'vs/base/common/objects'; export class ExpressionContainer implements IExpressionContainer { @@ -347,7 +347,7 @@ export class StackFrame implements IStackFrame { return sourceToString === UNKNOWN_SOURCE_LABEL ? this.name : `${this.name} (${sourceToString})`; } - async openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { + async openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { if (this.source.available) { return this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned); } diff --git a/src/vs/workbench/contrib/debug/common/debugSource.ts b/src/vs/workbench/contrib/debug/common/debugSource.ts index 7daccef440..79ed35630b 100644 --- a/src/vs/workbench/contrib/debug/common/debugSource.ts +++ b/src/vs/workbench/contrib/debug/common/debugSource.ts @@ -12,7 +12,7 @@ import { IRange } from 'vs/editor/common/core/range'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { Schemas } from 'vs/base/common/network'; import { isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; -import { ITextEditor } from 'vs/workbench/common/editor'; +import { ITextEditorPane } from 'vs/workbench/common/editor'; import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; export const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source"); @@ -71,7 +71,7 @@ export class Source { return this.uri.scheme === DEBUG_SCHEME; } - openInEditor(editorService: IEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { + openInEditor(editorService: IEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { return !this.available ? Promise.resolve(undefined) : editorService.openEditor({ resource: this.uri, description: this.origin, diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 885140bfc3..3b26f2cd9e 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -12,7 +12,7 @@ import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfigurat let externalTerminalService: IExternalTerminalService | undefined = undefined; -export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, configProvider: ExtHostConfigProvider): void { +export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestArguments, configProvider: ExtHostConfigProvider): Promise { if (!externalTerminalService) { if (env.isWindows) { externalTerminalService = new WindowsExternalTerminalService(undefined); @@ -20,12 +20,12 @@ export function runInExternalTerminal(args: DebugProtocol.RunInTerminalRequestAr externalTerminalService = new MacExternalTerminalService(undefined); } else if (env.isLinux) { externalTerminalService = new LinuxExternalTerminalService(undefined); + } else { + throw new Error('external terminals not supported on this platform'); } } - if (externalTerminalService) { - const config = configProvider.getConfiguration('terminal'); - externalTerminalService.runInTerminal(args.title!, args.cwd, args.args, args.env || {}, config.external || {}); - } + const config = configProvider.getConfiguration('terminal'); + return externalTerminalService.runInTerminal(args.title!, args.cwd, args.args, args.env || {}, config.external || {}); } function spawnAsPromised(command: string, args: string[]): Promise { diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index 754f0d359b..4a5add60c8 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -18,9 +18,10 @@ import { createBreakpointDecorations } from 'vs/workbench/contrib/debug/browser/ import { OverviewRulerLane } from 'vs/editor/common/model'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; +import { generateUuid } from 'vs/base/common/uuid'; function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { - return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); + return new DebugSession(generateUuid(), { resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); } function addBreakpointsAndCheckEvents(model: DebugModel, uri: uri, data: IBreakpointData[]): void { diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index c94584335b..9d08a50256 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -16,9 +16,10 @@ import { createDecorationsForStackFrame } from 'vs/workbench/contrib/debug/brows import { Constants } from 'vs/base/common/uint'; import { getContext, getContextForContributedActions } from 'vs/workbench/contrib/debug/browser/callStackView'; import { getStackFrameThreadAndSessionToFocus } from 'vs/workbench/contrib/debug/browser/debugService'; +import { generateUuid } from 'vs/base/common/uuid'; export function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { - return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); + return new DebugSession(generateUuid(), { resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); } function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFrame, secondStackFrame: StackFrame } { @@ -363,7 +364,7 @@ suite('Debug - CallStack', () => { get state(): State { return State.Stopped; } - }({ resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); + }(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); const runningSession = createMockSession(model); model.addSession(runningSession); diff --git a/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts index 2ffcc96c34..72ed9fd301 100644 --- a/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts @@ -12,7 +12,7 @@ import { workbenchInstantiationService } from 'vs/workbench/test/browser/workben import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { Color, RGBA } from 'vs/base/common/color'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { TestThemeService, TestTheme } from 'vs/platform/theme/test/common/testThemeService'; +import { TestThemeService, TestColorTheme } from 'vs/platform/theme/test/common/testThemeService'; import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; @@ -30,7 +30,7 @@ suite.skip('Debug - ANSI Handling', () => { */ setup(() => { model = new DebugModel([], [], [], [], [], { isDirty: (e: any) => false }); - session = new DebugSession({ resolved: { name: 'test', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); + session = new DebugSession(generateUuid(), { resolved: { name: 'test', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); const instantiationService: TestInstantiationService = workbenchInstantiationService(); linkDetector = instantiationService.createInstance(LinkDetector); @@ -39,7 +39,7 @@ suite.skip('Debug - ANSI Handling', () => { for (let color in ansiColorMap) { colors[color] = ansiColorMap[color].defaults.dark; } - const testTheme = new TestTheme(colors); + const testTheme = new TestColorTheme(colors); themeService = new TestThemeService(testTheme); }); diff --git a/src/vs/workbench/contrib/experiments/browser/experimentalPrompt.ts b/src/vs/workbench/contrib/experiments/browser/experimentalPrompt.ts index 6770187a15..f3e6072648 100644 --- a/src/vs/workbench/contrib/experiments/browser/experimentalPrompt.ts +++ b/src/vs/workbench/contrib/experiments/browser/experimentalPrompt.ts @@ -13,6 +13,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { language } from 'vs/base/common/platform'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; +import { ICommandService } from 'vs/platform/commands/common/commands'; export class ExperimentalPrompts extends Disposable implements IWorkbenchContribution { @@ -21,7 +22,8 @@ export class ExperimentalPrompts extends Disposable implements IWorkbenchContrib @IViewletService private readonly viewletService: IViewletService, @INotificationService private readonly notificationService: INotificationService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IOpenerService private readonly openerService: IOpenerService + @IOpenerService private readonly openerService: IOpenerService, + @ICommandService private readonly commandService: ICommandService ) { super(); @@ -77,6 +79,8 @@ export class ExperimentalPrompts extends Disposable implements IWorkbenchContrib viewlet.search('curated:' + command.curatedExtensionsKey); } }); + } else if (command.codeCommand) { + this.commandService.executeCommand(command.codeCommand.id, ...command.codeCommand.arguments); } this.experimentService.markAsCompleted(experiment.id); @@ -93,7 +97,7 @@ export class ExperimentalPrompts extends Disposable implements IWorkbenchContrib }); } - static getLocalizedText(text: string | { [key: string]: string }, displayLanguage: string): string { + static getLocalizedText(text: string | { [key: string]: string; }, displayLanguage: string): string { if (typeof text === 'string') { return text; } diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index d535b42482..0d60729c1f 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -55,11 +55,16 @@ export interface IExperimentActionPromptCommand { externalLink?: string; curatedExtensionsKey?: string; curatedExtensionsList?: string[]; + codeCommand?: { + id: string; + arguments: unknown[]; + }; } export interface IExperiment { id: string; enabled: boolean; + raw: IRawExperiment | undefined; state: ExperimentState; action?: IExperimentAction; } @@ -88,7 +93,7 @@ interface IExperimentStorageState { * be incremented when adding a condition, otherwise experiments might activate * on older versions of VS Code where not intended. */ -export const currentSchemaVersion = 1; +export const currentSchemaVersion = 3; interface IRawExperiment { id: string; @@ -123,6 +128,7 @@ interface IRawExperiment { userProbability?: number; }; action?: IExperimentAction; + action2?: IExperimentAction; } interface IActivationEventRecord { @@ -228,7 +234,7 @@ export class ExperimentService extends Disposable implements IExperimentService if (context.res.statusCode !== 200) { return null; } - const result = await asJson<{ experiments?: IRawExperiment }>(context); + const result = await asJson<{ experiments?: IRawExperiment; }>(context); return result && Array.isArray(result.experiments) ? result.experiments : []; } catch (_e) { // Bad request or invalid JSON @@ -248,6 +254,7 @@ export class ExperimentService extends Disposable implements IExperimentService if (experimentState) { this._experiments.push({ id: experimentId, + raw: undefined, enabled: experimentState.enabled, state: experimentState.state }); @@ -286,57 +293,7 @@ export class ExperimentService extends Disposable implements IExperimentService })); } - const promises = rawExperiments.map(experiment => { - const processedExperiment: IExperiment = { - id: experiment.id, - enabled: !!experiment.enabled, - state: !!experiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun - }; - - if (experiment.action) { - processedExperiment.action = { - type: ExperimentActionType[experiment.action.type] || ExperimentActionType.Custom, - properties: experiment.action.properties - }; - if (processedExperiment.action.type === ExperimentActionType.Prompt) { - ((processedExperiment.action.properties).commands || []).forEach(x => { - if (x.curatedExtensionsKey && Array.isArray(x.curatedExtensionsList)) { - this._curatedMapping[experiment.id] = x; - } - }); - } - if (!processedExperiment.action.properties) { - processedExperiment.action.properties = {}; - } - } - this._experiments.push(processedExperiment); - - if (!processedExperiment.enabled) { - return Promise.resolve(null); - } - - const storageKey = 'experiments.' + experiment.id; - const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); - if (!experimentState.hasOwnProperty('enabled')) { - experimentState.enabled = processedExperiment.enabled; - } - if (!experimentState.hasOwnProperty('state')) { - experimentState.state = processedExperiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun; - } else { - processedExperiment.state = experimentState.state; - } - - return this.shouldRunExperiment(experiment, processedExperiment).then((state: ExperimentState) => { - experimentState.state = processedExperiment.state = state; - this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL); - - if (state === ExperimentState.Run) { - this.fireRunExperiment(processedExperiment); - } - return Promise.resolve(null); - }); - - }); + const promises = rawExperiments.map(experiment => this.evaluateExperiment(experiment)); return Promise.all(promises).then(() => { type ExperimentsClassification = { experiments: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; @@ -346,6 +303,62 @@ export class ExperimentService extends Disposable implements IExperimentService }); } + private evaluateExperiment(experiment: IRawExperiment) { + const processedExperiment: IExperiment = { + id: experiment.id, + raw: experiment, + enabled: !!experiment.enabled, + state: !!experiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun + }; + + const action = experiment.action2 || experiment.action; + if (action) { + processedExperiment.action = { + type: ExperimentActionType[action.type] || ExperimentActionType.Custom, + properties: action.properties + }; + if (processedExperiment.action.type === ExperimentActionType.Prompt) { + ((processedExperiment.action.properties).commands || []).forEach(x => { + if (x.curatedExtensionsKey && Array.isArray(x.curatedExtensionsList)) { + this._curatedMapping[experiment.id] = x; + } + }); + } + if (!processedExperiment.action.properties) { + processedExperiment.action.properties = {}; + } + } + + this._experiments = this._experiments.filter(e => e.id !== processedExperiment.id); + this._experiments.push(processedExperiment); + + if (!processedExperiment.enabled) { + return Promise.resolve(null); + } + + const storageKey = 'experiments.' + experiment.id; + const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); + if (!experimentState.hasOwnProperty('enabled')) { + experimentState.enabled = processedExperiment.enabled; + } + if (!experimentState.hasOwnProperty('state')) { + experimentState.state = processedExperiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun; + } else { + processedExperiment.state = experimentState.state; + } + + return this.shouldRunExperiment(experiment, processedExperiment).then((state: ExperimentState) => { + experimentState.state = processedExperiment.state = state; + this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL); + + if (state === ExperimentState.Run) { + this.fireRunExperiment(processedExperiment); + } + + return Promise.resolve(null); + }); + } + private fireRunExperiment(experiment: IExperiment) { this._onExperimentEnabled.fire(experiment); const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.GLOBAL), []); @@ -386,6 +399,10 @@ export class ExperimentService extends Disposable implements IExperimentService const record = getCurrentActivationRecord(safeParse(this.storageService.get(key, StorageScope.GLOBAL), undefined)); record.count[0]++; this.storageService.store(key, JSON.stringify(record), StorageScope.GLOBAL); + + this._experiments + .filter(e => e.state === ExperimentState.Evaluating && e.raw?.condition?.activationEvent?.event === event) + .forEach(e => this.evaluateExperiment(e.raw!)); } private checkActivationEventFrequency(experiment: IRawExperiment) { @@ -542,7 +559,7 @@ export class ExperimentService extends Disposable implements IExperimentService if (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount) { processedExperiment.state = latestExperimentState.state = (typeof condition.userProbability === 'number' && Math.random() < condition.userProbability && this.checkExperimentDependencies(experiment)) ? ExperimentState.Run : ExperimentState.NoRun; this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL); - if (latestExperimentState.state === ExperimentState.Run && experiment.action && ExperimentActionType[experiment.action.type] === ExperimentActionType.Prompt) { + if (latestExperimentState.state === ExperimentState.Run && processedExperiment.action && ExperimentActionType[processedExperiment.action.type] === ExperimentActionType.Prompt) { this.fireRunExperiment(processedExperiment); } } diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts index 8d8ac3f22c..efd5e9e359 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts @@ -29,6 +29,7 @@ import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/ex import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IProductService } from 'vs/platform/product/common/productService'; import { IWillActivateEvent, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { timeout } from 'vs/base/common/async'; interface ExperimentSettings { enabled?: boolean; @@ -443,7 +444,7 @@ suite('Experiment Service', () => { condition: { activationEvent: { event: 'my:event', - minEvents: 5, + minEvents: 2, } } } @@ -467,12 +468,49 @@ suite('Experiment Service', () => { }); testObject = instantiationService.createInstance(TestExperimentService); - await testObject.getExperimentById('experiment1'); // ensure loaded + await testObject.getExperimentById('experiment1'); activationEvent.fire({ event: 'not our event', activation: Promise.resolve() }); activationEvent.fire({ event: 'my:event', activation: Promise.resolve() }); assert(didGetCall); }); + test('Activation events run experiments in realtime', async () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + activationEvent: { + event: 'my:event', + minEvents: 2, + } + } + } + ] + }; + + let calls = 0; + instantiationService.stub(IStorageService, 'get', (a: string, b: StorageScope, c?: string) => { + return a === 'experimentEventRecord-my-event' + ? JSON.stringify({ count: [++calls, 0, 0, 0, 0, 0, 0], mostRecentBucket: Date.now() }) + : undefined; + }); + + const enabledListener = sinon.stub(); + testObject = instantiationService.createInstance(TestExperimentService); + testObject.onExperimentEnabled(enabledListener); + + assert.equal((await testObject.getExperimentById('experiment1')).state, ExperimentState.Evaluating); + assert.equal((await testObject.getExperimentById('experiment1')).state, ExperimentState.Evaluating); + assert.equal(enabledListener.callCount, 0); + + activationEvent.fire({ event: 'my:event', activation: Promise.resolve() }); + await timeout(1); + assert.equal(enabledListener.callCount, 1); + assert.equal((await testObject.getExperimentById('experiment1')).state, ExperimentState.Run); + }); + test('Experiment not matching user setting should be disabled', () => { experimentData = { experiments: [ @@ -730,6 +768,7 @@ suite('Experiment Service', () => { testObject = instantiationService.createInstance(TestExperimentService); return testObject.getExperimentById('experiment1').then(result => { assert.equal(result.enabled, false); + assert.equal(result.action?.type, 'Prompt'); assert.equal(result.state, ExperimentState.NoRun); return testObject.getCuratedExtensionsList(curatedExtensionsKey).then(curatedList => { assert.equal(curatedList.length, 0); @@ -737,6 +776,29 @@ suite('Experiment Service', () => { }); }); + test('Maps action2 to action.', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: false, + action2: { + type: 'Prompt', + properties: { + promptText: 'Hello world', + commands: [] + } + } + } + ] + }; + + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.action?.type, 'Prompt'); + }); + }); + test('Experiment that is disabled or deleted should be removed from storage', () => { experimentData = { experiments: [ diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts index cefa453c76..2a9d7d098b 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts @@ -16,19 +16,23 @@ import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/browser/ex import { ExperimentActionType, ExperimentState, IExperiment, IExperimentActionPromptProperties, IExperimentService, LocalizedPromptText } from 'vs/workbench/contrib/experiments/common/experimentService'; import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestCommandService } from 'vs/editor/test/browser/editorTestServices'; +import { ICommandService } from 'vs/platform/commands/common/commands'; suite('Experimental Prompts', () => { let instantiationService: TestInstantiationService; let experimentService: TestExperimentService; let experimentalPrompt: ExperimentalPrompts; + let commandService: TestCommandService; let onExperimentEnabledEvent: Emitter; - let storageData: { [key: string]: any } = {}; + let storageData: { [key: string]: any; } = {}; const promptText = 'Hello there! Can you see this?'; const experiment: IExperiment = { id: 'experiment1', enabled: true, + raw: undefined, state: ExperimentState.Run, action: { type: ExperimentActionType.Prompt, @@ -70,6 +74,8 @@ suite('Experimental Prompts', () => { experimentService = instantiationService.createInstance(TestExperimentService); experimentService.onExperimentEnabled = onExperimentEnabledEvent.event; instantiationService.stub(IExperimentService, experimentService); + commandService = instantiationService.createInstance(TestCommandService); + instantiationService.stub(ICommandService, commandService); }); teardown(() => { @@ -81,32 +87,6 @@ suite('Experimental Prompts', () => { } }); - - test('Show experimental prompt if experiment should be run. Choosing option with link should mark experiment as complete', () => { - - storageData = { - enabled: true, - state: ExperimentState.Run - }; - - instantiationService.stub(INotificationService, { - prompt: (a: Severity, b: string, c: IPromptChoice[], options: IPromptOptions) => { - assert.equal(b, promptText); - assert.equal(c.length, 2); - c[0].run(); - return undefined!; - } - }); - - experimentalPrompt = instantiationService.createInstance(ExperimentalPrompts); - onExperimentEnabledEvent.fire(experiment); - - return Promise.resolve(null).then(result => { - assert.equal(storageData['state'], ExperimentState.Complete); - }); - - }); - test('Show experimental prompt if experiment should be run. Choosing negative option should mark experiment as complete', () => { storageData = { @@ -132,6 +112,45 @@ suite('Experimental Prompts', () => { }); + test('runs experiment command', () => { + + storageData = { + enabled: true, + state: ExperimentState.Run + }; + + const stub = instantiationService.stub(ICommandService, 'executeCommand', () => undefined); + instantiationService.stub(INotificationService, { + prompt: (a: Severity, b: string, c: IPromptChoice[], options: IPromptOptions) => { + c[0].run(); + return undefined!; + } + }); + + experimentalPrompt = instantiationService.createInstance(ExperimentalPrompts); + onExperimentEnabledEvent.fire({ + ...experiment, + action: { + type: ExperimentActionType.Prompt, + properties: { + promptText, + commands: [ + { + text: 'Yes', + codeCommand: { id: 'greet', arguments: ['world'] } + } + ] + } + } + }); + + return Promise.resolve(null).then(result => { + assert.deepStrictEqual(stub.args[0], ['greet', 'world']); + assert.equal(storageData['state'], ExperimentState.Complete); + }); + + }); + test('Show experimental prompt if experiment should be run. Cancelling should mark experiment as complete', () => { storageData = { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index a99eed56dd..92a1f9b95c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -84,7 +84,9 @@ class NavBar extends Disposable { private _onChange = this._register(new Emitter<{ id: string | null, focus: boolean }>()); get onChange(): Event<{ id: string | null, focus: boolean }> { return this._onChange.event; } - private currentId: string | null = null; + private _currentId: string | null = null; + get currentId(): string | null { return this._currentId; } + private actions: Action[]; private actionbar: ActionBar; @@ -114,11 +116,11 @@ class NavBar extends Disposable { } update(): void { - this._update(this.currentId); + this._update(this._currentId); } - _update(id: string | null = this.currentId, focus?: boolean): Promise { - this.currentId = id; + _update(id: string | null = this._currentId, focus?: boolean): Promise { + this._currentId = id; this._onChange.fire({ id, focus: !!focus }); this.actions.forEach(a => a.checked = a.id === id); return Promise.resolve(undefined); @@ -309,12 +311,12 @@ export class ExtensionEditor extends BaseEditor { async setInput(input: ExtensionsInput, options: EditorOptions | undefined, token: CancellationToken): Promise { if (this.template) { - await this.updateTemplate(input, this.template); + await this.updateTemplate(input, this.template, !!options?.preserveFocus); } return super.setInput(input, options, token); } - private async updateTemplate(input: ExtensionsInput, template: IExtensionEditorTemplate): Promise { + private async updateTemplate(input: ExtensionsInput, template: IExtensionEditorTemplate, preserveFocus: boolean): Promise { const runningExtensions = await this.extensionService.getExtensions(); const colorThemes = await this.workbenchThemeService.getColorThemes(); const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); @@ -444,31 +446,34 @@ export class ExtensionEditor extends BaseEditor { template.content.innerHTML = ''; // Clear content before setting navbar actions. template.navbar.clear(); - template.navbar.onChange(e => this.onNavbarChange(extension, e, template), this, this.transientDisposables); if (extension.hasReadme()) { template.navbar.push(NavbarSection.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file")); } - this.extensionManifest.get() - .promise - .then(manifest => { - if (manifest) { - combinedInstallAction.manifest = manifest; - } - if (extension.extensionPack.length) { - template.navbar.push(NavbarSection.ExtensionPack, localize('extensionPack', "Extension Pack"), localize('extensionsPack', "Set of extensions that can be installed together")); - } - if (manifest && manifest.contributes) { - template.navbar.push(NavbarSection.Contributions, localize('contributions', "Feature Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension")); - } - if (extension.hasChangelog()) { - template.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file")); - } - if (extension.dependencies.length) { - template.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"), localize('dependenciestooltip', "Lists extensions this extension depends on")); - } - this.editorLoadComplete = true; - }); + + const manifest = await this.extensionManifest.get().promise; + if (manifest) { + combinedInstallAction.manifest = manifest; + } + if (extension.extensionPack.length) { + template.navbar.push(NavbarSection.ExtensionPack, localize('extensionPack', "Extension Pack"), localize('extensionsPack', "Set of extensions that can be installed together")); + } + if (manifest && manifest.contributes) { + template.navbar.push(NavbarSection.Contributions, localize('contributions', "Feature Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension")); + } + if (extension.hasChangelog()) { + template.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file")); + } + if (extension.dependencies.length) { + template.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"), localize('dependenciestooltip', "Lists extensions this extension depends on")); + } + + if (template.navbar.currentId) { + this.onNavbarChange(extension, { id: template.navbar.currentId, focus: !preserveFocus }, template); + } + template.navbar.onChange(e => this.onNavbarChange(extension, e, template), this, this.transientDisposables); + + this.editorLoadComplete = true; } private setSubText(extension: IExtension, reloadAction: ReloadAction, template: IExtensionEditorTemplate): void { @@ -528,6 +533,7 @@ export class ExtensionEditor extends BaseEditor { if (e.enabled === false) { hide(template.subtextContainer); } + this.layout(); })); } @@ -598,18 +604,18 @@ export class ExtensionEditor extends BaseEditor { try { const body = await this.renderMarkdown(cacheResult, template); - const webviewElement = this.contentDisposables.add(this.webviewService.createWebviewEditorOverlay('extensionEditor', { + const webview = this.contentDisposables.add(this.webviewService.createWebviewOverlay('extensionEditor', { enableFindWidget: true, }, {})); - webviewElement.claim(this); - webviewElement.layoutWebviewOverElement(template.content); - webviewElement.html = body; + webview.claim(this); + webview.layoutWebviewOverElement(template.content); + webview.html = body; - this.contentDisposables.add(webviewElement.onDidFocus(() => this.fireOnDidFocus())); + this.contentDisposables.add(webview.onDidFocus(() => this.fireOnDidFocus())); const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout: () => { - webviewElement.layoutWebviewOverElement(template.content); + webview.layoutWebviewOverElement(template.content); } }); this.contentDisposables.add(toDisposable(removeLayoutParticipant)); @@ -617,15 +623,15 @@ export class ExtensionEditor extends BaseEditor { let isDisposed = false; this.contentDisposables.add(toDisposable(() => { isDisposed = true; })); - this.contentDisposables.add(this.themeService.onThemeChange(async () => { + this.contentDisposables.add(this.themeService.onDidColorThemeChange(async () => { // Render again since syntax highlighting of code blocks may have changed const body = await this.renderMarkdown(cacheResult, template); if (!isDisposed) { // Make sure we weren't disposed of in the meantime - webviewElement.html = body; + webview.html = body; } })); - this.contentDisposables.add(webviewElement.onDidClickLink(link => { + this.contentDisposables.add(webview.onDidClickLink(link => { if (!link) { return; } @@ -637,7 +643,7 @@ export class ExtensionEditor extends BaseEditor { } }, null, this.contentDisposables)); - return webviewElement; + return webview; } catch (e) { const p = append(template.content, $('p.nocontent')); p.textContent = noContentCopy; @@ -1224,7 +1230,7 @@ export class ExtensionEditor extends BaseEditor { $('th', undefined, localize('schema', "Schema")) ), ...contrib.map(v => $('tr', undefined, - $('td', undefined, $('code', undefined, v.fileMatch)), + $('td', undefined, $('code', undefined, Array.isArray(v.fileMatch) ? v.fileMatch.join(', ') : v.fileMatch)), $('td', undefined, v.url) )))); @@ -1484,9 +1490,9 @@ registerAction2(class StartExtensionEditorFindPreviousAction extends Action2 { }); function getExtensionEditor(accessor: ServicesAccessor): ExtensionEditor | null { - const activeControl = accessor.get(IEditorService).activeControl as ExtensionEditor; - if (activeControl instanceof ExtensionEditor) { - return activeControl; + const activeEditorPane = accessor.get(IEditorService).activeEditorPane; + if (activeEditorPane instanceof ExtensionEditor) { + return activeEditorPane; } return null; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 353693abc6..fea76be9c8 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -24,7 +24,7 @@ import { import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor'; import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; -import { IQuickOpenRegistry, Extensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; +import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; @@ -50,6 +50,8 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { CONTEXT_SYNC_ENABLEMENT } from 'vs/platform/userDataSync/common/userDataSync'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { InstallExtensionQuickAccessProvider, ManageExtensionsQuickAccessProvider } from 'vs/workbench/contrib/extensions/browser/extensionsQuickAccess'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -59,7 +61,7 @@ Registry.as(OutputExtensions.OutputChannels) .registerChannel({ id: ExtensionsChannelId, label: ExtensionsLabel, log: false }); // Quickopen -Registry.as(Extensions.Quickopen).registerQuickOpenHandler( +Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( QuickOpenHandlerDescriptor.create( ExtensionsHandler, ExtensionsHandler.ID, @@ -70,6 +72,14 @@ Registry.as(Extensions.Quickopen).registerQuickOpenHandler( ) ); +// Quick Access +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: ManageExtensionsQuickAccessProvider, + prefix: ManageExtensionsQuickAccessProvider.PREFIX, + placeholder: localize('manageExtensionsQuickAccessPlaceholder', "Press Enter to manage extensions."), + helpEntries: [{ description: localize('manageExtensionsHelp', "Manage Extensions"), needsEditor: false }] +}); + // Editor Registry.as(EditorExtensions.Editors).registerEditor( EditorDescriptor.create( @@ -484,7 +494,7 @@ class ExtensionsContributions implements IWorkbenchContribution { const canManageExtensions = extensionManagementServerService.localExtensionManagementServer || extensionManagementServerService.remoteExtensionManagementServer; if (canManageExtensions) { - Registry.as(Extensions.Quickopen).registerQuickOpenHandler( + Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( QuickOpenHandlerDescriptor.create( GalleryExtensionsHandler, GalleryExtensionsHandler.ID, @@ -494,6 +504,13 @@ class ExtensionsContributions implements IWorkbenchContribution { true ) ); + + Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: InstallExtensionQuickAccessProvider, + prefix: InstallExtensionQuickAccessProvider.PREFIX, + placeholder: localize('installExtensionQuickAccessPlaceholder', "Type the name of an extension to install or search."), + helpEntries: [{ description: localize('installExtensionQuickAccessHelp', "Install or Search Extensions"), needsEditor: false }] + }); } } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 3a1a9a4858..8f1da510cc 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -30,7 +30,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { URI } from 'vs/base/common/uri'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { buttonBackground, buttonForeground, buttonHoverBackground, contrastBorder, registerColor, foreground } from 'vs/platform/theme/common/colorRegistry'; import { Color } from 'vs/base/common/color'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; @@ -52,7 +52,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { coalesce } from 'vs/base/common/arrays'; -import { IWorkbenchThemeService, COLOR_THEME_SETTING, ICON_THEME_SETTING, IFileIconTheme, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, ThemeSettings, IWorkbenchFileIconTheme, IWorkbenchColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ILabelService } from 'vs/platform/label/common/label'; import { prefersExecuteOnUI, prefersExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -718,7 +718,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { this.update(); } - getActionGroups(runningExtensions: IExtensionDescription[], colorThemes: IColorTheme[], fileIconThemes: IFileIconTheme[]): IAction[][] { + getActionGroups(runningExtensions: IExtensionDescription[], colorThemes: IWorkbenchColorTheme[], fileIconThemes: IWorkbenchFileIconTheme[]): IAction[][] { const groups: ExtensionAction[][] = []; if (this.extension) { const extensionColorThemes = SetColorThemeAction.getColorThemes(colorThemes, this.extension); @@ -1319,7 +1319,7 @@ export class ReloadAction extends ExtensionAction { export class SetColorThemeAction extends ExtensionAction { - static getColorThemes(colorThemes: IColorTheme[], extension: IExtension): IColorTheme[] { + static getColorThemes(colorThemes: IWorkbenchColorTheme[], extension: IExtension): IWorkbenchColorTheme[] { return colorThemes.filter(c => c.extensionData && ExtensionIdentifier.equals(c.extensionData.extensionId, extension.identifier.id)); } @@ -1328,7 +1328,7 @@ export class SetColorThemeAction extends ExtensionAction { constructor( - private readonly colorThemes: IColorTheme[], + private readonly colorThemes: IWorkbenchColorTheme[], @IExtensionService extensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @IQuickInputService private readonly quickInputService: IQuickInputService, @@ -1357,7 +1357,7 @@ export class SetColorThemeAction extends ExtensionAction { return; } let extensionThemes = SetColorThemeAction.getColorThemes(this.colorThemes, this.extension!); - const currentTheme = this.colorThemes.filter(t => t.settingsId === this.configurationService.getValue(COLOR_THEME_SETTING))[0] || this.workbenchThemeService.getColorTheme(); + const currentTheme = this.colorThemes.filter(t => t.settingsId === this.configurationService.getValue(ThemeSettings.COLOR_THEME))[0] || this.workbenchThemeService.getColorTheme(); showCurrentTheme = showCurrentTheme || extensionThemes.some(t => t.id === currentTheme.id); if (showCurrentTheme) { extensionThemes = extensionThemes.filter(t => t.id !== currentTheme.id); @@ -1377,7 +1377,7 @@ export class SetColorThemeAction extends ExtensionAction { onDidFocus: item => delayer.trigger(() => this.workbenchThemeService.setColorTheme(item.id, undefined)), ignoreFocusLost }); - let confValue = this.configurationService.inspect(COLOR_THEME_SETTING); + let confValue = this.configurationService.inspect(ThemeSettings.COLOR_THEME); const target = typeof confValue.workspaceValue !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; return this.workbenchThemeService.setColorTheme(pickedTheme ? pickedTheme.id : currentTheme.id, target); } @@ -1389,12 +1389,12 @@ export class SetFileIconThemeAction extends ExtensionAction { private static readonly DisabledClass = `${SetFileIconThemeAction.EnabledClass} disabled`; - static getFileIconThemes(fileIconThemes: IFileIconTheme[], extension: IExtension): IFileIconTheme[] { + static getFileIconThemes(fileIconThemes: IWorkbenchFileIconTheme[], extension: IExtension): IWorkbenchFileIconTheme[] { return fileIconThemes.filter(c => c.extensionData && ExtensionIdentifier.equals(c.extensionData.extensionId, extension.identifier.id)); } constructor( - private readonly fileIconThemes: IFileIconTheme[], + private readonly fileIconThemes: IWorkbenchFileIconTheme[], @IExtensionService extensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @IQuickInputService private readonly quickInputService: IQuickInputService, @@ -1423,7 +1423,7 @@ export class SetFileIconThemeAction extends ExtensionAction { return; } let extensionThemes = SetFileIconThemeAction.getFileIconThemes(this.fileIconThemes, this.extension!); - const currentTheme = this.fileIconThemes.filter(t => t.settingsId === this.configurationService.getValue(ICON_THEME_SETTING))[0] || this.workbenchThemeService.getFileIconTheme(); + const currentTheme = this.fileIconThemes.filter(t => t.settingsId === this.configurationService.getValue(ThemeSettings.ICON_THEME))[0] || this.workbenchThemeService.getFileIconTheme(); showCurrentTheme = showCurrentTheme || extensionThemes.some(t => t.id === currentTheme.id); if (showCurrentTheme) { extensionThemes = extensionThemes.filter(t => t.id !== currentTheme.id); @@ -1443,7 +1443,7 @@ export class SetFileIconThemeAction extends ExtensionAction { onDidFocus: item => delayer.trigger(() => this.workbenchThemeService.setFileIconTheme(item.id, undefined)), ignoreFocusLost }); - let confValue = this.configurationService.inspect(ICON_THEME_SETTING); + let confValue = this.configurationService.inspect(ThemeSettings.ICON_THEME); const target = typeof confValue.workspaceValue !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; return this.workbenchThemeService.setFileIconTheme(pickedTheme ? pickedTheme.id : currentTheme.id, target); } @@ -3258,7 +3258,7 @@ export const extensionButtonProminentHoverBackground = registerColor('extensionB hc: null }, localize('extensionButtonProminentHoverBackground', "Button background hover color for actions extension that stand out (e.g. install button).")); -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const foregroundColor = theme.getColor(foreground); if (foregroundColor) { collector.addRule(`.extension .monaco-action-bar .action-item .action-label.extension-action.built-in-status { border-color: ${foregroundColor}; }`); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts new file mode 100644 index 0000000000..f7819c8e35 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts @@ -0,0 +1,117 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { localize } from 'vs/nls'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { ILogService } from 'vs/platform/log/common/log'; + +export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = 'ext install '; + + constructor( + @IViewletService private readonly viewletService: IViewletService, + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IExtensionManagementService private readonly extensionsService: IExtensionManagementService, + @INotificationService private readonly notificationService: INotificationService, + @ILogService private readonly logService: ILogService + ) { + super(InstallExtensionQuickAccessProvider.PREFIX); + } + + protected getPicks(filter: string, token: CancellationToken): Array | Promise> { + + // Nothing typed + if (!filter) { + return [{ + label: localize('type', "Type an extension name to install or search.") + }]; + } + + const genericSearchPickItem: IPickerQuickAccessItem = { + label: localize('searchFor', "Press Enter to search for extension '{0}'.", filter), + accept: () => this.searchExtension(filter) + }; + + // Extension ID typed: try to find it + if (/\./.test(filter)) { + return this.getPicksForExtensionId(filter, genericSearchPickItem, token); + } + + // Extension name typed: offer to search it + else { + return [genericSearchPickItem]; + } + } + + protected async getPicksForExtensionId(filter: string, fallback: IPickerQuickAccessItem, token: CancellationToken): Promise> { + try { + const galleryResult = await this.galleryService.query({ names: [filter], pageSize: 1 }, token); + if (token.isCancellationRequested) { + return []; // return early if canceled + } + + const galleryExtension = galleryResult.firstPage[0]; + if (!galleryExtension) { + return [fallback]; + } else { + return [{ + label: localize('install', "Press Enter to install extension '{0}'.", filter), + accept: () => this.installExtension(galleryExtension, filter) + }]; + } + } catch (error) { + if (token.isCancellationRequested) { + return []; // expected error + } + + this.logService.error(error); + + return [fallback]; + } + } + + private async installExtension(extension: IGalleryExtension, name: string): Promise { + try { + await openExtensionsViewlet(this.viewletService, `@id:${name}`); + await this.extensionsService.installFromGallery(extension); + } catch (error) { + this.notificationService.error(error); + } + } + + private async searchExtension(name: string): Promise { + openExtensionsViewlet(this.viewletService, name); + } +} + +export class ManageExtensionsQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = 'ext '; + + constructor(@IViewletService private readonly viewletService: IViewletService) { + super(ManageExtensionsQuickAccessProvider.PREFIX); + } + + protected getPicks(): Array { + return [{ + label: localize('manage', "Press Enter to manage your extensions."), + accept: () => openExtensionsViewlet(this.viewletService) + }]; + } +} + +async function openExtensionsViewlet(viewletService: IViewletService, search = ''): Promise { + const viewlet = await viewletService.openViewlet(VIEWLET_ID, true); + const view = viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer | undefined; + view?.search(search); + view?.focus(); +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index 1ed548c5cc..96e97a1844 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -169,7 +169,7 @@ class OpenExtensionAction extends Action { run(sideByside: boolean): Promise { if (this._extensionData) { - return this.extensionsWorkdbenchService.open(this._extensionData.extension, sideByside); + return this.extensionsWorkdbenchService.open(this._extensionData.extension, { sideByside }); } return Promise.resolve(); } @@ -218,7 +218,7 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree { if (event.browserEvent && event.browserEvent instanceof KeyboardEvent) { - extensionsWorkdbenchService.open(event.elements[0].extension, false); + extensionsWorkdbenchService.open(event.elements[0].extension, { sideByside: false }); } })); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index b9062e0599..424a2d4676 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -29,7 +29,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction, InstallLocalExtensionsInRemoteAction, getContextMenuActions } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; -import { WorkbenchPagedList } from 'vs/platform/list/browser/listService'; +import { WorkbenchPagedList, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -114,7 +114,7 @@ export class ExtensionsListView extends ViewPane { @IMenuService private readonly menuService: IMenuService, @IOpenerService openerService: IOpenerService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super({ ...(options as IViewPaneOptions), showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.server = options.server; } @@ -146,14 +146,14 @@ export class ExtensionsListView extends ViewPane { } }); this._register(this.list.onContextMenu(e => this.onContextMenu(e), this)); - this._register(this.list.onFocusChange(e => extensionsViewState.onFocusChange(coalesce(e.elements)), this)); + this._register(this.list.onDidChangeFocus(e => extensionsViewState.onFocusChange(coalesce(e.elements)), this)); this._register(this.list); this._register(extensionsViewState); - this._register(Event.chain(this.list.onOpen) - .map(e => e.elements[0]) - .filter(e => !!e) - .on(this.openExtension, this)); + const resourceNavigator = this._register(ResourceNavigator.createListResourceNavigator(this.list, { openOnFocus: false, openOnSelection: true, openOnSingleClick: true })); + this._register(Event.debounce(Event.filter(resourceNavigator.onDidOpenResource, e => e.element !== null), (last, event) => event, 75, true)(options => { + this.openExtension(this.list!.model.get(options.element!), { sideByside: options.sideBySide, ...options.editorOptions }); + })); this._register(Event.chain(this.list.onPin) .map(e => e.elements[0]) @@ -828,16 +828,16 @@ export class ExtensionsListView extends ViewPane { } } - private openExtension(extension: IExtension): void { + private openExtension(extension: IExtension, options: { sideByside?: boolean, preserveFocus?: boolean, pinned?: boolean }): void { extension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, extension.identifier))[0] || extension; - this.extensionsWorkbenchService.open(extension).then(undefined, err => this.onError(err)); + this.extensionsWorkbenchService.open(extension, options).then(undefined, err => this.onError(err)); } private pin(): void { - const activeControl = this.editorService.activeControl; - if (activeControl) { - activeControl.group.pinEditor(activeControl.input); - activeControl.focus(); + const activeEditorPane = this.editorService.activeEditorPane; + if (activeEditorPane) { + activeEditorPane.group.pinEditor(activeEditorPane.input); + activeEditorPane.focus(); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 6da8615486..fb324ae70d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -12,7 +12,7 @@ import { localize } from 'vs/nls'; import { IExtensionTipsService, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ILabelService } from 'vs/platform/label/common/label'; import { extensionButtonProminentBackground, extensionButtonProminentForeground, ExtensionToolTipAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { EXTENSION_BADGE_REMOTE_BACKGROUND, EXTENSION_BADGE_REMOTE_FOREGROUND } from 'vs/workbench/common/theme'; import { Emitter, Event } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -225,14 +225,14 @@ export class RecommendationWidget extends ExtensionWidget { this.element = append(this.parent, $('div.bookmark')); const recommendation = append(this.element, $('.recommendation')); append(recommendation, $('span.codicon.codicon-star')); - const applyBookmarkStyle = (theme: ITheme) => { + const applyBookmarkStyle = (theme: IColorTheme) => { const bgColor = theme.getColor(extensionButtonProminentBackground); const fgColor = theme.getColor(extensionButtonProminentForeground); recommendation.style.borderTopColor = bgColor ? bgColor.toString() : 'transparent'; recommendation.style.color = fgColor ? fgColor.toString() : 'white'; }; - applyBookmarkStyle(this.themeService.getTheme()); - this.themeService.onThemeChange(applyBookmarkStyle, this, this.disposables); + applyBookmarkStyle(this.themeService.getColorTheme()); + this.themeService.onDidColorThemeChange(applyBookmarkStyle, this, this.disposables); this.tooltip = extRecommendations[this.extension.identifier.id.toLowerCase()].reasonText; } } @@ -296,13 +296,13 @@ class RemoteBadge extends Disposable { if (!this.element) { return; } - const bgColor = this.themeService.getTheme().getColor(EXTENSION_BADGE_REMOTE_BACKGROUND); - const fgColor = this.themeService.getTheme().getColor(EXTENSION_BADGE_REMOTE_FOREGROUND); + const bgColor = this.themeService.getColorTheme().getColor(EXTENSION_BADGE_REMOTE_BACKGROUND); + const fgColor = this.themeService.getColorTheme().getColor(EXTENSION_BADGE_REMOTE_FOREGROUND); this.element.style.backgroundColor = bgColor ? bgColor.toString() : ''; this.element.style.color = fgColor ? fgColor.toString() : ''; }; applyBadgeStyle(); - this._register(this.themeService.onThemeChange(() => applyBadgeStyle())); + this._register(this.themeService.onDidColorThemeChange(() => applyBadgeStyle())); if (this.tooltip) { const updateTitle = () => { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index cbca427b70..cf11ca083b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -651,8 +651,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return text.substr(0, 350); } - open(extension: IExtension, sideByside: boolean = false): Promise { - return Promise.resolve(this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), undefined, sideByside ? SIDE_GROUP : ACTIVE_GROUP)); + open(extension: IExtension, { sideByside, preserveFocus, pinned }: { sideByside?: boolean, preserveFocus?: boolean, pinned?: boolean } = { sideByside: false, preserveFocus: false, pinned: false }): Promise { + return Promise.resolve(this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), { preserveFocus, pinned }, sideByside ? SIDE_GROUP : ACTIVE_GROUP)); } private getPrimaryExtension(extensions: IExtension[]): IExtension { diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 961fdd2eee..cbd04a3e42 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -87,7 +87,7 @@ export interface IExtensionsWorkbenchService { installVersion(extension: IExtension, version: string): Promise; reinstall(extension: IExtension): Promise; setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise; - open(extension: IExtension, sideByside?: boolean): Promise; + open(extension: IExtension, options?: { sideByside?: boolean, preserveFocus?: boolean, pinned?: boolean }): Promise; checkForUpdates(): Promise; } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css b/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css index 4c13879379..81fa04cacc 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css @@ -40,3 +40,53 @@ .runtime-extensions-editor .monaco-action-bar .actions-container { justify-content: left; } + +.runtime-extensions-editor .extension > .icon-container { + position: relative; +} + +.runtime-extensions-editor .extension > .icon-container > .icon { + width: 42px; + height: 42px; + padding: 10px 14px 10px 0; + flex-shrink: 0; + object-fit: contain; +} + +.runtime-extensions-editor .extension > .icon-container .extension-remote-badge .codicon { + color: currentColor; +} + +.vs .runtime-extensions-editor .extension > .icon-container > .icon, +.vs-dark .runtime-extensions-editor .extension > .icon-container > .icon { + opacity: 0.5; +} + +.runtime-extensions-editor .extension > .desc > .header-container { + display: flex; + overflow: hidden; +} + +.runtime-extensions-editor .extension > .desc > .header-container > .header { + display: flex; + align-items: baseline; + flex-wrap: nowrap; + overflow: hidden; + flex: 1; + min-width: 0; +} + +.runtime-extensions-editor .extension > .desc > .header-container > .header > .name { + font-weight: bold; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.runtime-extensions-editor .extension > .desc > .header-container > .header > .version { + opacity: 0.85; + font-size: 80%; + padding-left: 6px; + min-width: fit-content; + min-width: -moz-fit-content; +} diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index b82b12a8fa..139c80db17 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -47,6 +47,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; +import { domEvent } from 'vs/base/browser/event'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey('profileSessionState', 'none'); @@ -257,7 +258,9 @@ export class RuntimeExtensionsEditor extends BaseEditor { interface IRuntimeExtensionTemplateData { root: HTMLElement; element: HTMLElement; + icon: HTMLImageElement; name: HTMLElement; + version: HTMLElement; msgContainer: HTMLElement; actionbar: ActionBar; activationTime: HTMLElement; @@ -270,9 +273,14 @@ export class RuntimeExtensionsEditor extends BaseEditor { templateId: TEMPLATE_ID, renderTemplate: (root: HTMLElement): IRuntimeExtensionTemplateData => { const element = append(root, $('.extension')); + const iconContainer = append(element, $('.icon-container')); + const icon = append(iconContainer, $('img.icon')); const desc = append(element, $('div.desc')); - const name = append(desc, $('div.name')); + const headerContainer = append(desc, $('.header-container')); + const header = append(headerContainer, $('.header')); + const name = append(header, $('div.name')); + const version = append(header, $('span.version')); const msgContainer = append(desc, $('div.msg')); @@ -289,13 +297,15 @@ export class RuntimeExtensionsEditor extends BaseEditor { return { root, element, + icon, name, + version, actionbar, activationTime, profileTime, msgContainer, disposables, - elementDisposables: [] + elementDisposables: [], }; }, @@ -305,7 +315,18 @@ export class RuntimeExtensionsEditor extends BaseEditor { toggleClass(data.root, 'odd', index % 2 === 1); + const onError = Event.once(domEvent(data.icon, 'error')); + onError(() => data.icon.src = element.marketplaceInfo.iconUrlFallback, null, data.elementDisposables); + data.icon.src = element.marketplaceInfo.iconUrl; + + if (!data.icon.complete) { + data.icon.style.visibility = 'hidden'; + data.icon.onload = () => data.icon.style.visibility = 'inherit'; + } else { + data.icon.style.visibility = 'inherit'; + } data.name.textContent = element.marketplaceInfo ? element.marketplaceInfo.displayName : element.description.displayName || ''; + data.version.textContent = element.description.version; const activationTimes = element.status.activationTimes!; let syncTime = activationTimes.codeLoadingTime + activationTimes.activateCallTime; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 0db5bd44ce..15c4d453e9 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -27,7 +27,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { TestSharedProcessService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; @@ -39,7 +39,7 @@ import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browse import { ExtensionIdentifier, IExtensionContributions, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ILabelService } from 'vs/platform/label/common/label'; +import { ILabelService, IFormatterChangeEvent } from 'vs/platform/label/common/label'; import { ExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; @@ -92,7 +92,7 @@ suite('ExtensionsActions Test', () => { }()); instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - instantiationService.stub(ILabelService, { onDidChangeFormatters: new Emitter().event }); + instantiationService.stub(ILabelService, { onDidChangeFormatters: new Emitter().event }); instantiationService.set(IExtensionTipsService, instantiationService.createInstance(ExtensionTipsService)); instantiationService.stub(IURLService, URLService); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index 61f3628aed..77225fe1ae 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -22,7 +22,8 @@ import { Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService, TestLifecycleService, productService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestLifecycleService, productService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { TestSharedProcessService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index c1aef71d3e..6c3d678e22 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -27,7 +27,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService, TestMenuService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestMenuService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestSharedProcessService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; @@ -46,6 +46,8 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { IMenuService } from 'vs/platform/actions/common/actions'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; suite('ExtensionsListView Tests', () => { @@ -137,6 +139,12 @@ suite('ExtensionsListView Tests', () => { instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); instantiationService.stubPromise(IExperimentService, 'getExperimentsByType', []); + instantiationService.stub(IViewDescriptorService, { + getViewLocation(): ViewContainerLocation { + return ViewContainerLocation.Sidebar; + } + }); + instantiationService.stub(IExtensionService, { getExtensions: (): Promise => { return Promise.resolve([ diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 013b85165e..9981a0234b 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -27,7 +27,6 @@ import { IPager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestSharedProcessService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; @@ -41,6 +40,7 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; suite('ExtensionsWorkbenchServiceTest', () => { diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts index 006d772c1d..041dc265a9 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts @@ -11,7 +11,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import * as dom from 'vs/base/browser/dom'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { attachButtonStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { editorWidgetBackground, editorWidgetForeground, widgetShadow, inputBorder, inputForeground, inputBackground, inputActiveOptionBorder, editorBackground, buttonBackground, contrastBorder, darken } from 'vs/platform/theme/common/colorRegistry'; import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; @@ -119,7 +119,7 @@ export class FeedbackDropdown extends Dropdown { closeBtn.title = nls.localize('close', "Close"); disposables.add(dom.addDisposableListener(closeBtn, dom.EventType.MOUSE_OVER, () => { - const theme = this.themeService.getTheme(); + const theme = this.themeService.getColorTheme(); let darkenFactor: number | undefined; switch (theme.type) { case 'light': @@ -423,7 +423,7 @@ export class FeedbackDropdown extends Dropdown { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Sentiment Buttons const inputActiveOptionBorderColor = theme.getColor(inputActiveOptionBorder); diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index c3199aeaf3..a8a65fc97d 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -31,6 +31,7 @@ import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { MutableDisposable } from 'vs/base/common/lifecycle'; import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; /** * An implementation of editor for file system resources. @@ -49,14 +50,15 @@ export class TextFileEditor extends BaseTextEditor { @IInstantiationService instantiationService: IInstantiationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IStorageService storageService: IStorageService, - @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @ITextFileService private readonly textFileService: ITextFileService, - @IExplorerService private readonly explorerService: IExplorerService + @IExplorerService private readonly explorerService: IExplorerService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { - super(TextFileEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); + super(TextFileEditor.ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService); this.updateRestoreViewStateConfiguration(); @@ -87,7 +89,7 @@ export class TextFileEditor extends BaseTextEditor { } private updateRestoreViewStateConfiguration(): void { - this.restoreViewState = this.textResourceConfigurationService.getValue(undefined, 'workbench.editor.restoreViewState'); + this.restoreViewState = this.configurationService.getValue('workbench.editor.restoreViewState') ?? true /* default */; } getTitle(): string { diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts index 1199125e7e..0526303b9e 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts @@ -13,7 +13,6 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { RunOnceWorker } from 'vs/base/common/async'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { Schemas } from 'vs/base/common/network'; export class TextFileEditorTracker extends Disposable implements IWorkbenchContribution { @@ -45,7 +44,7 @@ export class TextFileEditorTracker extends Disposable implements IWorkbenchContr //#region Text File: Ensure every dirty text and untitled file is opened in an editor - private readonly ensureDirtyFilesAreOpenedWorker = this._register(new RunOnceWorker(units => this.ensureDirtyTextFilesAreOpened(units), 250)); + private readonly ensureDirtyFilesAreOpenedWorker = this._register(new RunOnceWorker(units => this.ensureDirtyTextFilesAreOpened(units), 50)); private ensureDirtyTextFilesAreOpened(resources: URI[]): void { this.doEnsureDirtyTextFilesAreOpened(distinct(resources.filter(resource => { @@ -58,7 +57,7 @@ export class TextFileEditorTracker extends Disposable implements IWorkbenchContr return false; // resource must not be pending to save } - if (this.editorService.isOpen(this.editorService.createInput({ resource, forceFile: resource.scheme !== Schemas.untitled, forceUntitled: resource.scheme === Schemas.untitled }))) { + if (this.editorService.isOpen({ resource })) { return false; // model must not be opened already as file } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index c56a2627fb..0334d78999 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -100,7 +100,7 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa } } - onSaveError(error: any, model: ITextFileEditorModel): void { + onSaveError(error: unknown, model: ITextFileEditorModel): void { const fileOperationError = error as FileOperationError; const resource = model.resource; @@ -208,8 +208,8 @@ class ResolveConflictLearnMoreAction extends Action { super('workbench.files.action.resolveConflictLearnMore', nls.localize('learnMore', "Learn More")); } - run(): Promise { - return this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?linkid=868264')); + async run(): Promise { + await this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?linkid=868264')); } } @@ -221,7 +221,7 @@ class DoNotShowResolveConflictLearnMoreAction extends Action { super('workbench.files.action.resolveConflictLearnMoreDoNotShowAgain', nls.localize('dontShowAgain', "Don't Show Again")); } - async run(notification: IDisposable): Promise { + async run(notification: IDisposable): Promise { this.storageService.store(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, true, StorageScope.GLOBAL); // Hide notification @@ -241,7 +241,7 @@ class ResolveSaveConflictAction extends Action { super('workbench.files.action.resolveConflict', nls.localize('compareChanges', "Compare")); } - async run(): Promise { + async run(): Promise { if (!this.model.isDisposed()) { const resource = this.model.resource; const name = basename(resource); @@ -272,7 +272,7 @@ class SaveElevatedAction extends Action { super('workbench.files.action.saveElevated', triedToMakeWriteable ? isWindows ? nls.localize('overwriteElevated', "Overwrite as Admin...") : nls.localize('overwriteElevatedSudo', "Overwrite as Sudo...") : isWindows ? nls.localize('saveElevated', "Retry as Admin...") : nls.localize('saveElevatedSudo', "Retry as Sudo...")); } - async run(): Promise { + async run(): Promise { if (!this.model.isDisposed()) { this.model.save({ writeElevated: true, @@ -291,7 +291,7 @@ class OverwriteReadonlyAction extends Action { super('workbench.files.action.overwrite', nls.localize('overwrite', "Overwrite")); } - async run(): Promise { + async run(): Promise { if (!this.model.isDisposed()) { this.model.save({ overwriteReadonly: true, reason: SaveReason.EXPLICIT }); } @@ -306,7 +306,7 @@ class SaveIgnoreModifiedSinceAction extends Action { super('workbench.files.action.saveIgnoreModifiedSince', nls.localize('overwrite', "Overwrite")); } - async run(): Promise { + async run(): Promise { if (!this.model.isDisposed()) { this.model.save({ ignoreModifiedSince: true, reason: SaveReason.EXPLICIT }); } @@ -321,7 +321,7 @@ class ConfigureSaveConflictAction extends Action { super('workbench.files.action.configureSaveConflict', nls.localize('configure', "Configure")); } - async run(): Promise { + async run(): Promise { this.preferencesService.openSettings(undefined, 'files.saveConflictResolution'); } } @@ -330,13 +330,13 @@ export const acceptLocalChangesCommand = async (accessor: ServicesAccessor, reso const editorService = accessor.get(IEditorService); const resolverService = accessor.get(ITextModelService); - const control = editorService.activeControl; - if (!control) { + const editorPane = editorService.activeEditorPane; + if (!editorPane) { return; } - const editor = control.input; - const group = control.group; + const editor = editorPane.input; + const group = editorPane.group; const reference = await resolverService.createModelReference(resource); const model = reference.object as IResolvedTextFileEditorModel; @@ -359,13 +359,13 @@ export const revertLocalChangesCommand = async (accessor: ServicesAccessor, reso const editorService = accessor.get(IEditorService); const resolverService = accessor.get(ITextModelService); - const control = editorService.activeControl; - if (!control) { + const editorPane = editorService.activeEditorPane; + if (!editorPane) { return; } - const editor = control.input; - const group = control.group; + const editor = editorPane.input; + const group = editorPane.group; const reference = await resolverService.createModelReference(resource); const model = reference.object as ITextFileEditorModel; diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 24c61a0704..1ec24de5b1 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -28,13 +28,14 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { DelegatingEditorService } from 'vs/workbench/services/editor/browser/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; import { ViewPane, ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { WorkbenchStateContext, RemoteNameContext, IsWebContext } from 'vs/workbench/browser/contextkeys'; +import { WorkbenchStateContext, RemoteNameContext } from 'vs/workbench/browser/contextkeys'; +import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { AddRootFolderAction, OpenFolderAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { isMacintosh } from 'vs/base/common/platform'; @@ -207,7 +208,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { // without causing the animation in the opened editors view to kick in and change scroll position. // We try to be smart and only use the delay if we recognize that the user action is likely to cause // a new entry in the opened editors view. - const delegatingEditorService = this.instantiationService.createInstance(DelegatingEditorService, async (delegate, group, editor, options): Promise => { + const delegatingEditorService = this.instantiationService.createInstance(DelegatingEditorService, async (delegate, group, editor, options): Promise => { let openEditorsView = this.getOpenEditorsView(); if (openEditorsView) { let delay = 0; diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 23505caf3a..9eb40fd635 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -12,7 +12,7 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/wor import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { openWindowCommand, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand, ReadonlyEditorContext } from 'vs/workbench/contrib/files/browser/fileCommands'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { isMacintosh } from 'vs/base/common/platform'; import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService, ExplorerResourceMoveableToTrash, ExplorerViewletVisibleContext } from 'vs/workbench/contrib/files/common/files'; @@ -22,7 +22,8 @@ import { AutoSaveAfterShortDelayContext } from 'vs/workbench/services/filesConfi import { ResourceContextKey } from 'vs/workbench/common/resources'; import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService'; import { Schemas } from 'vs/base/common/network'; -import { WorkspaceFolderCountContext, IsWebContext } from 'vs/workbench/browser/contextkeys'; +import { WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; +import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { OpenFileFolderAction, OpenFileAction, OpenFolderAction, OpenWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ActiveEditorIsReadonlyContext, DirtyWorkingCopiesContext, ActiveEditorContext } from 'vs/workbench/common/editor'; @@ -171,7 +172,7 @@ appendEditorTitleContextMenuItem(COPY_PATH_COMMAND_ID, copyPathCommand.title, Re appendEditorTitleContextMenuItem(COPY_RELATIVE_PATH_COMMAND_ID, copyRelativePathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste'); appendEditorTitleContextMenuItem(REVEAL_IN_EXPLORER_COMMAND_ID, nls.localize('revealInSideBar', "Reveal in Side Bar"), ResourceContextKey.IsFileSystemResource); -export function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpr | undefined, group?: string): void { +export function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpression | undefined, group?: string): void { // Menu MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { @@ -201,7 +202,7 @@ function appendSaveConflictEditorTitleAction(id: string, title: string, icon: Th // Menu registration - command palette -export function appendToCommandPalette(id: string, title: ILocalizedString, category: ILocalizedString, when?: ContextKeyExpr): void { +export function appendToCommandPalette(id: string, title: ILocalizedString, category: ILocalizedString, when?: ContextKeyExpression): void { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id, diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 8ed8ab1f39..e3dd44b692 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -100,7 +100,7 @@ export class NewFileAction extends Action { })); } - run(): Promise { + run(): Promise { return this.commandService.executeCommand(NEW_FILE_COMMAND_ID); } } @@ -122,7 +122,7 @@ export class NewFolderAction extends Action { })); } - run(): Promise { + run(): Promise { return this.commandService.executeCommand(NEW_FOLDER_COMMAND_ID); } } @@ -161,8 +161,8 @@ export class GlobalNewUntitledFileAction extends Action { super(id, label); } - run(): Promise { - return this.editorService.openEditor({ options: { pinned: true } }); // untitled are always pinned + async run(): Promise { + await this.editorService.openEditor({ options: { pinned: true } }); // untitled are always pinned } } @@ -457,7 +457,7 @@ export function incrementFileName(name: string, isFolder: boolean, incrementalNa // folder.1=>folder.2 if (isFolder && name.match(/(\d+)$/)) { - return name.replace(/(\d+)$/, (match: string, ...groups: any[]) => { + return name.replace(/(\d+)$/, (match, ...groups) => { let number = parseInt(groups[0]); return number < maxNumber ? strings.pad(number + 1, groups[0].length) @@ -467,7 +467,7 @@ export function incrementFileName(name: string, isFolder: boolean, incrementalNa // 1.folder=>2.folder if (isFolder && name.match(/^(\d+)/)) { - return name.replace(/^(\d+)(.*)$/, (match: string, ...groups: any[]) => { + return name.replace(/^(\d+)(.*)$/, (match, ...groups) => { let number = parseInt(groups[0]); return number < maxNumber ? strings.pad(number + 1, groups[0].length) + groups[1] @@ -495,7 +495,7 @@ export class GlobalCompareResourcesAction extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const activeInput = this.editorService.activeEditor; const activeResource = activeInput ? activeInput.resource : undefined; if (activeResource) { @@ -541,7 +541,7 @@ export class ToggleAutoSaveAction extends Action { super(id, label); } - run(): Promise { + run(): Promise { return this.filesConfigurationService.toggleAutoSave(); } } @@ -564,7 +564,7 @@ export abstract class BaseSaveAllAction extends Action { this.registerListeners(); } - protected abstract doRun(context: any): Promise; + protected abstract doRun(context: unknown): Promise; private registerListeners(): void { @@ -580,7 +580,7 @@ export abstract class BaseSaveAllAction extends Action { } } - async run(context?: any): Promise { + async run(context?: unknown): Promise { try { await this.doRun(context); } catch (error) { @@ -598,7 +598,7 @@ export class SaveAllAction extends BaseSaveAllAction { return 'explorer-action codicon-save-all'; } - protected doRun(context: any): Promise { + protected doRun(): Promise { return this.commandService.executeCommand(SAVE_ALL_COMMAND_ID); } } @@ -612,7 +612,7 @@ export class SaveAllInGroupAction extends BaseSaveAllAction { return 'explorer-action codicon-save-all'; } - protected doRun(context: any): Promise { + protected doRun(context: unknown): Promise { return this.commandService.executeCommand(SAVE_ALL_IN_GROUP_COMMAND_ID, {}, context); } } @@ -626,7 +626,7 @@ export class CloseGroupAction extends Action { super(id, label, 'codicon-close-all'); } - run(context?: any): Promise { + run(context?: unknown): Promise { return this.commandService.executeCommand(CLOSE_EDITORS_AND_GROUP_COMMAND_ID, {}, context); } } @@ -644,8 +644,8 @@ export class FocusFilesExplorer extends Action { super(id, label); } - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true); + async run(): Promise { + await this.viewletService.openViewlet(VIEWLET_ID, true); } } @@ -664,15 +664,13 @@ export class ShowActiveFileInExplorer extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const resource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { this.commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, resource); } else { this.notificationService.info(nls.localize('openFileToShow', "Open a file first to show it in the explorer")); } - - return true; } } @@ -693,7 +691,7 @@ export class CollapseExplorerView extends Action { })); } - async run(): Promise { + async run(): Promise { const explorerViewlet = (await this.viewletService.openViewlet(VIEWLET_ID))?.getViewPaneContainer() as ExplorerViewPaneContainer; const explorerView = explorerViewlet.getExplorerView(); if (explorerView) { @@ -720,7 +718,7 @@ export class RefreshExplorerView extends Action { })); } - async run(): Promise { + async run(): Promise { await this.viewletService.openViewlet(VIEWLET_ID); this.explorerService.refresh(); } @@ -742,7 +740,7 @@ export class ShowOpenedFileInNewWindow extends Action { super(id, label); } - async run(): Promise { + async run(): Promise { const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (fileResource) { if (this.fileService.canHandleResource(fileResource)) { @@ -753,8 +751,6 @@ export class ShowOpenedFileInNewWindow extends Action { } else { this.notificationService.info(nls.localize('openFileToShowInNewWindow.nofile', "Open a file first to open in new window")); } - - return true; } } @@ -838,7 +834,7 @@ export class CompareWithClipboardAction extends Action { this.enabled = true; } - async run(): Promise { + async run(): Promise { const resource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource && (this.fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { if (!this.registrationDisposal) { @@ -849,13 +845,11 @@ export class CompareWithClipboardAction extends Action { const name = resources.basename(resource); const editorLabel = nls.localize('clipboardComparisonLabel', "Clipboard ↔ {0}", name); - return this.editorService.openEditor({ leftResource: resource.with({ scheme: CompareWithClipboardAction.SCHEME }), rightResource: resource, label: editorLabel }).finally(() => { + await this.editorService.openEditor({ leftResource: resource.with({ scheme: CompareWithClipboardAction.SCHEME }), rightResource: resource, label: editorLabel }).finally(() => { dispose(this.registrationDisposal); this.registrationDisposal = undefined; }); } - - return true; } dispose(): void { @@ -880,7 +874,7 @@ class ClipboardContentProvider implements ITextModelContentProvider { } } -function onErrorWithRetry(notificationService: INotificationService, error: any, retry: () => Promise): void { +function onErrorWithRetry(notificationService: INotificationService, error: unknown, retry: () => Promise): void { notificationService.prompt(Severity.Error, toErrorMessage(error, false), [{ label: nls.localize('retry', "Retry"), diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index dbb0e4cafa..7ab89d6401 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -103,17 +103,17 @@ Registry.as(EditorExtensions.Editors).registerEditor( ); // Register default file input factory -Registry.as(EditorInputExtensions.EditorInputFactories).registerFileInputFactory({ - createFileInput: (resource, encoding, mode, instantiationService): IFileEditorInput => { +Registry.as(EditorInputExtensions.EditorInputFactories).registerFileEditorInputFactory({ + createFileEditorInput: (resource, encoding, mode, instantiationService): IFileEditorInput => { return instantiationService.createInstance(FileEditorInput, resource, encoding, mode); }, - isFileInput: (obj): obj is IFileEditorInput => { + isFileEditorInput: (obj): obj is IFileEditorInput => { return obj instanceof FileEditorInput; } }); -interface ISerializedFileInput { +interface ISerializedFileEditorInput { resourceJSON: UriComponents; encoding?: string; modeId?: string; @@ -129,23 +129,23 @@ class FileEditorInputFactory implements IEditorInputFactory { serialize(editorInput: EditorInput): string { const fileEditorInput = editorInput; const resource = fileEditorInput.resource; - const fileInput: ISerializedFileInput = { + const serializedFileEditorInput: ISerializedFileEditorInput = { resourceJSON: resource.toJSON(), encoding: fileEditorInput.getEncoding(), modeId: fileEditorInput.getPreferredMode() // only using the preferred user associated mode here if available to not store redundant data }; - return JSON.stringify(fileInput); + return JSON.stringify(serializedFileEditorInput); } deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): FileEditorInput { return instantiationService.invokeFunction(accessor => { - const fileInput: ISerializedFileInput = JSON.parse(serializedEditorInput); - const resource = URI.revive(fileInput.resourceJSON); - const encoding = fileInput.encoding; - const mode = fileInput.modeId; + const serializedFileEditorInput: ISerializedFileEditorInput = JSON.parse(serializedEditorInput); + const resource = URI.revive(serializedFileEditorInput.resourceJSON); + const encoding = serializedFileEditorInput.encoding; + const mode = serializedFileEditorInput.modeId; - return accessor.get(IEditorService).createInput({ resource, encoding, mode, forceFile: true }) as FileEditorInput; + return accessor.get(IEditorService).createEditorInput({ resource, encoding, mode, forceFile: true }) as FileEditorInput; }); } } @@ -306,7 +306,7 @@ configurationRegistry.registerConfiguration({ }, 'files.watcherExclude': { 'type': 'object', - 'default': platform.isWindows /* https://github.com/Microsoft/vscode/issues/23954 */ ? { '**/.git/objects/**': true, '**/.git/subtree-cache/**': true, '**/node_modules/*/**': true } : { '**/.git/objects/**': true, '**/.git/subtree-cache/**': true, '**/node_modules/**': true }, + 'default': platform.isWindows /* https://github.com/Microsoft/vscode/issues/23954 */ ? { '**/.git/objects/**': true, '**/.git/subtree-cache/**': true, '**/node_modules/*/**': true, '**/.hg/store/**': true } : { '**/.git/objects/**': true, '**/.git/subtree-cache/**': true, '**/node_modules/**': true, '**/.hg/store/**': true }, 'description': nls.localize('watcherExclude', "Configure glob patterns of file paths to exclude from file watching. Patterns must match on absolute paths (i.e. prefix with ** or the full path to match properly). Changing this setting requires a restart. When you experience Code consuming lots of cpu time on startup, you can exclude large folders to reduce the initial load."), 'scope': ConfigurationScope.RESOURCE }, diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index 12d25ecaa3..41a9714e57 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -11,7 +11,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { ResourcesDropHandler, DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { listDropBackground } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; @@ -40,7 +40,7 @@ export class EmptyView extends ViewPane { @IOpenerService openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Explorer Section: No Folder Opened") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this._register(this.contextService.onDidChangeWorkbenchState(() => this.refreshTitle())); this._register(this.labelService.onDidChangeFormatters(() => this.refreshTitle())); @@ -55,21 +55,21 @@ export class EmptyView extends ViewPane { this._register(new DragAndDropObserver(container, { onDrop: e => { - const color = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND); + const color = this.themeService.getColorTheme().getColor(SIDE_BAR_BACKGROUND); container.style.backgroundColor = color ? color.toString() : ''; const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true }); dropHandler.handleDrop(e, () => undefined, () => undefined); }, onDragEnter: () => { - const color = this.themeService.getTheme().getColor(listDropBackground); + const color = this.themeService.getColorTheme().getColor(listDropBackground); container.style.backgroundColor = color ? color.toString() : ''; }, onDragEnd: () => { - const color = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND); + const color = this.themeService.getColorTheme().getColor(SIDE_BAR_BACKGROUND); container.style.backgroundColor = color ? color.toString() : ''; }, onDragLeave: () => { - const color = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND); + const color = this.themeService.getColorTheme().getColor(SIDE_BAR_BACKGROUND); container.style.backgroundColor = color ? color.toString() : ''; }, onDragOver: e => { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts index b08a8abcbb..3ac08e8586 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts @@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IDecorationsProvider, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations'; -import { listInvalidItemForeground } from 'vs/platform/theme/common/colorRegistry'; +import { listInvalidItemForeground, listDeemphasizedForeground } from 'vs/platform/theme/common/colorRegistry'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { explorerRootErrorEmitter } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; @@ -34,6 +34,11 @@ export function provideDecorations(fileStat: ExplorerItem): IDecorationData | un letter: '?' }; } + if (fileStat.isExcluded) { + return { + color: listDeemphasizedForeground, + }; + } return undefined; } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index db3c30eb33..186d881cab 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -24,7 +24,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations'; -import { TreeResourceNavigator, WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { ResourceNavigator, WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService'; import { DelayedDragHandler } from 'vs/base/browser/dnd'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -172,7 +172,7 @@ export class ExplorerView extends ViewPane { @IFileService private readonly fileService: IFileService, @IOpenerService openerService: IOpenerService, ) { - super({ ...(options as IViewPaneOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Explorer Section: {0}", labelService.getWorkspaceLabel(contextService.getWorkspace())) }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.resourceContext = instantiationService.createInstance(ResourceContextKey); this._register(this.resourceContext); @@ -354,6 +354,7 @@ export class ExplorerView extends ViewPane { private createTree(container: HTMLElement): void { this.filter = this.instantiationService.createInstance(FilesFilter); this._register(this.filter); + this._register(this.filter.onDidChange(() => this.refresh(true))); const explorerLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this._register(explorerLabels); @@ -418,7 +419,7 @@ export class ExplorerView extends ViewPane { // Update resource context based on focused element this._register(this.tree.onDidChangeFocus(e => this.onFocusChanged(e.elements))); this.onFocusChanged([]); - const explorerNavigator = new TreeResourceNavigator(this.tree); + const explorerNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree); this._register(explorerNavigator); // Open when selecting via keyboard this._register(explorerNavigator.onDidOpenResource(async e => { @@ -458,18 +459,7 @@ export class ExplorerView extends ViewPane { this.autoReveal = configuration?.explorer?.autoReveal; // Push down config updates to components of viewer - let needsRefresh = false; - if (this.filter) { - needsRefresh = this.filter.updateConfiguration(); - } - - if (event && !needsRefresh) { - needsRefresh = event.affectsConfiguration('explorer.decorations.colors') - || event.affectsConfiguration('explorer.decorations.badges'); - } - - // Refresh viewer as needed if this originates from a config event - if (event && needsRefresh) { + if (event && (event.affectsConfiguration('explorer.decorations.colors') || event.affectsConfiguration('explorer.decorations.badges'))) { this.refresh(true); } } @@ -672,19 +662,23 @@ export class ExplorerView extends ViewPane { } if (item && item.parent) { - if (reveal) { - if (item.isDisposed) { - return this.onSelectResource(resource, reveal, retry + 1); + try { + if (reveal) { + if (item.isDisposed) { + return this.onSelectResource(resource, reveal, retry + 1); + } + + // Don't scroll to the item if it's already visible + if (this.tree.getRelativeTop(item) === null) { + this.tree.reveal(item, 0.5); + } } - // Don't scroll to the item if it's already visible - if (this.tree.getRelativeTop(item) === null) { - this.tree.reveal(item, 0.5); - } + this.tree.setFocus([item]); + this.tree.setSelection([item]); + } catch (e) { + // Element might not be in the tree, silently fail } - - this.tree.setFocus([item]); - this.tree.setSelection([item]); } } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 865255b170..d4054b8b46 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -55,6 +55,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { isNumber } from 'vs/base/common/types'; import { domEvent } from 'vs/base/browser/event'; import { IEditableData } from 'vs/workbench/common/views'; +import { IEditorInput } from 'vs/workbench/common/editor'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -472,28 +473,70 @@ interface CachedParsedExpression { parsed: glob.ParsedExpression; } +/** + * Respectes files.exclude setting in filtering out content from the explorer. + * Makes sure that visible editors are always shown in the explorer even if they are filtered out by settings. + */ export class FilesFilter implements ITreeFilter { private hiddenExpressionPerRoot: Map; - private workspaceFolderChangeListener: IDisposable; + private hiddenUris = new Set(); + private editorsAffectingFilter = new Set(); + private _onDidChange = new Emitter(); + private toDispose: IDisposable[] = []; constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IExplorerService private readonly explorerService: IExplorerService + @IExplorerService private readonly explorerService: IExplorerService, + @IEditorService private readonly editorService: IEditorService, ) { this.hiddenExpressionPerRoot = new Map(); - this.workspaceFolderChangeListener = this.contextService.onDidChangeWorkspaceFolders(() => this.updateConfiguration()); + this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => this.updateConfiguration())); + this.toDispose.push(this.configurationService.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration('files.exclude')) { + this.updateConfiguration(); + } + })); + this.toDispose.push(this.editorService.onDidVisibleEditorsChange(() => { + const editors = this.editorService.visibleEditors; + let shouldFire = false; + this.hiddenUris.forEach(u => { + editors.forEach(e => { + if (e.resource && isEqualOrParent(e.resource, u)) { + // A filtered resource suddenly became visible since user opened an editor + shouldFire = true; + } + }); + }); + + this.editorsAffectingFilter.forEach(e => { + if (editors.indexOf(e) === -1) { + // Editor that was affecting filtering is no longer visible + shouldFire = true; + } + }); + if (shouldFire) { + this.editorsAffectingFilter.clear(); + this.hiddenUris.clear(); + this._onDidChange.fire(); + } + })); + this.updateConfiguration(); } - updateConfiguration(): boolean { - let needsRefresh = false; + get onDidChange(): Event { + return this._onDidChange.event; + } + + private updateConfiguration(): void { + let shouldFire = false; this.contextService.getWorkspace().folders.forEach(folder => { const configuration = this.configurationService.getValue({ resource: folder.uri }); const excludesConfig: glob.IExpression = configuration?.files?.exclude || Object.create(null); - if (!needsRefresh) { + if (!shouldFire) { const cached = this.hiddenExpressionPerRoot.get(folder.uri.toString()); - needsRefresh = !cached || !equals(cached.original, excludesConfig); + shouldFire = !cached || !equals(cached.original, excludesConfig); } const excludesConfigCopy = deepClone(excludesConfig); // do not keep the config, as it gets mutated under our hoods @@ -501,11 +544,28 @@ export class FilesFilter implements ITreeFilter { this.hiddenExpressionPerRoot.set(folder.uri.toString(), { original: excludesConfigCopy, parsed: glob.parse(excludesConfigCopy) }); }); - return needsRefresh; + if (shouldFire) { + this.editorsAffectingFilter.clear(); + this.hiddenUris.clear(); + this._onDidChange.fire(); + } } filter(stat: ExplorerItem, parentVisibility: TreeVisibility): TreeFilterResult { + const isVisible = this.isVisible(stat, parentVisibility); + if (isVisible) { + this.hiddenUris.delete(stat.resource); + } else { + this.hiddenUris.add(stat.resource); + } + + return isVisible; + } + + private isVisible(stat: ExplorerItem, parentVisibility: TreeVisibility): boolean { + stat.isExcluded = false; if (parentVisibility === TreeVisibility.Hidden) { + stat.isExcluded = true; return false; } if (this.explorerService.getEditableData(stat) || stat.isRoot) { @@ -515,14 +575,22 @@ export class FilesFilter implements ITreeFilter { // Hide those that match Hidden Patterns const cached = this.hiddenExpressionPerRoot.get(stat.root.resource.toString()); if (cached && cached.parsed(path.relative(stat.root.resource.path, stat.resource.path), stat.name, name => !!(stat.parent && stat.parent.getChild(name)))) { + stat.isExcluded = true; + const editors = this.editorService.visibleEditors; + const editor = editors.filter(e => e.resource && isEqualOrParent(e.resource, stat.resource)).pop(); + if (editor) { + this.editorsAffectingFilter.add(editor); + return true; // Show all opened files and their parents + } + return false; // hidden through pattern } return true; } - public dispose(): void { - dispose(this.workspaceFolderChangeListener); + dispose(): void { + dispose(this.toDispose); } } diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 50139ef3af..ec1c62b450 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -31,10 +31,10 @@ import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; -import { DirtyEditorContext, OpenEditorsGroupContext, ReadonlyEditorContext as ReadonlyEditorContext } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { DirtyEditorContext, OpenEditorsGroupContext, ReadonlyEditorContext } from 'vs/workbench/contrib/files/browser/fileCommands'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { ResourcesDropHandler, fillResourceDataTransfers, CodeDataTransfers, containsDragType } from 'vs/workbench/browser/dnd'; -import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; import { memoize } from 'vs/base/common/decorators'; @@ -84,10 +84,7 @@ export class OpenEditorsView extends ViewPane { @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IOpenerService openerService: IOpenerService, ) { - super({ - ...(options as IViewPaneOptions), - ariaHeaderLabel: nls.localize({ key: 'openEditosrSection', comment: ['Open is an adjective'] }, "Open Editors Section"), - }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.structuralRefreshDelay = 0; this.listRefreshScheduler = new RunOnceScheduler(() => { @@ -247,7 +244,7 @@ export class OpenEditorsView extends ViewPane { this.readonlyEditorFocusedContext = ReadonlyEditorContext.bindTo(this.contextKeyService); this._register(this.list.onContextMenu(e => this.onListContextMenu(e))); - this.list.onFocusChange(e => { + this.list.onDidChangeFocus(e => { this.resourceContext.reset(); this.groupFocusedContext.reset(); this.dirtyEditorFocusedContext.reset(); @@ -491,9 +488,9 @@ interface IEditorGroupTemplateData { class OpenEditorActionRunner extends ActionRunner { public editor: OpenEditor | undefined; - run(action: IAction, context?: any): Promise { + async run(action: IAction): Promise { if (!this.editor) { - return Promise.resolve(); + return; } return super.run(action, { groupId: this.editor.groupId, editorIndex: this.editor.editorIndex }); @@ -677,7 +674,7 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop { disposables = []; }); - test('editor auto saves after short delay if configured', async function () { + async function createEditorAutoSave(autoSaveConfig: object): Promise<[TestServiceAccessor, EditorPart, EditorAutoSave]> { const instantiationService = workbenchInstantiationService(); const configurationService = new TestConfigurationService(); - configurationService.setUserConfiguration('files', { autoSave: 'afterDelay', autoSaveDelay: 1 }); + configurationService.setUserConfiguration('files', autoSaveConfig); instantiationService.stub(IConfigurationService, configurationService); instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService( @@ -73,10 +73,15 @@ suite('EditorAutoSave', () => { const editorAutoSave = instantiationService.createInstance(EditorAutoSave); + return [accessor, part, editorAutoSave]; + } + + test('editor auto saves after short delay if configured', async function () { + const [accessor, part, editorAutoSave] = await createEditorAutoSave({ autoSave: 'afterDelay', autoSaveDelay: 1 }); + const resource = toResource.call(this, '/path/index.txt'); const model = await accessor.textFileService.files.resolve(resource) as IResolvedTextFileEditorModel; - model.textEditorModel.setValue('Super Good'); assert.ok(model.isDirty()); @@ -90,6 +95,28 @@ suite('EditorAutoSave', () => { (accessor.textFileService.files).dispose(); }); + test('editor auto saves on focus change if configured', async function () { + const [accessor, part, editorAutoSave] = await createEditorAutoSave({ autoSave: 'onFocusChange' }); + + const resource = toResource.call(this, '/path/index.txt'); + await accessor.editorService.openEditor({ resource, forceFile: true }); + + const model = await accessor.textFileService.files.resolve(resource) as IResolvedTextFileEditorModel; + model.textEditorModel.setValue('Super Good'); + + assert.ok(model.isDirty()); + + await accessor.editorService.openEditor({ resource: toResource.call(this, '/path/index_other.txt') }); + + await awaitModelSaved(model); + + assert.ok(!model.isDirty()); + + part.dispose(); + editorAutoSave.dispose(); + (accessor.textFileService.files).dispose(); + }); + function awaitModelSaved(model: ITextFileEditorModel): Promise { return new Promise(c => { Event.once(model.onDidChangeDirty)(c); diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index d5b28bdb75..f2ac8323b7 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -16,6 +16,7 @@ import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textF import { timeout } from 'vs/base/common/async'; import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; suite('Files - FileEditorInput', () => { let instantiationService: IInstantiationService; @@ -148,7 +149,7 @@ suite('Files - FileEditorInput', () => { resolved.textEditorModel!.setValue('changed'); assert.ok(input.isDirty()); - assert.ok(await input.revert(0)); + await input.revert(0); assert.ok(!input.isDirty()); input.dispose(); @@ -196,4 +197,19 @@ suite('Files - FileEditorInput', () => { input.dispose(); listener.dispose(); }); + + test('force open text/binary', async function () { + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined); + input.setForceOpenAsBinary(); + + let resolved = await input.resolve(); + assert.ok(resolved instanceof BinaryEditorModel); + + input.setForceOpenAsText(); + + resolved = await input.resolve(); + assert.ok(resolved instanceof TextFileEditorModel); + + resolved.dispose(); + }); }); diff --git a/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts b/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts new file mode 100644 index 0000000000..b52dd26694 --- /dev/null +++ b/src/vs/workbench/contrib/files/test/browser/textFileEditor.test.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { toResource } from 'vs/base/test/common/utils'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { workbenchInstantiationService, TestServiceAccessor, TestFilesConfigurationService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { EditorInput } from 'vs/workbench/common/editor'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { Selection } from 'vs/editor/common/core/selection'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; + +suite('Files - TextFileEditor', () => { + + let disposables: IDisposable[] = []; + + setup(() => { + disposables.push(Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create( + TextFileEditor, + TextFileEditor.ID, + 'Text File Editor' + ), + [new SyncDescriptor(FileEditorInput)] + )); + }); + + teardown(() => { + dispose(disposables); + disposables = []; + }); + + async function createPart(restoreViewState: boolean): Promise<[EditorPart, TestServiceAccessor, IInstantiationService, IEditorService]> { + const instantiationService = workbenchInstantiationService(); + + const configurationService = new TestConfigurationService(); + configurationService.setUserConfiguration('workbench', { editor: { restoreViewState } }); + instantiationService.stub(IConfigurationService, configurationService); + + instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService( + instantiationService.createInstance(MockContextKeyService), + configurationService, + TestEnvironmentService + )); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + instantiationService.stub(IEditorGroupsService, part); + + const editorService: EditorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + const accessor = instantiationService.createInstance(TestServiceAccessor); + + await part.whenRestored; + + return [part, accessor, instantiationService, editorService]; + } + + test('text file editor preserves viewstate', async function () { + return viewStateTest(this, true); + }); + + test('text file editor resets viewstate if configured as such', async function () { + return viewStateTest(this, false); + }); + + async function viewStateTest(context: Mocha.ITestCallbackContext, restoreViewState: boolean): Promise { + const [part, accessor] = await createPart(restoreViewState); + + let editor = await accessor.editorService.openEditor(accessor.editorService.createEditorInput({ resource: toResource.call(context, '/path/index.txt'), forceFile: true })); + + let codeEditor = editor?.getControl() as CodeEditorWidget; + const selection = new Selection(1, 3, 1, 4); + codeEditor.setSelection(selection); + + editor = await accessor.editorService.openEditor(accessor.editorService.createEditorInput({ resource: toResource.call(context, '/path/index-other.txt'), forceFile: true })); + editor = await accessor.editorService.openEditor(accessor.editorService.createEditorInput({ resource: toResource.call(context, '/path/index.txt'), forceFile: true })); + + codeEditor = editor?.getControl() as CodeEditorWidget; + + if (restoreViewState) { + assert.ok(codeEditor.getSelection()?.equalsSelection(selection)); + } else { + assert.ok(!codeEditor.getSelection()?.equalsSelection(selection)); + } + + part.dispose(); + (accessor.textFileService.files).dispose(); + } +}); diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts similarity index 80% rename from src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts rename to src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts index a0ea4fabde..6c19851189 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts @@ -9,7 +9,7 @@ import { TextFileEditorTracker } from 'vs/workbench/contrib/files/browser/editor import { toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; -import { IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { IResolvedTextFileEditorModel, snapshotToString, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { timeout } from 'vs/base/common/async'; @@ -25,6 +25,8 @@ import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { isEqual } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; suite('Files - TextFileEditorTracker', () => { @@ -46,32 +48,6 @@ suite('Files - TextFileEditorTracker', () => { disposables = []; }); - test.skip('file change event updates model', async function () { // {{SQL CARBON EDIT}} tabcolormode failure - const instantiationService = workbenchInstantiationService(); - const accessor = instantiationService.createInstance(TestServiceAccessor); - - const tracker = instantiationService.createInstance(TextFileEditorTracker); - - const resource = toResource.call(this, '/path/index.txt'); - - const model = await accessor.textFileService.files.resolve(resource) as IResolvedTextFileEditorModel; - - model.textEditorModel.setValue('Super Good'); - assert.equal(snapshotToString(model.createSnapshot()!), 'Super Good'); - - await model.save(); - - // change event (watcher) - accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.UPDATED }])); - - await timeout(0); // due to event updating model async - - assert.equal(snapshotToString(model.createSnapshot()!), 'Hello Html'); - - tracker.dispose(); - (accessor.textFileService.files).dispose(); - }); - async function createTracker(): Promise<[EditorPart, TestServiceAccessor, TextFileEditorTracker, IInstantiationService, IEditorService]> { const instantiationService = workbenchInstantiationService(); @@ -93,19 +69,42 @@ suite('Files - TextFileEditorTracker', () => { return [part, accessor, tracker, instantiationService, editorService]; } + test.skip('file change event updates model', async function () { // {{SQL CARBON EDIT}} tabcolormode failure + const [, accessor, tracker] = await createTracker(); + + const resource = toResource.call(this, '/path/index.txt'); + + const model = await accessor.textFileService.files.resolve(resource) as IResolvedTextFileEditorModel; + + model.textEditorModel.setValue('Super Good'); + assert.equal(snapshotToString(model.createSnapshot()!), 'Super Good'); + + await model.save(); + + // change event (watcher) + accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.UPDATED }])); + + await timeout(0); // due to event updating model async + + assert.equal(snapshotToString(model.createSnapshot()!), 'Hello Html'); + + tracker.dispose(); + (accessor.textFileService.files).dispose(); + }); + test.skip('dirty text file model opens as editor', async function () { // {{SQL CARBON EDIT}} tabcolormode failure const [part, accessor, tracker] = await createTracker(); const resource = toResource.call(this, '/path/index.txt'); - assert.ok(!accessor.editorService.isOpen(accessor.editorService.createInput({ resource, forceFile: true }))); + assert.ok(!accessor.editorService.isOpen(accessor.editorService.createEditorInput({ resource, forceFile: true }))); const model = await accessor.textFileService.files.resolve(resource) as IResolvedTextFileEditorModel; model.textEditorModel.setValue('Super Good'); await awaitEditorOpening(accessor.editorService); - assert.ok(accessor.editorService.isOpen(accessor.editorService.createInput({ resource, forceFile: true }))); + assert.ok(accessor.editorService.isOpen(accessor.editorService.createEditorInput({ resource, forceFile: true }))); part.dispose(); tracker.dispose(); @@ -115,7 +114,7 @@ suite('Files - TextFileEditorTracker', () => { test.skip('dirty untitled text file model opens as editor', async function () { // {{SQL CARBON EDIT}} tabcolormode failure const [part, accessor, tracker, , editorService] = await createTracker(); - const untitledEditor = editorService.createInput({ forceUntitled: true }) as UntitledTextEditorInput; + const untitledEditor = editorService.createEditorInput({ forceUntitled: true }) as UntitledTextEditorInput; const model = await untitledEditor.resolve(); assert.ok(!accessor.editorService.isOpen(untitledEditor)); @@ -135,4 +134,32 @@ suite('Files - TextFileEditorTracker', () => { Event.once(editorService.onDidActiveEditorChange)(c); }); } + + test('non-dirty files reload on window focus', async function () { + const [part, accessor, tracker] = await createTracker(); + + const resource = toResource.call(this, '/path/index.txt'); + + await accessor.editorService.openEditor(accessor.editorService.createEditorInput({ resource, forceFile: true })); + + accessor.hostService.setFocus(false); + accessor.hostService.setFocus(true); + + await awaitModelLoadEvent(accessor.textFileService, resource); + + part.dispose(); + tracker.dispose(); + (accessor.textFileService.files).dispose(); + }); + + function awaitModelLoadEvent(textFileService: ITextFileService, resource: URI): Promise { + return new Promise(c => { + const listener = textFileService.files.onDidLoad(e => { + if (isEqual(e.model.resource, resource)) { + listener.dispose(); + c(); + } + }); + }); + } }); diff --git a/src/vs/workbench/contrib/issue/electron-browser/issueService.ts b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts index 64b23708cf..29f040a425 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issueService.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IssueReporterStyles, IIssueService, IssueReporterData, ProcessExplorerData, IssueReporterExtensionData } from 'vs/platform/issue/node/issue'; -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { textLinkForeground, inputBackground, inputBorder, inputForeground, buttonBackground, buttonHoverBackground, buttonForeground, inputValidationErrorBorder, foreground, inputActiveOptionBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, editorBackground, editorForeground, listHoverBackground, listHoverForeground, listHighlightForeground, textLinkActiveForeground, inputValidationErrorBackground, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -12,8 +12,9 @@ import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/exte import { webFrame } from 'electron'; import { assign } from 'vs/base/common/objects'; import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issue'; -import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { ExtensionType } from 'vs/platform/extensions/common/extensions'; export class WorkbenchIssueService implements IWorkbenchIssueService { _serviceBrand: undefined; @@ -23,43 +24,40 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { @IThemeService private readonly themeService: IThemeService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService ) { } - openReporter(dataOverrides: Partial = {}): Promise { - return this.extensionManagementService.getInstalled(ExtensionType.User).then(extensions => { - const enabledExtensions = extensions.filter(extension => this.extensionEnablementService.isEnabled(extension)); - const extensionData: IssueReporterExtensionData[] = enabledExtensions.map(extension => { - const { manifest } = extension; - const manifestKeys = manifest.contributes ? Object.keys(manifest.contributes) : []; - const isTheme = !manifest.activationEvents && manifestKeys.length === 1 && manifestKeys[0] === 'themes'; - - return { - name: manifest.name, - publisher: manifest.publisher, - version: manifest.version, - repositoryUrl: manifest.repository && manifest.repository.url, - bugsUrl: manifest.bugs && manifest.bugs.url, - displayName: manifest.displayName, - id: extension.identifier.id, - isTheme: isTheme - }; - }); - const theme = this.themeService.getTheme(); - const issueReporterData: IssueReporterData = assign( - { - styles: getIssueReporterStyles(theme), - zoomLevel: webFrame.getZoomLevel(), - enabledExtensions: extensionData - }, - dataOverrides); - - return this.issueService.openReporter(issueReporterData); + async openReporter(dataOverrides: Partial = {}): Promise { + const extensions = await this.extensionManagementService.getInstalled(); + const enabledExtensions = extensions.filter(extension => this.extensionEnablementService.isEnabled(extension)); + const extensionData = enabledExtensions.map((extension): IssueReporterExtensionData => { + const { manifest } = extension; + const manifestKeys = manifest.contributes ? Object.keys(manifest.contributes) : []; + const isTheme = !manifest.activationEvents && manifestKeys.length === 1 && manifestKeys[0] === 'themes'; + const isBuiltin = extension.type === ExtensionType.System; + return { + name: manifest.name, + publisher: manifest.publisher, + version: manifest.version, + repositoryUrl: manifest.repository && manifest.repository.url, + bugsUrl: manifest.bugs && manifest.bugs.url, + displayName: manifest.displayName, + id: extension.identifier.id, + isTheme, + isBuiltin, + }; }); + const theme = this.themeService.getColorTheme(); + const issueReporterData: IssueReporterData = assign({ + styles: getIssueReporterStyles(theme), + zoomLevel: webFrame.getZoomLevel(), + enabledExtensions: extensionData, + }, dataOverrides); + return this.issueService.openReporter(issueReporterData); } openProcessExplorer(): Promise { - const theme = this.themeService.getTheme(); + const theme = this.themeService.getColorTheme(); const data: ProcessExplorerData = { pid: this.environmentService.configuration.mainPid, zoomLevel: webFrame.getZoomLevel(), @@ -75,7 +73,7 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { } } -export function getIssueReporterStyles(theme: ITheme): IssueReporterStyles { +export function getIssueReporterStyles(theme: IColorTheme): IssueReporterStyles { return { backgroundColor: getColor(theme, SIDE_BAR_BACKGROUND), color: getColor(theme, foreground), @@ -97,7 +95,7 @@ export function getIssueReporterStyles(theme: ITheme): IssueReporterStyles { }; } -function getColor(theme: ITheme, key: string): string | undefined { +function getColor(theme: IColorTheme, key: string): string | undefined { const color = theme.getColor(key); return color ? color.toString() : undefined; } diff --git a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts index e40f5b3297..5840cc6947 100644 --- a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts +++ b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts @@ -9,8 +9,9 @@ import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileService } from 'vs/platform/files/common/files'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class OpenLogsFolderAction extends Action { @@ -35,7 +36,7 @@ export class OpenExtensionLogsFolderAction extends Action { static readonly LABEL = nls.localize('openExtensionLogsFolder', "Open Extension Logs Folder"); constructor(id: string, label: string, - @IElectronEnvironmentService private readonly electronEnvironmentSerice: IElectronEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentSerice: INativeWorkbenchEnvironmentService, @IFileService private readonly fileService: IFileService, @IElectronService private readonly electronService: IElectronService ) { @@ -43,7 +44,7 @@ export class OpenExtensionLogsFolderAction extends Action { } async run(): Promise { - const folderStat = await this.fileService.resolve(this.electronEnvironmentSerice.extHostLogsPath); + const folderStat = await this.fileService.resolve(this.environmentSerice.extHostLogsPath); if (folderStat.children && folderStat.children[0]) { return this.electronService.showItemInFolder(folderStat.children[0].resource.fsPath); } diff --git a/src/vs/workbench/contrib/markers/browser/constants.ts b/src/vs/workbench/contrib/markers/browser/constants.ts index 019ce2fbc3..664b6db9c1 100644 --- a/src/vs/workbench/contrib/markers/browser/constants.ts +++ b/src/vs/workbench/contrib/markers/browser/constants.ts @@ -14,6 +14,7 @@ export default { RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID: 'problems.action.copyRelatedInformationMessage', FOCUS_PROBLEMS_FROM_FILTER: 'problems.action.focusProblemsFromFilter', MARKERS_VIEW_FOCUS_FILTER: 'problems.action.focusFilter', + MARKERS_VIEW_CLEAR_FILTER_TEXT: 'problems.action.clearFilterText', MARKERS_VIEW_SHOW_MULTILINE_MESSAGE: 'problems.action.showMultilineMessage', MARKERS_VIEW_SHOW_SINGLELINE_MESSAGE: 'problems.action.showSinglelineMessage', MARKER_OPEN_SIDE_ACTION_ID: 'problems.action.openToSide', @@ -21,7 +22,7 @@ export default { MARKER_SHOW_QUICK_FIX: 'problems.action.showQuickFixes', TOGGLE_MARKERS_VIEW_ACTION_ID: 'workbench.actions.view.toggleProblems', - MarkerViewFocusContextKey: new RawContextKey('problemsViewFocus', false), + MarkersViewSmallLayoutContextKey: new RawContextKey(`problemsView.smallLayout`, false), MarkerFocusContextKey: new RawContextKey('problemFocus', false), MarkerViewFilterFocusContextKey: new RawContextKey('problemsFilterFocus', false), RelatedInformationFocusContextKey: new RawContextKey('relatedInformationFocus', false) diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 3700a36303..f53cbf4402 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -28,7 +28,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry, IViewsService, getVisbileViewContextKey } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry, IViewsService, getVisbileViewContextKey, FocusedViewContext } from 'vs/workbench/common/views'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -211,7 +211,7 @@ registerAction2(class extends Action2 { id: Constants.MARKERS_VIEW_FOCUS_FILTER, title: localize('focusProblemsFilter', "Focus problems filter"), keybinding: { - when: Constants.MarkerViewFocusContextKey, + when: FocusedViewContext.isEqualTo(Constants.MARKERS_VIEW_ID), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KEY_F } @@ -259,6 +259,25 @@ registerAction2(class extends Action2 { } } }); +registerAction2(class extends Action2 { + constructor() { + super({ + id: Constants.MARKERS_VIEW_CLEAR_FILTER_TEXT, + title: localize('clearFiltersText', "Clear filters text"), + category: localize('problems', "Problems"), + keybinding: { + when: Constants.MarkerViewFilterFocusContextKey, + weight: KeybindingWeight.WorkbenchContrib, + } + }); + } + run(accessor: ServicesAccessor) { + const markersView = accessor.get(IViewsService).getActiveViewWithId(Constants.MARKERS_VIEW_ID); + if (markersView) { + markersView.clearFilterText(); + } + } +}); async function copyMarker(viewsService: IViewsService, clipboardService: IClipboardService) { const markersView = viewsService.getActiveViewWithId(Constants.MARKERS_VIEW_ID); diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 0237816fd1..f2f19ed456 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -857,6 +857,6 @@ registerThemingParticipant((theme, collector) => { const linkFg = theme.getColor(textLinkForeground); if (linkFg) { collector.addRule(`.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .details-container a.code-link .marker-code > span:hover { color: ${linkFg}; }`); - collector.addRule(`.markers-panel .markers-panel-container .tree-container .monaco-list:focus .monaco-tl-contents .details-container a.code-link .marker-code > span:hover { color: ${linkFg.lighten(.4)}; }`); + collector.addRule(`.markers-panel .markers-panel-container .tree-container .monaco-list:focus .monaco-tl-contents .details-container a.code-link .marker-code > span:hover { color: inherit; }`); } }); diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index 21c02fabac..2f2bfbfe19 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -13,20 +13,20 @@ import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/ import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkersFilterActionViewItem, MarkersFilterAction, IMarkersFilterActionChangeEvent, IMarkerFilterController } from 'vs/workbench/contrib/markers/browser/markersViewActions'; +import { MarkersFilterActionViewItem, MarkersFilters, IMarkersFiltersChangeEvent, IMarkerFilterController } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IMarkersWorkbenchService } from 'vs/workbench/contrib/markers/browser/markers'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { localize } from 'vs/nls'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService, ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Iterator } from 'vs/base/common/iterator'; import { ITreeElement, ITreeNode, ITreeContextMenuEvent, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Relay, Event, Emitter } from 'vs/base/common/event'; -import { WorkbenchObjectTree, TreeResourceNavigator, IListService, IWorkbenchObjectTreeOptions } from 'vs/platform/list/browser/listService'; +import { WorkbenchObjectTree, ResourceNavigator, IListService, IWorkbenchObjectTreeOptions } from 'vs/platform/list/browser/listService'; import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; import { IExpression } from 'vs/base/common/glob'; import { deepClone } from 'vs/base/common/objects'; @@ -34,7 +34,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, TreeElement, MarkersTreeAccessibilityProvider, MarkersViewModel, ResourceDragAndDrop } from 'vs/workbench/contrib/markers/browser/markersTreeViewer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Separator, ActionViewItem, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { domEvent } from 'vs/base/browser/event'; @@ -75,22 +75,28 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private filterActionBar: ActionBar | undefined; private messageBoxContainer: HTMLElement | undefined; private ariaLabelElement: HTMLElement | undefined; - private readonly collapseAllAction: IAction; - private readonly filterAction: MarkersFilterAction; + readonly filters: MarkersFilters; private readonly panelState: MementoObject; - private panelFoucusContextKey: IContextKey; - private _onDidFilter = this._register(new Emitter()); - readonly onDidFilter: Event = this._onDidFilter.event; + private _onDidChangeFilterStats = this._register(new Emitter<{ total: number, filtered: number }>()); + readonly onDidChangeFilterStats: Event<{ total: number, filtered: number }> = this._onDidChangeFilterStats.event; private cachedFilterStats: { total: number; filtered: number; } | undefined = undefined; private currentResourceGotAddedToMarkersData: boolean = false; readonly markersViewModel: MarkersViewModel; - private isSmallLayout: boolean = false; + private readonly smallLayoutContextKey: IContextKey; + private get smallLayout(): boolean { return !!this.smallLayoutContextKey.get(); } + private set smallLayout(smallLayout: boolean) { this.smallLayoutContextKey.set(smallLayout); } readonly onDidChangeVisibility = this.onDidChangeBodyVisibility; + private readonly _onDidFocusFilter: Emitter = this._register(new Emitter()); + readonly onDidFocusFilter: Event = this._onDidFocusFilter.event; + + private readonly _onDidClearFilterText: Emitter = this._register(new Emitter()); + readonly onDidClearFilterText: Event = this._onDidClearFilterText.event; + constructor( options: IViewPaneOptions, @IInstantiationService instantiationService: IInstantiationService, @@ -108,8 +114,8 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, ) { - super({ ...(options as IViewPaneOptions), id: Constants.MARKERS_VIEW_ID, ariaHeaderLabel: Messages.MARKERS_PANEL_TITLE_PROBLEMS }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); - this.panelFoucusContextKey = Constants.MarkerViewFocusContextKey.bindTo(contextKeyService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + this.smallLayoutContextKey = Constants.MarkersViewSmallLayoutContextKey.bindTo(this.contextKeyService); this.panelState = new Memento(Constants.MARKERS_VIEW_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE); this.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState['multiline'])); this._register(this.markersViewModel.onDidChange(marker => this.onDidChangeViewState(marker))); @@ -119,15 +125,16 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { this.rangeHighlightDecorations = this._register(this.instantiationService.createInstance(RangeHighlightDecorations)); // actions - this.collapseAllAction = this._register(new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action codicon-collapse-all', true, async () => this.collapseAll())); - this.filterAction = this._register(this.instantiationService.createInstance(MarkersFilterAction, { + this.regiserActions(); + this.filters = this._register(new MarkersFilters({ filterText: this.panelState['filter'] || '', filterHistory: this.panelState['filterHistory'] || [], showErrors: this.panelState['showErrors'] !== false, showWarnings: this.panelState['showWarnings'] !== false, showInfos: this.panelState['showInfos'] !== false, excludedFiles: !!this.panelState['useFilesExclude'], - activeFile: !!this.panelState['activeFile'] + activeFile: !!this.panelState['activeFile'], + layout: new dom.Dimension(0, 0) })); } @@ -146,9 +153,6 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { this.updateFilter(); - this._register(this.onDidFocus(() => this.panelFoucusContextKey.set(true))); - this._register(this.onDidBlur(() => this.panelFoucusContextKey.set(false))); - this._register(this.onDidChangeVisibility(visible => { if (visible) { this.refreshPanel(); @@ -157,7 +161,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } })); - this.filterActionBar!.push(this.filterAction); + this.filterActionBar!.push(new Action(`workbench.actions.treeView.${this.id}.filter`)); this.renderContent(); } @@ -166,22 +170,21 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } public layoutBody(height: number, width: number): void { - const wasSmallLayout = this.isSmallLayout; - this.isSmallLayout = width < 600 && height > 100; - if (this.isSmallLayout !== wasSmallLayout) { - this.updateActions(); + const wasSmallLayout = this.smallLayout; + this.smallLayout = width < 600 && height > 100; + if (this.smallLayout !== wasSmallLayout) { if (this.filterActionBar) { - dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout); + dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.smallLayout); } } - const contentHeight = this.isSmallLayout ? height - 44 : height; + const contentHeight = this.smallLayout ? height - 44 : height; if (this.tree) { this.tree.layout(contentHeight, width); } if (this.messageBoxContainer) { this.messageBoxContainer.style.height = `${contentHeight}px`; } - this.filterAction.layout(this.isSmallLayout ? width : width - 200); + this.filters.layout = new dom.Dimension(this.smallLayout ? width : width - 200, height); } public focus(): void { @@ -197,14 +200,48 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } public focusFilter(): void { - this.filterAction.focus(); + this._onDidFocusFilter.fire(); } - public getActions(): IAction[] { - if (this.isSmallLayout) { - return [this.collapseAllAction]; - } - return [this.filterAction, this.collapseAllAction]; + public clearFilterText(): void { + this._onDidClearFilterText.fire(); + } + + private regiserActions(): void { + const that = this; + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.treeView.${that.id}.collapseAll`, + title: localize('collapseAll', "Collapse All"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyEqualsExpr.create('view', that.id), + group: 'navigation', + order: Number.MAX_SAFE_INTEGER, + }, + icon: { id: 'codicon/collapse-all' } + }); + } + async run(): Promise { + return that.collapseAll(); + } + })); + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.treeView.${that.id}.filter`, + title: localize('filter', "Filter"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), Constants.MarkersViewSmallLayoutContextKey.negate()), + group: 'navigation', + order: 1, + }, + }); + } + async run(): Promise { } + })); } public showQuickFixes(marker: Marker): void { @@ -279,7 +316,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { const { total, filtered } = this.getFilterStats(); this.tree.toggleVisibility(total === 0 || filtered === 0); this.renderMessage(); - this._onDidFilter.fire(); + this._onDidChangeFilterStats.fire(this.getFilterStats()); } } @@ -292,7 +329,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { return; } let resourceMarkers: ResourceMarkers[] = []; - if (this.filterAction.activeFile) { + if (this.filters.activeFile) { if (this.currentActiveResource) { const activeResourceMarkers = this.markersWorkbenchService.markersModel.getResourceMarkers(this.currentActiveResource); if (activeResourceMarkers) { @@ -307,11 +344,11 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private updateFilter() { this.cachedFilterStats = undefined; - this.filter.options = new FilterOptions(this.filterAction.filterText, this.getFilesExcludeExpressions(), this.filterAction.showWarnings, this.filterAction.showErrors, this.filterAction.showInfos); + this.filter.options = new FilterOptions(this.filters.filterText, this.getFilesExcludeExpressions(), this.filters.showWarnings, this.filters.showErrors, this.filters.showInfos); if (this.tree) { this.tree.refilter(); } - this._onDidFilter.fire(); + this._onDidChangeFilterStats.fire(this.getFilterStats()); const { total, filtered } = this.getFilterStats(); if (this.tree) { @@ -321,7 +358,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } private getFilesExcludeExpressions(): { root: URI, expression: IExpression }[] | IExpression { - if (!this.filterAction.excludedFiles) { + if (!this.filters.excludedFiles) { return []; } @@ -338,7 +375,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private createFilterActionBar(parent: HTMLElement): void { this.filterActionBar = this._register(new ActionBar(parent, { actionViewItemProvider: action => this.getActionViewItem(action) })); dom.addClass(this.filterActionBar.getContainer(), 'markers-panel-filter-container'); - dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout); + dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.smallLayout); } private createMessageBox(parent: HTMLElement): void { @@ -396,7 +433,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { relatedInformationFocusContextKey.set(focus.elements.some(e => e instanceof RelatedInformation)); })); - const markersNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true })); + const markersNavigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: true })); this._register(Event.debounce(markersNavigator.onDidOpenResource, (last, event) => event, 75, true)(options => { this.openFileAtElement(options.element, !!options.editorOptions.preserveFocus, options.sideBySide, !!options.editorOptions.pinned); })); @@ -416,7 +453,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { this._register(this.tree.onContextMenu(this.onContextMenu, this)); this._register(this.configurationService.onDidChangeConfiguration(e => { - if (this.filterAction.excludedFiles && e.affectsConfiguration('files.exclude')) { + if (this.filters.excludedFiles && e.affectsConfiguration('files.exclude')) { this.updateFilter(); } })); @@ -424,7 +461,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { // move focus to input, whenever a key is pressed in the panel container this._register(domEvent(parent, 'keydown')(e => { if (this.keybindingService.mightProducePrintableCharacter(new StandardKeyboardEvent(e))) { - this.filterAction.focus(); + this.focusFilter(); } })); @@ -462,7 +499,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { if (this.tree) { this._register(this.tree.onDidChangeSelection(() => this.onSelected())); } - this._register(this.filterAction.onDidChange((event: IMarkersFilterActionChangeEvent) => { + this._register(this.filters.onDidChange((event: IMarkersFiltersChangeEvent) => { this.reportFilteringUsed(); if (event.activeFile) { this.refreshPanel(); @@ -508,7 +545,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private onActiveEditorChanged(): void { this.setCurrentActiveEditor(); - if (this.filterAction.activeFile) { + if (this.filters.activeFile) { this.refreshPanel(); } this.autoReveal(); @@ -552,7 +589,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { if (filtered === 0) { this.messageBoxContainer.style.display = 'block'; this.messageBoxContainer.setAttribute('tabIndex', '0'); - if (this.filterAction.activeFile) { + if (this.filters.activeFile) { this.renderFilterMessageForActiveFile(this.messageBoxContainer); } else { if (total > 0) { @@ -611,16 +648,16 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } private clearFilters(): void { - this.filterAction.filterText = ''; - this.filterAction.excludedFiles = false; - this.filterAction.showErrors = true; - this.filterAction.showWarnings = true; - this.filterAction.showInfos = true; + this.filters.filterText = ''; + this.filters.excludedFiles = false; + this.filters.showErrors = true; + this.filters.showWarnings = true; + this.filters.showInfos = true; } private autoReveal(focus: boolean = false): void { // No need to auto reveal if active file filter is on - if (this.filterAction.activeFile || !this.tree) { + if (this.filters.activeFile || !this.tree) { return; } let autoReveal = this.configurationService.getValue('problems.autoReveal'); @@ -749,16 +786,12 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } public getActionViewItem(action: IAction): IActionViewItem | undefined { - if (action.id === MarkersFilterAction.ID) { - return this.instantiationService.createInstance(MarkersFilterActionViewItem, this.filterAction, this); + if (action.id === `workbench.actions.treeView.${this.id}.filter`) { + return this.instantiationService.createInstance(MarkersFilterActionViewItem, action, this); } return super.getActionViewItem(action); } - getFilterOptions(): FilterOptions { - return this.filter.options; - } - getFilterStats(): { total: number; filtered: number; } { if (!this.cachedFilterStats) { this.cachedFilterStats = this.computeFilterStats(); @@ -790,11 +823,11 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private reportFilteringUsed(): void { const data = { - errors: this.filterAction.showErrors, - warnings: this.filterAction.showWarnings, - infos: this.filterAction.showInfos, - activeFile: this.filterAction.activeFile, - excludedFiles: this.filterAction.excludedFiles, + errors: this.filters.showErrors, + warnings: this.filters.showWarnings, + infos: this.filters.showInfos, + activeFile: this.filters.activeFile, + excludedFiles: this.filters.excludedFiles, }; /* __GDPR__ "problems.filter" : { @@ -809,13 +842,13 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { } saveState(): void { - this.panelState['filter'] = this.filterAction.filterText; - this.panelState['filterHistory'] = this.filterAction.filterHistory; - this.panelState['showErrors'] = this.filterAction.showErrors; - this.panelState['showWarnings'] = this.filterAction.showWarnings; - this.panelState['showInfos'] = this.filterAction.showInfos; - this.panelState['useFilesExclude'] = this.filterAction.excludedFiles; - this.panelState['activeFile'] = this.filterAction.activeFile; + this.panelState['filter'] = this.filters.filterText; + this.panelState['filterHistory'] = this.filters.filterHistory; + this.panelState['showErrors'] = this.filters.showErrors; + this.panelState['showWarnings'] = this.filters.showWarnings; + this.panelState['showInfos'] = this.filters.showInfos; + this.panelState['useFilesExclude'] = this.filters.excludedFiles; + this.panelState['activeFile'] = this.filters.activeFile; this.panelState['multiline'] = this.markersViewModel.multiline; super.saveState(); @@ -852,7 +885,7 @@ class MarkersTree extends WorkbenchObjectTree { } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Lightbulb Icon const editorLightBulbForegroundColor = theme.getColor(editorLightBulbForeground); diff --git a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts index 8130c049b5..753f53b1f0 100644 --- a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts @@ -5,16 +5,16 @@ import { Delayer } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; -import { Action, IActionChangeEvent, IAction, IActionRunner } from 'vs/base/common/actions'; +import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; -import { IThemeService, registerThemingParticipant, ICssStyleCollector, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, ICssStyleCollector, IColorTheme } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; -import { toDisposable } from 'vs/base/common/lifecycle'; +import { toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { BaseActionViewItem, ActionViewItem, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { badgeBackground, badgeForeground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; @@ -23,7 +23,6 @@ import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedH import { Marker } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; -import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IViewsService } from 'vs/workbench/common/views'; @@ -44,16 +43,17 @@ export class ShowProblemsPanelAction extends Action { } } -export interface IMarkersFilterActionChangeEvent extends IActionChangeEvent { +export interface IMarkersFiltersChangeEvent { filterText?: boolean; excludedFiles?: boolean; showWarnings?: boolean; showErrors?: boolean; showInfos?: boolean; activeFile?: boolean; + layout?: boolean; } -export interface IMarkersFilterActionOptions { +export interface IMarkersFiltersOptions { filterText: string; filterHistory: string[]; showErrors: boolean; @@ -61,17 +61,16 @@ export interface IMarkersFilterActionOptions { showInfos: boolean; excludedFiles: boolean; activeFile: boolean; + layout: DOM.Dimension; } -export class MarkersFilterAction extends Action { +export class MarkersFilters extends Disposable { - public static readonly ID: string = 'workbench.actions.problems.filter'; + private readonly _onDidChange: Emitter = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; - private readonly _onFocus: Emitter = this._register(new Emitter()); - readonly onFocus: Event = this._onFocus.event; - - constructor(options: IMarkersFilterActionOptions) { - super(MarkersFilterAction.ID, Messages.MARKERS_PANEL_ACTION_TOOLTIP_FILTER, 'markers-panel-action-filter', true); + constructor(options: IMarkersFiltersOptions) { + super(); this._filterText = options.filterText; this._showErrors = options.showErrors; this._showWarnings = options.showWarnings; @@ -79,6 +78,7 @@ export class MarkersFilterAction extends Action { this._excludedFiles = options.excludedFiles; this._activeFile = options.activeFile; this.filterHistory = options.filterHistory; + this._layout = options.layout; } private _filterText: string; @@ -88,7 +88,7 @@ export class MarkersFilterAction extends Action { set filterText(filterText: string) { if (this._filterText !== filterText) { this._filterText = filterText; - this._onDidChange.fire({ filterText: true }); + this._onDidChange.fire({ filterText: true }); } } @@ -101,7 +101,7 @@ export class MarkersFilterAction extends Action { set excludedFiles(filesExclude: boolean) { if (this._excludedFiles !== filesExclude) { this._excludedFiles = filesExclude; - this._onDidChange.fire({ excludedFiles: true }); + this._onDidChange.fire({ excludedFiles: true }); } } @@ -112,7 +112,7 @@ export class MarkersFilterAction extends Action { set activeFile(activeFile: boolean) { if (this._activeFile !== activeFile) { this._activeFile = activeFile; - this._onDidChange.fire({ activeFile: true }); + this._onDidChange.fire({ activeFile: true }); } } @@ -123,7 +123,7 @@ export class MarkersFilterAction extends Action { set showWarnings(showWarnings: boolean) { if (this._showWarnings !== showWarnings) { this._showWarnings = showWarnings; - this._onDidChange.fire({ showWarnings: true }); + this._onDidChange.fire({ showWarnings: true }); } } @@ -134,7 +134,7 @@ export class MarkersFilterAction extends Action { set showErrors(showErrors: boolean) { if (this._showErrors !== showErrors) { this._showErrors = showErrors; - this._onDidChange.fire({ showErrors: true }); + this._onDidChange.fire({ showErrors: true }); } } @@ -145,35 +145,34 @@ export class MarkersFilterAction extends Action { set showInfos(showInfos: boolean) { if (this._showInfos !== showInfos) { this._showInfos = showInfos; - this._onDidChange.fire({ showInfos: true }); + this._onDidChange.fire({ showInfos: true }); } } - focus(): void { - this._onFocus.fire(); + private _layout: DOM.Dimension = new DOM.Dimension(0, 0); + get layout(): DOM.Dimension { + return this._layout; } - - layout(width: number): void { - if (width > 600) { - this.class = 'markers-panel-action-filter grow'; - } else if (width < 400) { - this.class = 'markers-panel-action-filter small'; - } else { - this.class = 'markers-panel-action-filter'; + set layout(layout: DOM.Dimension) { + if (this._layout.width !== layout.width || this._layout.height !== layout.height) { + this._layout = layout; + this._onDidChange.fire({ layout: true }); } } } export interface IMarkerFilterController { - onDidFilter: Event; - getFilterOptions(): FilterOptions; + readonly onDidFocusFilter: Event; + readonly onDidClearFilterText: Event; + readonly filters: MarkersFilters; + readonly onDidChangeFilterStats: Event<{ total: number, filtered: number }>; getFilterStats(): { total: number, filtered: number }; } class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { constructor( - action: IAction, private filterAction: MarkersFilterAction, actionRunner: IActionRunner, + action: IAction, private filters: MarkersFilters, actionRunner: IActionRunner, @IContextMenuService contextMenuService: IContextMenuService ) { super(action, @@ -194,53 +193,53 @@ class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { private getActions(): IAction[] { return [ { - checked: this.filterAction.showErrors, + checked: this.filters.showErrors, class: undefined, enabled: true, id: 'showErrors', label: Messages.MARKERS_PANEL_FILTER_LABEL_SHOW_ERRORS, - run: async () => this.filterAction.showErrors = !this.filterAction.showErrors, + run: async () => this.filters.showErrors = !this.filters.showErrors, tooltip: '', dispose: () => null }, { - checked: this.filterAction.showWarnings, + checked: this.filters.showWarnings, class: undefined, enabled: true, id: 'showWarnings', label: Messages.MARKERS_PANEL_FILTER_LABEL_SHOW_WARNINGS, - run: async () => this.filterAction.showWarnings = !this.filterAction.showWarnings, + run: async () => this.filters.showWarnings = !this.filters.showWarnings, tooltip: '', dispose: () => null }, { - checked: this.filterAction.showInfos, + checked: this.filters.showInfos, class: undefined, enabled: true, id: 'showInfos', label: Messages.MARKERS_PANEL_FILTER_LABEL_SHOW_INFOS, - run: async () => this.filterAction.showInfos = !this.filterAction.showInfos, + run: async () => this.filters.showInfos = !this.filters.showInfos, tooltip: '', dispose: () => null }, new Separator(), { - checked: this.filterAction.activeFile, + checked: this.filters.activeFile, class: undefined, enabled: true, id: 'activeFile', label: Messages.MARKERS_PANEL_FILTER_LABEL_ACTIVE_FILE, - run: async () => this.filterAction.activeFile = !this.filterAction.activeFile, + run: async () => this.filters.activeFile = !this.filters.activeFile, tooltip: '', dispose: () => null }, { - checked: this.filterAction.excludedFiles, + checked: this.filters.excludedFiles, class: undefined, enabled: true, id: 'useFilesExclude', label: Messages.MARKERS_PANEL_FILTER_LABEL_EXCLUDED_FILES, - run: async () => this.filterAction.excludedFiles = !this.filterAction.excludedFiles, + run: async () => this.filters.excludedFiles = !this.filters.excludedFiles, tooltip: '', dispose: () => null }, @@ -263,7 +262,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private readonly filtersAction: IAction; constructor( - readonly action: MarkersFilterAction, + action: IAction, private filterController: IMarkerFilterController, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextViewService private readonly contextViewService: IContextViewService, @@ -274,10 +273,11 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { this.focusContextKey = Constants.MarkerViewFilterFocusContextKey.bindTo(contextKeyService); this.delayedFilterUpdate = new Delayer(200); this._register(toDisposable(() => this.delayedFilterUpdate.cancel())); - this._register(action.onFocus(() => this.focus())); + this._register(filterController.onDidFocusFilter(() => this.focus())); + this._register(filterController.onDidClearFilterText(() => this.clearFilterText())); this.filtersAction = new Action('markersFiltersAction', Messages.MARKERS_PANEL_ACTION_TOOLTIP_MORE_FILTERS, 'markers-filters codicon-filter'); this.filtersAction.checked = this.hasFiltersChanged(); - this._register(action.onDidChange(() => this.filtersAction.checked = this.hasFiltersChanged())); + this._register(filterController.filters.onDidChange(e => this.onDidFiltersChange(e))); } render(container: HTMLElement): void { @@ -285,7 +285,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { DOM.addClass(this.container, 'markers-panel-action-filter-container'); this.element = DOM.append(this.container, DOM.$('')); - this.element.className = this.action.class || ''; + this.element.className = this.class; this.createInput(this.element); this.createControls(this.element); this.updateClass(); @@ -299,23 +299,36 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { } } + private clearFilterText(): void { + if (this.filterInputBox) { + this.filterInputBox.value = ''; + } + } + + private onDidFiltersChange(e: IMarkersFiltersChangeEvent): void { + this.filtersAction.checked = this.hasFiltersChanged(); + if (e.layout) { + this.updateClass(); + } + } + private hasFiltersChanged(): boolean { - return !this.action.showErrors || !this.action.showWarnings || !this.action.showInfos || this.action.excludedFiles || this.action.activeFile; + return !this.filterController.filters.showErrors || !this.filterController.filters.showWarnings || !this.filterController.filters.showInfos || this.filterController.filters.excludedFiles || this.filterController.filters.activeFile; } private createInput(container: HTMLElement): void { this.filterInputBox = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, container, this.contextViewService, { placeholder: Messages.MARKERS_PANEL_FILTER_PLACEHOLDER, ariaLabel: Messages.MARKERS_PANEL_FILTER_ARIA_LABEL, - history: this.action.filterHistory + history: this.filterController.filters.filterHistory })); this.filterInputBox.inputElement.setAttribute('aria-labelledby', 'markers-panel-arialabel'); this._register(attachInputBoxStyler(this.filterInputBox, this.themeService)); - this.filterInputBox.value = this.action.filterText; + this.filterInputBox.value = this.filterController.filters.filterText; this._register(this.filterInputBox.onDidChange(filter => this.delayedFilterUpdate.trigger(() => this.onDidInputChange(this.filterInputBox!)))); - this._register(this.action.onDidChange((event: IMarkersFilterActionChangeEvent) => { + this._register(this.filterController.filters.onDidChange((event: IMarkersFiltersChangeEvent) => { if (event.filterText) { - this.filterInputBox!.value = this.action.filterText; + this.filterInputBox!.value = this.filterController.filters.filterText; } })); this._register(DOM.addStandardDisposableListener(this.filterInputBox.inputElement, DOM.EventType.KEY_DOWN, (e: any) => this.onInputKeyDown(e, this.filterInputBox!))); @@ -349,14 +362,14 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { filterBadge.style.color = foreground; })); this.updateBadge(); - this._register(this.filterController.onDidFilter(() => this.updateBadge())); + this._register(this.filterController.onDidChangeFilterStats(() => this.updateBadge())); } private createFilters(container: HTMLElement): void { const actionbar = this._register(new ActionBar(container, { actionViewItemProvider: action => { if (action.id === this.filtersAction.id) { - return this.instantiationService.createInstance(FiltersDropdownMenuActionViewItem, action, this.action, this.actionRunner); + return this.instantiationService.createInstance(FiltersDropdownMenuActionViewItem, action, this.filterController.filters, this.actionRunner); } return undefined; } @@ -366,8 +379,8 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private onDidInputChange(inputbox: HistoryInputBox) { inputbox.addToHistory(); - this.action.filterText = inputbox.value; - this.action.filterHistory = inputbox.getHistory(); + this.filterController.filters.filterText = inputbox.value; + this.filterController.filters.filterHistory = inputbox.getHistory(); } private updateBadge(): void { @@ -399,7 +412,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private onInputKeyDown(event: StandardKeyboardEvent, filterInputBox: HistoryInputBox) { let handled = false; if (event.equals(KeyCode.Escape)) { - filterInputBox.value = ''; + this.clearFilterText(); handled = true; } if (handled) { @@ -410,11 +423,21 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { protected updateClass(): void { if (this.element && this.container) { - this.element.className = this.action.class || ''; + this.element.className = this.class; DOM.toggleClass(this.container, 'grow', DOM.hasClass(this.element, 'grow')); this.adjustInputBox(); } } + + protected get class(): string { + if (this.filterController.filters.layout.width > 600) { + return 'markers-panel-action-filter grow'; + } else if (this.filterController.filters.layout.width < 400) { + return 'markers-panel-action-filter small'; + } else { + return 'markers-panel-action-filter'; + } + } } export class QuickFixAction extends Action { @@ -482,7 +505,7 @@ export class QuickFixActionViewItem extends ActionViewItem { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const inputActiveOptionBorderColor = theme.getColor(inputActiveOptionBorder); if (inputActiveOptionBorderColor) { collector.addRule(`.markers-panel-action-filter > .markers-panel-filter-controls > .monaco-action-bar .action-label.markers-filters.checked { border-color: ${inputActiveOptionBorderColor}; }`); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index 19a0b5e58b..ddb24a09b6 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -95,12 +95,12 @@ class RequestOracle { private _update(): void { - let widget = this._editorService.activeTextEditorWidget; + let control = this._editorService.activeTextEditorControl; let codeEditor: ICodeEditor | undefined = undefined; - if (isCodeEditor(widget)) { - codeEditor = widget; - } else if (isDiffEditor(widget)) { - codeEditor = widget.getModifiedEditor(); + if (isCodeEditor(control)) { + codeEditor = control; + } else if (isDiffEditor(control)) { + codeEditor = control.getModifiedEditor(); } if (!codeEditor || !codeEditor.hasModel()) { diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 3199ae21b3..a45aba273d 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -4,15 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; +import * as aria from 'vs/base/browser/ui/aria/aria'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -import { MenuId, MenuRegistry, SyncActionDescriptor, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { OutputService, LogContentProvider } from 'vs/workbench/contrib/output/browser/outputServices'; -import { ToggleOutputAction, ClearOutputAction, OpenLogOutputFile, ShowLogsOutputChannelAction, OpenOutputLogFileAction } from 'vs/workbench/contrib/output/browser/outputActions'; -import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_SCHEME, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_LOG_OUTPUT } from 'vs/workbench/contrib/output/common/output'; +import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_SCHEME, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_LOG_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK } from 'vs/workbench/contrib/output/common/output'; import { OutputViewPane } from 'vs/workbench/contrib/output/browser/outputView'; import { IEditorRegistry, Extensions as EditorExtensions, EditorDescriptor } from 'vs/workbench/browser/editor'; import { LogViewer, LogViewerInput } from 'vs/workbench/contrib/output/browser/logViewer'; @@ -21,8 +20,18 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWo import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ViewContainer, IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsRegistry } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsRegistry, IViewsService } from 'vs/workbench/common/views'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IOutputChannelDescriptor, IFileOutputChannelDescriptor } from 'vs/workbench/services/output/common/output'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { assertIsDefined } from 'vs/base/common/types'; +import { TogglePanelAction } from 'vs/workbench/browser/panel'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; // Register Service registerSingleton(IOutputService, OutputService); @@ -42,18 +51,18 @@ ModesRegistry.registerLanguage({ }); // register output container +const toggleOutputAcitonId = 'workbench.action.output.toggleOutput'; +const toggleOutputActionKeybindings = { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_U, + linux: { + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_H) // On Ubuntu Ctrl+Shift+U is taken by some global OS command + } +}; const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: OUTPUT_VIEW_ID, name: nls.localize('output', "Output"), ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [OUTPUT_VIEW_ID, OUTPUT_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), - focusCommand: { - id: ToggleOutputAction.ID, keybindings: { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_U, - linux: { - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_H) // On Ubuntu Ctrl+Shift+U is taken by some global OS command - } - } - } + focusCommand: { id: toggleOutputAcitonId, keybindings: toggleOutputActionKeybindings } }, ViewContainerLocation.Panel); Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ @@ -85,63 +94,237 @@ class OutputContribution implements IWorkbenchContribution { Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(OutputContribution, LifecyclePhase.Restored); -// register toggle output action globally -const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleOutputAction, ToggleOutputAction.ID, ToggleOutputAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_U, - linux: { - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_H) // On Ubuntu Ctrl+Shift+U is taken by some global OS command - } -}), 'View: Toggle Output', nls.localize('viewCategory', "View")); - -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ClearOutputAction, ClearOutputAction.ID, ClearOutputAction.LABEL), - 'View: Clear Output', nls.localize('viewCategory', "View")); - -const devCategory = nls.localize('developer', "Developer"); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ShowLogsOutputChannelAction, ShowLogsOutputChannelAction.ID, ShowLogsOutputChannelAction.LABEL), 'Developer: Show Logs...', devCategory); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(OpenOutputLogFileAction, OpenOutputLogFileAction.ID, OpenOutputLogFileAction.LABEL), 'Developer: Open Log File...', devCategory); - -// Define clear command, contribute to editor context menu registerAction2(class extends Action2 { constructor() { super({ - id: 'editor.action.clearoutput', - title: { value: nls.localize('clearOutput.label', "Clear Output"), original: 'Clear Output' }, + id: `workbench.output.action.switchBetweenOutputs`, + title: nls.localize('switchToOutput.label', "Switch to Output"), menu: { - id: MenuId.EditorContext, - when: CONTEXT_IN_OUTPUT + id: MenuId.ViewTitle, + when: ContextKeyEqualsExpr.create('view', OUTPUT_VIEW_ID), + group: 'navigation', + order: 1 }, }); } - run(accessor: ServicesAccessor) { - const activeChannel = accessor.get(IOutputService).getActiveChannel(); + async run(accessor: ServicesAccessor, channelId: string): Promise { + accessor.get(IOutputService).showChannel(channelId); + } +}); +registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.output.action.clearOutput`, + title: { value: nls.localize('clearOutput.label', "Clear Output"), original: 'Clear Output' }, + category: nls.localize('viewCategory', "View"), + menu: [{ + id: MenuId.ViewTitle, + when: ContextKeyEqualsExpr.create('view', OUTPUT_VIEW_ID), + group: 'navigation', + order: 2 + }, { + id: MenuId.CommandPalette + }, { + id: MenuId.EditorContext, + when: CONTEXT_IN_OUTPUT + }], + icon: { id: 'codicon/clear-all' } + }); + } + async run(accessor: ServicesAccessor): Promise { + const outputService = accessor.get(IOutputService); + const activeChannel = outputService.getActiveChannel(); if (activeChannel) { activeChannel.clear(); + aria.status(nls.localize('outputCleared', "Output was cleared")); + } + } +}); +registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.output.action.turnOffAutoScroll`, + title: nls.localize('outputScrollOff', "Turn Auto Scrolling Off"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', OUTPUT_VIEW_ID), CONTEXT_OUTPUT_SCROLL_LOCK.negate()), + group: 'navigation', + order: 3, + }, + icon: { id: 'codicon/unlock' } + }); + } + async run(accessor: ServicesAccessor): Promise { + const outputView = accessor.get(IViewsService).getActiveViewWithId(OUTPUT_VIEW_ID)!; + outputView.scrollLock = true; + } +}); +registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.output.action.turnOnAutoScroll`, + title: nls.localize('outputScrollOn', "Turn Auto Scrolling On"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', OUTPUT_VIEW_ID), CONTEXT_OUTPUT_SCROLL_LOCK), + group: 'navigation', + order: 3, + }, + icon: { id: 'codicon/lock' }, + }); + } + async run(accessor: ServicesAccessor): Promise { + const outputView = accessor.get(IViewsService).getActiveViewWithId(OUTPUT_VIEW_ID)!; + outputView.scrollLock = false; + } +}); +registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.action.openActiveLogOutputFile`, + title: { value: nls.localize('openActiveLogOutputFile', "Open Log Output File"), original: 'Open Log Output File' }, + menu: [{ + id: MenuId.ViewTitle, + when: ContextKeyEqualsExpr.create('view', OUTPUT_VIEW_ID), + group: 'navigation', + order: 4 + }, { + id: MenuId.CommandPalette, + when: CONTEXT_ACTIVE_LOG_OUTPUT, + }], + icon: { id: 'codicon/go-to-file' }, + precondition: CONTEXT_ACTIVE_LOG_OUTPUT + }); + } + async run(accessor: ServicesAccessor): Promise { + const outputService = accessor.get(IOutputService); + const editorService = accessor.get(IEditorService); + const instantiationService = accessor.get(IInstantiationService); + const logFileOutputChannelDescriptor = this.getLogFileOutputChannelDescriptor(outputService); + if (logFileOutputChannelDescriptor) { + await editorService.openEditor(instantiationService.createInstance(LogViewerInput, logFileOutputChannelDescriptor)); + } + } + private getLogFileOutputChannelDescriptor(outputService: IOutputService): IFileOutputChannelDescriptor | null { + const channel = outputService.getActiveChannel(); + if (channel) { + const descriptor = outputService.getChannelDescriptors().filter(c => c.id === channel.id)[0]; + if (descriptor && descriptor.file && descriptor.log) { + return descriptor; + } + } + return null; + } +}); + +// register toggle output action globally +registerAction2(class extends Action2 { + constructor() { + super({ + id: toggleOutputAcitonId, + title: { value: nls.localize('toggleOutput', "Toggle Output"), original: 'Toggle Output' }, + category: { value: nls.localize('viewCategory', "View"), original: 'View' }, + menu: { + id: MenuId.CommandPalette, + }, + keybinding: { + ...toggleOutputActionKeybindings, + ...{ + weight: KeybindingWeight.WorkbenchContrib, + when: undefined + } + }, + }); + } + async run(accessor: ServicesAccessor): Promise { + const panelService = accessor.get(IPanelService); + const layoutService = accessor.get(IWorkbenchLayoutService); + return new class ToggleOutputAction extends TogglePanelAction { + constructor() { + super(toggleOutputAcitonId, 'Toggle Output', OUTPUT_VIEW_ID, panelService, layoutService); + } + }().run(); + } +}); + +const devCategory = { value: nls.localize('developer', "Developer"), original: 'Developer' }; +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.showLogs', + title: { value: nls.localize('showLogs', "Show Logs..."), original: 'Show Logs...' }, + category: devCategory, + menu: { + id: MenuId.CommandPalette, + }, + }); + } + async run(accessor: ServicesAccessor): Promise { + const outputService = accessor.get(IOutputService); + const quickInputService = accessor.get(IQuickInputService); + const entries: { id: string, label: string }[] = outputService.getChannelDescriptors().filter(c => c.file && c.log) + .map(({ id, label }) => ({ id, label })); + + const entry = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlog', "Select Log") }); + if (entry) { + return outputService.showChannel(entry.id); } } }); +interface IOutputChannelQuickPickItem extends IQuickPickItem { + channel: IOutputChannelDescriptor; +} + registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.openActiveLogOutputFile', - title: { value: nls.localize('openActiveLogOutputFile', "Open Active Log Output File"), original: 'Open Active Log Output File' }, + id: 'workbench.action.openLogFile', + title: { value: nls.localize('openLogFile', "Open Log File..."), original: 'Open Log File...' }, + category: devCategory, menu: { id: MenuId.CommandPalette, - when: CONTEXT_ACTIVE_LOG_OUTPUT }, }); } - run(accessor: ServicesAccessor) { - accessor.get(IInstantiationService).createInstance(OpenLogOutputFile).run(); + async run(accessor: ServicesAccessor): Promise { + const outputService = accessor.get(IOutputService); + const quickInputService = accessor.get(IQuickInputService); + const instantiationService = accessor.get(IInstantiationService); + const editorService = accessor.get(IEditorService); + + const entries: IOutputChannelQuickPickItem[] = outputService.getChannelDescriptors().filter(c => c.file && c.log) + .map(channel => ({ id: channel.id, label: channel.label, channel })); + + const entry = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlogFile', "Select Log file") }); + if (entry) { + assertIsDefined(entry.channel.file); + await editorService.openEditor(instantiationService.createInstance(LogViewerInput, (entry.channel as IFileOutputChannelDescriptor))); + } } }); MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { group: '4_panels', command: { - id: ToggleOutputAction.ID, + id: toggleOutputAcitonId, title: nls.localize({ key: 'miToggleOutput', comment: ['&& denotes a mnemonic'] }, "&&Output") }, order: 1 }); + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + id: 'output', + order: 30, + title: nls.localize('output', "Output"), + type: 'object', + properties: { + 'output.smartScroll.enabled': { + type: 'boolean', + description: nls.localize('output.smartScroll.enabled', "Enable/disable the ability of smart scrolling in the output view. Smart scrolling allows you to lock scrolling automatically when you click in the output view and unlocks when you click in the last line."), + default: true, + scope: ConfigurationScope.APPLICATION, + tags: ['output'] + } + } +}); diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts deleted file mode 100644 index 7bfd466958..0000000000 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ /dev/null @@ -1,269 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import * as aria from 'vs/base/browser/ui/aria/aria'; -import { IAction, Action } from 'vs/base/common/actions'; -import { IOutputChannelRegistry, Extensions as OutputExt, IOutputChannelDescriptor, IFileOutputChannelDescriptor } from 'vs/workbench/services/output/common/output'; -import { IOutputService, OUTPUT_VIEW_ID } from 'vs/workbench/contrib/output/common/output'; -import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { TogglePanelAction } from 'vs/workbench/browser/panel'; -import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { groupBy } from 'vs/base/common/arrays'; -import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { LogViewerInput } from 'vs/workbench/contrib/output/browser/logViewer'; -import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; -import { assertIsDefined } from 'vs/base/common/types'; - -export class ToggleOutputAction extends TogglePanelAction { - - static readonly ID = 'workbench.action.output.toggleOutput'; - static readonly LABEL = nls.localize('toggleOutput', "Toggle Output"); - - constructor( - id: string, label: string, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IPanelService panelService: IPanelService, - ) { - super(id, label, OUTPUT_VIEW_ID, panelService, layoutService); - } -} - -export class ClearOutputAction extends Action { - - static readonly ID = 'workbench.output.action.clearOutput'; - static readonly LABEL = nls.localize('clearOutput', "Clear Output"); - - constructor( - id: string, label: string, - @IOutputService private readonly outputService: IOutputService - ) { - super(id, label, 'output-action codicon-clear-all'); - } - - run(): Promise { - const activeChannel = this.outputService.getActiveChannel(); - if (activeChannel) { - activeChannel.clear(); - aria.status(nls.localize('outputCleared', "Output was cleared")); - } - return Promise.resolve(true); - } -} - -// this action can be triggered in two ways: -// 1. user clicks the action icon, In which case the action toggles the lock state -// 2. user clicks inside the output view, which sets the lock, Or unsets it if they click the last line. -export class ToggleOrSetOutputScrollLockAction extends Action { - - static readonly ID = 'workbench.output.action.toggleOutputScrollLock'; - static readonly LABEL = nls.localize({ key: 'toggleOutputScrollLock', comment: ['Turn on / off automatic output scrolling'] }, "Toggle Output Scroll Lock"); - - constructor(id: string, label: string, @IOutputService private readonly outputService: IOutputService) { - super(id, label, 'output-action codicon-unlock'); - this._register(this.outputService.onActiveOutputChannel(channel => { - const activeChannel = this.outputService.getActiveChannel(); - if (activeChannel) { - this.setClassAndLabel(activeChannel.scrollLock); - } - })); - } - - run(newLockState?: boolean): Promise { - - const activeChannel = this.outputService.getActiveChannel(); - if (activeChannel) { - if (typeof (newLockState) === 'boolean') { - activeChannel.scrollLock = newLockState; - } - else { - activeChannel.scrollLock = !activeChannel.scrollLock; - } - this.setClassAndLabel(activeChannel.scrollLock); - } - - return Promise.resolve(true); - } - - private setClassAndLabel(locked: boolean) { - if (locked) { - this.class = 'output-action codicon-lock'; - this.label = nls.localize('outputScrollOn', "Turn Auto Scrolling On"); - } else { - this.class = 'output-action codicon-unlock'; - this.label = nls.localize('outputScrollOff', "Turn Auto Scrolling Off"); - } - } -} - -export class SwitchOutputAction extends Action { - - static readonly ID = 'workbench.output.action.switchBetweenOutputs'; - - constructor(@IOutputService private readonly outputService: IOutputService) { - super(SwitchOutputAction.ID, nls.localize('switchToOutput.label', "Switch to Output")); - - this.class = 'output-action switch-to-output'; - } - - run(channelId: string): Promise { - return this.outputService.showChannel(channelId); - } -} - -export class SwitchOutputActionViewItem extends SelectActionViewItem { - - private static readonly SEPARATOR = '─────────'; - - private outputChannels: IOutputChannelDescriptor[] = []; - private logChannels: IOutputChannelDescriptor[] = []; - - constructor( - action: IAction, - @IOutputService private readonly outputService: IOutputService, - @IThemeService themeService: IThemeService, - @IContextViewService contextViewService: IContextViewService - ) { - super(null, action, [], 0, contextViewService, { ariaLabel: nls.localize('outputChannels', 'Output Channels.') }); - - let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); - this._register(outputChannelRegistry.onDidRegisterChannel(() => this.updateOtions())); - this._register(outputChannelRegistry.onDidRemoveChannel(() => this.updateOtions())); - this._register(this.outputService.onActiveOutputChannel(() => this.updateOtions())); - this._register(attachSelectBoxStyler(this.selectBox, themeService)); - - this.updateOtions(); - } - - protected getActionContext(option: string, index: number): string { - const channel = index < this.outputChannels.length ? this.outputChannels[index] : this.logChannels[index - this.outputChannels.length - 1]; - return channel ? channel.id : option; - } - - private updateOtions(): void { - const groups = groupBy(this.outputService.getChannelDescriptors(), (c1: IOutputChannelDescriptor, c2: IOutputChannelDescriptor) => { - if (!c1.log && c2.log) { - return -1; - } - if (c1.log && !c2.log) { - return 1; - } - return 0; - }); - this.outputChannels = groups[0] || []; - this.logChannels = groups[1] || []; - const showSeparator = this.outputChannels.length && this.logChannels.length; - const separatorIndex = showSeparator ? this.outputChannels.length : -1; - const options: string[] = [...this.outputChannels.map(c => c.label), ...(showSeparator ? [SwitchOutputActionViewItem.SEPARATOR] : []), ...this.logChannels.map(c => nls.localize('logChannel', "Log ({0})", c.label))]; - let selected = 0; - const activeChannel = this.outputService.getActiveChannel(); - if (activeChannel) { - selected = this.outputChannels.map(c => c.id).indexOf(activeChannel.id); - if (selected === -1) { - const logChannelIndex = this.logChannels.map(c => c.id).indexOf(activeChannel.id); - selected = logChannelIndex !== -1 ? separatorIndex + 1 + logChannelIndex : 0; - } - } - this.setOptions(options.map((label, index) => { text: label, isDisabled: (index === separatorIndex ? true : false) }), Math.max(0, selected)); - } -} - -export class OpenLogOutputFile extends Action { - - static readonly ID = 'workbench.output.action.openLogOutputFile'; - static readonly LABEL = nls.localize('openInLogViewer', "Open Log File"); - - constructor( - @IOutputService private readonly outputService: IOutputService, - @IEditorService private readonly editorService: IEditorService, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(OpenLogOutputFile.ID, OpenLogOutputFile.LABEL, 'output-action codicon-go-to-file'); - this._register(this.outputService.onActiveOutputChannel(this.update, this)); - this.update(); - } - - private update(): void { - this.enabled = !!this.getLogFileOutputChannelDescriptor(); - } - - async run(): Promise { - const logFileOutputChannelDescriptor = this.getLogFileOutputChannelDescriptor(); - if (logFileOutputChannelDescriptor) { - await this.editorService.openEditor(this.instantiationService.createInstance(LogViewerInput, logFileOutputChannelDescriptor)); - } - } - - private getLogFileOutputChannelDescriptor(): IFileOutputChannelDescriptor | null { - const channel = this.outputService.getActiveChannel(); - if (channel) { - const descriptor = this.outputService.getChannelDescriptors().filter(c => c.id === channel.id)[0]; - if (descriptor && descriptor.file && descriptor.log) { - return descriptor; - } - } - return null; - } -} - -export class ShowLogsOutputChannelAction extends Action { - - static readonly ID = 'workbench.action.showLogs'; - static readonly LABEL = nls.localize('showLogs', "Show Logs..."); - - constructor(id: string, label: string, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IOutputService private readonly outputService: IOutputService - ) { - super(id, label); - } - - async run(): Promise { - const entries: { id: string, label: string }[] = this.outputService.getChannelDescriptors().filter(c => c.file && c.log) - .map(({ id, label }) => ({ id, label })); - - const entry = await this.quickInputService.pick(entries, { placeHolder: nls.localize('selectlog', "Select Log") }); - if (entry) { - return this.outputService.showChannel(entry.id); - } - } -} - -interface IOutputChannelQuickPickItem extends IQuickPickItem { - channel: IOutputChannelDescriptor; -} - -export class OpenOutputLogFileAction extends Action { - - static readonly ID = 'workbench.action.openLogFile'; - static readonly LABEL = nls.localize('openLogFile', "Open Log File..."); - - constructor(id: string, label: string, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IOutputService private readonly outputService: IOutputService, - @IEditorService private readonly editorService: IEditorService, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(id, label); - } - - async run(): Promise { - const entries: IOutputChannelQuickPickItem[] = this.outputService.getChannelDescriptors().filter(c => c.file && c.log) - .map(channel => ({ id: channel.id, label: channel.label, channel })); - - const entry = await this.quickInputService.pick(entries, { placeHolder: nls.localize('selectlogFile', "Select Log file") }); - if (entry) { - assertIsDefined(entry.channel.file); - await this.editorService.openEditor(this.instantiationService.createInstance(LogViewerInput, (entry.channel as IFileOutputChannelDescriptor))); - } - } -} diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index da31ecb799..199c324831 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { IAction } from 'vs/base/common/actions'; -import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem, SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -13,11 +13,10 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { AbstractTextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; -import { OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, IOutputChannel, CONTEXT_ACTIVE_LOG_OUTPUT } from 'vs/workbench/contrib/output/common/output'; -import { SwitchOutputAction, SwitchOutputActionViewItem, ClearOutputAction, ToggleOrSetOutputScrollLockAction, OpenLogOutputFile } from 'vs/workbench/contrib/output/browser/outputActions'; +import { OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, IOutputChannel, CONTEXT_ACTIVE_LOG_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK } from 'vs/workbench/contrib/output/common/output'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -26,10 +25,15 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IOutputChannelDescriptor, IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; +import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; +import { groupBy } from 'vs/base/common/arrays'; export class OutputViewPane extends ViewPane { @@ -38,6 +42,10 @@ export class OutputViewPane extends ViewPane { private editorPromise: Promise | null = null; private actions: IAction[] | undefined; + private readonly scrollLockContextKey: IContextKey; + get scrollLock(): boolean { return !!this.scrollLockContextKey.get(); } + set scrollLock(scrollLock: boolean) { this.scrollLockContextKey.set(scrollLock); } + constructor( options: IViewPaneOptions, @IKeybindingService keybindingService: IKeybindingService, @@ -52,6 +60,7 @@ export class OutputViewPane extends ViewPane { @ITelemetryService telemetryService: ITelemetryService, ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + this.scrollLockContextKey = CONTEXT_OUTPUT_SCROLL_LOCK.bindTo(this.contextKeyService); this.editor = instantiationService.createInstance(OutputEditor); this._register(this.editor.onTitleAreaUpdate(() => { this.updateTitle(this.editor.getTitle()); @@ -81,7 +90,7 @@ export class OutputViewPane extends ViewPane { const codeEditor = this.editor.getControl(); this._register(codeEditor.onDidChangeModelContent(() => { const activeChannel = this.outputService.getActiveChannel(); - if (activeChannel && !activeChannel.scrollLock) { + if (activeChannel && !this.scrollLock) { this.editor.revealLastLine(); } })); @@ -90,13 +99,15 @@ export class OutputViewPane extends ViewPane { return; } + if (!this.configurationService.getValue('output.smartScroll.enabled')) { + return; + } + const model = codeEditor.getModel(); if (model && this.actions) { const newPositionLine = e.position.lineNumber; const lastLine = model.getLineCount(); - const newLockState = lastLine !== newPositionLine; - const lockAction = this.actions.filter((action) => action.id === ToggleOrSetOutputScrollLockAction.ID)[0]; - lockAction.run(newLockState); + this.scrollLock = lastLine !== newPositionLine; } })); } @@ -108,10 +119,10 @@ export class OutputViewPane extends ViewPane { getActions(): IAction[] { if (!this.actions) { this.actions = [ - this._register(this.instantiationService.createInstance(SwitchOutputAction)), - this._register(this.instantiationService.createInstance(ClearOutputAction, ClearOutputAction.ID, ClearOutputAction.LABEL)), - this._register(this.instantiationService.createInstance(ToggleOrSetOutputScrollLockAction, ToggleOrSetOutputScrollLockAction.ID, ToggleOrSetOutputScrollLockAction.LABEL)), - this._register(this.instantiationService.createInstance(OpenLogOutputFile)) + // this._register(this.instantiationService.createInstance(SwitchOutputAction)), + // this._register(this.instantiationService.createInstance(ClearOutputAction, ClearOutputAction.ID, ClearOutputAction.LABEL)), + // this._register(this.instantiationService.createInstance(ToggleOrSetOutputScrollLockAction, ToggleOrSetOutputScrollLockAction.ID, ToggleOrSetOutputScrollLockAction.LABEL)), + // this._register(this.instantiationService.createInstance(OpenLogOutputFile)) ]; } return [...super.getActions(), ...this.actions]; @@ -122,13 +133,12 @@ export class OutputViewPane extends ViewPane { } getActionViewItem(action: IAction): IActionViewItem | undefined { - if (action.id === SwitchOutputAction.ID) { + if (action.id === 'workbench.output.action.switchBetweenOutputs') { return this.instantiationService.createInstance(SwitchOutputActionViewItem, action); } return super.getActionViewItem(action); } - private onDidChangeVisibility(visible: boolean): void { this.editor.setVisible(visible); let channel: IOutputChannel | undefined = undefined; @@ -264,3 +274,61 @@ export class OutputEditor extends AbstractTextResourceEditor { CONTEXT_IN_OUTPUT.bindTo(scopedContextKeyService).set(true); } } + +class SwitchOutputActionViewItem extends SelectActionViewItem { + + private static readonly SEPARATOR = '─────────'; + + private outputChannels: IOutputChannelDescriptor[] = []; + private logChannels: IOutputChannelDescriptor[] = []; + + constructor( + action: IAction, + @IOutputService private readonly outputService: IOutputService, + @IThemeService themeService: IThemeService, + @IContextViewService contextViewService: IContextViewService + ) { + super(null, action, [], 0, contextViewService, { ariaLabel: nls.localize('outputChannels', 'Output Channels.') }); + + let outputChannelRegistry = Registry.as(Extensions.OutputChannels); + this._register(outputChannelRegistry.onDidRegisterChannel(() => this.updateOtions())); + this._register(outputChannelRegistry.onDidRemoveChannel(() => this.updateOtions())); + this._register(this.outputService.onActiveOutputChannel(() => this.updateOtions())); + this._register(attachSelectBoxStyler(this.selectBox, themeService)); + + this.updateOtions(); + } + + protected getActionContext(option: string, index: number): string { + const channel = index < this.outputChannels.length ? this.outputChannels[index] : this.logChannels[index - this.outputChannels.length - 1]; + return channel ? channel.id : option; + } + + private updateOtions(): void { + const groups = groupBy(this.outputService.getChannelDescriptors(), (c1: IOutputChannelDescriptor, c2: IOutputChannelDescriptor) => { + if (!c1.log && c2.log) { + return -1; + } + if (c1.log && !c2.log) { + return 1; + } + return 0; + }); + this.outputChannels = groups[0] || []; + this.logChannels = groups[1] || []; + const showSeparator = this.outputChannels.length && this.logChannels.length; + const separatorIndex = showSeparator ? this.outputChannels.length : -1; + const options: string[] = [...this.outputChannels.map(c => c.label), ...(showSeparator ? [SwitchOutputActionViewItem.SEPARATOR] : []), ...this.logChannels.map(c => nls.localize('logChannel', "Log ({0})", c.label))]; + let selected = 0; + const activeChannel = this.outputService.getActiveChannel(); + if (activeChannel) { + selected = this.outputChannels.map(c => c.id).indexOf(activeChannel.id); + if (selected === -1) { + const logChannelIndex = this.logChannels.map(c => c.id).indexOf(activeChannel.id); + selected = logChannelIndex !== -1 ? separatorIndex + 1 + logChannelIndex : 0; + } + } + this.setOptions(options.map((label, index) => { text: label, isDisabled: (index === separatorIndex ? true : false) }), Math.max(0, selected)); + } +} + diff --git a/src/vs/workbench/contrib/output/common/output.ts b/src/vs/workbench/contrib/output/common/output.ts index 5c5c788de4..a78678f80f 100644 --- a/src/vs/workbench/contrib/output/common/output.ts +++ b/src/vs/workbench/contrib/output/common/output.ts @@ -52,6 +52,8 @@ export const CONTEXT_IN_OUTPUT = new RawContextKey('inOutput', false); export const CONTEXT_ACTIVE_LOG_OUTPUT = new RawContextKey('activeLogOutput', false); +export const CONTEXT_OUTPUT_SCROLL_LOCK = new RawContextKey(`outputView.scrollLock`, false); + export const IOutputService = createDecorator(OUTPUT_SERVICE_ID); /** @@ -105,11 +107,6 @@ export interface IOutputChannel { */ label: string; - /** - * Returns the value indicating whether the channel has scroll locked. - */ - scrollLock: boolean; - /** * URI of the output channel. */ diff --git a/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts b/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts index f5f482aea9..20225a569c 100644 --- a/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts +++ b/src/vs/workbench/contrib/output/test/browser/outputLinkProvider.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { isMacintosh, isLinux, isWindows } from 'vs/base/common/platform'; import { OutputLinkComputer } from 'vs/workbench/contrib/output/common/outputLinkComputer'; -import { TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; function toOSPath(p: string): string { if (isMacintosh || isLinux) { diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts index 3a40f56643..e4550c3077 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts @@ -98,8 +98,8 @@ export class StartupTimings implements IWorkbenchContribution { if (!activeViewlet || activeViewlet.getId() !== files.VIEWLET_ID) { return false; } - const visibleControls = this._editorService.visibleControls; - if (visibleControls.length !== 1 || !isCodeEditor(visibleControls[0].getControl())) { + const visibleEditorPanes = this._editorService.visibleEditorPanes; + if (visibleEditorPanes.length !== 1 || !isCodeEditor(visibleEditorPanes[0].getControl())) { return false; } if (this._panelService.getActivePanel()) { diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 0c77784eb2..1fc7e907d1 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -23,14 +23,14 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService, IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { DefineKeybindingWidget, KeybindingsSearchWidget, KeybindingsSearchOptions } from 'vs/workbench/contrib/preferences/browser/keybindingWidgets'; import { - IKeybindingsEditor, CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_COPY, + IKeybindingsEditorPane, CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN } from 'vs/workbench/contrib/preferences/common/preferences'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes'; @@ -55,7 +55,7 @@ interface ColumnItem { width: number; } -export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor { +export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditorPane { static readonly ID: string = 'workbench.editor.keybindings'; @@ -463,7 +463,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } })) as WorkbenchList; this._register(this.keybindingsList.onContextMenu(e => this.onContextMenu(e))); - this._register(this.keybindingsList.onFocusChange(e => this.onFocusChange(e))); + this._register(this.keybindingsList.onDidChangeFocus(e => this.onFocusChange(e))); this._register(this.keybindingsList.onDidFocus(() => { DOM.addClass(this.keybindingsList.getHTMLElement(), 'focused'); })); @@ -551,7 +551,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.unAssignedKeybindingItemToRevealAndFocus = null; } else if (currentSelectedIndex !== -1 && currentSelectedIndex < this.listEntries.length) { this.selectEntry(currentSelectedIndex, preserveFocus); - } else if (this.editorService.activeControl === this && !preserveFocus) { + } else if (this.editorService.activeEditorPane === this && !preserveFocus) { this.focus(); } } @@ -854,7 +854,7 @@ abstract class Column extends Disposable { abstract readonly element: HTMLElement; abstract render(keybindingItemEntry: IKeybindingItemEntry): void; - constructor(protected keybindingsEditor: IKeybindingsEditor) { + constructor(protected keybindingsEditor: IKeybindingsEditorPane) { super(); } @@ -867,7 +867,7 @@ class ActionsColumn extends Column { constructor( parent: HTMLElement, - keybindingsEditor: IKeybindingsEditor, + keybindingsEditor: IKeybindingsEditorPane, @IKeybindingService private keybindingsService: IKeybindingService ) { super(keybindingsEditor); @@ -921,7 +921,7 @@ class CommandColumn extends Column { constructor( parent: HTMLElement, - keybindingsEditor: IKeybindingsEditor, + keybindingsEditor: IKeybindingsEditorPane, ) { super(keybindingsEditor); this.element = this.commandColumn = DOM.append(parent, $('.column.command', { id: 'command_' + ++Column.COUNTER })); @@ -953,7 +953,7 @@ class CommandColumn extends Column { } private getAriaLabel(keybindingItemEntry: IKeybindingItemEntry): string { - return localize('commandAriaLabel', "Command is {0}.", keybindingItemEntry.keybindingItem.commandLabel ? keybindingItemEntry.keybindingItem.commandLabel : keybindingItemEntry.keybindingItem.command); + return keybindingItemEntry.keybindingItem.commandLabel ? keybindingItemEntry.keybindingItem.commandLabel : keybindingItemEntry.keybindingItem.command; } } @@ -964,7 +964,7 @@ class KeybindingColumn extends Column { constructor( parent: HTMLElement, - keybindingsEditor: IKeybindingsEditor, + keybindingsEditor: IKeybindingsEditorPane, ) { super(keybindingsEditor); @@ -992,7 +992,7 @@ class SourceColumn extends Column { constructor( parent: HTMLElement, - keybindingsEditor: IKeybindingsEditor, + keybindingsEditor: IKeybindingsEditorPane, ) { super(keybindingsEditor); this.element = this.sourceColumn = DOM.append(parent, $('.column.source', { id: 'source_' + ++Column.COUNTER })); @@ -1024,7 +1024,7 @@ class WhenColumn extends Column { constructor( parent: HTMLElement, - keybindingsEditor: IKeybindingsEditor, + keybindingsEditor: IKeybindingsEditorPane, @IContextViewService private readonly contextViewService: IContextViewService, @IThemeService private readonly themeService: IThemeService ) { @@ -1123,7 +1123,7 @@ class WhenColumn extends Column { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const listHighlightForegroundColor = theme.getColor(listHighlightForeground); if (listHighlightForegroundColor) { collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .highlight { color: ${listHighlightForegroundColor}; }`); diff --git a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts index 6e45800a7f..47e413a03a 100644 --- a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts +++ b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts @@ -21,7 +21,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IFileService } from 'vs/platform/files/common/files'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { VSBuffer } from 'vs/base/common/buffer'; -import { IEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; export class KeyboardLayoutPickerContribution extends Disposable implements IWorkbenchContribution { private readonly pickerElement = this._register(new MutableDisposable()); @@ -156,7 +156,7 @@ export class KeyboardLayoutPickerAction extends Action { await this.fileService.resolve(file).then(undefined, (error) => { return this.fileService.createFile(file, VSBuffer.fromString(KeyboardLayoutPickerAction.DEFAULT_CONTENT)); - }).then((stat): Promise | undefined => { + }).then((stat): Promise | undefined => { if (!stat) { return undefined; } diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index f397b6474f..2cc128bae1 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -20,7 +20,8 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IsMacNativeContext, RemoteNameContext, WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; +import { RemoteNameContext, WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; +import { IsMacNativeContext } from 'vs/platform/contextkey/common/contextkeys'; import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; import { Extensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; @@ -31,7 +32,7 @@ import { KeybindingsEditor } from 'vs/workbench/contrib/preferences/browser/keyb import { ConfigureLanguageBasedSettingsAction, OpenDefaultKeybindingsFileAction, OpenFolderSettingsAction, OpenGlobalKeybindingsAction, OpenGlobalKeybindingsFileAction, OpenGlobalSettingsAction, OpenRawDefaultSettingsAction, OpenRemoteSettingsAction, OpenSettings2Action, OpenSettingsJsonAction, OpenWorkspaceSettingsAction, OPEN_FOLDER_SETTINGS_COMMAND, OPEN_FOLDER_SETTINGS_LABEL } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; import { PreferencesEditor } from 'vs/workbench/contrib/preferences/browser/preferencesEditor'; import { SettingsEditor2 } from 'vs/workbench/contrib/preferences/browser/settingsEditor2'; -import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, IKeybindingsEditor, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, MODIFIED_SETTING_TAG, SETTINGS_COMMAND_OPEN_SETTINGS, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED, SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST, SETTINGS_EDITOR_COMMAND_SEARCH, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON } from 'vs/workbench/contrib/preferences/common/preferences'; +import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, MODIFIED_SETTING_TAG, SETTINGS_COMMAND_OPEN_SETTINGS, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED, SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST, SETTINGS_EDITOR_COMMAND_SEARCH, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON } from 'vs/workbench/contrib/preferences/common/preferences'; import { PreferencesContribution } from 'vs/workbench/contrib/preferences/common/preferencesContribution'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -243,9 +244,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_K), handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control && control instanceof KeybindingsEditor) { - control.defineKeybinding(control.activeKeybindingEntry!); + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.defineKeybinding(editorPane.activeKeybindingEntry!); } } }); @@ -256,9 +257,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_E), handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control && control instanceof KeybindingsEditor && control.activeKeybindingEntry!.keybindingItem.keybinding) { - control.defineWhenExpression(control.activeKeybindingEntry!); + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor && editorPane.activeKeybindingEntry!.keybindingItem.keybinding) { + editorPane.defineWhenExpression(editorPane.activeKeybindingEntry!); } } }); @@ -272,9 +273,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.Backspace) }, handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control && control instanceof KeybindingsEditor) { - control.removeKeybinding(control.activeKeybindingEntry!); + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.removeKeybinding(editorPane.activeKeybindingEntry!); } } }); @@ -285,9 +286,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), primary: 0, handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control && control instanceof KeybindingsEditor) { - control.resetKeybinding(control.activeKeybindingEntry!); + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.resetKeybinding(editorPane.activeKeybindingEntry!); } } }); @@ -298,9 +299,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), primary: KeyMod.CtrlCmd | KeyCode.KEY_F, handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control && control instanceof KeybindingsEditor) { - control.focusSearch(); + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.focusSearch(); } } }); @@ -312,9 +313,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.Alt | KeyCode.KEY_K, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_K }, handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control && control instanceof KeybindingsEditor) { - control.recordSearchKeys(); + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.recordSearchKeys(); } } }); @@ -326,9 +327,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.Alt | KeyCode.KEY_P, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_P }, handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control && control instanceof KeybindingsEditor) { - control.toggleSortByPrecedence(); + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.toggleSortByPrecedence(); } } }); @@ -339,9 +340,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), primary: 0, handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control) { - control.showSimilarKeybindings(control.activeKeybindingEntry!); + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.showSimilarKeybindings(editorPane.activeKeybindingEntry!); } } }); @@ -352,9 +353,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), primary: KeyMod.CtrlCmd | KeyCode.KEY_C, handler: async (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control) { - await control.copyKeybinding(control.activeKeybindingEntry!); + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + await editorPane.copyKeybinding(editorPane.activeKeybindingEntry!); } } }); @@ -365,9 +366,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), primary: 0, handler: async (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control) { - await control.copyKeybindingCommand(control.activeKeybindingEntry!); + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + await editorPane.copyKeybindingCommand(editorPane.activeKeybindingEntry!); } } }); @@ -378,9 +379,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS), primary: KeyCode.DownArrow, handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control) { - control.focusKeybindings(); + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.focusKeybindings(); } } }); @@ -525,9 +526,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS), primary: KeyCode.Escape, handler: (accessor, args: any) => { - const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control) { - control.clearSearchResults(); + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.clearSearchResults(); } } }); @@ -546,9 +547,9 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { }); CommandsRegistry.registerCommand(KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, serviceAccessor => { - const control = serviceAccessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control) { - control.search('@source:default'); + const editorPane = serviceAccessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.search('@source:default'); } }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { @@ -561,9 +562,9 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { }); CommandsRegistry.registerCommand(KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, serviceAccessor => { - const control = serviceAccessor.get(IEditorService).activeControl as IKeybindingsEditor; - if (control) { - control.search('@source:user'); + const editorPane = serviceAccessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof KeybindingsEditor) { + editorPane.search('@source:user'); } }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { @@ -576,9 +577,9 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { }); function getPreferencesEditor(accessor: ServicesAccessor): PreferencesEditor | SettingsEditor2 | null { - const activeControl = accessor.get(IEditorService).activeControl; - if (activeControl instanceof PreferencesEditor || activeControl instanceof SettingsEditor2) { - return activeControl; + const activeEditorPane = accessor.get(IEditorService).activeEditorPane; + if (activeEditorPane instanceof PreferencesEditor || activeEditorPane instanceof SettingsEditor2) { + return activeEditorPane; } return null; @@ -787,25 +788,25 @@ registerAction2(class extends Action2 { }); CommandsRegistry.registerCommand(SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, serviceAccessor => { - const control = serviceAccessor.get(IEditorService).activeControl as SettingsEditor2; - if (control instanceof SettingsEditor2) { - return control.switchToSettingsFile(); + const editorPane = serviceAccessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof SettingsEditor2) { + return editorPane.switchToSettingsFile(); } return Promise.resolve(null); }); CommandsRegistry.registerCommand(SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED, serviceAccessor => { - const control = serviceAccessor.get(IEditorService).activeControl as SettingsEditor2; - if (control instanceof SettingsEditor2) { - control.focusSearch(`@${MODIFIED_SETTING_TAG}`); + const editorPane = serviceAccessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof SettingsEditor2) { + editorPane.focusSearch(`@${MODIFIED_SETTING_TAG}`); } }); CommandsRegistry.registerCommand(SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, serviceAccessor => { - const control = serviceAccessor.get(IEditorService).activeControl as SettingsEditor2; - if (control instanceof SettingsEditor2) { - control.focusSearch(`@tag:usesOnlineServices`); + const editorPane = serviceAccessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof SettingsEditor2) { + editorPane.focusSearch(`@tag:usesOnlineServices`); } else { serviceAccessor.get(IPreferencesService).openSettings(false, '@tag:usesOnlineServices'); } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts index 3b53024ce8..d8073f208e 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts @@ -270,7 +270,7 @@ export class ConfigureLanguageBasedSettingsAction extends Action { if (pick) { const modeId = this.modeService.getModeIdForLanguageName(pick.label.toLowerCase()); if (typeof modeId === 'string') { - return this.preferencesService.configureSettingsForLanguage(modeId); + return this.preferencesService.openGlobalSettings(true, { editSetting: `[${modeId}]` }); } } return undefined; diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 6680f7fdf4..1f543b31ec 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -249,10 +249,10 @@ export class PreferencesEditor extends BaseEditor { private switchSettings(target: SettingsTarget): void { // Focus the editor if this editor is not active editor - if (this.editorService.activeControl !== this) { + if (this.editorService.activeEditorPane !== this) { this.focus(); } - const promise: Promise = this.input && this.input.isDirty() ? this.editorService.save({ editor: this.input, groupId: this.group!.id }) : Promise.resolve(true); + const promise = this.input && this.input.isDirty() ? this.editorService.save({ editor: this.input, groupId: this.group!.id }) : Promise.resolve(true); promise.then(() => { if (target === ConfigurationTarget.USER_LOCAL) { this.preferencesService.switchSettings(ConfigurationTarget.USER_LOCAL, this.preferencesService.userSettingsResource, true); diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 7741da8008..fd93f800a8 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -28,7 +28,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { activeContrastBorder, badgeBackground, badgeForeground, contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; -import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND } from 'vs/workbench/common/theme'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -642,7 +642,7 @@ export class SearchWidget extends Widget { this.countElement.style.borderStyle = border ? 'solid' : ''; this.countElement.style.borderColor = border; - const color = this.themeService.getTheme().getColor(badgeForeground); + const color = this.themeService.getColorTheme().getColor(badgeForeground); this.countElement.style.color = color ? color.toString() : ''; })); } @@ -803,7 +803,7 @@ export class EditPreferenceWidget extends Disposable { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { collector.addRule(` .settings-tabs-widget > .monaco-action-bar .action-item .action-label:focus, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 2426a5f4cc..71856eb6e2 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -33,7 +33,7 @@ import { badgeBackground, badgeForeground, contrastBorder, editorForeground } fr import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IEditor, IEditorMemento } from 'vs/workbench/common/editor'; +import { IEditorPane, IEditorMemento } from 'vs/workbench/common/editor'; import { attachSuggestEnabledInputBoxStyler, SuggestEnabledInput } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; import { SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; // {{SQL CARBON EDIT}} @@ -501,15 +501,14 @@ export class SettingsEditor2 extends BaseEditor { } } - switchToSettingsFile(): Promise { - const query = parseQuery(this.searchWidget.getValue()); - return this.openSettingsFile(query.query); + switchToSettingsFile(): Promise { + const query = parseQuery(this.searchWidget.getValue()).query; + return this.openSettingsFile({ query }); } - private async openSettingsFile(query?: string): Promise { + private async openSettingsFile(options?: ISettingsEditorOptions): Promise { const currentSettingsTarget = this.settingsTargetsWidget.settingsTarget; - const options: ISettingsEditorOptions = { query }; if (currentSettingsTarget === ConfigurationTarget.USER_LOCAL) { return this.preferencesService.openGlobalSettings(true, options); } else if (currentSettingsTarget === ConfigurationTarget.USER_REMOTE) { @@ -668,7 +667,7 @@ export class SettingsEditor2 extends BaseEditor { this.settingRenderers = this.instantiationService.createInstance(SettingTreeRenderers); this._register(this.settingRenderers.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value, e.type))); this._register(this.settingRenderers.onDidOpenSettings(settingKey => { - this.openSettingsFile(settingKey); + this.openSettingsFile({ editSetting: settingKey }); })); this._register(this.settingRenderers.onDidClickSettingLink(settingName => this.onDidClickSetting(settingName))); this._register(this.settingRenderers.onDidFocusSetting(element => { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index 8993ff163d..e2c436cac3 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -165,6 +165,11 @@ export const tocData: ITOCEntry = { label: localize('problems', "Problems"), settings: ['problems.*'] }, + { + id: 'features/output', + label: localize('output', "Output"), + settings: ['output.*'] + }, { id: 'features/comments', label: localize('comments', "Comments"), diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 9ec53893ca..d885dfd974 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -41,7 +41,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorBackground, errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler, attachStyler } from 'vs/platform/theme/common/styler'; -import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; import { ITOCEntry } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { ISettingsEditorViewState, settingKeyToDisplayFormat, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; @@ -1508,7 +1508,7 @@ export class SettingsTree extends ObjectTree { }); this.disposables.clear(); - this.disposables.add(registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + this.disposables.add(registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const activeBorderColor = theme.getColor(focusBorder); if (activeBorderColor) { // TODO@rob - why isn't this applied when added to the stylesheet from tocTree.ts? Seems like a chromium glitch. diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index bd2425476c..5c37b43f5e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -18,7 +18,7 @@ import { localize } from 'vs/nls'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { foreground, inputBackground, inputBorder, inputForeground, listActiveSelectionBackground, listActiveSelectionForeground, listHoverBackground, listHoverForeground, listInactiveSelectionBackground, listInactiveSelectionForeground, registerColor, selectBackground, selectBorder, selectForeground, textLinkForeground, textPreformatForeground, editorWidgetBorder, textLinkActiveForeground, simpleCheckboxBackground, simpleCheckboxForeground, simpleCheckboxBorder } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; -import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { disposableTimeout } from 'vs/base/common/async'; import { isUndefinedOrNull } from 'vs/base/common/types'; @@ -51,7 +51,7 @@ export const settingsNumberInputBackground = registerColor('settings.numberInput export const settingsNumberInputForeground = registerColor('settings.numberInputForeground', { dark: inputForeground, light: inputForeground, hc: inputForeground }, localize('numberInputBoxForeground', "Settings editor number input box foreground.")); export const settingsNumberInputBorder = registerColor('settings.numberInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('numberInputBoxBorder', "Settings editor number input box border.")); -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const checkboxBackgroundColor = theme.getColor(settingsCheckboxBackground); if (checkboxBackgroundColor) { collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox { background-color: ${checkboxBackgroundColor} !important; }`); diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts index af35550cdb..6ad0304ca7 100644 --- a/src/vs/workbench/contrib/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -6,7 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ISettingsEditorModel, ISearchResult } from 'vs/workbench/services/preferences/common/preferences'; -import { IEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; import { IKeybindingItemEntry } from 'vs/workbench/services/preferences/common/keybindingsEditorModel'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; @@ -43,7 +43,7 @@ export interface ISearchProvider { searchModel(preferencesModel: ISettingsEditorModel, token?: CancellationToken): Promise; } -export interface IKeybindingsEditor extends IEditor { +export interface IKeybindingsEditorPane extends IEditorPane { readonly activeKeybindingEntry: IKeybindingItemEntry | null; readonly onDefineWhenExpression: Event; diff --git a/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts b/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts new file mode 100644 index 0000000000..9fcc4730d2 --- /dev/null +++ b/src/vs/workbench/contrib/quickaccess/browser/quickAccess.contribution.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { HelpQuickAccessProvider } from 'vs/platform/quickinput/browser/helpQuickAccess'; +import { ViewQuickAccessProvider } from 'vs/workbench/contrib/quickaccess/browser/viewQuickAccess'; +import { QUICK_ACCESS_COMMAND_ID } from 'vs/workbench/contrib/quickaccess/browser/quickAccessCommands'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; + +const registry = Registry.as(Extensions.Quickaccess); + +registry.defaultProvider = { + ctor: HelpQuickAccessProvider, + prefix: '', + placeholder: localize('defaultAccessPlaceholder', "Type the name of a file to open."), + helpEntries: [{ description: localize('gotoFileQuickAccess', "Go to File"), needsEditor: false }] +}; + +registry.registerQuickAccessProvider({ + ctor: HelpQuickAccessProvider, + prefix: HelpQuickAccessProvider.PREFIX, + placeholder: localize('helpQuickAccessPlaceholder', "Type '{0}' to get help on the actions you can take from here.", HelpQuickAccessProvider.PREFIX), + helpEntries: [{ description: localize('helpQuickAccess', "Show all Quick Access Providers"), needsEditor: false }] +}); + +registry.registerQuickAccessProvider({ + ctor: ViewQuickAccessProvider, + prefix: ViewQuickAccessProvider.PREFIX, + placeholder: localize('viewQuickAccessPlaceholder', "Type the name of a view, output channel or terminal to open."), + helpEntries: [{ description: localize('viewQuickAccess', "Open View"), needsEditor: false }] +}); + +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: QUICK_ACCESS_COMMAND_ID, title: { + value: localize('openQuickAccess', "Open Quick Access"), original: 'Open Quick Access' + }, + category: localize('quickAccess', "Quick Access") + } +}); diff --git a/src/vs/workbench/contrib/quickaccess/browser/quickAccessCommands.ts b/src/vs/workbench/contrib/quickaccess/browser/quickAccessCommands.ts new file mode 100644 index 0000000000..308c2f9ac6 --- /dev/null +++ b/src/vs/workbench/contrib/quickaccess/browser/quickAccessCommands.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; + +export const QUICK_ACCESS_COMMAND_ID = 'workbench.action.openQuickAccess'; + +CommandsRegistry.registerCommand({ + id: QUICK_ACCESS_COMMAND_ID, + handler: async function (accessor: ServicesAccessor, prefix: string | null = null) { + const quickInputService = accessor.get(IQuickInputService); + + quickInputService.quickAccess.show(typeof prefix === 'string' ? prefix : undefined); + }, + description: { + description: `Quick access`, + args: [{ + name: 'prefix', + schema: { + 'type': 'string' + } + }] + } +}); diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts new file mode 100644 index 0000000000..ecd3771cb9 --- /dev/null +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -0,0 +1,176 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { IViewDescriptorService, IViewsService, ViewContainer, IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry } from 'vs/workbench/common/views'; +import { IOutputService } from 'vs/workbench/contrib/output/common/output'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; +import { matchesFuzzy } from 'vs/base/common/filters'; +import { fuzzyContains } from 'vs/base/common/strings'; +import { withNullAsUndefined } from 'vs/base/common/types'; + +interface IViewQuickPickItem extends IPickerQuickAccessItem { + containerLabel: string; +} + +export class ViewQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = 'view '; + + constructor( + @IViewletService private readonly viewletService: IViewletService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IViewsService private readonly viewsService: IViewsService, + @IOutputService private readonly outputService: IOutputService, + @ITerminalService private readonly terminalService: ITerminalService, + @IPanelService private readonly panelService: IPanelService, + @IContextKeyService private readonly contextKeyService: IContextKeyService + ) { + super(ViewQuickAccessProvider.PREFIX); + } + + protected getPicks(filter: string): Array { + const filteredViewEntries = this.doGetViewPickItems().filter(entry => { + if (!filter) { + return true; + } + + // Match fuzzy on label + entry.highlights = { label: withNullAsUndefined(matchesFuzzy(filter, entry.label, true)) }; + + // Return if we have a match on label or container + return entry.highlights.label || fuzzyContains(entry.containerLabel, filter); + }); + + // Map entries to container labels + const mapEntryToContainer = new Map(); + for (const entry of filteredViewEntries) { + if (!mapEntryToContainer.has(entry.label)) { + mapEntryToContainer.set(entry.label, entry.containerLabel); + } + } + + // Add separators for containers + const filteredViewEntriesWithSeparators: Array = []; + let lastContainer: string | undefined = undefined; + for (const entry of filteredViewEntries) { + if (lastContainer !== entry.containerLabel) { + lastContainer = entry.containerLabel; + + // When the entry container has a parent container, set container + // label as Parent / Child. For example, `Views / Explorer`. + let separatorLabel: string; + if (mapEntryToContainer.has(lastContainer)) { + separatorLabel = `${mapEntryToContainer.get(lastContainer)} / ${lastContainer}`; + } else { + separatorLabel = lastContainer; + } + + filteredViewEntriesWithSeparators.push({ type: 'separator', label: separatorLabel }); + + } + + filteredViewEntriesWithSeparators.push(entry); + } + + return filteredViewEntriesWithSeparators; + } + + private doGetViewPickItems(): Array { + const viewEntries: Array = []; + + const getViewEntriesForViewlet = (viewlet: ViewletDescriptor, viewContainer: ViewContainer): IViewQuickPickItem[] => { + const views = Registry.as(ViewExtensions.ViewsRegistry).getViews(viewContainer); + const result: IViewQuickPickItem[] = []; + for (const view of views) { + if (this.contextKeyService.contextMatchesRules(view.when)) { + result.push({ + label: view.name, + containerLabel: viewlet.name, + accept: () => this.viewsService.openView(view.id, true) + }); + } + } + + return result; + }; + + // Viewlets + const viewlets = this.viewletService.getViewlets(); + for (const viewlet of viewlets) { + if (this.includeViewlet(viewlet)) { + viewEntries.push({ + label: viewlet.name, + containerLabel: localize('views', "Side Bar"), + accept: () => this.viewletService.openViewlet(viewlet.id, true) + }); + } + } + + // Panels + const panels = this.panelService.getPanels(); + for (const panel of panels) { + viewEntries.push({ + label: panel.name, + containerLabel: localize('panels', "Panel"), + accept: () => this.panelService.openPanel(panel.id, true) + }); + } + + // Viewlet Views + for (const viewlet of viewlets) { + const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(viewlet.id); + if (viewContainer) { + viewEntries.push(...getViewEntriesForViewlet(viewlet, viewContainer)); + } + } + + // Terminals + this.terminalService.terminalTabs.forEach((tab, tabIndex) => { + tab.terminalInstances.forEach((terminal, terminalIndex) => { + viewEntries.push({ + label: localize('terminalTitle', "{0}: {1}", `${tabIndex + 1}.${terminalIndex + 1}`, terminal.title), + containerLabel: localize('terminals', "Terminal"), + accept: async () => { + await this.terminalService.showPanel(true); + + this.terminalService.setActiveInstance(terminal); + } + }); + }); + }); + + // Output Channels + const channels = this.outputService.getChannelDescriptors(); + for (const channel of channels) { + viewEntries.push({ + label: channel.log ? localize('logChannel', "Log ({0})", channel.label) : channel.label, + containerLabel: localize('channels', "Output"), + accept: () => this.outputService.showChannel(channel.id) + }); + } + + // Add generic ARIA label + viewEntries.forEach(entry => entry.ariaLabel = localize('entryAriaLabel', "{0}, view picker", entry.label)); + + return viewEntries; + } + + private includeViewlet(viewlet: ViewletDescriptor): boolean { + const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(viewlet.id); + if (viewContainer?.hideIfEmpty) { + return this.viewDescriptorService.getViewDescriptors(viewContainer).activeViewDescriptors.length > 0; + } + + return true; + } +} diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index 4c64d9ce69..a14f0c1ba4 100644 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -461,10 +461,10 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { lastCommandPaletteInput = searchValue; // Editor Actions - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; + const activeTextEditorControl = this.editorService.activeTextEditorControl; let editorActions: IEditorAction[] = []; - if (activeTextEditorWidget && isFunction(activeTextEditorWidget.getSupportedActions)) { - editorActions = activeTextEditorWidget.getSupportedActions(); + if (activeTextEditorControl && isFunction(activeTextEditorControl.getSupportedActions)) { + editorActions = activeTextEditorControl.getSupportedActions(); } const editorEntries = this.editorActionsToEntries(editorActions, searchValue); diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts index e44c27019d..199a3133cd 100644 --- a/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts @@ -39,21 +39,21 @@ export class GotoLineAction extends QuickOpenAction { run(): Promise { - let activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (!activeTextEditorWidget) { + let activeTextEditorControl = this.editorService.activeTextEditorControl; + if (!activeTextEditorControl) { return Promise.resolve(); } - if (isDiffEditor(activeTextEditorWidget)) { - activeTextEditorWidget = activeTextEditorWidget.getModifiedEditor(); + if (isDiffEditor(activeTextEditorControl)) { + activeTextEditorControl = activeTextEditorControl.getModifiedEditor(); } let restoreOptions: IEditorOptions | null = null; - if (isCodeEditor(activeTextEditorWidget)) { - const options = activeTextEditorWidget.getOptions(); + if (isCodeEditor(activeTextEditorControl)) { + const options = activeTextEditorControl.getOptions(); const lineNumbers = options.get(EditorOption.lineNumbers); if (lineNumbers.renderType === RenderLineNumbersType.Relative) { - activeTextEditorWidget.updateOptions({ + activeTextEditorControl.updateOptions({ lineNumbers: 'on' }); restoreOptions = { @@ -66,7 +66,7 @@ export class GotoLineAction extends QuickOpenAction { if (restoreOptions) { Event.once(this._quickOpenService.onHide)(() => { - activeTextEditorWidget!.updateOptions(restoreOptions!); + activeTextEditorControl!.updateOptions(restoreOptions!); }); } @@ -99,8 +99,8 @@ class GotoLineEntry extends EditorQuickOpenEntry { // Inform user about valid range if input is invalid const maxLineNumber = this.getMaxLineNumber(); - if (this.editorService.activeTextEditorWidget && this.invalidRange(maxLineNumber)) { - const position = this.editorService.activeTextEditorWidget.getPosition(); + if (this.editorService.activeTextEditorControl && this.invalidRange(maxLineNumber)) { + const position = this.editorService.activeTextEditorControl.getPosition(); if (position) { if (maxLineNumber > 0) { @@ -120,12 +120,12 @@ class GotoLineEntry extends EditorQuickOpenEntry { } private getMaxLineNumber(): number { - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (!activeTextEditorWidget) { + const activeTextEditorControl = this.editorService.activeTextEditorControl; + if (!activeTextEditorControl) { return -1; } - let model = activeTextEditorWidget.getModel(); + let model = activeTextEditorControl.getModel(); if (model && (model).modified && (model).original) { model = (model).modified; // Support for diff editor models } @@ -167,10 +167,10 @@ class GotoLineEntry extends EditorQuickOpenEntry { // Apply selection and focus const range = this.toSelection(); - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - activeTextEditorWidget.setSelection(range); - activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth); + const activeTextEditorControl = this.editorService.activeTextEditorControl; + if (activeTextEditorControl) { + activeTextEditorControl.setSelection(range); + activeTextEditorControl.revealRangeInCenter(range, ScrollType.Smooth); } return true; @@ -187,13 +187,13 @@ class GotoLineEntry extends EditorQuickOpenEntry { // Select Line Position const range = this.toSelection(); - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth); + const activeTextEditorControl = this.editorService.activeTextEditorControl; + if (activeTextEditorControl) { + activeTextEditorControl.revealRangeInCenter(range, ScrollType.Smooth); // Decorate if possible - if (this.editorService.activeControl && types.isFunction(activeTextEditorWidget.changeDecorations)) { - this.handler.decorateOutline(range, activeTextEditorWidget, this.editorService.activeControl.group); + if (this.editorService.activeEditorPane && types.isFunction(activeTextEditorControl.changeDecorations)) { + this.handler.decorateOutline(range, activeTextEditorControl, this.editorService.activeEditorPane.group); } } @@ -228,8 +228,8 @@ export class GotoLineHandler extends QuickOpenHandler { } getAriaLabel(): string { - if (this.editorService.activeTextEditorWidget) { - const position = this.editorService.activeTextEditorWidget.getPosition(); + if (this.editorService.activeTextEditorControl) { + const position = this.editorService.activeTextEditorControl.getPosition(); if (position) { return nls.localize('gotoLineLabelEmpty', "Current Line: {0}, Column: {1}. Type a line number to navigate to.", position.lineNumber, position.column); } @@ -243,9 +243,9 @@ export class GotoLineHandler extends QuickOpenHandler { // Remember view state to be able to restore on cancel if (!this.lastKnownEditorViewState) { - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - this.lastKnownEditorViewState = activeTextEditorWidget.saveViewState(); + const activeTextEditorControl = this.editorService.activeTextEditorControl; + if (activeTextEditorControl) { + this.lastKnownEditorViewState = activeTextEditorControl.saveViewState(); } } @@ -253,7 +253,7 @@ export class GotoLineHandler extends QuickOpenHandler { } canRun(): boolean | string { - const canRun = !!this.editorService.activeTextEditorWidget; + const canRun = !!this.editorService.activeTextEditorControl; return canRun ? true : nls.localize('cannotRunGotoLine', "Open a text file first to go to a line."); } @@ -305,9 +305,9 @@ export class GotoLineHandler extends QuickOpenHandler { clearDecorations(): void { const rangeHighlightDecorationId = this.rangeHighlightDecorationId; if (rangeHighlightDecorationId) { - this.editorService.visibleControls.forEach(editor => { - if (editor.group && editor.group.id === rangeHighlightDecorationId.groupId) { - const editorControl = editor.getControl(); + this.editorService.visibleEditorPanes.forEach(editorPane => { + if (editorPane.group && editorPane.group.id === rangeHighlightDecorationId.groupId) { + const editorControl = editorPane.getControl(); editorControl.changeDecorations(changeAccessor => { changeAccessor.deltaDecorations([ rangeHighlightDecorationId.lineDecorationId, @@ -328,9 +328,9 @@ export class GotoLineHandler extends QuickOpenHandler { // Restore selection if canceled if (canceled && this.lastKnownEditorViewState) { - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - activeTextEditorWidget.restoreViewState(this.lastKnownEditorViewState); + const activeTextEditorControl = this.editorService.activeTextEditorControl; + if (activeTextEditorControl) { + activeTextEditorControl.restoreViewState(this.lastKnownEditorViewState); } } diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts index bb170cfb4d..243590beb9 100644 --- a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts @@ -245,10 +245,10 @@ class SymbolEntry extends EditorQuickOpenEntryGroup { // Apply selection and focus else { const range = Range.collapseToStart(this.revealRange); - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - activeTextEditorWidget.setSelection(range); - activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth); + const activeTextEditorControl = this.editorService.activeTextEditorControl; + if (activeTextEditorControl) { + activeTextEditorControl.setSelection(range); + activeTextEditorControl.revealRangeInCenter(range, ScrollType.Smooth); } } @@ -259,13 +259,13 @@ class SymbolEntry extends EditorQuickOpenEntryGroup { // Select Outline Position const range = Range.collapseToStart(this.revealRange); - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth); + const activeTextEditorControl = this.editorService.activeTextEditorControl; + if (activeTextEditorControl) { + activeTextEditorControl.revealRangeInCenter(range, ScrollType.Smooth); // Decorate if possible - if (this.editorService.activeControl && types.isFunction(activeTextEditorWidget.changeDecorations)) { - this.handler.decorateOutline(this.range, range, activeTextEditorWidget, this.editorService.activeControl.group); + if (this.editorService.activeEditorPane && types.isFunction(activeTextEditorControl.changeDecorations)) { + this.handler.decorateOutline(this.range, range, activeTextEditorControl, this.editorService.activeEditorPane.group); } } @@ -350,9 +350,9 @@ export class GotoSymbolHandler extends QuickOpenHandler { // Remember view state to be able to restore on cancel if (!this.lastKnownEditorViewState) { - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - this.lastKnownEditorViewState = activeTextEditorWidget.saveViewState(); + const activeTextEditorControl = this.editorService.activeTextEditorControl; + if (activeTextEditorControl) { + this.lastKnownEditorViewState = activeTextEditorControl.saveViewState(); } } @@ -387,9 +387,9 @@ export class GotoSymbolHandler extends QuickOpenHandler { canRun(): boolean | string { let canRun = false; - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - let model = activeTextEditorWidget.getModel(); + const activeTextEditorControl = this.editorService.activeTextEditorControl; + if (activeTextEditorControl) { + let model = activeTextEditorControl.getModel(); if (model && (model).modified && (model).original) { model = (model).modified; // Support for diff editor models } @@ -399,7 +399,7 @@ export class GotoSymbolHandler extends QuickOpenHandler { } } - return canRun ? true : activeTextEditorWidget !== null ? nls.localize('cannotRunGotoSymbolInFile', "No symbol information for the file") : nls.localize('cannotRunGotoSymbol', "Open a text file first to go to a symbol"); + return canRun ? true : activeTextEditorControl !== null ? nls.localize('cannotRunGotoSymbolInFile', "No symbol information for the file") : nls.localize('cannotRunGotoSymbol', "Open a text file first to go to a symbol"); } getAutoFocus(searchValue: string): IAutoFocus { @@ -446,9 +446,9 @@ export class GotoSymbolHandler extends QuickOpenHandler { } private async doGetActiveOutline(): Promise { - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - let model = activeTextEditorWidget.getModel(); + const activeTextEditorControl = this.editorService.activeTextEditorControl; + if (activeTextEditorControl) { + let model = activeTextEditorControl.getModel(); if (model && (model).modified && (model).original) { model = (model).modified; // Support for diff editor models } @@ -512,9 +512,9 @@ export class GotoSymbolHandler extends QuickOpenHandler { private clearDecorations(): void { const rangeHighlightDecorationId = this.rangeHighlightDecorationId; if (rangeHighlightDecorationId) { - this.editorService.visibleControls.forEach(editor => { - if (editor.group && editor.group.id === rangeHighlightDecorationId.groupId) { - const editorControl = editor.getControl(); + this.editorService.visibleEditorPanes.forEach(editorPane => { + if (editorPane.group && editorPane.group.id === rangeHighlightDecorationId.groupId) { + const editorControl = editorPane.getControl(); editorControl.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => { changeAccessor.deltaDecorations([ rangeHighlightDecorationId.lineDecorationId, @@ -538,9 +538,9 @@ export class GotoSymbolHandler extends QuickOpenHandler { // Restore selection if canceled if (canceled && this.lastKnownEditorViewState) { - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (activeTextEditorWidget) { - activeTextEditorWidget.restoreViewState(this.lastKnownEditorViewState); + const activeTextEditorControl = this.editorService.activeTextEditorControl; + if (activeTextEditorControl) { + activeTextEditorControl.restoreViewState(this.lastKnownEditorViewState); } this.lastKnownEditorViewState = null; diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 93bdc66a06..88aa48e755 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -50,7 +50,7 @@ import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views' import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Event } from 'vs/base/common/event'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -402,7 +402,7 @@ class HelpPanel extends ViewPane { this.tree.setInput(model); - const helpItemNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false })); + const helpItemNavigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false })); this._register(Event.debounce(helpItemNavigator.onDidOpenResource, (last, event) => event, 75, true)(e => { e.element.handleClick(); @@ -584,26 +584,100 @@ Registry.as(WorkbenchActionExtensions.WorkbenchActions nls.localize('view', "View") ); +class VisibleProgress { -class ProgressReporter { - private _currentProgress: IProgress | null = null; - private lastReport: string | null = null; + private _isDisposed: boolean; + private _lastReport: string | null; + private _currentProgressPromiseResolve: (() => void) | null; + private _currentProgress: IProgress | null; + private _currentTimer: ReconnectionTimer2 | null; - constructor(currentProgress: IProgress | null) { - this._currentProgress = currentProgress; + public get lastReport(): string | null { + return this._lastReport; } - set currentProgress(progress: IProgress) { - this._currentProgress = progress; + constructor(progressService: IProgressService, location: ProgressLocation, initialReport: string | null, buttons: string[], onDidCancel: (choice: number | undefined, lastReport: string | null) => void) { + this._isDisposed = false; + this._lastReport = initialReport; + this._currentProgressPromiseResolve = null; + this._currentProgress = null; + this._currentTimer = null; + + const promise = new Promise((resolve) => this._currentProgressPromiseResolve = resolve); + + progressService.withProgress( + { location: location, buttons: buttons }, + (progress) => { if (!this._isDisposed) { this._currentProgress = progress; } return promise; }, + (choice) => onDidCancel(choice, this._lastReport) + ); + + if (this._lastReport) { + this.report(); + } } - report(message?: string) { + public dispose(): void { + this._isDisposed = true; + if (this._currentProgressPromiseResolve) { + this._currentProgressPromiseResolve(); + this._currentProgressPromiseResolve = null; + } + this._currentProgress = null; + if (this._currentTimer) { + this._currentTimer.dispose(); + this._currentTimer = null; + } + } + + public report(message?: string) { if (message) { - this.lastReport = message; + this._lastReport = message; } - if (this.lastReport && this._currentProgress) { - this._currentProgress.report({ message: this.lastReport }); + if (this._lastReport && this._currentProgress) { + this._currentProgress.report({ message: this._lastReport }); + } + } + + public startTimer(completionTime: number): void { + this.stopTimer(); + this._currentTimer = new ReconnectionTimer2(this, completionTime); + } + + public stopTimer(): void { + if (this._currentTimer) { + this._currentTimer.dispose(); + this._currentTimer = null; + } + } +} + +class ReconnectionTimer2 implements IDisposable { + private readonly _parent: VisibleProgress; + private readonly _completionTime: number; + private readonly _token: any; + + constructor(parent: VisibleProgress, completionTime: number) { + this._parent = parent; + this._completionTime = completionTime; + this._token = setInterval(() => this._render(), 1000); + this._render(); + } + + public dispose(): void { + clearInterval(this._token); + } + + private _render() { + const remainingTimeMs = this._completionTime - Date.now(); + if (remainingTimeMs < 0) { + return; + } + const remainingTime = Math.ceil(remainingTimeMs / 1000); + if (remainingTime === 1) { + this._parent.report(nls.localize('reconnectionWaitOne', "Attempting to reconnect in {0} second...", remainingTime)); + } else { + this._parent.report(nls.localize('reconnectionWaitMany', "Attempting to reconnect in {0} seconds...", remainingTime)); } } } @@ -618,58 +692,41 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { ) { const connection = remoteAgentService.getConnection(); if (connection) { - let currentProgressPromiseResolve: (() => void) | null = null; - let progressReporter: ProgressReporter | null = null; - let lastLocation: ProgressLocation | null = null; - let currentTimer: ReconnectionTimer | null = null; + let visibleProgress: VisibleProgress | null = null; + let lastLocation: ProgressLocation.Dialog | ProgressLocation.Notification | null = null; let reconnectWaitEvent: ReconnectionWaitEvent | null = null; let disposableListener: IDisposable | null = null; - function showProgress(location: ProgressLocation, buttons: { label: string, callback: () => void }[]) { - if (currentProgressPromiseResolve) { - currentProgressPromiseResolve(); + function showProgress(location: ProgressLocation.Dialog | ProgressLocation.Notification, buttons: { label: string, callback: () => void }[], initialReport: string | null = null): VisibleProgress { + if (visibleProgress) { + visibleProgress.dispose(); + visibleProgress = null; } - const promise = new Promise((resolve) => currentProgressPromiseResolve = resolve); lastLocation = location; - if (location === ProgressLocation.Dialog) { - // Show dialog - progressService!.withProgress( - { location: ProgressLocation.Dialog, buttons: buttons.map(button => button.label) }, - (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; }, - (choice?) => { - // Handle choice from dialog - if (typeof choice !== 'undefined' && buttons[choice]) { - buttons[choice].callback(); - } else { - showProgress(ProgressLocation.Notification, buttons); - } - - progressReporter!.report(); - }); - } else { - // Show notification - progressService!.withProgress( - { location: ProgressLocation.Notification, buttons: buttons.map(button => button.label) }, - (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; }, - (choice?) => { - // Handle choice from dialog - if (typeof choice !== 'undefined' && buttons[choice]) { - buttons[choice].callback(); + return new VisibleProgress( + progressService, location, initialReport, buttons.map(button => button.label), + (choice, lastReport) => { + // Handle choice from dialog + if (typeof choice !== 'undefined' && buttons[choice]) { + buttons[choice].callback(); + } else { + if (location === ProgressLocation.Dialog) { + visibleProgress = showProgress(ProgressLocation.Notification, buttons, lastReport); } else { hideProgress(); } - }); - } + } + } + ); } function hideProgress() { - if (currentProgressPromiseResolve) { - currentProgressPromiseResolve(); + if (visibleProgress) { + visibleProgress.dispose(); + visibleProgress = null; } - - currentProgressPromiseResolve = null; } const reconnectButton = { @@ -689,9 +746,8 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { }; connection.onDidStateChange((e) => { - if (currentTimer) { - currentTimer.dispose(); - currentTimer = null; + if (visibleProgress) { + visibleProgress.stopTimer(); } if (disposableListener) { @@ -700,33 +756,27 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { } switch (e.type) { case PersistentConnectionEventType.ConnectionLost: - if (!currentProgressPromiseResolve) { - progressReporter = new ProgressReporter(null); - showProgress(ProgressLocation.Dialog, [reconnectButton, reloadButton]); + if (!visibleProgress) { + visibleProgress = showProgress(ProgressLocation.Dialog, [reconnectButton, reloadButton]); } - - progressReporter!.report(nls.localize('connectionLost', "Connection Lost")); + visibleProgress.report(nls.localize('connectionLost', "Connection Lost")); break; case PersistentConnectionEventType.ReconnectionWait: - hideProgress(); reconnectWaitEvent = e; - showProgress(lastLocation || ProgressLocation.Notification, [reconnectButton, reloadButton]); - currentTimer = new ReconnectionTimer(progressReporter!, Date.now() + 1000 * e.durationSeconds); + visibleProgress = showProgress(lastLocation || ProgressLocation.Notification, [reconnectButton, reloadButton]); + visibleProgress.startTimer(Date.now() + 1000 * e.durationSeconds); break; case PersistentConnectionEventType.ReconnectionRunning: - hideProgress(); - showProgress(lastLocation || ProgressLocation.Notification, [reloadButton]); - progressReporter!.report(nls.localize('reconnectionRunning', "Attempting to reconnect...")); + visibleProgress = showProgress(lastLocation || ProgressLocation.Notification, [reloadButton]); + visibleProgress.report(nls.localize('reconnectionRunning', "Attempting to reconnect...")); // Register to listen for quick input is opened disposableListener = contextKeyService.onDidChangeContext((contextKeyChangeEvent) => { const reconnectInteraction = new Set(['inQuickOpen']); if (contextKeyChangeEvent.affectsSome(reconnectInteraction)) { // Need to move from dialog if being shown and user needs to type in a prompt - if (lastLocation === ProgressLocation.Dialog && progressReporter !== null) { - hideProgress(); - showProgress(ProgressLocation.Notification, [reloadButton]); - progressReporter.report(); + if (lastLocation === ProgressLocation.Dialog && visibleProgress !== null) { + visibleProgress = showProgress(ProgressLocation.Notification, [reloadButton], visibleProgress.lastReport); } } }); @@ -734,7 +784,6 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { break; case PersistentConnectionEventType.ReconnectionPermanentFailure: hideProgress(); - progressReporter = null; dialogService.show(Severity.Error, nls.localize('reconnectionPermanentFailure', "Cannot reconnect. Please reload the window."), [nls.localize('reloadWindow', "Reload Window"), nls.localize('cancel', "Cancel")], { cancelId: 1 }).then(result => { // Reload the window @@ -745,7 +794,6 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { break; case PersistentConnectionEventType.ConnectionGain: hideProgress(); - progressReporter = null; break; } }); @@ -753,35 +801,5 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { } } -class ReconnectionTimer implements IDisposable { - private readonly _progressReporter: ProgressReporter; - private readonly _completionTime: number; - private readonly _token: any; - - constructor(progressReporter: ProgressReporter, completionTime: number) { - this._progressReporter = progressReporter; - this._completionTime = completionTime; - this._token = setInterval(() => this._render(), 1000); - this._render(); - } - - public dispose(): void { - clearInterval(this._token); - } - - private _render() { - const remainingTimeMs = this._completionTime - Date.now(); - if (remainingTimeMs < 0) { - return; - } - const remainingTime = Math.ceil(remainingTimeMs / 1000); - if (remainingTime === 1) { - this._progressReporter.report(nls.localize('reconnectionWaitOne', "Attempting to reconnect in {0} second...", remainingTime)); - } else { - this._progressReporter.report(nls.localize('reconnectionWaitMany', "Attempting to reconnect in {0} seconds...", remainingTime)); - } - } -} - const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index c60b4eac52..dae1e39dd1 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/tunnelView'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IViewDescriptor, IEditableData, IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; -import { WorkbenchAsyncDataTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree, ResourceNavigator } from 'vs/platform/list/browser/listService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -159,7 +159,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { this._candidates.forEach(value => { const key = MakeAddress(value.host, value.port); if (!this.model.forwarded.has(key) && !this.model.detected.has(key)) { - candidates.push(new TunnelItem(TunnelType.Candidate, value.host, value.port, undefined, false, undefined, value.detail)); + candidates.push(new TunnelItem(TunnelType.Candidate, value.host, value.port, undefined, undefined, false, undefined, value.detail)); } }); return candidates; @@ -375,7 +375,7 @@ interface ITunnelGroup { class TunnelItem implements ITunnelItem { static createFromTunnel(tunnel: Tunnel, type: TunnelType = TunnelType.Forwarded, closeable?: boolean) { - return new TunnelItem(type, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, closeable === undefined ? tunnel.closeable : closeable, tunnel.name, tunnel.description); + return new TunnelItem(type, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, tunnel.localPort, closeable === undefined ? tunnel.closeable : closeable, tunnel.name, tunnel.description); } constructor( @@ -383,6 +383,7 @@ class TunnelItem implements ITunnelItem { public remoteHost: string, public remotePort: number, public localAddress?: string, + public localPort?: number, public closeable?: boolean, public name?: string, private _description?: string, @@ -415,11 +416,6 @@ class TunnelItem implements ITunnelItem { } } -function isHostAndPort(address: string | undefined): boolean { - const result = address ? address.match(/^(localhost|([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)|([0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+:[0-9]+)):[0-9]+$/) : []; - return (!!result && result.length > 0); -} - export const TunnelTypeContextKey = new RawContextKey('tunnelType', TunnelType.Add); export const TunnelCloseableContextKey = new RawContextKey('tunnelCloseable', false); const TunnelViewFocusContextKey = new RawContextKey('tunnelViewFocus', false); @@ -528,7 +524,7 @@ export class TunnelPanel extends ViewPane { this.tree.updateChildren(undefined, true); })); - const navigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false })); + const navigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false })); this._register(Event.debounce(navigator.onDidOpenResource, (last, event) => event, 75, true)(e => { if (e.element && (e.element.tunnelType === TunnelType.Add)) { @@ -577,7 +573,7 @@ export class TunnelPanel extends ViewPane { this.tunnelViewSelectionContext.set(item); this.tunnelTypeContext.set(item.tunnelType); this.tunnelCloseableContext.set(!!item.closeable); - this.portChangableContextKey.set(isHostAndPort(item.localAddress)); + this.portChangableContextKey.set(!!item.localPort); } else { this.tunnelTypeContext.reset(); this.tunnelViewSelectionContext.reset(); @@ -600,7 +596,7 @@ export class TunnelPanel extends ViewPane { this.tree!.setFocus([node]); this.tunnelTypeContext.set(node.tunnelType); this.tunnelCloseableContext.set(!!node.closeable); - this.portChangableContextKey.set(isHostAndPort(node.localAddress)); + this.portChangableContextKey.set(!!node.localPort); } else { this.tunnelTypeContext.set(TunnelType.Add); this.tunnelCloseableContext.set(false); @@ -1039,7 +1035,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ id: ChangeLocalPortAction.ID, title: ChangeLocalPortAction.LABEL, }, - when: TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded) + when: ContextKeyExpr.and(TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded), PortChangableContextKey) })); MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ group: '0_manage', diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 3e3e662fce..73af5d81fb 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -38,6 +38,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { RemoteConnectionState, Deprecated_RemoteAuthorityContext, RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; import { IDownloadService } from 'vs/platform/download/common/download'; import { OpenLocalFileFolderCommand, OpenLocalFileCommand, OpenLocalFolderCommand, SaveLocalFileCommand } from 'vs/workbench/services/dialogs/browser/simpleFileDialog'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; const WINDOW_ACTIONS_COMMAND_ID = 'workbench.action.remote.showMenu'; const CLOSE_REMOTE_COMMAND_ID = 'workbench.action.remote.close'; @@ -331,7 +332,7 @@ class RemoteTelemetryEnablementUpdater extends Disposable implements IWorkbenchC class RemoteEmptyWorkbenchPresentation extends Disposable implements IWorkbenchContribution { constructor( - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IConfigurationService configurationService: IConfigurationService, @ICommandService commandService: ICommandService, diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index 01efac2831..f18a0e5be0 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -159,8 +159,7 @@ export class SCMStatusController implements IWorkbenchContribution { disposables.add(this.statusbarService.addEntry({ text: c.title, tooltip: `${label} - ${c.tooltip}`, - command: c.id, - arguments: c.arguments + command: c }, 'status.scm', localize('status.scm', "Source Control"), MainThreadStatusBarAlignment.LEFT, 10000)); } diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 934dc021a0..d85d8f7de7 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -19,7 +19,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { URI } from 'vs/base/common/uri'; import { ISCMService, ISCMRepository, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { registerThemingParticipant, ITheme, ICssStyleCollector, themeColorFromId, IThemeService } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector, themeColorFromId, IThemeService } from 'vs/platform/theme/common/themeService'; import { registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; import { Color, RGBA } from 'vs/base/common/color'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; @@ -140,7 +140,7 @@ function getChangeType(change: IChange): ChangeType { } } -function getChangeTypeColor(theme: ITheme, changeType: ChangeType): Color | undefined { +function getChangeTypeColor(theme: IColorTheme, changeType: ChangeType): Color | undefined { switch (changeType) { case ChangeType.Modify: return theme.getColor(editorGutterModifiedBackground); case ChangeType.Add: return theme.getColor(editorGutterAddedBackground); @@ -183,8 +183,8 @@ class DirtyDiffWidget extends PeekViewWidget { ) { super(editor, { isResizeable: true, frameWidth: 1, keepEditorSelection: true }); - this._disposables.add(themeService.onThemeChange(this._applyTheme, this)); - this._applyTheme(themeService.getTheme()); + this._disposables.add(themeService.onDidColorThemeChange(this._applyTheme, this)); + this._applyTheme(themeService.getColorTheme()); this.contextKeyService = contextKeyService.createScoped(); this.contextKeyService.createKey('originalResourceScheme', this.model.original!.uri.scheme); @@ -230,7 +230,7 @@ class DirtyDiffWidget extends PeekViewWidget { this.renderTitle(); const changeType = getChangeType(change); - const changeTypeColor = getChangeTypeColor(this.themeService.getTheme(), changeType); + const changeTypeColor = getChangeTypeColor(this.themeService.getColorTheme(), changeType); this.style({ frameColor: changeTypeColor, arrowColor: changeTypeColor }); this._actionbarWidget!.context = [this.model.modified!.uri, this.model.changes, index]; @@ -343,7 +343,7 @@ class DirtyDiffWidget extends PeekViewWidget { this.diffEditor.revealLinesInCenter(start, end, ScrollType.Immediate); } - private _applyTheme(theme: ITheme) { + private _applyTheme(theme: IColorTheme) { const borderColor = theme.getColor(peekViewBorder) || Color.transparent; this.style({ arrowColor: borderColor, @@ -1317,7 +1317,7 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor // or not. Needs context from the editor, to know whether it is a diff editor, in place editor // etc. private onEditorsChanged(): void { - const models = this.editorService.visibleTextEditorWidgets + const models = this.editorService.visibleTextEditorControls // only interested in code editor widgets .filter(c => c instanceof CodeEditorWidget) @@ -1372,7 +1372,7 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor registerEditorContribution(DirtyDiffController.ID, DirtyDiffController); -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const editorGutterModifiedBackgroundColor = theme.getColor(editorGutterModifiedBackground); if (editorGutterModifiedBackgroundColor) { collector.addRule(` diff --git a/src/vs/workbench/contrib/scm/browser/mainPane.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts index d9cdb14cbb..9e8df53668 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPane.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -212,8 +212,8 @@ export class MainPane extends ViewPane { }); this._register(renderer.onDidRenderElement(e => this.list.updateWidth(this.viewModel.repositories.indexOf(e)), null)); - this._register(this.list.onSelectionChange(this.onListSelectionChange, this)); - this._register(this.list.onFocusChange(this.onListFocusChange, this)); + this._register(this.list.onDidChangeSelection(this.onListSelectionChange, this)); + this._register(this.list.onDidChangeFocus(this.onListFocusChange, this)); this._register(this.list.onContextMenu(this.onListContextMenu, this)); this._register(this.viewModel.onDidChangeVisibleRepositories(this.updateListSelection, this)); diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index 2b30baa53b..0ff838b909 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -24,7 +24,7 @@ import { IAction, IActionViewItem, ActionRunner, Action } from 'vs/base/common/a import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { SCMMenus } from './menus'; import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IThemeService, LIGHT, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IThemeService, LIGHT, registerThemingParticipant, IFileIconTheme } from 'vs/platform/theme/common/themeService'; import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar } from './util'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; @@ -45,7 +45,6 @@ import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/vie import { localize } from 'vs/nls'; import { flatten, find } from 'vs/base/common/arrays'; import { memoize } from 'vs/base/common/decorators'; -import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; @@ -67,6 +66,9 @@ import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { Schemas } from 'vs/base/common/network'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ModesHoverController } from 'vs/editor/contrib/hover/hover'; +import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector'; +import { LinkDetector } from 'vs/editor/contrib/links/links'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -206,7 +208,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer().workbench.editor.enablePreviewFromQuickOpen, @@ -167,7 +167,11 @@ export class OpenFileHandler extends QuickOpenHandler { } else { - complete = await this.searchService.fileSearch(this.queryBuilder.file(this.contextService.getWorkspace().folders.map(folder => folder.uri), queryOptions), token); + let fileQuery = this.queryBuilder.file( + this.contextService.getWorkspace().folders, + queryOptions + ); + complete = await this.searchService.fileSearch(fileQuery, token); } const results: QuickOpenEntry[] = []; @@ -238,10 +242,7 @@ export class OpenFileHandler extends QuickOpenHandler { sortByScore: true, }; - const folderResources = this.contextService.getWorkspace().folders.map(folder => folder.uri); - const query = this.queryBuilder.file(folderResources, options); - - return query; + return this.queryBuilder.file(this.contextService.getWorkspace().folders, options); } get isCacheLoaded(): boolean { diff --git a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts index 5ac0284855..e9e5e0812a 100644 --- a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts @@ -15,7 +15,7 @@ import * as strings from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { SymbolKinds, SymbolTag } from 'vs/editor/common/modes'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceSymbolProvider, getWorkspaceSymbols, IWorkspaceSymbol } from 'vs/workbench/contrib/search/common/search'; @@ -115,8 +115,8 @@ class SymbolEntry extends EditorQuickOpenEntry { return mode === Mode.OPEN; } - getInput(): IResourceInput { - const input: IResourceInput = { + getInput(): IResourceEditorInput { + const input: IResourceEditorInput = { resource: this.bearing.location.uri, options: { pinned: !this.configurationService.getValue().workbench.editor.enablePreviewFromQuickOpen, diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index a71c5acd64..eef1092482 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -52,9 +52,9 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { assertType, assertIsDefined } from 'vs/base/common/types'; -import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true); registerSingleton(ISearchHistoryService, SearchHistoryService, true); @@ -72,7 +72,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: accessor => { const contextService = accessor.get(IContextKeyService).getContext(document.activeElement); if (contextService.getValue(SearchEditorConstants.InSearchEditor.serialize())) { - (accessor.get(IEditorService).activeControl as SearchEditor).toggleQueryDetails(); + (accessor.get(IEditorService).activeEditorPane as SearchEditor).toggleQueryDetails(); } else if (contextService.getValue(Constants.SearchViewFocusedKey.serialize())) { const searchView = getSearchView(accessor.get(IViewsService)); assertIsDefined(searchView).toggleQueryDetails(); @@ -80,6 +80,19 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.searchEditor.deleteResultBlock', + weight: KeybindingWeight.WorkbenchContrib, + when: SearchEditorConstants.InSearchEditor, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Backspace, + handler: accessor => { + const contextService = accessor.get(IContextKeyService).getContext(document.activeElement); + if (contextService.getValue(SearchEditorConstants.InSearchEditor.serialize())) { + (accessor.get(IEditorService).activeEditorPane as SearchEditor).deleteResultBlock(); + } + } +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: Constants.FocusSearchFromResults, weight: KeybindingWeight.WorkbenchContrib, @@ -486,7 +499,7 @@ class ShowAllSymbolsAction extends Action { const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: nls.localize('name', "Search"), - ctorDescriptor: new SyncDescriptor(SearchViewPaneContainer), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), hideIfEmpty: true, icon: 'codicon-search', order: 1 diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index c50131924d..16cb5f0302 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -19,7 +19,7 @@ import { getSelectionKeyboardEvent, WorkbenchObjectTree } from 'vs/platform/list import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; -import { FolderMatch, FileMatch, FileMatchOrMatch, FolderMatchWithResource, Match, RenderableMatch, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { FolderMatch, FileMatch, FolderMatchWithResource, Match, RenderableMatch, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ISearchConfiguration, VIEW_ID } from 'vs/workbench/services/search/common/search'; @@ -96,7 +96,7 @@ export class FocusNextInputAction extends Action { const input = this.editorService.activeEditor; if (input instanceof SearchEditorInput) { // cast as we cannot import SearchEditor as a value b/c cyclic dependency. - (this.editorService.activeControl as SearchEditor).focusNextInput(); + (this.editorService.activeEditorPane as SearchEditor).focusNextInput(); } const searchView = getSearchView(this.viewsService); @@ -121,7 +121,7 @@ export class FocusPreviousInputAction extends Action { const input = this.editorService.activeEditor; if (input instanceof SearchEditorInput) { // cast as we cannot import SearchEditor as a value b/c cyclic dependency. - (this.editorService.activeControl as SearchEditor).focusPrevInput(); + (this.editorService.activeEditorPane as SearchEditor).focusPrevInput(); } const searchView = getSearchView(this.viewsService); @@ -498,7 +498,7 @@ export class FocusNextSearchResultAction extends Action { const input = this.editorService.activeEditor; if (input instanceof SearchEditorInput) { // cast as we cannot import SearchEditor as a value b/c cyclic dependency. - return (this.editorService.activeControl as SearchEditor).focusNextResult(); + return (this.editorService.activeEditorPane as SearchEditor).focusNextResult(); } return openSearchView(this.viewsService).then(searchView => { @@ -524,7 +524,7 @@ export class FocusPreviousSearchResultAction extends Action { const input = this.editorService.activeEditor; if (input instanceof SearchEditorInput) { // cast as we cannot import SearchEditor as a value b/c cyclic dependency. - return (this.editorService.activeControl as SearchEditor).focusPreviousResult(); + return (this.editorService.activeEditorPane as SearchEditor).focusPreviousResult(); } return openSearchView(this.viewsService).then(searchView => { @@ -711,16 +711,16 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { }); } - private getElementToFocusAfterReplace(): Match { - const navigator: ITreeNavigator = this.viewer.navigate(); + private getElementToFocusAfterReplace(): RenderableMatch { + const navigator: ITreeNavigator = this.viewer.navigate(); let fileMatched = false; - let elementToFocus: any = null; + let elementToFocus: RenderableMatch | null = null; do { elementToFocus = navigator.current(); if (elementToFocus instanceof Match) { if (elementToFocus.parent().id() === this.element.parent().id()) { fileMatched = true; - if (this.element.range().getStartPosition().isBeforeOrEqual((elementToFocus).range().getStartPosition())) { + if (this.element.range().getStartPosition().isBeforeOrEqual(elementToFocus.range().getStartPosition())) { // Closest next match in the same file break; } @@ -735,10 +735,10 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { } } } while (!!navigator.next()); - return elementToFocus; + return elementToFocus!; } - private async getElementToShowReplacePreview(elementToFocus: FileMatchOrMatch): Promise { + private async getElementToShowReplacePreview(elementToFocus: RenderableMatch): Promise { if (this.hasSameParent(elementToFocus)) { return elementToFocus; } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 31e3cde25a..29e15ee355 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -31,17 +31,17 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie import { IConfirmation, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TreeResourceNavigator, WorkbenchObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; +import { ResourceNavigator, WorkbenchObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IProgressService, IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; -import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, VIEW_ID, SearchSortOrder, SearchCompletionExitCode } from 'vs/workbench/services/search/common/search'; +import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, SearchSortOrder, SearchCompletionExitCode } from 'vs/workbench/services/search/common/search'; import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; -import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { OpenFileFolderAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ResourceLabels } from 'vs/workbench/browser/labels'; -import { IEditor } from 'vs/workbench/common/editor'; +import { IEditorPane } from 'vs/workbench/common/editor'; import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction, IFindInFilesArgs, appendKeyBindingLabel, ExpandAllAction, ToggleCollapseAndExpandAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate, SearchDND } from 'vs/workbench/contrib/search/browser/searchResultsView'; @@ -149,6 +149,8 @@ export class SearchView extends ViewPane { private triggerQueryDelayer: Delayer; private pauseSearching = false; + private treeAccessibilityProvider: SearchAccessibilityProvider; + constructor( options: IViewPaneOptions, @IFileService private readonly fileService: IFileService, @@ -177,7 +179,7 @@ export class SearchView extends ViewPane { @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...options, id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.container = dom.$('.search-view'); @@ -240,6 +242,8 @@ export class SearchView extends ViewPane { this.refreshAction = this._register(this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL)); this.cancelAction = this._register(this.instantiationService.createInstance(CancelSearchAction, CancelSearchAction.ID, CancelSearchAction.LABEL)); this.toggleCollapseAction = this._register(this.instantiationService.createInstance(ToggleCollapseAndExpandAction, ToggleCollapseAndExpandAction.ID, ToggleCollapseAndExpandAction.LABEL, collapseDeepestExpandedLevelAction, expandAllAction)); + + this.treeAccessibilityProvider = this.instantiationService.createInstance(SearchAccessibilityProvider, this.viewModel); } getContainer(): HTMLElement { @@ -716,7 +720,7 @@ export class SearchView extends ViewPane { ], { identityProvider, - accessibilityProvider: this.instantiationService.createInstance(SearchAccessibilityProvider, this.viewModel), + accessibilityProvider: this.treeAccessibilityProvider, dnd: this.instantiationService.createInstance(SearchDND), multipleSelectionSupport: false, overrideStyles: { @@ -728,7 +732,7 @@ export class SearchView extends ViewPane { this.toggleCollapseStateDelayer.trigger(() => this.toggleCollapseAction.onTreeCollapseStateChange()) )); - const resourceNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true, openOnSelection: false })); + const resourceNavigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: true, openOnSelection: false })); this._register(Event.debounce(resourceNavigator.onDidOpenResource, (last, event) => event, 75, true)(options => { if (options.element instanceof Match) { const selectedMatch: Match = options.element; @@ -819,6 +823,8 @@ export class SearchView extends ViewPane { } this.tree.setFocus([next], getSelectionKeyboardEvent(undefined, false)); this.tree.reveal(next); + const ariaLabel = this.treeAccessibilityProvider.getAriaLabel(next); + if (ariaLabel) { aria.alert(ariaLabel); } } } @@ -848,6 +854,8 @@ export class SearchView extends ViewPane { } this.tree.setFocus([prev], getSelectionKeyboardEvent(undefined, false)); this.tree.reveal(prev); + const ariaLabel = this.treeAccessibilityProvider.getAriaLabel(prev); + if (ariaLabel) { aria.alert(ariaLabel); } } } @@ -1046,26 +1054,26 @@ export class SearchView extends ViewPane { return null; } - let activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (isDiffEditor(activeTextEditorWidget)) { - if (activeTextEditorWidget.getOriginalEditor().hasTextFocus()) { - activeTextEditorWidget = activeTextEditorWidget.getOriginalEditor(); + let activeTextEditorControl = this.editorService.activeTextEditorControl; + if (isDiffEditor(activeTextEditorControl)) { + if (activeTextEditorControl.getOriginalEditor().hasTextFocus()) { + activeTextEditorControl = activeTextEditorControl.getOriginalEditor(); } else { - activeTextEditorWidget = activeTextEditorWidget.getModifiedEditor(); + activeTextEditorControl = activeTextEditorControl.getModifiedEditor(); } } - if (!isCodeEditor(activeTextEditorWidget) || !activeTextEditorWidget.hasModel()) { + if (!isCodeEditor(activeTextEditorControl) || !activeTextEditorControl.hasModel()) { return null; } - const range = activeTextEditorWidget.getSelection(); + const range = activeTextEditorControl.getSelection(); if (!range) { return null; } if (range.isEmpty() && !this.searchWidget.searchInput.getValue() && allowUnselectedWord) { - const wordAtPosition = activeTextEditorWidget.getModel().getWordAtPosition(range.getStartPosition()); + const wordAtPosition = activeTextEditorControl.getModel().getWordAtPosition(range.getStartPosition()); if (wordAtPosition) { return wordAtPosition.word; } @@ -1074,7 +1082,7 @@ export class SearchView extends ViewPane { if (!range.isEmpty()) { let searchText = ''; for (let i = range.startLineNumber; i <= range.endLineNumber; i++) { - let lineText = activeTextEditorWidget.getModel().getLineContent(i); + let lineText = activeTextEditorControl.getModel().getLineContent(i); if (i === range.endLineNumber) { lineText = lineText.substring(0, range.endColumn - 1); } @@ -1532,7 +1540,7 @@ export class SearchView extends ViewPane { this.openSettings('.exclude'); }; - private openSettings(query: string): Promise { + private openSettings(query: string): Promise { const options: ISettingsEditorOptions = { query }; return this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? this.preferencesService.openWorkspaceSettings(undefined, options) : @@ -1846,7 +1854,7 @@ export class SearchView extends ViewPane { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const matchHighlightColor = theme.getColor(editorFindMatchHighlight); if (matchHighlightColor) { collector.addRule(`.monaco-workbench .search-view .findInFileMatch { background-color: ${matchHighlightColor}; }`); @@ -1884,7 +1892,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const foregroundColor = theme.getColor(foreground); if (foregroundColor) { - const fgWithOpacity = new Color(new RGBA(foregroundColor.rgba.r, foregroundColor.rgba.g, foregroundColor.rgba.b, 0.5)); + const fgWithOpacity = new Color(new RGBA(foregroundColor.rgba.r, foregroundColor.rgba.g, foregroundColor.rgba.b, 0.65)); collector.addRule(`.vs-dark .search-view .message { color: ${fgWithOpacity}; }`); } }); diff --git a/src/vs/workbench/contrib/search/browser/searchViewlet.ts b/src/vs/workbench/contrib/search/browser/searchViewlet.ts deleted file mode 100644 index 13e3c550dd..0000000000 --- a/src/vs/workbench/contrib/search/browser/searchViewlet.ts +++ /dev/null @@ -1,42 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { VIEWLET_ID, VIEW_ID } from 'vs/workbench/services/search/common/search'; -import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; -import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IViewDescriptorService } from 'vs/workbench/common/views'; - - -export class SearchViewPaneContainer extends ViewPaneContainer { - - constructor( - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @ITelemetryService telemetryService: ITelemetryService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IStorageService protected storageService: IStorageService, - @IConfigurationService configurationService: IConfigurationService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService - ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); - } - - getSearchView(): SearchView | undefined { - const view = super.getView(VIEW_ID); - return view ? view as SearchView : undefined; - } -} diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 5e9ffb6ca8..5604c46149 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -634,10 +634,17 @@ export class SearchWidget extends Widget { this._onSearchSubmit.fire({ triggeredOnType, delay }); } - contextLines() { + getContextLines() { return this.showContextCheckbox.checked ? +this.contextLinesInput.value : 0; } + modifyContextLines(increase: boolean) { + const current = +this.contextLinesInput.value; + const modified = current + (increase ? 1 : -1); + this.showContextCheckbox.checked = modified !== 0; + this.contextLinesInput.value = '' + modified; + } + toggleContextLines() { this.showContextCheckbox.checked = !this.showContextCheckbox.checked; this.onContextLinesChanged(); diff --git a/src/vs/workbench/contrib/search/common/queryBuilder.ts b/src/vs/workbench/contrib/search/common/queryBuilder.ts index 149605d092..d7fd973d17 100644 --- a/src/vs/workbench/contrib/search/common/queryBuilder.ts +++ b/src/vs/workbench/contrib/search/common/queryBuilder.ts @@ -16,7 +16,7 @@ import { isMultilineRegexSource } from 'vs/editor/common/model/textModelSearch'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, WorkbenchState, toWorkspaceFolder, IWorkspaceFolderData } from 'vs/platform/workspace/common/workspace'; import { getExcludes, ICommonQueryProps, IFileQuery, IFolderQuery, IPatternInfo, ISearchConfiguration, ITextQuery, ITextSearchPreviewOptions, pathIncludedInQuery, QueryType } from 'vs/workbench/services/search/common/search'; import { Schemas } from 'vs/base/common/network'; @@ -94,7 +94,7 @@ export class QueryBuilder { return !folderConfig.search.useRipgrep; }); - const commonQuery = this.commonQuery(folderResources, options); + const commonQuery = this.commonQuery(folderResources?.map(toWorkspaceFolder), options); return { ...commonQuery, type: QueryType.Text, @@ -134,8 +134,8 @@ export class QueryBuilder { return newPattern; } - file(folderResources: uri[] | undefined, options: IFileQueryBuilderOptions = {}): IFileQuery { - const commonQuery = this.commonQuery(folderResources, options); + file(folders: IWorkspaceFolderData[], options: IFileQueryBuilderOptions = {}): IFileQuery { + const commonQuery = this.commonQuery(folders, options); return { ...commonQuery, type: QueryType.File, @@ -144,11 +144,11 @@ export class QueryBuilder { : options.filePattern, exists: options.exists, sortByScore: options.sortByScore, - cacheKey: options.cacheKey + cacheKey: options.cacheKey, }; } - private commonQuery(folderResources: uri[] = [], options: ICommonQueryBuilderOptions = {}): ICommonQueryProps { + private commonQuery(folderResources: IWorkspaceFolderData[] = [], options: ICommonQueryBuilderOptions = {}): ICommonQueryProps { let includeSearchPathsInfo: ISearchPathsInfo = {}; if (options.includePattern) { const includePattern = normalizeSlashes(options.includePattern); @@ -166,9 +166,10 @@ export class QueryBuilder { } // Build folderQueries from searchPaths, if given, otherwise folderResources + const includeFolderName = folderResources.length > 1; const folderQueries = (includeSearchPathsInfo.searchPaths && includeSearchPathsInfo.searchPaths.length ? includeSearchPathsInfo.searchPaths.map(searchPath => this.getFolderQueryForSearchPath(searchPath, options, excludeSearchPathsInfo)) : - folderResources.map(uri => this.getFolderQueryForRoot(uri, options, excludeSearchPathsInfo))) + folderResources.map(folder => this.getFolderQueryForRoot(folder, options, excludeSearchPathsInfo, includeFolderName))) .filter(query => !!query) as IFolderQuery[]; const queryProps: ICommonQueryProps = { @@ -403,7 +404,7 @@ export class QueryBuilder { } private getFolderQueryForSearchPath(searchPath: ISearchPathPattern, options: ICommonQueryBuilderOptions, searchPathExcludes: ISearchPathsInfo): IFolderQuery | null { - const rootConfig = this.getFolderQueryForRoot(searchPath.searchPath, options, searchPathExcludes); + const rootConfig = this.getFolderQueryForRoot(toWorkspaceFolder(searchPath.searchPath), options, searchPathExcludes, false); if (!rootConfig) { return null; } @@ -416,10 +417,10 @@ export class QueryBuilder { }; } - private getFolderQueryForRoot(folder: uri, options: ICommonQueryBuilderOptions, searchPathExcludes: ISearchPathsInfo): IFolderQuery | null { + private getFolderQueryForRoot(folder: IWorkspaceFolderData, options: ICommonQueryBuilderOptions, searchPathExcludes: ISearchPathsInfo, includeFolderName: boolean): IFolderQuery | null { let thisFolderExcludeSearchPathPattern: glob.IExpression | undefined; if (searchPathExcludes.searchPaths) { - const thisFolderExcludeSearchPath = searchPathExcludes.searchPaths.filter(sp => isEqual(sp.searchPath, folder))[0]; + const thisFolderExcludeSearchPath = searchPathExcludes.searchPaths.filter(sp => isEqual(sp.searchPath, folder.uri))[0]; if (thisFolderExcludeSearchPath && !thisFolderExcludeSearchPath.pattern) { // entire folder is excluded return null; @@ -428,7 +429,7 @@ export class QueryBuilder { } } - const folderConfig = this.configurationService.getValue({ resource: folder }); + const folderConfig = this.configurationService.getValue({ resource: folder.uri }); const settingExcludes = this.getExcludesForFolder(folderConfig, options); const excludePattern: glob.IExpression = { ...(settingExcludes || {}), @@ -436,7 +437,8 @@ export class QueryBuilder { }; return { - folder, + folder: folder.uri, + folderName: includeFolderName ? folder.name : undefined, excludePattern: Object.keys(excludePattern).length > 0 ? excludePattern : undefined, fileEncoding: folderConfig.files && folderConfig.files.encoding, disregardIgnoreFiles: typeof options.disregardIgnoreFiles === 'boolean' ? options.disregardIgnoreFiles : !folderConfig.search.useIgnoreFiles, diff --git a/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts b/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts index 4195d672bc..8944fdffa9 100644 --- a/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts @@ -13,8 +13,9 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { IFolderQuery, IPatternInfo, QueryType, ITextQuery, IFileQuery } from 'vs/workbench/services/search/common/search'; import { IWorkspaceContextService, toWorkspaceFolder, Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { ISearchPathsInfo, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; -import { TestContextService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; import { isWindows } from 'vs/base/common/platform'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; const DEFAULT_EDITOR_CONFIG = {}; const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true, useGlobalIgnoreFiles: true }; @@ -25,6 +26,7 @@ suite('QueryBuilder', () => { const PATTERN_INFO: IPatternInfo = { pattern: 'a' }; const ROOT_1 = fixPath('/foo/root1'); const ROOT_1_URI = getUri(ROOT_1); + const ROOT_1_NAMED_FOLDER = toWorkspaceFolder(ROOT_1_URI); const WS_CONFIG_PATH = getUri('/bar/test.code-workspace'); // location of the workspace file (not important except that it is a file URI) let instantiationService: TestInstantiationService; @@ -89,7 +91,10 @@ suite('QueryBuilder', () => { test('does not split glob pattern when expandPatterns disabled', () => { assertEqualQueries( - queryBuilder.file([ROOT_1_URI], { includePattern: '**/foo, **/bar' }), + queryBuilder.file( + [ROOT_1_NAMED_FOLDER], + { includePattern: '**/foo, **/bar' }, + ), { folderQueries: [{ folder: ROOT_1_URI @@ -362,7 +367,7 @@ suite('QueryBuilder', () => { const content = 'content'; assertEqualQueries( queryBuilder.file( - undefined, + [], { filePattern: ` ${content} ` } ), { @@ -902,10 +907,13 @@ suite('QueryBuilder', () => { suite('file', () => { test('simple file query', () => { const cacheKey = 'asdf'; - const query = queryBuilder.file([ROOT_1_URI], { - cacheKey, - sortByScore: true - }); + const query = queryBuilder.file( + [ROOT_1_NAMED_FOLDER], + { + cacheKey, + sortByScore: true + }, + ); assert.equal(query.folderQueries.length, 1); assert.equal(query.cacheKey, cacheKey); diff --git a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index 9daaa1448c..7612ac77f1 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -13,8 +13,8 @@ import { IFileMatch, ITextSearchMatch, OneLineRange, QueryType, SearchSortOrder import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { FileMatch, Match, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; -import { TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; import { isWindows } from 'vs/base/common/platform'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; suite('Search - Viewlet', () => { let instantiation: TestInstantiationService; diff --git a/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts b/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts index bdc927332f..8d78b8f5da 100644 --- a/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts @@ -8,9 +8,9 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IWorkspaceContextService, toWorkspaceFolder, Workspace } from 'vs/platform/workspace/common/workspace'; import { ISearchPathsInfo, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; -import { TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestEnvironmentService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { assertEqualSearchPathResults, getUri, patternsToIExpression, globalGlob, fixPath } from 'vs/workbench/contrib/search/test/browser/queryBuilder.test'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; const DEFAULT_EDITOR_CONFIG = {}; const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true, useGlobalIgnoreFiles: true }; diff --git a/src/vs/workbench/contrib/searchEditor/browser/constants.ts b/src/vs/workbench/contrib/searchEditor/browser/constants.ts index da545a328a..42c002b760 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/constants.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/constants.ts @@ -7,11 +7,15 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const OpenInEditorCommandId = 'search.action.openInEditor'; export const OpenNewEditorCommandId = 'search.action.openNewEditor'; +export const OpenNewEditorToSideCommandId = 'search.action.openNewEditorToSide'; export const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive'; export const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord'; export const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex'; export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines'; +export const IncreaseSearchEditorContextLinesCommandId = 'increaseSearchEditorContextLines'; +export const DecreaseSearchEditorContextLinesCommandId = 'decreaseSearchEditorContextLines'; + export const RerunSearchEditorSearchCommandId = 'rerunSearchEditorSearch'; export const CleanSearchEditorStateCommandId = 'cleanSearchEditorState'; export const SelectAllSearchEditorMatchesCommandId = 'selectAllSearchEditorMatches'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 8686ad86b6..63d357c5d7 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -24,7 +24,7 @@ import { Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputF import * as SearchConstants from 'vs/workbench/contrib/search/common/constants'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; -import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, selectAllSearchEditorMatchesCommand, RerunSearchEditorSearchAction } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; +import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, selectAllSearchEditorMatchesCommand, RerunSearchEditorSearchAction, OpenSearchEditorToSideAction, modifySearchEditorContextLinesCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -151,6 +151,24 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_L } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: SearchEditorConstants.IncreaseSearchEditorContextLinesCommandId, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor), + handler: (accessor: ServicesAccessor) => modifySearchEditorContextLinesCommand(accessor, true), + primary: KeyMod.Alt | KeyCode.KEY_L, + mac: { primary: KeyMod.Alt | KeyCode.US_EQUAL } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: SearchEditorConstants.DecreaseSearchEditorContextLinesCommandId, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor), + handler: (accessor: ServicesAccessor) => modifySearchEditorContextLinesCommand(accessor, false), + primary: KeyMod.Alt | KeyCode.KEY_L, + mac: { primary: KeyMod.Alt | KeyCode.US_MINUS } +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: SearchEditorConstants.SelectAllSearchEditorMatchesCommandId, weight: KeybindingWeight.WorkbenchContrib, @@ -162,9 +180,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ CommandsRegistry.registerCommand( SearchEditorConstants.CleanSearchEditorStateCommandId, (accessor: ServicesAccessor) => { - const activeControl = accessor.get(IEditorService).activeControl; - if (activeControl instanceof SearchEditor) { - activeControl.cleanState(); + const activeEditorPane = accessor.get(IEditorService).activeEditorPane; + if (activeEditorPane instanceof SearchEditor) { + activeEditorPane.cleanState(); } }); //#endregion @@ -183,6 +201,10 @@ registry.registerWorkbenchAction( SyncActionDescriptor.create(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL), 'Search Editor: Open New Search Editor', category); +registry.registerWorkbenchAction( + SyncActionDescriptor.create(OpenSearchEditorToSideAction, OpenSearchEditorToSideAction.ID, OpenSearchEditorToSideAction.LABEL), + 'Search Editor: Open New Search Editor to Side', category); + registry.registerWorkbenchAction(SyncActionDescriptor.create(RerunSearchEditorSearchAction, RerunSearchEditorSearchAction.ID, RerunSearchEditorSearchAction.LABEL, { mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R } }, ContextKeyExpr.and(SearchEditorConstants.InSearchEditor)), 'Search Editor: Rerun', category); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index ad9e23e4b1..e2d8fb1ae6 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -49,6 +49,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; +import { alert } from 'vs/base/browser/ui/aria/aria'; const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/; const FILE_LINE_REGEX = /^(\S.*):$/; @@ -294,10 +295,65 @@ export class SearchEditor extends BaseTextEditor { this.queryEditorWidget.toggleContextLines(); } + modifyContextLines(increase: boolean) { + this.queryEditorWidget.modifyContextLines(increase); + } + toggleQueryDetails() { this.toggleIncludesExcludes(); } + deleteResultBlock() { + const linesToDelete = new Set(); + + const selections = this.searchResultEditor.getSelections(); + const model = this.searchResultEditor.getModel(); + if (!(selections && model)) { return; } + + const maxLine = model.getLineCount(); + const minLine = 1; + + const deleteUp = (start: number) => { + for (let cursor = start; cursor >= minLine; cursor--) { + const line = model.getLineContent(cursor); + linesToDelete.add(cursor); + if (line[0] !== undefined && line[0] !== ' ') { + break; + } + } + }; + + const deleteDown = (start: number): number | undefined => { + linesToDelete.add(start); + for (let cursor = start + 1; cursor <= maxLine; cursor++) { + const line = model.getLineContent(cursor); + if (line[0] !== undefined && line[0] !== ' ') { + return cursor; + } + linesToDelete.add(cursor); + } + return undefined; // {{SQL CARBON EDIT}} strict-null-checks + }; + + const endingCursorLines: Array = []; + for (const selection of selections) { + const lineNumber = selection.startLineNumber; + endingCursorLines.push(deleteDown(lineNumber)); + deleteUp(lineNumber); + for (let inner = selection.startLineNumber; inner <= selection.endLineNumber; inner++) { + linesToDelete.add(inner); + } + } + + if (endingCursorLines.length === 0) { endingCursorLines.push(1); } + + const isDefined = (x: T | undefined): x is T => x !== undefined; + + model.pushEditOperations(this.searchResultEditor.getSelections(), + [...linesToDelete].map(line => ({ range: new Range(line, 1, line + 1, 1), text: '' })), + () => endingCursorLines.filter(isDefined).map(line => new Selection(line, 1, line, 1))); + } + cleanState() { this.getInput()?.setDirty(false); } @@ -325,6 +381,15 @@ export class SearchEditor extends BaseTextEditor { this.searchResultEditor.setSelection(matchRange); this.searchResultEditor.revealLineInCenterIfOutsideViewport(matchRange.startLineNumber); this.searchResultEditor.focus(); + + const matchLineText = model.getLineContent(matchRange.startLineNumber); + const matchText = model.getValueInRange(matchRange); + let file = ''; + for (let line = matchRange.startLineNumber; line >= 1; line--) { + let lineText = model.getValueInRange(new Range(line, 1, line, 2)); + if (lineText !== ' ') { file = model.getLineContent(line); break; } + } + alert(localize('searchResultItem', "Matched {0} at {1} in file {2}", matchText, matchLineText, file.slice(0, file.length - 1))); } focusNextResult() { @@ -360,7 +425,7 @@ export class SearchEditor extends BaseTextEditor { private readConfigFromWidget() { return { caseSensitive: this.queryEditorWidget.searchInput.getCaseSensitive(), - contextLines: this.queryEditorWidget.contextLines(), + contextLines: this.queryEditorWidget.getContextLines(), excludes: this.inputPatternExcludes.getValue(), includes: this.inputPatternIncludes.getValue(), query: this.queryEditorWidget.searchInput.getValue(), diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index 2cb18075cb..baf844d360 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -19,14 +19,14 @@ import * as Constants from 'vs/workbench/contrib/searchEditor/browser/constants' import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; export const toggleSearchEditorCaseSensitiveCommand = (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); const input = editorService.activeEditor; if (input instanceof SearchEditorInput) { - (editorService.activeControl as SearchEditor).toggleCaseSensitive(); + (editorService.activeEditorPane as SearchEditor).toggleCaseSensitive(); } }; @@ -34,7 +34,7 @@ export const toggleSearchEditorWholeWordCommand = (accessor: ServicesAccessor) = const editorService = accessor.get(IEditorService); const input = editorService.activeEditor; if (input instanceof SearchEditorInput) { - (editorService.activeControl as SearchEditor).toggleWholeWords(); + (editorService.activeEditorPane as SearchEditor).toggleWholeWords(); } }; @@ -42,7 +42,7 @@ export const toggleSearchEditorRegexCommand = (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); const input = editorService.activeEditor; if (input instanceof SearchEditorInput) { - (editorService.activeControl as SearchEditor).toggleRegex(); + (editorService.activeEditorPane as SearchEditor).toggleRegex(); } }; @@ -50,7 +50,15 @@ export const toggleSearchEditorContextLinesCommand = (accessor: ServicesAccessor const editorService = accessor.get(IEditorService); const input = editorService.activeEditor; if (input instanceof SearchEditorInput) { - (editorService.activeControl as SearchEditor).toggleContextLines(); + (editorService.activeEditorPane as SearchEditor).toggleContextLines(); + } +}; + +export const modifySearchEditorContextLinesCommand = (accessor: ServicesAccessor, increase: boolean) => { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeEditorPane as SearchEditor).modifyContextLines(increase); } }; @@ -58,7 +66,7 @@ export const selectAllSearchEditorMatchesCommand = (accessor: ServicesAccessor) const editorService = accessor.get(IEditorService); const input = editorService.activeEditor; if (input instanceof SearchEditorInput) { - (editorService.activeControl as SearchEditor).focusAllResults(); + (editorService.activeEditorPane as SearchEditor).focusAllResults(); } }; @@ -87,6 +95,22 @@ export class OpenSearchEditorAction extends Action { } } +export class OpenSearchEditorToSideAction extends Action { + + static readonly ID: string = Constants.OpenNewEditorToSideCommandId; + static readonly LABEL = localize('search.openNewEditorToSide', "Open New Search Editor to Side"); + + constructor(id: string, label: string, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(id, label, 'codicon-new-file'); + } + + async run() { + await this.instantiationService.invokeFunction(openNewSearchEditor, true); + } +} + export class OpenResultsInEditorAction extends Action { static readonly ID: string = Constants.OpenInEditorCommandId; @@ -129,36 +153,36 @@ export class RerunSearchEditorSearchAction extends Action { async run() { const input = this.editorService.activeEditor; if (input instanceof SearchEditorInput) { - (this.editorService.activeControl as SearchEditor).triggerSearch({ resetCursor: false }); + (this.editorService.activeEditorPane as SearchEditor).triggerSearch({ resetCursor: false }); } } } const openNewSearchEditor = - async (accessor: ServicesAccessor) => { + async (accessor: ServicesAccessor, toSide = false) => { const editorService = accessor.get(IEditorService); const telemetryService = accessor.get(ITelemetryService); const instantiationService = accessor.get(IInstantiationService); const configurationService = accessor.get(IConfigurationService); - const activeEditor = editorService.activeTextEditorWidget; + const activeEditorControl = editorService.activeTextEditorControl; let activeModel: ICodeEditor | undefined; let selected = ''; - if (activeEditor) { - if (isDiffEditor(activeEditor)) { - if (activeEditor.getOriginalEditor().hasTextFocus()) { - activeModel = activeEditor.getOriginalEditor(); + if (activeEditorControl) { + if (isDiffEditor(activeEditorControl)) { + if (activeEditorControl.getOriginalEditor().hasTextFocus()) { + activeModel = activeEditorControl.getOriginalEditor(); } else { - activeModel = activeEditor.getModifiedEditor(); + activeModel = activeEditorControl.getModifiedEditor(); } } else { - activeModel = activeEditor as ICodeEditor; + activeModel = activeEditorControl as ICodeEditor; } const selection = activeModel?.getSelection(); selected = (selection && activeModel?.getModel()?.getValueInRange(selection)) ?? ''; } else { if (editorService.activeEditor instanceof SearchEditorInput) { - const active = editorService.activeControl as SearchEditor; + const active = editorService.activeEditorPane as SearchEditor; selected = active.getSelected(); } } @@ -166,7 +190,7 @@ const openNewSearchEditor = telemetryService.publicLog2('searchEditor/openNewSearchEditor'); const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config: { query: selected } }); - const editor = await editorService.openEditor(input, { pinned: true }) as SearchEditor; + const editor = await editorService.openEditor(input, { pinned: true }, toSide ? SIDE_GROUP : ACTIVE_GROUP) as SearchEditor; if (selected && configurationService.getValue('search').searchOnType) { editor.triggerSearch(); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index 8e25116706..f378991795 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -53,7 +53,7 @@ export class SearchEditorInput extends EditorInput { private _cachedContentsModel: ITextModel | undefined; private _cachedConfig?: SearchConfiguration; - private readonly _onDidChangeContent = new Emitter(); + private readonly _onDidChangeContent = this._register(new Emitter()); readonly onDidChangeContent: Event = this._onDidChangeContent.event; private oldDecorationsIDs: string[] = []; @@ -112,7 +112,7 @@ export class SearchEditorInput extends EditorInput { isDirty(): boolean { return input.isDirty(); } backup(): Promise { return input.backup(); } save(options?: ISaveOptions): Promise { return input.save(0, options).then(editor => !!editor); } - revert(options?: IRevertOptions): Promise { return input.revert(0, options); } + revert(options?: IRevertOptions): Promise { return input.revert(0, options); } }; this.workingCopyService.registerWorkingCopy(workingCopyAdapter); @@ -249,6 +249,7 @@ export class SearchEditorInput extends EditorInput { public getMatchRanges(): Range[] { return (this._cachedContentsModel?.getAllDecorations() ?? []) .filter(decoration => decoration.options.className === SearchEditorFindMatchClass) + .filter(({ range }) => !(range.startColumn === 1 && range.endColumn === 1)) .map(({ range }) => range); } @@ -261,7 +262,6 @@ export class SearchEditorInput extends EditorInput { // TODO: this should actually revert the contents. But it needs to set dirty false. super.revert(group, options); this.setDirty(false); - return true; } private async backup(): Promise { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts index ea7bd775bf..f871bde7a6 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts @@ -22,11 +22,10 @@ const translateRangeLines = (range: Range) => new Range(range.startLineNumber + n, range.startColumn, range.endLineNumber + n, range.endColumn); -const matchToSearchResultFormat = (match: Match): { line: string, ranges: Range[], lineNumber: string }[] => { +const matchToSearchResultFormat = (match: Match, longestLineNumber: number): { line: string, ranges: Range[], lineNumber: string }[] => { const getLinePrefix = (i: number) => `${match.range().startLineNumber + i}`; const fullMatchLines = match.fullPreviewLines(); - const largestPrefixSize = fullMatchLines.reduce((largest, _, i) => Math.max(getLinePrefix(i).length, largest), 0); const results: { line: string, ranges: Range[], lineNumber: string }[] = []; @@ -34,8 +33,8 @@ const matchToSearchResultFormat = (match: Match): { line: string, ranges: Range[ fullMatchLines .forEach((sourceLine, i) => { const lineNumber = getLinePrefix(i); - const paddingStr = repeat(' ', largestPrefixSize - lineNumber.length); - const prefix = ` ${lineNumber}: ${paddingStr}`; + const paddingStr = repeat(' ', longestLineNumber - lineNumber.length); + const prefix = ` ${paddingStr}${lineNumber}: `; const prefixOffset = prefix.length; const line = (prefix + sourceLine).replace(/\r?\n?$/, ''); @@ -60,9 +59,9 @@ const matchToSearchResultFormat = (match: Match): { line: string, ranges: Range[ type SearchResultSerialization = { text: string[], matchRanges: Range[] }; function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: URI) => string): SearchResultSerialization { - const serializedMatches = flatten(fileMatch.matches() - .sort(searchMatchComparer) - .map(match => matchToSearchResultFormat(match))); + const sortedMatches = fileMatch.matches().sort(searchMatchComparer); + const longestLineNumber = sortedMatches[sortedMatches.length - 1].range().endLineNumber.toString().length; + const serializedMatches = flatten(sortedMatches.map(match => matchToSearchResultFormat(match, longestLineNumber))); const uriString = labelFormatter(fileMatch.resource); let text: string[] = [`${uriString}:`]; @@ -84,7 +83,7 @@ function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: if (lastLine !== undefined && lineNumber !== lastLine + 1) { text.push(''); } - text.push(` ${lineNumber} ${line}`); + text.push(` ${repeat(' ', longestLineNumber - `${lineNumber}`.length)}${lineNumber} ${line}`); lastLine = lineNumber; } diff --git a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts index 7de3733876..516611ebd7 100644 --- a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts @@ -12,7 +12,6 @@ import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; -import { values } from 'vs/base/common/map'; import { IQuickPickItem, IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -73,7 +72,7 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir filepath: file.location, description: names.size === 0 ? nls.localize('global.scope', "(global)") - : nls.localize('global.1', "({0})", values(names).join(', ')) + : nls.localize('global.1', "({0})", [...names].join(', ')) }); } else { diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index 22e29a69e4..996076b54a 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { MarkdownString } from 'vs/base/common/htmlContent'; -import { compare, startsWith } from 'vs/base/common/strings'; +import { compare } from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; @@ -55,8 +55,6 @@ export class SnippetCompletion implements CompletionItem { export class SnippetCompletionProvider implements CompletionItemProvider { - private static readonly _maxPrefix = 10000; - readonly _debugDisplayName = 'snippetCompletions'; constructor( @@ -66,92 +64,84 @@ export class SnippetCompletionProvider implements CompletionItemProvider { // } - provideCompletionItems(model: ITextModel, position: Position, context: CompletionContext): Promise | undefined { - - if (position.column >= SnippetCompletionProvider._maxPrefix) { - return undefined; - } + async provideCompletionItems(model: ITextModel, position: Position, context: CompletionContext): Promise { if (context.triggerKind === CompletionTriggerKind.TriggerCharacter && context.triggerCharacter === ' ') { // no snippets when suggestions have been triggered by space - return undefined; + return { suggestions: [] }; } const languageId = this._getLanguageIdAtPosition(model, position); - return this._snippets.getSnippets(languageId).then(snippets => { + const snippets = await this._snippets.getSnippets(languageId); - let suggestions: SnippetCompletion[]; - let pos = { lineNumber: position.lineNumber, column: 1 }; - let lineOffsets: number[] = []; - const lineContent = model.getLineContent(position.lineNumber); - const linePrefixLow = lineContent.substr(0, position.column - 1).toLowerCase(); - let endsInWhitespace = linePrefixLow.match(/\s$/); + let pos = { lineNumber: position.lineNumber, column: 1 }; + let lineOffsets: number[] = []; + const lineContent = model.getLineContent(position.lineNumber).toLowerCase(); + const endsInWhitespace = /\s/.test(lineContent[position.column - 2]); - while (pos.column < position.column) { - let word = model.getWordAtPosition(pos); - if (word) { - // at a word - lineOffsets.push(word.startColumn - 1); - pos.column = word.endColumn + 1; - if (word.endColumn - 1 < linePrefixLow.length && !/\s/.test(linePrefixLow[word.endColumn - 1])) { - lineOffsets.push(word.endColumn - 1); - } - } - else if (!/\s/.test(linePrefixLow[pos.column - 1])) { - // at a none-whitespace character - lineOffsets.push(pos.column - 1); - pos.column += 1; - } - else { - // always advance! - pos.column += 1; + while (pos.column < position.column) { + let word = model.getWordAtPosition(pos); + if (word) { + // at a word + lineOffsets.push(word.startColumn - 1); + pos.column = word.endColumn + 1; + if (word.endColumn < position.column && !/\s/.test(lineContent[word.endColumn - 1])) { + lineOffsets.push(word.endColumn - 1); } } - - const lineSuffixLow = lineContent.substr(position.column - 1).toLowerCase(); - let availableSnippets = new Set(); - snippets.forEach(availableSnippets.add, availableSnippets); - suggestions = []; - for (let start of lineOffsets) { - availableSnippets.forEach(snippet => { - if (isPatternInWord(linePrefixLow, start, linePrefixLow.length, snippet.prefixLow, 0, snippet.prefixLow.length)) { - const snippetPrefixSubstr = snippet.prefixLow.substr(linePrefixLow.length - start); - const endColumn = startsWith(lineSuffixLow, snippetPrefixSubstr) ? position.column + snippetPrefixSubstr.length : position.column; - const replace = Range.fromPositions(position.delta(0, -(linePrefixLow.length - start)), { lineNumber: position.lineNumber, column: endColumn }); - const insert = replace.setEndPosition(position.lineNumber, position.column); - - suggestions.push(new SnippetCompletion(snippet, { replace, insert })); - availableSnippets.delete(snippet); - } - }); + else if (!/\s/.test(lineContent[pos.column - 1])) { + // at a none-whitespace character + lineOffsets.push(pos.column - 1); + pos.column += 1; } - if (endsInWhitespace || lineOffsets.length === 0) { - // add remaing snippets when the current prefix ends in whitespace or when no - // interesting positions have been found - availableSnippets.forEach(snippet => { - let insert = Range.fromPositions(position); - let replace = startsWith(lineSuffixLow, snippet.prefixLow) ? insert.setEndPosition(position.lineNumber, position.column + snippet.prefixLow.length) : insert; + else { + // always advance! + pos.column += 1; + } + } + + const availableSnippets = new Set(snippets); + const suggestions: SnippetCompletion[] = []; + + for (let start of lineOffsets) { + availableSnippets.forEach(snippet => { + if (isPatternInWord(lineContent, start, position.column - 1, snippet.prefixLow, 0, snippet.prefixLow.length)) { + const snippetPrefixSubstr = snippet.prefixLow.substr(position.column - (1 + start)); + const endColumn = lineContent.indexOf(snippetPrefixSubstr, position.column - 1) >= 0 ? position.column + snippetPrefixSubstr.length : position.column; + const replace = Range.fromPositions(position.delta(0, -(position.column - (1 + start))), { lineNumber: position.lineNumber, column: endColumn }); + const insert = replace.setEndPosition(position.lineNumber, position.column); + suggestions.push(new SnippetCompletion(snippet, { replace, insert })); - }); - } - - - // dismbiguate suggestions with same labels - suggestions.sort(SnippetCompletion.compareByLabel); - for (let i = 0; i < suggestions.length; i++) { - let item = suggestions[i]; - let to = i + 1; - for (; to < suggestions.length && item.label === suggestions[to].label; to++) { - suggestions[to].label.name = localize('snippetSuggest.longLabel', "{0}, {1}", suggestions[to].label.name, suggestions[to].snippet.name); + availableSnippets.delete(snippet); } - if (to > i + 1) { - suggestions[i].label.name = localize('snippetSuggest.longLabel', "{0}, {1}", suggestions[i].label.name, suggestions[i].snippet.name); - i = to; - } - } + }); + } + if (endsInWhitespace || lineOffsets.length === 0) { + // add remaing snippets when the current prefix ends in whitespace or when no + // interesting positions have been found + availableSnippets.forEach(snippet => { + const insert = Range.fromPositions(position); + const replace = lineContent.indexOf(snippet.prefixLow, position.column - 1) >= 0 ? insert.setEndPosition(position.lineNumber, position.column + snippet.prefixLow.length) : insert; + suggestions.push(new SnippetCompletion(snippet, { replace, insert })); + }); + } - return { suggestions }; - }); + + // dismbiguate suggestions with same labels + suggestions.sort(SnippetCompletion.compareByLabel); + for (let i = 0; i < suggestions.length; i++) { + let item = suggestions[i]; + let to = i + 1; + for (; to < suggestions.length && item.label === suggestions[to].label; to++) { + suggestions[to].label.name = localize('snippetSuggest.longLabel', "{0}, {1}", suggestions[to].label.name, suggestions[to].snippet.name); + } + if (to > i + 1) { + suggestions[i].label.name = localize('snippetSuggest.longLabel', "{0}, {1}", suggestions[i].label.name, suggestions[i].snippet.name); + i = to; + } + } + + return { suggestions }; } resolveCompletionItem(_model: ITextModel, _position: Position, item: CompletionItem): CompletionItem { diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index 0e691e7f0c..d84aa3f48e 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -17,7 +17,7 @@ export interface ISnippetsService { _serviceBrand: undefined; - getSnippetFiles(): Promise; + getSnippetFiles(): Promise>; getSnippets(languageId: LanguageId): Promise; diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index c9bde5b96f..b5c0313bd6 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -5,7 +5,6 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { combinedDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { values } from 'vs/base/common/map'; import * as resources from 'vs/base/common/resources'; import { endsWith, isFalsyOrWhitespace } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; @@ -162,8 +161,9 @@ class SnippetsService implements ISnippetsService { return Promise.all(promises); } - getSnippetFiles(): Promise { - return this._joinSnippets().then(() => values(this._files)); + async getSnippetFiles(): Promise> { + await this._joinSnippets(); + return this._files.values(); } getSnippets(languageId: LanguageId): Promise { diff --git a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts index d7f0761298..46fd3006cb 100644 --- a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts @@ -24,7 +24,7 @@ import { URI } from 'vs/base/common/uri'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import * as perf from 'vs/base/common/performance'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { assertIsDefined } from 'vs/base/common/types'; class PartsSplash { @@ -41,8 +41,7 @@ class PartsSplash { @IThemeService private readonly _themeService: IThemeService, @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, @ITextFileService private readonly _textFileService: ITextFileService, - @IWorkbenchEnvironmentService private readonly _envService: IWorkbenchEnvironmentService, - @IElectronEnvironmentService private readonly _electronEnvService: IElectronEnvironmentService, + @IWorkbenchEnvironmentService private readonly _envService: INativeWorkbenchEnvironmentService, @ILifecycleService lifecycleService: ILifecycleService, @IEditorGroupsService editorGroupsService: IEditorGroupsService, @IConfigurationService configService: IConfigurationService, @@ -63,7 +62,7 @@ class PartsSplash { } }, this, this._disposables); - _themeService.onThemeChange(_ => { + _themeService.onDidColorThemeChange(_ => { this._savePartsSplash(); }, this, this._disposables); } @@ -73,7 +72,7 @@ class PartsSplash { } private _savePartsSplash() { - const baseTheme = getThemeTypeSelector(this._themeService.getTheme().type); + const baseTheme = getThemeTypeSelector(this._themeService.getColorTheme().type); const colorInfo = { foreground: this._getThemeColor(foreground), editorBackground: this._getThemeColor(editorBackground), @@ -111,14 +110,14 @@ class PartsSplash { this._lastBackground = colorInfo.editorBackground; // the color needs to be in hex - const backgroundColor = this._themeService.getTheme().getColor(editorBackground) || themes.WORKBENCH_BACKGROUND(this._themeService.getTheme()); + const backgroundColor = this._themeService.getColorTheme().getColor(editorBackground) || themes.WORKBENCH_BACKGROUND(this._themeService.getColorTheme()); const payload = JSON.stringify({ baseTheme, background: Color.Format.CSS.formatHex(backgroundColor) }); - ipc.send('vscode:changeColorTheme', this._electronEnvService.windowId, payload); + ipc.send('vscode:changeColorTheme', this._envService.configuration.windowId, payload); } } private _getThemeColor(id: ColorIdentifier): string | undefined { - const theme = this._themeService.getTheme(); + const theme = this._themeService.getColorTheme(); const color = theme.getColor(id); return color ? color.toString() : undefined; } diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index b9277ac719..4bec8bcf7b 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -74,7 +74,7 @@ import { IRemotePathService } from 'vs/workbench/services/path/common/remotePath import { format } from 'vs/base/common/jsonFormatter'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { applyEdits } from 'vs/base/common/jsonEdit'; -import { ITextEditor } from 'vs/workbench/common/editor'; +import { ITextEditorPane } from 'vs/workbench/common/editor'; import { ITextEditorSelection, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { find } from 'vs/base/common/arrays'; @@ -201,6 +201,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer // private static autoDetectTelemetryName: string = 'taskServer.autoDetect'; private static readonly RecentlyUsedTasks_Key = 'workbench.tasks.recentlyUsedTasks'; + private static readonly RecentlyUsedTasks_KeyV2 = 'workbench.tasks.recentlyUsedTasks2'; private static readonly IgnoreTask010DonotShowAgain_key = 'workbench.tasks.ignoreTask010Shown'; private static CustomizationTelemetryEventName: string = 'taskService.customize'; @@ -226,6 +227,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected _taskSystem?: ITaskSystem; protected _taskSystemListener?: IDisposable; + private _recentlyUsedTasksV1: LRUCache | undefined; private _recentlyUsedTasks: LRUCache | undefined; protected _taskRunningState: IContextKey; @@ -647,12 +649,12 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return Promise.resolve(this._taskSystem.getBusyTasks()); } - public getRecentlyUsedTasks(): LRUCache { - if (this._recentlyUsedTasks) { - return this._recentlyUsedTasks; + public getRecentlyUsedTasksV1(): LRUCache { + if (this._recentlyUsedTasksV1) { + return this._recentlyUsedTasksV1; } const quickOpenHistoryLimit = this.configurationService.getValue(QUICKOPEN_HISTORY_LIMIT_CONFIG); - this._recentlyUsedTasks = new LRUCache(quickOpenHistoryLimit); + this._recentlyUsedTasksV1 = new LRUCache(quickOpenHistoryLimit); let storageValue = this.storageService.get(AbstractTaskService.RecentlyUsedTasks_Key, StorageScope.WORKSPACE); if (storageValue) { @@ -660,7 +662,30 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer let values: string[] = JSON.parse(storageValue); if (Array.isArray(values)) { for (let value of values) { - this._recentlyUsedTasks.set(value, value); + this._recentlyUsedTasksV1.set(value, value); + } + } + } catch (error) { + // Ignore. We use the empty result + } + } + return this._recentlyUsedTasksV1; + } + + public getRecentlyUsedTasks(): LRUCache { + if (this._recentlyUsedTasks) { + return this._recentlyUsedTasks; + } + const quickOpenHistoryLimit = this.configurationService.getValue(QUICKOPEN_HISTORY_LIMIT_CONFIG); + this._recentlyUsedTasks = new LRUCache(quickOpenHistoryLimit); + + let storageValue = this.storageService.get(AbstractTaskService.RecentlyUsedTasks_KeyV2, StorageScope.WORKSPACE); + if (storageValue) { + try { + let values: [string, string][] = JSON.parse(storageValue); + if (Array.isArray(values)) { + for (let value of values) { + this._recentlyUsedTasks.set(value[0], value[1]); } } } catch (error) { @@ -677,13 +702,16 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } - private setRecentlyUsedTask(key: string): void { - this.getRecentlyUsedTasks().set(key, key); - this.saveRecentlyUsedTasks(); + private setRecentlyUsedTask(task: Task): void { + const key = task.getRecentlyUsedKey(); + if (!InMemoryTask.is(task) && key) { + this.getRecentlyUsedTasks().set(key, JSON.stringify(this.createCustomizableTask(task))); + this.saveRecentlyUsedTasks(); + } } private saveRecentlyUsedTasks(): void { - if (!this._taskSystem || !this._recentlyUsedTasks) { + if (!this._recentlyUsedTasks) { return; } const quickOpenHistoryLimit = this.configurationService.getValue(QUICKOPEN_HISTORY_LIMIT_CONFIG); @@ -691,11 +719,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (quickOpenHistoryLimit === 0) { return; } - let values = this._recentlyUsedTasks.values(); + let values = this._recentlyUsedTasks.toJSON(); if (values.length > quickOpenHistoryLimit) { values = values.slice(0, quickOpenHistoryLimit); } - this.storageService.store(AbstractTaskService.RecentlyUsedTasks_Key, JSON.stringify(values), StorageScope.WORKSPACE); + this.storageService.store(AbstractTaskService.RecentlyUsedTasks_KeyV2, JSON.stringify(values), StorageScope.WORKSPACE); } private openDocumentation(): void { @@ -945,7 +973,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return false; } - private openEditorAtTask(resource: URI | undefined, task: TaskConfig.CustomTask | TaskConfig.ConfiguringTask | string | undefined): Promise { + private openEditorAtTask(resource: URI | undefined, task: TaskConfig.CustomTask | TaskConfig.ConfiguringTask | string | undefined): Promise { if (resource === undefined) { return Promise.resolve(undefined); } @@ -999,23 +1027,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } - public customize(task: ContributedTask | CustomTask, properties?: CustomizationProperties, openConfig?: boolean): Promise { - const workspaceFolder = task.getWorkspaceFolder(); - if (!workspaceFolder) { - return Promise.resolve(undefined); - } - let configuration = this.getConfiguration(workspaceFolder, task._source.kind); - if (configuration.hasParseErrors) { - this.notificationService.warn(nls.localize('customizeParseErrors', 'The current task configuration has errors. Please fix the errors first before customizing a task.')); - return Promise.resolve(undefined); - } - - let fileConfig = configuration.config; - let index: number | undefined; + private createCustomizableTask(task: ContributedTask | CustomTask): TaskConfig.CustomTask | TaskConfig.ConfiguringTask | undefined { let toCustomize: TaskConfig.CustomTask | TaskConfig.ConfiguringTask | undefined; let taskConfig = CustomTask.is(task) ? task._source.config : undefined; if (taskConfig && taskConfig.element) { - index = taskConfig.index; toCustomize = { ...(taskConfig.element) }; } else if (ContributedTask.is(task)) { toCustomize = { @@ -1031,8 +1046,31 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } if (!toCustomize) { + return undefined; + } + if (toCustomize.problemMatcher === undefined && task.configurationProperties.problemMatchers === undefined || (task.configurationProperties.problemMatchers && task.configurationProperties.problemMatchers.length === 0)) { + toCustomize.problemMatcher = []; + } + return toCustomize; + } + + public customize(task: ContributedTask | CustomTask, properties?: CustomizationProperties, openConfig?: boolean): Promise { + const workspaceFolder = task.getWorkspaceFolder(); + if (!workspaceFolder) { return Promise.resolve(undefined); } + let configuration = this.getConfiguration(workspaceFolder, task._source.kind); + if (configuration.hasParseErrors) { + this.notificationService.warn(nls.localize('customizeParseErrors', 'The current task configuration has errors. Please fix the errors first before customizing a task.')); + return Promise.resolve(undefined); + } + + let fileConfig = configuration.config; + const toCustomize = this.createCustomizableTask(task); + if (!toCustomize) { + return Promise.resolve(undefined); + } + const index: number | undefined = CustomTask.is(task) ? task._source.config.index : undefined; if (properties) { for (let property of Object.getOwnPropertyNames(properties)) { let value = (properties)[property]; @@ -1040,10 +1078,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer (toCustomize)[property] = value; } } - } else { - if (toCustomize.problemMatcher === undefined && task.configurationProperties.problemMatchers === undefined || (task.configurationProperties.problemMatchers && task.configurationProperties.problemMatchers.length === 0)) { - toCustomize.problemMatcher = []; - } } let promise: Promise | undefined; @@ -1284,7 +1318,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private executeTask(task: Task, resolver: ITaskResolver): Promise { return ProblemMatcherRegistry.onReady().then(() => { - return this.editorService.saveAll().then((value) => { // make sure all dirty editors are saved + return this.editorService.saveAll().then(() => { // make sure all dirty editors are saved let executeResult = this.getTaskSystem().run(task, resolver); return this.handleExecuteResult(executeResult); }); @@ -1299,10 +1333,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.showOutput(); } - let key = executeResult.task.getRecentlyUsedKey(); - if (key) { - this.setRecentlyUsedTask(key); - } + this.setRecentlyUsedTask(executeResult.task); if (executeResult.kind === TaskExecuteKind.Active) { let active = executeResult.active; if (active && active.same) { @@ -1402,6 +1433,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private getGroupedTasks(type?: string): Promise { + const needsRecentTasksMigration = this.needsRecentTasksMigration(); return Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), this.extensionService.whenInstalledExtensionsRegistered()]).then(() => { let validTypes: IStringDictionary = Object.create(null); TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true); @@ -1558,7 +1590,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); await Promise.all(customTasksPromises); - + if (needsRecentTasksMigration) { + // At this point we have all the tasks and can migrate the recently used tasks. + this.migrateRecentTasks(result.all()); + } return result; }, () => { // If we can't read the tasks.json file provide at least the contributed tasks @@ -1989,7 +2024,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this.configurationService.getValue(QUICKOPEN_DETAIL_CONFIG); } - private createTaskQuickPickEntries(tasks: Task[], group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry): TaskQuickPickEntry[] { + private createTaskQuickPickEntries(tasks: Task[], group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry, includeRecents: boolean = true): TaskQuickPickEntry[] { let count: { [key: string]: number; } = {}; if (tasks === undefined || tasks === null || tasks.length === 0) { return []; @@ -2054,7 +2089,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } const sorter = this.createSorter(); - fillEntries(entries, recent, nls.localize('recentlyUsed', 'recently used tasks')); + if (includeRecents) { + fillEntries(entries, recent, nls.localize('recentlyUsed', 'recently used tasks')); + } configured = configured.sort((a, b) => sorter.compare(a, b)); fillEntries(entries, configured, nls.localize('configured', 'configured tasks')); detected = detected.sort((a, b) => sorter.compare(a, b)); @@ -2173,6 +2210,31 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } + private needsRecentTasksMigration(): boolean { + return (this.getRecentlyUsedTasksV1().size > 0) && (this.getRecentlyUsedTasks().size === 0); + } + + public migrateRecentTasks(tasks: Task[]) { + if (!this.needsRecentTasksMigration()) { + return; + } + let recentlyUsedTasks = this.getRecentlyUsedTasksV1(); + let taskMap: IStringDictionary = Object.create(null); + tasks.forEach(task => { + let key = task.getRecentlyUsedKey(); + if (key) { + taskMap[key] = task; + } + }); + recentlyUsedTasks.keys().reverse().forEach(key => { + let task = taskMap[key]; + if (task) { + this.setRecentlyUsedTask(task); + } + }); + this.storageService.remove(AbstractTaskService.RecentlyUsedTasks_Key, StorageScope.WORKSPACE); + } + private showIgnoredFoldersMessage(): Promise { if (this.ignoredWorkspaceFolders.length === 0 || !this.showIgnoreMessage) { return Promise.resolve(undefined); @@ -2257,7 +2319,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } ProblemMatcherRegistry.onReady().then(() => { - return this.editorService.saveAll().then((value) => { // make sure all dirty editors are saved + return this.editorService.saveAll().then(() => { // make sure all dirty editors are saved let executeResult = this.getTaskSystem().rerun(); if (executeResult) { return this.handleExecuteResult(executeResult); diff --git a/src/vs/workbench/contrib/tasks/browser/quickOpen.ts b/src/vs/workbench/contrib/tasks/browser/quickOpen.ts index e8f880ae86..6888cb7cd5 100644 --- a/src/vs/workbench/contrib/tasks/browser/quickOpen.ts +++ b/src/vs/workbench/contrib/tasks/browser/quickOpen.ts @@ -85,6 +85,7 @@ export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler { return Promise.resolve(null); } return this.tasks.then((tasks) => { + this.taskService.migrateRecentTasks(tasks); let entries: Model.QuickOpenEntry[] = []; if (tasks.length === 0 || token.isCancellationRequested) { return new Model.QuickOpenModel(entries); diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 6b2743dfe1..237148378f 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -39,6 +39,8 @@ import { AbstractTaskService, ConfigureTaskAction } from 'vs/workbench/contrib/t import { tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; +import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { TasksQuickAccessProvider } from 'vs/workbench/contrib/tasks/browser/tasksQuickAccess'; let tasksCategory = nls.localize('tasksCategory', "Tasks"); @@ -274,6 +276,17 @@ quickOpenRegistry.registerQuickOpenHandler( ) ); +// Register Quick Access +const quickAccessRegistry = (Registry.as(QuickAccessExtensions.Quickaccess)); + +quickAccessRegistry.registerQuickAccessProvider({ + ctor: TasksQuickAccessProvider, + prefix: TasksQuickAccessProvider.PREFIX, + contextKey: tasksPickerContextKey, + placeholder: nls.localize('tasksQuickAccessPlaceholder', "Type the name of a task to run."), + helpEntries: [{ description: nls.localize('tasksQuickAccessHelp', "Run Task"), needsEditor: false }] +}); + const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionContributor); diff --git a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts new file mode 100644 index 0000000000..797d52b795 --- /dev/null +++ b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts @@ -0,0 +1,140 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { matchesFuzzy } from 'vs/base/common/filters'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; +import { CustomTask, ContributedTask } from 'vs/workbench/contrib/tasks/common/tasks'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IStringDictionary } from 'vs/base/common/collections'; + +export class TasksQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = 'task '; + + private activationPromise: Promise; + + constructor( + @IExtensionService extensionService: IExtensionService, + @ITaskService private taskService: ITaskService + ) { + super(TasksQuickAccessProvider.PREFIX); + + this.activationPromise = extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'); + } + + protected async getPicks(filter: string, token: CancellationToken): Promise> { + + // always await extensions + await this.activationPromise; + + if (token.isCancellationRequested) { + return []; + } + + // Resolve custom and contributed tasks + const tasks = (await this.taskService.tasks()) + .filter((task): task is CustomTask | ContributedTask => ContributedTask.is(task) || CustomTask.is(task)); + + if (token.isCancellationRequested) { + return []; + } + + this.taskService.migrateRecentTasks(tasks); + + // Split up tasks across recently used, configured and detected + const recentlyUsedTasks = this.taskService.getRecentlyUsedTasks(); + const recent: Array = []; + const configured: CustomTask[] = []; + const detected: ContributedTask[] = []; + const taskMap: IStringDictionary = Object.create(null); + for (const task of tasks) { + const key = task.getRecentlyUsedKey(); + if (key) { + taskMap[key] = task; + } + } + + recentlyUsedTasks.keys().forEach(key => { + const task = taskMap[key]; + if (task) { + recent.push(task); + } + }); + + for (const task of tasks) { + const key = task.getRecentlyUsedKey(); + if (!key || !recentlyUsedTasks.has(key)) { + if (CustomTask.is(task)) { + configured.push(task); + } else { + detected.push(task); + } + } + } + + const taskPicks: Array = []; + const sorter = this.taskService.createSorter(); + + // Fill picks in sorted order + + this.fillPicks(taskPicks, filter, recent, localize('recentlyUsed', 'recently used tasks')); + + configured.sort((a, b) => sorter.compare(a, b)); + this.fillPicks(taskPicks, filter, configured, localize('configured', 'configured tasks')); + + detected.sort((a, b) => sorter.compare(a, b)); + this.fillPicks(taskPicks, filter, detected, localize('detected', 'detected tasks')); + + return taskPicks; + } + + private fillPicks(taskPicks: Array, input: string, tasks: Array, groupLabel: string): void { + let first = true; + for (const task of tasks) { + const highlights = matchesFuzzy(input, task._label); + if (!highlights) { + continue; + } + if (first) { + first = false; + taskPicks.push({ type: 'separator', label: groupLabel }); + } + taskPicks.push({ + label: task._label, + ariaLabel: localize('entryAriaLabel', "{0}, tasks", task._label), + description: this.taskService.getTaskDescription(task), + highlights: { label: highlights }, + buttons: (() => { + const buttons = []; + + if (ContributedTask.is(task) || CustomTask.is(task)) { + buttons.push({ + iconClass: 'codicon-gear', + tooltip: localize('customizeTask', "Configure Task") + }); + } + + return buttons; + })(), + accept: () => { + this.taskService.run(task, { attachProblemMatcher: true }); + }, + trigger: () => { + if (ContributedTask.is(task)) { + this.taskService.customize(task, undefined, true); + } else { + this.taskService.openConfig(task); + } + + return true; // close picker + } + }); + } + } +} diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index fb27b01e37..f5827288c6 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -33,7 +33,7 @@ import { IOutputService } from 'vs/workbench/contrib/output/common/output'; import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind, ProblemHandlingStrategy } from 'vs/workbench/contrib/tasks/common/problemCollectors'; import { Task, CustomTask, ContributedTask, RevealKind, CommandOptions, ShellConfiguration, RuntimeType, PanelKind, - TaskEvent, TaskEventKind, ShellQuotingOptions, ShellQuoting, CommandString, CommandConfiguration, ExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder + TaskEvent, TaskEventKind, ShellQuotingOptions, ShellQuoting, CommandString, CommandConfiguration, ExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder, TaskSourceKind } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver, @@ -545,7 +545,7 @@ export class TerminalTaskSystem implements ITaskSystem { resolveSet.process.path = envPath; } } - resolvedVariables = taskSystemInfo.resolveVariables(workspaceFolder, resolveSet).then(async (resolved) => { + resolvedVariables = taskSystemInfo.resolveVariables(workspaceFolder, resolveSet, TaskSourceKind.toConfigurationTarget(task._source.kind)).then(async (resolved) => { this.mergeMaps(alreadyResolved, resolved.variables); resolved.variables = new Map(alreadyResolved); if (isProcess) { @@ -563,7 +563,7 @@ export class TerminalTaskSystem implements ITaskSystem { unresolved.forEach(variable => variablesArray.push(variable)); return new Promise((resolve, reject) => { - this.configurationResolverService.resolveWithInteraction(workspaceFolder, variablesArray, 'tasks').then(async (resolvedVariablesMap: Map | undefined) => { + this.configurationResolverService.resolveWithInteraction(workspaceFolder, variablesArray, 'tasks', undefined, TaskSourceKind.toConfigurationTarget(task._source.kind)).then(async (resolvedVariablesMap: Map | undefined) => { if (resolvedVariablesMap) { this.mergeMaps(alreadyResolved, resolvedVariablesMap); resolvedVariablesMap = new Map(alreadyResolved); diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts index 1579f43511..b922bd67eb 100644 --- a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts +++ b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts @@ -206,7 +206,7 @@ const group: IJSONSchema = { const taskType: IJSONSchema = { type: 'string', enum: ['shell'], - default: 'shell', + default: 'process', description: nls.localize('JsonSchema.tasks.type', 'Defines whether the task is run as a process or as a command inside a shell.') }; @@ -474,7 +474,7 @@ const processTask = Objects.deepClone(taskDescription); processTask.properties!.type = { type: 'string', enum: ['process'], - default: 'shell', + default: 'process', description: nls.localize('JsonSchema.tasks.type', 'Defines whether the task is run as a process or as a command inside a shell.') }; processTask.required!.push('command'); diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index b364b14866..da23516e8e 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -76,6 +76,7 @@ export interface ITaskService { getTask(workspaceFolder: IWorkspace | IWorkspaceFolder | string, alias: string | TaskIdentifier, compareId?: boolean): Promise; getTasksForGroup(group: string): Promise; getRecentlyUsedTasks(): LinkedMap; + migrateRecentTasks(tasks: Task[]): void; createSorter(): TaskSorter; getTaskDescription(task: Task): string | undefined; diff --git a/src/vs/workbench/contrib/tasks/common/taskSystem.ts b/src/vs/workbench/contrib/tasks/common/taskSystem.ts index e908ff67d7..d6053b97fd 100644 --- a/src/vs/workbench/contrib/tasks/common/taskSystem.ts +++ b/src/vs/workbench/contrib/tasks/common/taskSystem.ts @@ -10,6 +10,7 @@ import { Event } from 'vs/base/common/event'; import { Platform } from 'vs/base/common/platform'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Task, TaskEvent, KeyedTaskIdentifier } from './tasks'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; export const enum TaskErrors { NotConfigured, @@ -118,7 +119,7 @@ export interface TaskSystemInfo { platform: Platform; context: any; uriProvider: (this: void, path: string) => URI; - resolveVariables(workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet): Promise; + resolveVariables(workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet, target: ConfigurationTarget): Promise; getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }>; } diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 68f0a85816..3e6ec7b666 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -15,6 +15,7 @@ import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/works import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; export const TASK_RUNNING_STATE = new RawContextKey('taskRunning', false); @@ -377,6 +378,14 @@ export namespace TaskSourceKind { export const InMemory: 'inMemory' = 'inMemory'; export const WorkspaceFile: 'workspaceFile' = 'workspaceFile'; export const User: 'user' = 'user'; + + export function toConfigurationTarget(kind: string): ConfigurationTarget { + switch (kind) { + case TaskSourceKind.User: return ConfigurationTarget.USER; + case TaskSourceKind.WorkspaceFile: return ConfigurationTarget.WORKSPACE; + default: return ConfigurationTarget.WORKSPACE_FOLDER; + } + } } export interface TaskSourceConfigElement { diff --git a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts index f454dd96b3..e4e6a0c729 100644 --- a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts @@ -514,11 +514,7 @@ function assertProblemMatcher(actual: string | ProblemMatcher, expected: string } if (typeof actual !== 'string' && typeof expected !== 'string') { if (expected.owner === ProblemMatcherBuilder.DEFAULT_UUID) { - try { - UUID.parse(actual.owner); - } catch (err) { - assert.fail(actual.owner, 'Owner must be a UUID'); - } + assert.ok(UUID.isUUID(actual.owner), 'Owner must be a UUID'); } else { assert.strictEqual(actual.owner, expected.owner); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 5c03f1f623..7a830ca068 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -282,6 +282,11 @@ configurationRegistry.registerConfiguration({ type: 'boolean', default: true }, + 'terminal.integrated.allowMenubarMnemonics': { + markdownDescription: nls.localize('terminal.integrated.allowMenubarMnemonics', "Whether to allow menubar mnemonics (eg. alt+f) to trigger the open the menubar. Note that this will cause all alt keystrokes will skip the shell when true."), + type: 'boolean', + default: false + }, 'terminal.integrated.inheritEnv': { markdownDescription: nls.localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from Azure Data Studio. This is not supported on Windows."), // {{SQL CARBON EDIT}} Change product name to ADS type: 'boolean', diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 24c048f1ea..852a691e52 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -22,7 +22,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; -import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager'; import { IShellLaunchConfig, ITerminalDimensions, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, TERMINAL_COMMAND_ID, LEGACY_CONSOLE_MODE_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -527,7 +527,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._commandTrackerAddon = new CommandTrackerAddon(); this._xterm.loadAddon(this._commandTrackerAddon); - this._register(this._themeService.onThemeChange(theme => this._updateTheme(xterm, theme))); + this._register(this._themeService.onDidColorThemeChange(theme => this._updateTheme(xterm, theme))); return xterm; } @@ -595,19 +595,30 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return false; } - // Skip processing by xterm.js of keyboard events that resolve to commands described - // within commandsToSkipShell const standardKeyboardEvent = new StandardKeyboardEvent(event); const resolveResult = this._keybindingService.softDispatch(standardKeyboardEvent, standardKeyboardEvent.target); + // Respect chords if the allowChords setting is set and it's not Escape. Escape is // handled specially for Zen Mode's Escape, Escape chord, plus it's important in // terminals generally - const allowChords = resolveResult?.enterChord && this._configHelper.config.allowChords && event.key !== 'Escape'; - if (this._keybindingService.inChordMode || allowChords || resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) { + const isValidChord = resolveResult?.enterChord && this._configHelper.config.allowChords && event.key !== 'Escape'; + if (this._keybindingService.inChordMode || isValidChord) { event.preventDefault(); return false; } + // Skip processing by xterm.js of keyboard events that resolve to commands described + // within commandsToSkipShell + if (resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) { + event.preventDefault(); + return false; + } + + // Skip processing by xterm.js of keyboard events that match menu bar mnemonics + if (this._configHelper.config.allowMenubarMnemonics && event.altKey) { + return false; + } + // If tab focus mode is on, tab is not passed to the terminal if (TabFocus.getTabFocusMode() && event.keyCode === 9) { return false; @@ -1431,9 +1442,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._shellLaunchConfig.env = shellLaunchConfig.env; } - private _getXtermTheme(theme?: ITheme): any { + private _getXtermTheme(theme?: IColorTheme): any { if (!theme) { - theme = this._themeService.getTheme(); + theme = this._themeService.getColorTheme(); } const foregroundColor = theme.getColor(TERMINAL_FOREGROUND_COLOR); @@ -1467,7 +1478,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }; } - private _updateTheme(xterm: XTermTerminal, theme?: ITheme): void { + private _updateTheme(xterm: XTermTerminal, theme?: IColorTheme): void { xterm.setOption('theme', this._getXtermTheme(theme)); } @@ -1486,7 +1497,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Border const border = theme.getColor(activeContrastBorder); if (border) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index 69f9fa02cd..b9df0221c8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -69,7 +69,7 @@ export class TerminalInstanceService implements ITerminalInstanceService { throw new Error('Not implemented'); } - public getDefaultShellAndArgs(useAutomationShell: boolean, ): Promise<{ shell: string, args: string[] | string | undefined }> { + public getDefaultShellAndArgs(useAutomationShell: boolean,): Promise<{ shell: string, args: string[] | string | undefined }> { return new Promise(r => this._onRequestDefaultShellAndArgs.fire({ useAutomationShell, callback: (shell, args) => r({ shell, args }) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index a1cb0125ea..659dac65b7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -12,7 +12,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService, ITheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { TerminalFindWidget } from 'vs/workbench/contrib/terminal/browser/terminalFindWidget'; import { editorHoverBackground, editorHoverBorder, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { KillTerminalAction, SwitchTerminalAction, SwitchTerminalActionViewItem, CopyTerminalSelectionAction, TerminalPasteAction, ClearTerminalAction, SelectAllTerminalAction, CreateNewTerminalAction, SplitTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; @@ -79,7 +79,7 @@ export class TerminalViewPane extends ViewPane { this._terminalService.setContainers(container, this._terminalContainer); - this._register(this.themeService.onThemeChange(theme => this._updateTheme(theme))); + this._register(this.themeService.onDidColorThemeChange(theme => this._updateTheme(theme))); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('terminal.integrated') || e.affectsConfiguration('editor.fontFamily')) { this._updateFont(); @@ -305,9 +305,9 @@ export class TerminalViewPane extends ViewPane { })); } - private _updateTheme(theme?: ITheme): void { + private _updateTheme(theme?: IColorTheme): void { if (!theme) { - theme = this.themeService.getTheme(); + theme = this.themeService.getColorTheme(); } if (this._findWidget) { @@ -325,7 +325,7 @@ export class TerminalViewPane extends ViewPane { } } -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const backgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR); collector.addRule(`.monaco-workbench .pane-body.integrated-terminal .terminal-outer-container { background-color: ${backgroundColor ? backgroundColor.toString() : ''}; }`); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 04f06255dd..42895d9e0a 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; import { OperatingSystem } from 'vs/base/common/platform'; @@ -19,25 +19,25 @@ export const KEYBINDING_CONTEXT_TERMINAL_IS_OPEN = new RawContextKey('t /** A context key that is set when the integrated terminal has focus. */ export const KEYBINDING_CONTEXT_TERMINAL_FOCUS = new RawContextKey('terminalFocus', false); /** A context key that is set when the integrated terminal does not have focus. */ -export const KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED: ContextKeyExpr = KEYBINDING_CONTEXT_TERMINAL_FOCUS.toNegated(); +export const KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED = KEYBINDING_CONTEXT_TERMINAL_FOCUS.toNegated(); /** A context key that is set when the user is navigating the accessibility tree */ export const KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS = new RawContextKey('terminalA11yTreeFocus', false); /** A keybinding context key that is set when the integrated terminal has text selected. */ export const KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED = new RawContextKey('terminalTextSelected', false); /** A keybinding context key that is set when the integrated terminal does not have text selected. */ -export const KEYBINDING_CONTEXT_TERMINAL_TEXT_NOT_SELECTED: ContextKeyExpr = KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED.toNegated(); +export const KEYBINDING_CONTEXT_TERMINAL_TEXT_NOT_SELECTED = KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED.toNegated(); /** A context key that is set when the find widget in integrated terminal is visible. */ export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE = new RawContextKey('terminalFindWidgetVisible', false); /** A context key that is set when the find widget in integrated terminal is not visible. */ -export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE: ContextKeyExpr = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE.toNegated(); +export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE.toNegated(); /** A context key that is set when the find widget find input in integrated terminal is focused. */ export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED = new RawContextKey('terminalFindWidgetInputFocused', false); /** A context key that is set when the find widget in integrated terminal is focused. */ export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED = new RawContextKey('terminalFindWidgetFocused', false); /** A context key that is set when the find widget find input in integrated terminal is not focused. */ -export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_NOT_FOCUSED: ContextKeyExpr = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED.toNegated(); +export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_NOT_FOCUSED = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED.toNegated(); export const IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY = 'terminal.integrated.isWorkspaceShellAllowed'; export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverMeasureRenderTime'; @@ -107,6 +107,7 @@ export interface ITerminalConfiguration { scrollback: number; commandsToSkipShell: string[]; allowChords: boolean; + allowMenubarMnemonics: boolean; cwd: string; confirmOnExit: boolean; enableBell: boolean; diff --git a/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts b/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts index 5a95842d9b..c6ceddc0db 100644 --- a/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts +++ b/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts @@ -7,13 +7,13 @@ import * as assert from 'assert'; import { Extensions as ThemeingExtensions, IColorRegistry, ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { ansiColorIdentifiers, registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; -import { ITheme, ThemeType } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, ThemeType } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; registerColors(); let themingRegistry = Registry.as(ThemeingExtensions.ColorContribution); -function getMockTheme(type: ThemeType): ITheme { +function getMockTheme(type: ThemeType): IColorTheme { let theme = { selector: '', label: '', diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index f1f390666d..55dcc7c3f6 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -10,7 +10,7 @@ import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { IWorkbenchThemeService, COLOR_THEME_SETTING, ICON_THEME_SETTING, IColorTheme, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, ThemeSettings, IWorkbenchColorTheme, IWorkbenchFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; // import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -63,7 +63,7 @@ export class SelectColorThemeAction extends Action { const themeId = theme && theme.id !== undefined ? theme.id : currentTheme.id; let target: ConfigurationTarget | undefined = undefined; if (applyTheme) { - const confValue = this.configurationService.inspect(COLOR_THEME_SETTING); + const confValue = this.configurationService.inspect(ThemeSettings.COLOR_THEME); target = typeof confValue.workspaceValue !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; } @@ -149,7 +149,7 @@ class SelectIconThemeAction extends Action { const themeId = theme && theme.id !== undefined ? theme.id : currentTheme.id; let target: ConfigurationTarget | undefined = undefined; if (applyTheme) { - const confValue = this.configurationService.inspect(ICON_THEME_SETTING); + const confValue = this.configurationService.inspect(ThemeSettings.ICON_THEME); target = typeof confValue.workspaceValue !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; } this.themeService.setFileIconTheme(themeId, target).then(undefined, @@ -229,8 +229,8 @@ function isItem(i: QuickPickInput): i is ThemeItem { return (i)['type'] !== 'separator'; } -function toEntries(themes: Array, label?: string): QuickPickInput[] { - const toEntry = (theme: IColorTheme | IFileIconTheme): ThemeItem => ({ id: theme.id, label: theme.label, description: theme.description }); +function toEntries(themes: Array, label?: string): QuickPickInput[] { + const toEntry = (theme: IWorkbenchColorTheme | IWorkbenchFileIconTheme): ThemeItem => ({ id: theme.id, label: theme.label, description: theme.description }); const sorter = (t1: ThemeItem, t2: ThemeItem) => t1.label.localeCompare(t2.label); let entries: QuickPickInput[] = themes.map(toEntry).sort(sorter); if (entries.length > 0 && label) { diff --git a/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts index 3ec872a9eb..a37c1403fd 100644 --- a/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts +++ b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { IModeService } from 'vs/editor/common/services/modeService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IWorkbenchColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { toResource } from 'vs/workbench/common/editor'; import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; @@ -37,11 +37,11 @@ interface IThemesResult { } class ThemeDocument { - private readonly _theme: IColorTheme; + private readonly _theme: IWorkbenchColorTheme; private readonly _cache: { [scopes: string]: ThemeRule; }; private readonly _defaultColor: string; - constructor(theme: IColorTheme) { + constructor(theme: IWorkbenchColorTheme) { this._theme = theme; this._cache = Object.create(null); this._defaultColor = '#000000'; diff --git a/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css b/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css index a01f458678..9e6bc24bb5 100644 --- a/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css +++ b/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css @@ -7,9 +7,25 @@ position: relative; } +.monaco-workbench .timeline-view.pane-header .description { + margin-left: 10px; + opacity: 0.6; + text-transform: none; + font-weight: normal; +} + +.monaco-workbench .timeline-view.pane-header:not(.expanded) .description { + display: none; +} + +.monaco-workbench .timeline-view.pane-header .description span.codicon { + font-size: 9px; + margin-left: 2px; +} + .monaco-workbench .timeline-tree-view .message.timeline-subtle { - padding: 10px 22px 0 22px; opacity: 0.5; + padding: 10px 22px 0 22px; position: absolute; pointer-events: none; z-index: 1; diff --git a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts index b7c8ee9110..786fe7666b 100644 --- a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts +++ b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts @@ -9,7 +9,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/browser/explorerViewlet'; -import { ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline'; +import { ITimelineService, TimelinePaneId } from 'vs/workbench/contrib/timeline/common/timeline'; import { TimelineService } from 'vs/workbench/contrib/timeline/common/timelineService'; import { TimelinePane } from './timelinePane'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; @@ -17,9 +17,11 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; import product from 'vs/platform/product/common/product'; +import { ExplorerFolderContext } from 'vs/workbench/contrib/files/common/files'; +import { ResourceContextKey } from 'vs/workbench/common/resources'; export class TimelinePaneDescriptor implements IViewDescriptor { - readonly id = TimelinePane.ID; + readonly id = TimelinePaneId; readonly name = TimelinePane.TITLE; readonly ctorDescriptor = new SyncDescriptor(TimelinePane); readonly when = ContextKeyExpr.equals('config.timeline.showView', true); @@ -56,90 +58,30 @@ configurationRegistry.registerConfiguration({ Registry.as(ViewExtensions.ViewsRegistry).registerViews([new TimelinePaneDescriptor()], VIEW_CONTAINER); -namespace TimelineViewRefreshAction { +namespace OpenTimelineAction { - export const ID = 'timeline.refresh'; - export const LABEL = localize('timeline.refreshView', "Refresh"); + export const ID = 'files.openTimeline'; + export const LABEL = localize('files.openTimeline', "Open Timeline"); export function handler(): ICommandHandler { return (accessor, arg) => { const service = accessor.get(ITimelineService); - return service.reset(); + return service.setUri(arg); }; } } -CommandsRegistry.registerCommand(TimelineViewRefreshAction.ID, TimelineViewRefreshAction.handler()); +CommandsRegistry.registerCommand(OpenTimelineAction.ID, OpenTimelineAction.handler()); -// namespace TimelineViewRefreshHardAction { - -// export const ID = 'timeline.refreshHard'; -// export const LABEL = localize('timeline.refreshHard', "Refresh (Hard)"); - -// export function handler(fetch?: 'all' | 'more'): ICommandHandler { -// return (accessor, arg) => { -// const service = accessor.get(ITimelineService); -// return service.refresh(fetch); -// }; -// } -// } - -// CommandsRegistry.registerCommand(TimelineViewRefreshAction.ID, TimelineViewRefreshAction.handler()); - -// namespace TimelineViewLoadMoreAction { - -// export const ID = 'timeline.loadMore'; -// export const LABEL = localize('timeline.loadMoreInView', "Load More"); - -// export function handler(): ICommandHandler { -// return (accessor, arg) => { -// const service = accessor.get(ITimelineService); -// return service.refresh('more'); -// }; -// } -// } - -// CommandsRegistry.registerCommand(TimelineViewLoadMoreAction.ID, TimelineViewLoadMoreAction.handler()); - -// namespace TimelineViewLoadAllAction { - -// export const ID = 'timeline.loadAll'; -// export const LABEL = localize('timeline.loadAllInView', "Load All"); - -// export function handler(): ICommandHandler { -// return (accessor, arg) => { -// const service = accessor.get(ITimelineService); -// return service.refresh('all'); -// }; -// } -// } - -// CommandsRegistry.registerCommand(TimelineViewLoadAllAction.ID, TimelineViewLoadAllAction.handler()); - -MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ - group: 'navigation', +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, ({ + group: '4_timeline', order: 1, command: { - id: TimelineViewRefreshAction.ID, - title: TimelineViewRefreshAction.LABEL, - icon: { id: 'codicon/refresh' } - } + id: OpenTimelineAction.ID, + title: OpenTimelineAction.LABEL, + icon: { id: 'codicon/history' } + }, + when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.HasResource) })); -// MenuRegistry.appendMenuItem(MenuId.TimelineTitle, ({ -// group: 'navigation', -// order: 2, -// command: { -// id: TimelineViewLoadMoreAction.ID, -// title: TimelineViewLoadMoreAction.LABEL, -// icon: { id: 'codicon/unfold' } -// }, -// alt: { -// id: TimelineViewLoadAllAction.ID, -// title: TimelineViewLoadAllAction.LABEL, -// icon: { id: 'codicon/unfold' } - -// } -// })); - registerSingleton(ITimelineService, TimelineService, true); diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index e3b5bd93bc..f0e5699d34 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -15,13 +15,13 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IListVirtualDelegate, IIdentityProvider, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list'; import { ITreeNode, ITreeRenderer, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { TreeResourceNavigator, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; +import { ResourceNavigator, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ITimelineService, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvidersChangeEvent, TimelineRequest, Timeline } from 'vs/workbench/contrib/timeline/common/timeline'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ITimelineService, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvidersChangeEvent, TimelineRequest, Timeline, TimelinePaneId } from 'vs/workbench/contrib/timeline/common/timeline'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { SideBySideEditor, toResource } from 'vs/workbench/common/editor'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -34,9 +34,10 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IActionViewItemProvider, ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction, ActionRunner } from 'vs/base/common/actions'; import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { MenuItemAction, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { MenuItemAction, IMenuService, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { fromNow } from 'vs/base/common/date'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { escapeRegExpCharacters } from 'vs/base/common/strings'; const InitialPageSize = 20; const SubsequentPageSize = 40; @@ -80,27 +81,30 @@ interface TimelineActionContext { } interface TimelineCursors { - startCursors?: { before: any; after?: any }; - endCursors?: { before: any; after?: any }; + startCursors?: { before: string; after?: string }; + endCursors?: { before: string; after?: string }; more: boolean; } -export class TimelinePane extends ViewPane { - static readonly ID = 'timeline'; - static readonly TITLE = localize('timeline', 'Timeline'); +export const TimelineFollowActiveEditorContext = new RawContextKey('timelineFollowActiveEditor', true); - private _container!: HTMLElement; - private _messageElement!: HTMLDivElement; - private _treeElement!: HTMLDivElement; +export class TimelinePane extends ViewPane { + static readonly TITLE = localize('timeline', "Timeline"); + + private _$container!: HTMLElement; + private _$message!: HTMLDivElement; + private _$titleDescription!: HTMLSpanElement; + private _$tree!: HTMLDivElement; private _tree!: WorkbenchObjectTree; private _treeRenderer: TimelineTreeRenderer | undefined; - private _menus: TimelineMenus; + private commands: TimelinePaneCommands; private _visibilityDisposables: DisposableStore | undefined; + private _followActiveEditorContext: IContextKey; + private _excludedSources: Set; private _cursorsByProvider: Map = new Map(); private _items: { element: TreeElement }[] = []; - private _loadingMessageTimer: any | undefined; private _pendingRequests = new Map(); private _uri: URI | undefined; @@ -122,13 +126,53 @@ export class TimelinePane extends ViewPane { ) { super({ ...options, titleMenuId: MenuId.TimelineTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); - this._menus = this._register(this.instantiationService.createInstance(TimelineMenus, this.id)); + this.commands = this._register(this.instantiationService.createInstance(TimelinePaneCommands, this)); const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); - scopedContextKeyService.createKey('view', TimelinePane.ID); + scopedContextKeyService.createKey('view', TimelinePaneId); + + this._followActiveEditorContext = TimelineFollowActiveEditorContext.bindTo(this.contextKeyService); this._excludedSources = new Set(configurationService.getValue('timeline.excludeSources')); configurationService.onDidChangeConfiguration(this.onConfigurationChanged, this); + + this._register(timelineService.onDidChangeUri(uri => this.setUri(uri), this)); + } + + private _followActiveEditor: boolean = true; + get followActiveEditor(): boolean { + return this._followActiveEditor; + } + set followActiveEditor(value: boolean) { + if (this._followActiveEditor === value) { + return; + } + + this._followActiveEditor = value; + this._followActiveEditorContext.set(value); + + if (value) { + this.onActiveEditorChanged(); + } + } + + reset() { + this.loadTimeline(true); + } + + setUri(uri: URI) { + this.setUriCore(uri, true); + } + + private setUriCore(uri: URI | undefined, disableFollowing: boolean) { + if (disableFollowing) { + this.followActiveEditor = false; + } + + this._uri = uri; + this.titleDescription = uri ? basename(uri.fsPath) : ''; + this._treeRenderer?.setUri(uri); + this.loadTimeline(true); } private onConfigurationChanged(e: IConfigurationChangeEvent) { @@ -141,6 +185,10 @@ export class TimelinePane extends ViewPane { } private onActiveEditorChanged() { + if (!this.followActiveEditor) { + return; + } + let uri; const editor = this.editorService.activeEditor; @@ -154,9 +202,7 @@ export class TimelinePane extends ViewPane { return; } - this._uri = uri; - this._treeRenderer?.setUri(uri); - this.loadTimeline(true); + this.setUriCore(uri, false); } private onProvidersChanged(e: TimelineProvidersChangeEvent) { @@ -177,8 +223,14 @@ export class TimelinePane extends ViewPane { } } - private onReset() { - this.loadTimeline(true); + private _titleDescription: string | undefined; + get titleDescription(): string | undefined { + return this._titleDescription; + } + + set titleDescription(description: string | undefined) { + this._titleDescription = description; + this._$titleDescription.textContent = description ?? ''; } private _message: string | undefined; @@ -192,7 +244,7 @@ export class TimelinePane extends ViewPane { } private updateMessage(): void { - if (this._message) { + if (this._message !== undefined) { this.showMessage(this._message); } else { this.hideMessage(); @@ -200,35 +252,32 @@ export class TimelinePane extends ViewPane { } private showMessage(message: string): void { - DOM.removeClass(this._messageElement, 'hide'); + DOM.removeClass(this._$message, 'hide'); this.resetMessageElement(); - this._messageElement.textContent = message; + this._$message.textContent = message; } private hideMessage(): void { this.resetMessageElement(); - DOM.addClass(this._messageElement, 'hide'); + DOM.addClass(this._$message, 'hide'); } private resetMessageElement(): void { - DOM.clearNode(this._messageElement); + DOM.clearNode(this._$message); } + private _pendingAnyResults: boolean = false; private async loadTimeline(reset: boolean, sources?: string[], options: TimelineOptions = {}) { const defaultPageSize = reset ? InitialPageSize : SubsequentPageSize; // If we have no source, we are reseting all sources, so cancel everything in flight and reset caches if (sources === undefined) { if (reset) { + this._pendingAnyResults = this._pendingAnyResults || this._items.length !== 0; this._items.length = 0; this._cursorsByProvider.clear(); - if (this._loadingMessageTimer) { - clearTimeout(this._loadingMessageTimer); - this._loadingMessageTimer = undefined; - } - for (const { tokenSource } of this._pendingRequests.values()) { tokenSource.dispose(true); } @@ -237,30 +286,27 @@ export class TimelinePane extends ViewPane { } // TODO[ECA]: Are these the right the list of schemes to exclude? Is there a better way? - if (this._uri && (this._uri.scheme === 'vscode-settings' || this._uri.scheme === 'webview-panel' || this._uri.scheme === 'walkThrough')) { - this.message = localize('timeline.editorCannotProvideTimeline', 'The active editor cannot provide timeline information.'); - this._tree.setChildren(null, undefined); + if (this._uri?.scheme === 'vscode-settings' || this._uri?.scheme === 'webview-panel' || this._uri?.scheme === 'walkThrough') { + this._uri = undefined; + this._items.length = 0; + this.refresh(); return; } - if (reset && this._uri !== undefined) { - this._loadingMessageTimer = setTimeout((uri: URI) => { - if (uri !== this._uri) { - return; - } - - this._tree.setChildren(null, undefined); - this.message = localize('timeline.loading', 'Loading timeline for {0}...', basename(uri.fsPath)); - }, 500, this._uri); + if (!this._pendingAnyResults && this._uri !== undefined) { + this.setLoadingUriMessage(); } } if (this._uri === undefined) { + this._items.length = 0; + this.refresh(); + return; } - const filteredSources = (sources ?? this.timelineService.getSources()).filter(s => !this._excludedSources.has(s)); + const filteredSources = (sources ?? this.timelineService.getSources().map(s => s.id)).filter(s => !this._excludedSources.has(s)); if (filteredSources.length === 0) { if (reset) { this.refresh(); @@ -284,6 +330,8 @@ export class TimelinePane extends ViewPane { } } + let noRequests = true; + for (const source of filteredSources) { let request = this._pendingRequests.get(source); @@ -301,15 +349,18 @@ export class TimelinePane extends ViewPane { { cursor: options.before ? cursors?.startCursors?.before : (cursors?.endCursors ?? cursors?.startCursors)?.after, ...options, - limit: options.limit === 0 ? undefined : options.limit ?? defaultPageSize + limit: options.limit === 0 + ? undefined + : options.limit ?? defaultPageSize }, - request?.tokenSource ?? new CancellationTokenSource(), { cacheResults: true } + request?.tokenSource ?? new CancellationTokenSource(), { cacheResults: true, resetCache: false } )!; if (request === undefined) { continue; } + noRequests = false; this._pendingRequests.set(source, request); if (!reusingToken) { request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source)); @@ -321,21 +372,32 @@ export class TimelinePane extends ViewPane { source, this._uri, { ...options, - limit: options.limit === 0 ? undefined : (reset ? cursors?.endCursors?.after : undefined) ?? options.limit ?? defaultPageSize + limit: options.limit === 0 + ? undefined + : (reset && cursors?.endCursors?.after !== undefined + ? { cursor: cursors.endCursors.after } + : undefined) ?? options.limit ?? defaultPageSize }, - new CancellationTokenSource(), { cacheResults: true } + new CancellationTokenSource(), { cacheResults: true, resetCache: true } )!; if (request === undefined) { continue; } + noRequests = false; this._pendingRequests.set(source, request); request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source)); } this.handleRequest(request); } + + if (noRequests) { + this.refresh(); + } else if (this.message !== undefined) { + this.setLoadingUriMessage(); + } } private async handleRequest(request: TimelineRequest) { @@ -348,13 +410,20 @@ export class TimelinePane extends ViewPane { } if ( - timeline === undefined || request.tokenSource.token.isCancellationRequested || request.uri !== this._uri ) { return; } + if (timeline === undefined) { + if (this._pendingRequests.size === 0) { + this.refresh(); + } + + return; + } + let items: TreeElement[]; const source = request.source; @@ -432,8 +501,7 @@ export class TimelinePane extends ViewPane { // If we have items already and there are other pending requests, debounce for a bit to wait for other requests if (alreadyHadItems && this._pendingRequests.size !== 0) { this.refreshDebounced(); - } - else { + } else { this.refresh(); } } @@ -512,17 +580,22 @@ export class TimelinePane extends ViewPane { } private refresh() { - if (this._loadingMessageTimer) { - clearTimeout(this._loadingMessageTimer); - this._loadingMessageTimer = undefined; - } - - if (this._items.length === 0) { - this.message = localize('timeline.noTimelineInfo', 'No timeline information was provided.'); + if (this._uri === undefined) { + this.titleDescription = undefined; + this.message = localize('timeline.editorCannotProvideTimeline', 'The active editor cannot provide timeline information.'); + } else if (this._items.length === 0) { + if (this._pendingRequests.size !== 0) { + this.setLoadingUriMessage(); + } else { + this.titleDescription = basename(this._uri.fsPath); + this.message = localize('timeline.noTimelineInfo', 'No timeline information was provided.'); + } } else { + this.titleDescription = basename(this._uri.fsPath); this.message = undefined; } + this._pendingAnyResults = false; this._tree.setChildren(null, this._items); } @@ -542,36 +615,46 @@ export class TimelinePane extends ViewPane { this.timelineService.onDidChangeProviders(this.onProvidersChanged, this, this._visibilityDisposables); this.timelineService.onDidChangeTimeline(this.onTimelineChanged, this, this._visibilityDisposables); - this.timelineService.onDidReset(this.onReset, this, this._visibilityDisposables); this.editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this, this._visibilityDisposables); this.onActiveEditorChanged(); } else { this._visibilityDisposables?.dispose(); } + + super.setVisible(visible); } protected layoutBody(height: number, width: number): void { this._tree.layout(height, width); } + protected renderHeaderTitle(container: HTMLElement): void { + super.renderHeaderTitle(container, this.title); + + DOM.addClass(container, 'timeline-view'); + this._$titleDescription = DOM.append(container, DOM.$('span.description', undefined, this.titleDescription ?? '')); + } + protected renderBody(container: HTMLElement): void { - this._container = container; + super.renderBody(container); + + this._$container = container; DOM.addClasses(container, 'tree-explorer-viewlet-tree-view', 'timeline-tree-view'); - this._messageElement = DOM.append(this._container, DOM.$('.message')); - DOM.addClass(this._messageElement, 'timeline-subtle'); + this._$message = DOM.append(this._$container, DOM.$('.message')); + DOM.addClass(this._$message, 'timeline-subtle'); this.message = localize('timeline.editorCannotProvideTimeline', 'The active editor cannot provide timeline information.'); - this._treeElement = document.createElement('div'); - DOM.addClasses(this._treeElement, 'customview-tree', 'file-icon-themable-tree', 'hide-arrows'); + this._$tree = document.createElement('div'); + DOM.addClasses(this._$tree, 'customview-tree', 'file-icon-themable-tree', 'hide-arrows'); // DOM.addClass(this._treeElement, 'show-file-icons'); - container.appendChild(this._treeElement); + container.appendChild(this._$tree); - this._treeRenderer = this.instantiationService.createInstance(TimelineTreeRenderer, this._menus); + this._treeRenderer = this.instantiationService.createInstance(TimelineTreeRenderer, this.commands); this._tree = >this.instantiationService.createInstance(WorkbenchObjectTree, 'TimelinePane', - this._treeElement, new TimelineListVirtualDelegate(), [this._treeRenderer], { + this._$tree, new TimelineListVirtualDelegate(), [this._treeRenderer], { identityProvider: new TimelineIdentityProvider(), keyboardNavigationLabelProvider: new TimelineKeyboardNavigationLabelProvider(), overrideStyles: { @@ -580,12 +663,13 @@ export class TimelinePane extends ViewPane { } }); - const customTreeNavigator = new TreeResourceNavigator(this._tree, { openOnFocus: false, openOnSelection: false }); + const customTreeNavigator = ResourceNavigator.createTreeResourceNavigator(this._tree, { openOnFocus: false, openOnSelection: false }); this._register(customTreeNavigator); - this._register(this._tree.onContextMenu(e => this.onContextMenu(this._menus, e))); + this._register(this._tree.onContextMenu(e => this.onContextMenu(this.commands, e))); + this._register(this._tree.onDidChangeSelection(e => this.ensureValidItems())); this._register( customTreeNavigator.onDidOpenResource(e => { - if (!e.browserEvent) { + if (!e.browserEvent || !this.ensureValidItems()) { return; } @@ -612,8 +696,26 @@ export class TimelinePane extends ViewPane { }) ); } + ensureValidItems() { + if (this._pendingAnyResults) { + this._tree.setChildren(null, undefined); - private onContextMenu(menus: TimelineMenus, treeEvent: ITreeContextMenuEvent): void { + this.setLoadingUriMessage(); + + this._pendingAnyResults = false; + return false; + } + + return true; + } + + setLoadingUriMessage() { + const file = this._uri && basename(this._uri.fsPath); + this.titleDescription = file ?? ''; + this.message = file ? localize('timeline.loading', 'Loading timeline for {0}...', file) : ''; + } + + private onContextMenu(commands: TimelinePaneCommands, treeEvent: ITreeContextMenuEvent): void { const item = treeEvent.element; if (item === null) { return; @@ -623,8 +725,12 @@ export class TimelinePane extends ViewPane { event.preventDefault(); event.stopPropagation(); + if (!this.ensureValidItems()) { + return; + } + this._tree.setFocus([item]); - const actions = menus.getResourceContextActions(item); + const actions = commands.getItemContextActions(item); if (!actions.length) { return; } @@ -733,7 +839,7 @@ class TimelineTreeRenderer implements ITreeRenderer this.updateTimelineSourceFilters())); + this.updateTimelineSourceFilters(); } - getResourceActions(element: TreeElement): IAction[] { + getItemActions(element: TreeElement): IAction[] { return this.getActions(MenuId.TimelineItemContext, { key: 'timelineItem', value: element.contextValue }).primary; } - getResourceContextActions(element: TreeElement): IAction[] { + getItemContextActions(element: TreeElement): IAction[] { return this.getActions(MenuId.TimelineItemContext, { key: 'timelineItem', value: element.contextValue }).secondary; } private getActions(menuId: MenuId, context: { key: string, value?: string }): { primary: IAction[]; secondary: IAction[]; } { const contextKeyService = this.contextKeyService.createScoped(); - contextKeyService.createKey('view', this.id); + contextKeyService.createKey('view', this.pane.id); contextKeyService.createKey(context.key, context.value); const menu = this.menuService.createMenu(menuId, contextKeyService); @@ -827,4 +993,37 @@ class TimelineMenus extends Disposable { return result; } + + private updateTimelineSourceFilters() { + this.sourceDisposables.clear(); + + const excluded = new Set(this.configurationService.getValue('timeline.excludeSources') ?? []); + + for (const source of this.timelineService.getSources()) { + this.sourceDisposables.add(registerAction2(class extends Action2 { + constructor() { + super({ + id: `timeline.toggleExcludeSource:${source.id}`, + title: { value: localize('timeline.filterSource', "Include: {0}", source.label), original: `Include: ${source.label}` }, + category: { value: localize('timeline', "Timeline"), original: 'Timeline' }, + menu: { + id: MenuId.TimelineTitle, + group: '2_sources', + }, + toggled: ContextKeyExpr.regex(`config.timeline.excludeSources`, new RegExp(`\\b${escapeRegExpCharacters(source.id)}\\b`)).negate() + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + if (excluded.has(source.id)) { + excluded.delete(source.id); + } else { + excluded.add(source.id); + } + + const configurationService = accessor.get(IConfigurationService); + configurationService.updateValue('timeline.excludeSources', [...excluded.keys()]); + } + })); + } + } } diff --git a/src/vs/workbench/contrib/timeline/common/timeline.ts b/src/vs/workbench/contrib/timeline/common/timeline.ts index 9029fc3814..df79b7e36d 100644 --- a/src/vs/workbench/contrib/timeline/common/timeline.ts +++ b/src/vs/workbench/contrib/timeline/common/timeline.ts @@ -15,6 +15,8 @@ export function toKey(extension: ExtensionIdentifier | string, source: string) { return `${typeof extension === 'string' ? extension : ExtensionIdentifier.toKey(extension)}|${source}`; } +export const TimelinePaneId = 'timeline'; + export interface TimelineItem { handle: string; source: string; @@ -40,7 +42,12 @@ export interface TimelineChangeEvent { export interface TimelineOptions { cursor?: string; before?: boolean; - limit?: number | string; + limit?: number | { cursor: string }; +} + +export interface InternalTimelineOptions { + cacheResults: boolean; + resetCache: boolean; } export interface Timeline { @@ -59,7 +66,12 @@ export interface Timeline { export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable { onDidChange?: Event; - provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean }): Promise; + provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: InternalTimelineOptions): Promise; +} + +export interface TimelineSource { + id: string; + label: string; } export interface TimelineProviderDescriptor { @@ -86,17 +98,16 @@ export interface ITimelineService { onDidChangeProviders: Event; onDidChangeTimeline: Event; - onDidReset: Event; + onDidChangeUri: Event; registerTimelineProvider(provider: TimelineProvider): IDisposable; unregisterTimelineProvider(id: string): void; - getSources(): string[]; + getSources(): TimelineSource[]; - getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: { cacheResults?: boolean }): TimelineRequest | undefined; + getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: InternalTimelineOptions): TimelineRequest | undefined; - // refresh(fetch?: 'all' | 'more'): void; - reset(): void; + setUri(uri: URI): void; } const TIMELINE_SERVICE_ID = 'timeline'; diff --git a/src/vs/workbench/contrib/timeline/common/timelineService.ts b/src/vs/workbench/contrib/timeline/common/timelineService.ts index 335a24f2a5..d67735497a 100644 --- a/src/vs/workbench/contrib/timeline/common/timelineService.ts +++ b/src/vs/workbench/contrib/timeline/common/timelineService.ts @@ -9,7 +9,8 @@ import { IDisposable } from 'vs/base/common/lifecycle'; // import { basename } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; -import { ITimelineService, TimelineChangeEvent, TimelineOptions, TimelineProvidersChangeEvent, TimelineProvider } from './timeline'; +import { ITimelineService, TimelineChangeEvent, TimelineOptions, TimelineProvidersChangeEvent, TimelineProvider, InternalTimelineOptions, TimelinePaneId } from './timeline'; +import { IViewsService } from 'vs/workbench/common/views'; export class TimelineService implements ITimelineService { _serviceBrand: undefined; @@ -19,72 +20,88 @@ export class TimelineService implements ITimelineService { private readonly _onDidChangeTimeline = new Emitter(); readonly onDidChangeTimeline: Event = this._onDidChangeTimeline.event; - - private readonly _onDidReset = new Emitter(); - readonly onDidReset: Event = this._onDidReset.event; + private readonly _onDidChangeUri = new Emitter(); + readonly onDidChangeUri: Event = this._onDidChangeUri.event; private readonly _providers = new Map(); private readonly _providerSubscriptions = new Map(); - constructor(@ILogService private readonly logService: ILogService) { + constructor( + @ILogService private readonly logService: ILogService, + @IViewsService protected viewsService: IViewsService, + ) { + // let source = 'slow-source'; // this.registerTimelineProvider({ - // id: 'local-history', - // label: 'Local History', - // provideTimeline(uri: URI, token: CancellationToken) { + // scheme: '*', + // id: source, + // label: 'Slow Source', + // provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean | undefined; }) { // return new Promise(resolve => setTimeout(() => { - // resolve([ - // { - // id: '1', - // label: 'Slow Timeline1', - // description: basename(uri.fsPath), - // timestamp: Date.now(), - // source: 'local-history' - // }, - // { - // id: '2', - // label: 'Slow Timeline2', - // description: basename(uri.fsPath), - // timestamp: new Date(0).getTime(), - // source: 'local-history' - // } - // ]); - // }, 3000)); + // resolve({ + // source: source, + // items: [ + // { + // handle: `${source}|1`, + // id: '1', + // label: 'Slow Timeline1', + // description: basename(uri.fsPath), + // timestamp: Date.now(), + // source: source + // }, + // { + // handle: `${source}|2`, + // id: '2', + // label: 'Slow Timeline2', + // description: basename(uri.fsPath), + // timestamp: new Date(0).getTime(), + // source: source + // } + // ] + // }); + // }, 5000)); // }, // dispose() { } // }); + // source = 'very-slow-source'; // this.registerTimelineProvider({ - // id: 'slow-history', - // label: 'Slow History', - // provideTimeline(uri: URI, token: CancellationToken) { + // scheme: '*', + // id: source, + // label: 'Very Slow Source', + // provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: { cacheResults?: boolean | undefined; }) { // return new Promise(resolve => setTimeout(() => { - // resolve([ - // { - // id: '1', - // label: 'VERY Slow Timeline1', - // description: basename(uri.fsPath), - // timestamp: Date.now(), - // source: 'slow-history' - // }, - // { - // id: '2', - // label: 'VERY Slow Timeline2', - // description: basename(uri.fsPath), - // timestamp: new Date(0).getTime(), - // source: 'slow-history' - // } - // ]); - // }, 6000)); + // resolve({ + // source: source, + // items: [ + // { + // handle: `${source}|1`, + // id: '1', + // label: 'VERY Slow Timeline1', + // description: basename(uri.fsPath), + // timestamp: Date.now(), + // source: source + // }, + // { + // handle: `${source}|2`, + // id: '2', + // label: 'VERY Slow Timeline2', + // description: basename(uri.fsPath), + // timestamp: new Date(0).getTime(), + // source: source + // } + // ] + // }); + // }, 10000)); // }, // dispose() { } // }); } getSources() { - return [...this._providers.keys()]; + return [...this._providers.values()].map(p => ({ id: p.id, label: p.label })); } - getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: { cacheResults?: boolean }) { + getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: InternalTimelineOptions) { this.logService.trace(`TimelineService#getTimeline(${id}): uri=${uri.toString(true)}`); const provider = this._providers.get(id); @@ -161,11 +178,8 @@ export class TimelineService implements ITimelineService { this._onDidChangeProviders.fire({ removed: [id] }); } - // refresh(fetch?: 'all' | 'more') { - // this._onDidChangeTimeline.fire({ fetch: fetch }); - // } - - reset() { - this._onDidReset.fire(); + setUri(uri: URI) { + this.viewsService.openView(TimelinePaneId, true); + this._onDidChangeUri.fire(uri); } } diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index c5756e5596..3484bf0647 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -67,11 +67,11 @@ export class ReleaseNotesManager { const html = await this.renderBody(releaseNoteText); const title = nls.localize('releaseNotesInputName', "Release Notes: {0}", version); - const activeControl = this._editorService.activeControl; + const activeEditorPane = this._editorService.activeEditorPane; if (this._currentReleaseNotes) { this._currentReleaseNotes.setName(title); this._currentReleaseNotes.webview.html = html; - this._webviewWorkbenchService.revealWebview(this._currentReleaseNotes, activeControl ? activeControl.group : this._editorGroupService.activeGroup, false); + this._webviewWorkbenchService.revealWebview(this._currentReleaseNotes, activeEditorPane ? activeEditorPane.group : this._editorGroupService.activeGroup, false); } else { this._currentReleaseNotes = this._webviewWorkbenchService.createWebview( generateUuid(), diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 3d64a28395..f702114a4d 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -22,10 +22,9 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ // import { ReleaseNotesManager } from './releaseNotesEditor'; import { isWindows } from 'vs/base/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey, IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { FalseContext } from 'vs/platform/contextkey/common/contextkeys'; import { ShowCurrentReleaseNotesActionId, CheckForVSCodeUpdateActionId } from 'vs/workbench/contrib/update/common/update'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -417,7 +416,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu command: { id: 'update.checking', title: nls.localize('checkingForUpdates', "Checking for Updates..."), - precondition: FalseContext + precondition: ContextKeyExpr.false() }, when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.CheckingForUpdates) }); @@ -438,7 +437,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu command: { id: 'update.downloading', title: nls.localize('DownloadingUpdate', "Downloading Update..."), - precondition: FalseContext + precondition: ContextKeyExpr.false() }, when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloading) }); @@ -459,7 +458,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu command: { id: 'update.updating', title: nls.localize('installingUpdate', "Installing Update..."), - precondition: FalseContext + precondition: ContextKeyExpr.false() }, when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Updating) }); diff --git a/src/vs/workbench/contrib/url/common/trustedDomains.ts b/src/vs/workbench/contrib/url/common/trustedDomains.ts index 6873083464..efd8240dbc 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomains.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomains.ts @@ -10,6 +10,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const TRUSTED_DOMAINS_URI = URI.parse('trustedDomains:/Trusted Domains'); @@ -26,64 +27,86 @@ export const manageTrustedDomainSettingsCommand = { } }; +type ConfigureTrustedDomainChoice = 'trustDomain' | 'trustSubdomain' | 'trustAll' | 'manage'; +interface ConfigureTrustedDomainsQuickPickItem extends IQuickPickItem { + id: ConfigureTrustedDomainChoice; +} +type ConfigureTrustedDomainsChoiceClassification = { + choice: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; + export async function configureOpenerTrustedDomainsHandler( trustedDomains: string[], domainToConfigure: string, quickInputService: IQuickInputService, storageService: IStorageService, - editorService: IEditorService + editorService: IEditorService, + telemetryService: ITelemetryService ) { const parsedDomainToConfigure = URI.parse(domainToConfigure); const toplevelDomainSegements = parsedDomainToConfigure.authority.split('.'); const domainEnd = toplevelDomainSegements.slice(toplevelDomainSegements.length - 2).join('.'); const topLevelDomain = '*.' + domainEnd; - const trustDomainAndOpenLinkItem: IQuickPickItem = { + const trustDomainAndOpenLinkItem: ConfigureTrustedDomainsQuickPickItem = { type: 'item', label: localize('trustedDomain.trustDomain', 'Trust {0}', domainToConfigure), - id: domainToConfigure, + id: 'trustDomain', picked: true }; - const trustSubDomainAndOpenLinkItem: IQuickPickItem = { + const trustSubDomainAndOpenLinkItem: ConfigureTrustedDomainsQuickPickItem = { type: 'item', label: localize('trustedDomain.trustSubDomain', 'Trust {0} and all its subdomains', domainEnd), - id: topLevelDomain + id: 'trustSubdomain' }; - const openAllLinksItem: IQuickPickItem = { + const openAllLinksItem: ConfigureTrustedDomainsQuickPickItem = { type: 'item', label: localize('trustedDomain.trustAllDomains', 'Trust all domains (disables link protection)'), - id: '*' + id: 'trustAll' }; - const manageTrustedDomainItem: IQuickPickItem = { + const manageTrustedDomainItem: ConfigureTrustedDomainsQuickPickItem = { type: 'item', label: localize('trustedDomain.manageTrustedDomains', 'Manage Trusted Domains'), id: 'manage' }; - const pickedResult = await quickInputService.pick( + const pickedResult = await quickInputService.pick( [trustDomainAndOpenLinkItem, trustSubDomainAndOpenLinkItem, openAllLinksItem, manageTrustedDomainItem], { activeItem: trustDomainAndOpenLinkItem } ); - if (pickedResult) { - if (pickedResult.id === 'manage') { - editorService.openEditor({ - resource: TRUSTED_DOMAINS_URI, - mode: 'jsonc' - }); - return trustedDomains; - } - if (pickedResult.id && trustedDomains.indexOf(pickedResult.id) === -1) { - storageService.remove('http.linkProtectionTrustedDomainsContent', StorageScope.GLOBAL); - storageService.store( - 'http.linkProtectionTrustedDomains', - JSON.stringify([...trustedDomains, pickedResult.id]), - StorageScope.GLOBAL - ); + if (pickedResult && pickedResult.id) { + telemetryService.publicLog2<{ choice: string }, ConfigureTrustedDomainsChoiceClassification>( + 'trustedDomains.configureTrustedDomainsQuickPickChoice', + { choice: pickedResult.id } + ); - return [...trustedDomains, pickedResult.id]; + switch (pickedResult.id) { + case 'manage': + editorService.openEditor({ + resource: TRUSTED_DOMAINS_URI, + mode: 'jsonc' + }); + return trustedDomains; + case 'trustDomain': + case 'trustSubdomain': + case 'trustAll': + const itemToTrust = pickedResult.id === 'trustDomain' + ? domainToConfigure + : pickedResult.id === 'trustSubdomain' ? topLevelDomain : '*'; + + if (trustedDomains.indexOf(itemToTrust) === -1) { + storageService.remove('http.linkProtectionTrustedDomainsContent', StorageScope.GLOBAL); + storageService.store( + 'http.linkProtectionTrustedDomains', + JSON.stringify([...trustedDomains, itemToTrust]), + StorageScope.GLOBAL + ); + + return [...trustedDomains, pickedResult.id]; + } } } diff --git a/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts index eb5f25f99b..4b48a3bc7c 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts @@ -19,7 +19,11 @@ import { } from 'vs/workbench/contrib/url/common/trustedDomains'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +type TrustedDomainsDialogActionClassification = { + action: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; export class OpenerValidatorContributions implements IWorkbenchContribution { constructor( @@ -29,7 +33,8 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { @IProductService private readonly _productService: IProductService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IEditorService private readonly _editorService: IEditorService, - @IClipboardService private readonly _clipboardService: IClipboardService + @IClipboardService private readonly _clipboardService: IClipboardService, + @ITelemetryService private readonly _telemetryService: ITelemetryService ) { this._openerService.registerValidator({ shouldOpen: r => this.validateLink(r) }); } @@ -88,20 +93,34 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { // Open Link if (choice === 0) { + this._telemetryService.publicLog2<{ action: string }, TrustedDomainsDialogActionClassification>( + 'trustedDomains.dialogAction', + { action: 'open' } + ); return true; } // Copy Link else if (choice === 1) { + this._telemetryService.publicLog2<{ action: string }, TrustedDomainsDialogActionClassification>( + 'trustedDomains.dialogAction', + { action: 'copy' } + ); this._clipboardService.writeText(resource.toString(true)); } // Configure Trusted Domains else if (choice === 3) { + this._telemetryService.publicLog2<{ action: string }, TrustedDomainsDialogActionClassification>( + 'trustedDomains.dialogAction', + { action: 'configure' } + ); + const pickedDomains = await configureOpenerTrustedDomainsHandler( trustedDomains, domainToOpen, this._quickInputService, this._storageService, - this._editorService + this._editorService, + this._telemetryService ); // Trust all domains if (pickedDomains.indexOf('*') !== -1) { @@ -114,6 +133,11 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { return false; } + this._telemetryService.publicLog2<{ action: string }, TrustedDomainsDialogActionClassification>( + 'trustedDomains.dialogAction', + { action: 'cancel' } + ); + return false; } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts index 2c9482fb8c..93d28c7f2e 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts @@ -7,6 +7,8 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { UserDataSyncWorkbenchContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSync'; +import { UserDataSyncViewContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncView'; const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(UserDataSyncWorkbenchContribution, LifecyclePhase.Ready); +workbenchRegistry.registerWorkbenchContribution(UserDataSyncViewContribution, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 9f87229716..5aab7b99c1 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -23,18 +23,18 @@ import { localize } from 'vs/nls'; import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey, ContextKeyRegexExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { CONTEXT_SYNC_STATE, getSyncSourceFromRemoteContentResource, getUserDataSyncStore, ISyncConfiguration, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, toRemoteContentResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource, CONTEXT_SYNC_ENABLEMENT } from 'vs/platform/userDataSync/common/userDataSync'; +import { CONTEXT_SYNC_STATE, getUserDataSyncStore, ISyncConfiguration, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource, CONTEXT_SYNC_ENABLEMENT, toRemoteSyncResourceFromSource, PREVIEW_QUERY, resolveSyncResource, getSyncSourceFromResourceKey } from 'vs/platform/userDataSync/common/userDataSync'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import type { IEditorInput } from 'vs/workbench/common/editor'; +import { IEditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; @@ -65,7 +65,7 @@ type ConfigureSyncQuickPickItem = { id: ResourceKey, label: string, description? function getSyncAreaLabel(source: SyncSource): string { switch (source) { case SyncSource.Settings: return localize('settings', "Settings"); - case SyncSource.Keybindings: return localize('keybindings', "Keybindings"); + case SyncSource.Keybindings: return localize('keybindings', "Keyboard Shortcuts"); case SyncSource.Extensions: return localize('extensions', "Extensions"); case SyncSource.GlobalState: return localize('ui state label', "UI State"); } @@ -155,7 +155,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this._register(Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeSyncStatus(this.userDataSyncService.status))); this._register(userDataSyncService.onDidChangeConflicts(() => this.onDidChangeConflicts(this.userDataSyncService.conflictsSources))); this._register(userDataSyncService.onSyncErrors(errors => this.onSyncErrors(errors))); - this._register(this.authTokenService.onTokenFailed(_ => this.authenticationService.getSessions(this.userDataSyncStore!.authenticationProviderId))); + this._register(this.authTokenService.onTokenFailed(_ => this.onTokenFailed())); this._register(this.userDataSyncEnablementService.onDidChangeEnablement(enabled => this.onDidChangeEnablement(enabled))); this._register(this.authenticationService.onDidRegisterAuthenticationProvider(e => this.onDidRegisterAuthenticationProvider(e))); this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(e => this.onDidUnregisterAuthenticationProvider(e))); @@ -255,6 +255,16 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } + private async onTokenFailed(): Promise { + if (this.activeAccount) { + const accounts = (await this.authenticationService.getSessions(this.userDataSyncStore!.authenticationProviderId) || []); + const matchingAccount = accounts.filter(a => a.id === this.activeAccount?.id)[0]; + this.setActiveAccount(matchingAccount); + } else { + this.setActiveAccount(undefined); + } + } + private async onDidRegisterAuthenticationProvider(providerId: string) { if (providerId === this.userDataSyncStore!.authenticationProviderId) { await this.initializeActiveAccount(); @@ -344,7 +354,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private async acceptRemote(syncSource: SyncSource) { try { - const contents = await this.userDataSyncService.getRemoteContent(syncSource, false); + const contents = await this.userDataSyncService.resolveContent(toRemoteSyncResourceFromSource(syncSource).with({ query: PREVIEW_QUERY })); if (contents) { await this.userDataSyncService.accept(syncSource, contents); } @@ -458,7 +468,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo return; } const resource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsResource : this.workbenchEnvironmentService.keybindingsResource; - if (isEqual(resource, this.editorService.activeEditor?.resource)) { + if (isEqual(resource, toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }))) { // Do not show notification if the file in error is active return; } @@ -744,7 +754,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo label = localize('keybindings conflicts preview', "Keybindings Conflicts (Remote ↔ Local)"); } if (previewResource) { - const remoteContentResource = toRemoteContentResource(source); + const remoteContentResource = toRemoteSyncResourceFromSource(source).with({ query: PREVIEW_QUERY }); await this.editorService.openEditor({ leftResource: remoteContentResource, rightResource: previewResource, @@ -835,7 +845,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private registerShowSettingsConflictsAction(): void { - const resolveSettingsConflictsWhenContext = ContextKeyRegexExpr.create(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i); + const resolveSettingsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i); CommandsRegistry.registerCommand(resolveSettingsConflictsCommand.id, () => this.handleConflicts(SyncSource.Settings)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', @@ -862,7 +872,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private registerShowKeybindingsConflictsAction(): void { - const resolveKeybindingsConflictsWhenContext = ContextKeyRegexExpr.create(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*keybindings.*/i); + const resolveKeybindingsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*keybindings.*/i); CommandsRegistry.registerCommand(resolveKeybindingsConflictsCommand.id, () => this.handleConflicts(SyncSource.Keybindings)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', @@ -1046,15 +1056,8 @@ class UserDataRemoteContentProvider implements ITextModelContentProvider { } provideTextContent(uri: URI): Promise | null { - let promise: Promise | undefined; - if (isEqual(uri, toRemoteContentResource(SyncSource.Settings))) { - promise = this.userDataSyncService.getRemoteContent(SyncSource.Settings, true); - } - if (isEqual(uri, toRemoteContentResource(SyncSource.Keybindings))) { - promise = this.userDataSyncService.getRemoteContent(SyncSource.Keybindings, true); - } - if (promise) { - return promise.then(content => this.modelService.createModel(content || '', this.modeService.create('jsonc'), uri)); + if (uri.scheme === USER_DATA_SYNC_SCHEME) { + return this.userDataSyncService.resolveContent(uri).then(content => this.modelService.createModel(content || '', this.modeService.create('jsonc'), uri)); } return null; } @@ -1110,7 +1113,7 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio return true; } - if (getSyncSourceFromRemoteContentResource(model.uri) !== undefined) { + if (resolveSyncResource(model.uri) !== null && model.uri.query === PREVIEW_QUERY) { return this.configurationService.getValue('diffEditor.renderSideBySide'); } @@ -1120,14 +1123,14 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio private createAcceptChangesWidgetRenderer(): void { if (!this.acceptChangesButton) { - const isRemote = getSyncSourceFromRemoteContentResource(this.editor.getModel()!.uri) !== undefined; + const isRemote = resolveSyncResource(this.editor.getModel()!.uri) !== null; const acceptRemoteLabel = localize('accept remote', "Accept Remote"); const acceptLocalLabel = localize('accept local', "Accept Local"); this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, isRemote ? acceptRemoteLabel : acceptLocalLabel, null); this._register(this.acceptChangesButton.onClick(async () => { const model = this.editor.getModel(); if (model) { - const conflictsSource = (getSyncSourceFromPreviewResource(model.uri, this.environmentService) || getSyncSourceFromRemoteContentResource(model.uri))!; + const conflictsSource = (getSyncSourceFromPreviewResource(model.uri, this.environmentService) || getSyncSourceFromResourceKey(resolveSyncResource(model.uri)!.resourceKey))!; this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource, action: isRemote ? 'acceptRemote' : 'acceptLocal' }); const syncAreaLabel = getSyncAreaLabel(conflictsSource); const result = await this.dialogService.confirm({ @@ -1136,8 +1139,8 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio ? localize('Sync accept remote', "Sync: {0}", acceptRemoteLabel) : localize('Sync accept local', "Sync: {0}", acceptLocalLabel), message: isRemote - ? localize('confirm replace and overwrite local', "Would you like to accept Remote {0} and replace Local {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()) - : localize('confirm replace and overwrite remote', "Would you like to accept Local {0} and replace Remote {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()), + ? localize('confirm replace and overwrite local', "Would you like to accept remote {0} and replace local {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()) + : localize('confirm replace and overwrite remote', "Would you like to accept local {0} and replace remote {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()), primaryButton: isRemote ? acceptRemoteLabel : acceptLocalLabel }); if (result.confirmed) { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts new file mode 100644 index 0000000000..73600442ef --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts @@ -0,0 +1,223 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IViewsRegistry, Extensions, ITreeViewDescriptor, ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState, IViewsService, TreeViewItemHandleArg, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; +import { localize } from 'vs/nls'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { TreeViewPane, TreeView } from 'vs/workbench/browser/parts/views/treeView'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ALL_RESOURCE_KEYS, CONTEXT_SYNC_ENABLEMENT, IUserDataSyncStoreService, toRemoteSyncResource, resolveSyncResource, IUserDataSyncBackupStoreService, IResourceRefHandle, ResourceKey, toLocalBackupSyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService, RawContextKey, ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; +import { URI } from 'vs/base/common/uri'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { FolderThemeIcon, FileThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { fromNow } from 'vs/base/common/date'; +import { pad } from 'vs/base/common/strings'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; + +export class UserDataSyncViewContribution implements IWorkbenchContribution { + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncBackupStoreService private readonly userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + ) { + const container = this.registerSyncViewContainer(); + // Disable remote backup view until server is upgraded. + // this.registerBackupView(container, true); + this.registerBackupView(container, false); + } + + private registerSyncViewContainer(): ViewContainer { + return Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( + { + id: 'workbench.view.sync', + name: localize('sync', "Sync"), + ctorDescriptor: new SyncDescriptor( + ViewPaneContainer, + ['workbench.view.sync', `workbench.view.sync.state`, { mergeViewWithContainerWhenSingleView: true }] + ), + icon: 'codicon-sync', + hideIfEmpty: true, + }, ViewContainerLocation.Sidebar); + } + + private registerBackupView(container: ViewContainer, remote: boolean): void { + const id = `workbench.views.sync.${remote ? 'remote' : 'local'}BackupView`; + const name = remote ? localize('remote title', "Remote Backup") : localize('local title', "Local Backup"); + const contextKey = new RawContextKey(`showUserDataSync${remote ? 'Remote' : 'Local'}BackupView`, false); + const viewEnablementContext = contextKey.bindTo(this.contextKeyService); + const treeView = this.instantiationService.createInstance(TreeView, id, name); + treeView.showCollapseAllAction = true; + treeView.showRefreshAction = true; + const disposable = treeView.onDidChangeVisibility(visible => { + if (visible && !treeView.dataProvider) { + disposable.dispose(); + treeView.dataProvider = this.instantiationService.createInstance(UserDataSyncHistoryViewDataProvider, id, + (resourceKey: ResourceKey) => remote ? this.userDataSyncStoreService.getAllRefs(resourceKey) : this.userDataSyncBackupStoreService.getAllRefs(resourceKey), + (resourceKey: ResourceKey, ref: string) => remote ? toRemoteSyncResource(resourceKey, ref) : toLocalBackupSyncResource(resourceKey, ref)); + } + }); + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + viewsRegistry.registerViews([{ + id, + name, + ctorDescriptor: new SyncDescriptor(TreeViewPane), + when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, contextKey), + canToggleVisibility: true, + canMoveView: true, + treeView, + collapsed: false, + order: 100, + }], container); + + registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.showSync${remote ? 'Remote' : 'Local'}BackupView`, + title: remote ? + { value: localize('workbench.action.showSyncRemoteBackup', "Show Remote Backup"), original: `Show Remote Backup` } + : { value: localize('workbench.action.showSyncLocalBackup', "Show Local Backup"), original: `Show Local Backup` }, + category: { value: localize('sync', "Sync"), original: `Sync` }, + menu: { + id: MenuId.CommandPalette, + when: CONTEXT_SYNC_ENABLEMENT + }, + }); + } + async run(accessor: ServicesAccessor): Promise { + viewEnablementContext.set(true); + accessor.get(IViewsService).openView(id, true); + } + }); + + this.registerActions(id); + } + + private registerActions(viewId: string) { + registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.sync.${viewId}.resolveResourceRef`, + title: localize('workbench.actions.sync.resolveResourceRef', "Resolve Resource Ref"), + }); + } + async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { + const editorService = accessor.get(IEditorService); + let resource = URI.parse(handle.$treeItemHandle); + const result = resolveSyncResource(resource); + if (result) { + resource = resource.with({ fragment: result.resourceKey }); + await editorService.openEditor({ resource }); + } + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.sync.${viewId}.resolveResourceRefCompletely`, + title: localize('workbench.actions.sync.resolveResourceRefCompletely', "Show full content"), + menu: { + id: MenuId.ViewItemContext, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', viewId), ContextKeyExpr.regex('viewItem', /syncref-.*/i)) + }, + }); + } + async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { + const editorService = accessor.get(IEditorService); + await editorService.openEditor({ resource: URI.parse(handle.$treeItemHandle) }); + } + }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.${viewId}.commpareWithLocal`, + title: localize('workbench.action.deleteRef', "Open Changes"), + menu: { + id: MenuId.ViewItemContext, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', viewId), ContextKeyExpr.regex('viewItem', /syncref-(settings|keybindings).*/i)) + }, + }); + } + async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise { + const editorService = accessor.get(IEditorService); + const environmentService = accessor.get(IEnvironmentService); + const resource = URI.parse(handle.$treeItemHandle); + const result = resolveSyncResource(resource); + if (result) { + const leftResource: URI = resource.with({ fragment: result.resourceKey }); + const rightResource: URI = result.resourceKey === 'settings' ? environmentService.settingsResource : environmentService.keybindingsResource; + await editorService.openEditor({ + leftResource, + rightResource, + options: { + preserveFocus: false, + pinned: true, + revealIfVisible: true, + }, + }); + } + } + }); + } + +} + +class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider { + + constructor( + private readonly viewId: string, + private getAllRefs: (resourceKey: ResourceKey) => Promise, + private toResource: (resourceKey: ResourceKey, ref: string) => URI + ) { + } + + async getChildren(element?: ITreeItem): Promise { + if (element) { + return this.getResources(element.handle); + } + return ALL_RESOURCE_KEYS.map(resourceKey => ({ + handle: resourceKey, + collapsibleState: TreeItemCollapsibleState.Collapsed, + label: { label: resourceKey }, + themeIcon: FolderThemeIcon, + contextValue: `sync-${resourceKey}` + })); + } + + private async getResources(handle: string): Promise { + const resourceKey = ALL_RESOURCE_KEYS.filter(key => key === handle)[0]; + if (resourceKey) { + const refHandles = await this.getAllRefs(resourceKey); + return refHandles.map(({ ref, created }) => { + const handle = this.toResource(resourceKey, ref).toString(); + return { + handle, + collapsibleState: TreeItemCollapsibleState.None, + label: { label: label(new Date(created)) }, + description: fromNow(created, true), + command: { id: `workbench.actions.sync.${this.viewId}.resolveResourceRef`, title: '', arguments: [{ $treeItemHandle: handle, $treeViewId: '' }] }, + themeIcon: FileThemeIcon, + contextValue: `syncref-${resourceKey}` + }; + }); + } + return []; + } + +} + +function label(date: Date): string { + return date.toLocaleDateString() + + ' ' + pad(date.getHours(), 2) + + ':' + pad(date.getMinutes(), 2) + + ':' + pad(date.getSeconds(), 2); +} + diff --git a/src/vs/workbench/contrib/watermark/browser/watermark.ts b/src/vs/workbench/contrib/watermark/browser/watermark.ts index d1755926ca..76f100ff4d 100644 --- a/src/vs/workbench/contrib/watermark/browser/watermark.ts +++ b/src/vs/workbench/contrib/watermark/browser/watermark.ts @@ -26,8 +26,6 @@ import * as dom from 'vs/base/browser/dom'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IDimension } from 'vs/platform/layout/browser/layoutService'; -// import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { assertIsDefined } from 'vs/base/common/types'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; @@ -165,7 +163,7 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr this.handleEditorPartSize(container, this.editorGroupsService.contentDimension); } - private handleEditorPartSize(container: HTMLElement, dimension: IDimension): void { + private handleEditorPartSize(container: HTMLElement, dimension: dom.IDimension): void { if (dimension.height <= 478) { dom.addClass(container, 'max-height-478px'); } else { diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index 7171e6f842..af0e2f1c0d 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -3,19 +3,21 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Dimension } from 'vs/base/browser/dom'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { memoize } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IWebviewService, Webview, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions, WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { Dimension } from 'vs/base/browser/dom'; +import { IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; /** * Webview editor overlay that creates and destroys the underlying webview as needed. */ -export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEditorOverlay { +export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOverlay { + private readonly _onDidWheel = this._register(new Emitter()); public readonly onDidWheel = this._onDidWheel.event; @@ -33,19 +35,32 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd private _owner: any = undefined; + private readonly _scopedContextKeyService = this._register(new MutableDisposable()); + private _findWidgetVisible: IContextKey; + public constructor( private readonly id: string, initialOptions: WebviewOptions, initialContentOptions: WebviewContentOptions, - @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, - @IWebviewService private readonly _webviewService: IWebviewService + @ILayoutService private readonly _layoutService: ILayoutService, + @IWebviewService private readonly _webviewService: IWebviewService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService ) { super(); this._options = initialOptions; this._contentOptions = initialContentOptions; - this._register(toDisposable(() => this.container.remove())); + this._findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(_contextKeyService); + } + + private readonly _onDispose = this._register(new Emitter()); + public onDispose = this._onDispose.event; + + dispose() { + this.container.remove(); + this._onDispose.fire(); + super.dispose(); } @memoize @@ -56,7 +71,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd // Webviews cannot be reparented in the dom as it will destory their contents. // Mount them to a high level node to avoid this. - this._layoutService.getWorkbenchElement().appendChild(container); + this._layoutService.container.appendChild(container); return container; } @@ -93,7 +108,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd private show() { if (!this._webview.value) { - const webview = this._webviewService.createWebview(this.id, this._options, this._contentOptions); + const webview = this._webviewService.createWebviewElement(this.id, this._options, this._contentOptions); this._webview.value = webview; webview.state = this._state; webview.html = this._html; @@ -101,7 +116,10 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd if (this._options.tryRestoreScrollPosition) { webview.initialScrollProgress = this._initialScrollProgress; } + webview.mountTo(this.container); + this._scopedContextKeyService.value = this._contextKeyService.createScoped(this.container); + this._findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(this._scopedContextKeyService.value); // Forward events from inner webview to outer listeners this._webviewEvents.clear(); @@ -188,11 +206,22 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd focus(): void { this.withWebview(webview => webview.focus()); } reload(): void { this.withWebview(webview => webview.reload()); } - showFind(): void { this.withWebview(webview => webview.showFind()); } - hideFind(): void { this.withWebview(webview => webview.hideFind()); } - runFindAction(previous: boolean): void { this.withWebview(webview => webview.runFindAction(previous)); } selectAll(): void { this.withWebview(webview => webview.selectAll()); } + showFind() { + if (this._webview.value) { + this._webview.value.showFind(); + this._findWidgetVisible.set(true); + } + } + + hideFind() { + this._findWidgetVisible.reset(); + this._webview.value?.hideFind(); + } + + runFindAction(previous: boolean): void { this.withWebview(webview => webview.runFindAction(previous)); } + public getInnerWebview() { return this._webview.value; } diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 09410d9fed..f68282d0c1 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -36,17 +36,17 @@ export interface WebviewIcons { export interface IWebviewService { _serviceBrand: undefined; - createWebview( + createWebviewElement( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, ): WebviewElement; - createWebviewEditorOverlay( + createWebviewOverlay( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, - ): WebviewEditorOverlay; + ): WebviewOverlay; setIcons(id: string, value: WebviewIcons | undefined): void; } @@ -71,7 +71,6 @@ export interface WebviewExtensionDescription { } export interface Webview extends IDisposable { - html: string; contentOptions: WebviewContentOptions; extension: WebviewExtensionDescription | undefined; @@ -101,14 +100,22 @@ export interface Webview extends IDisposable { windowDidDragEnd(): void; } +/** + * Basic webview rendered in the dom + */ export interface WebviewElement extends Webview { mountTo(parent: HTMLElement): void; } -export interface WebviewEditorOverlay extends Webview { +/** + * Dynamically created webview drawn over another element. + */ +export interface WebviewOverlay extends Webview { readonly container: HTMLElement; options: WebviewOptions; + readonly onDispose: Event; + claim(owner: any): void; release(owner: any): void; diff --git a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts index 61c6b710e6..6f332660dd 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts @@ -7,19 +7,19 @@ import { Action } from 'vs/base/common/actions'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as nls from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; +import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; +import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; export class ShowWebViewEditorFindWidgetAction extends Action2 { public static readonly ID = 'editor.action.webvieweditor.showFind'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.showFind', "Show find"); - constructor(contextKeyExpr: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression) { super({ id: ShowWebViewEditorFindWidgetAction.ID, title: ShowWebViewEditorFindWidgetAction.LABEL, @@ -40,7 +40,7 @@ export class HideWebViewEditorFindCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.hideFind'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.hideFind', "Stop find"); - constructor(contextKeyExpr: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression) { super({ id: HideWebViewEditorFindCommand.ID, title: HideWebViewEditorFindCommand.LABEL, @@ -61,7 +61,7 @@ export class WebViewEditorFindNextCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.findNext'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.findNext', 'Find next'); - constructor(contextKeyExpr: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression) { super({ id: WebViewEditorFindNextCommand.ID, title: WebViewEditorFindNextCommand.LABEL, @@ -74,7 +74,7 @@ export class WebViewEditorFindNextCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewEditor(accessor)?.find(false); + getActiveWebviewEditor(accessor)?.runFindAction(false); } } @@ -82,7 +82,7 @@ export class WebViewEditorFindPreviousCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.findPrevious'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.findPrevious', 'Find previous'); - constructor(contextKeyExpr: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression) { super({ id: WebViewEditorFindPreviousCommand.ID, title: WebViewEditorFindPreviousCommand.LABEL, @@ -95,7 +95,7 @@ export class WebViewEditorFindPreviousCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewEditor(accessor)?.find(true); + getActiveWebviewEditor(accessor)?.runFindAction(true); } } @@ -103,7 +103,7 @@ export class SelectAllWebviewEditorCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.selectAll'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.selectAll', 'Select all'); - constructor(contextKeyExpr: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression) { const precondition = ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)); super({ id: SelectAllWebviewEditorCommand.ID, @@ -116,7 +116,7 @@ export class SelectAllWebviewEditorCommand extends Action2 { }); } - public run(accessor: ServicesAccessor, args: any): void { + public run(accessor: ServicesAccessor): void { getActiveWebviewEditor(accessor)?.selectAll(); } } @@ -128,27 +128,22 @@ export class ReloadWebviewAction extends Action { public constructor( id: string, label: string, - @IEditorService private readonly editorService: IEditorService + @IEditorService private readonly _editorService: IEditorService ) { super(id, label); } - public run(): Promise { - for (const webview of this.getVisibleWebviews()) { - webview.reload(); + public async run(): Promise { + for (const editor of this._editorService.visibleEditors) { + if (editor instanceof WebviewInput) { + editor.webview.reload(); + } } - return Promise.resolve(true); - } - - private getVisibleWebviews() { - return this.editorService.visibleControls - .filter(control => control && (control as WebviewEditor).isWebviewEditor) - .map(control => control as WebviewEditor); } } -export function getActiveWebviewEditor(accessor: ServicesAccessor): WebviewEditor | undefined { +export function getActiveWebviewEditor(accessor: ServicesAccessor): Webview | undefined { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl as WebviewEditor | undefined; - return activeControl?.isWebviewEditor ? activeControl : undefined; + const activeEditor = editorService.activeEditor; + return activeEditor instanceof WebviewInput ? activeEditor.webview : undefined; } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index 7d1e0f0802..5834c4980b 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -8,14 +8,13 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; -import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -25,10 +24,7 @@ export class WebviewEditor extends BaseEditor { public static readonly ID = 'WebviewEditor'; - private readonly _scopedContextKeyService = this._register(new MutableDisposable()); - private _findWidgetVisible: IContextKey; - private _editorFrame?: HTMLElement; - private _content?: HTMLElement; + private _element?: HTMLElement; private _dimension?: DOM.Dimension; private readonly _webviewVisibleDisposables = this._register(new DisposableStore()); @@ -41,59 +37,32 @@ export class WebviewEditor extends BaseEditor { @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IEditorService private readonly _editorService: IEditorService, @IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService, @IHostService private readonly _hostService: IHostService, ) { super(WebviewEditor.ID, telemetryService, themeService, storageService); - - this._findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(_contextKeyService); } - public get isWebviewEditor() { - return true; + private get webview(): WebviewOverlay | undefined { + return this.input instanceof WebviewInput ? this.input.webview : undefined; } protected createEditor(parent: HTMLElement): void { - this._editorFrame = parent; - this._content = document.createElement('div'); - parent.appendChild(this._content); + const element = document.createElement('div'); + this._element = element; + parent.appendChild(element); } public dispose(): void { - if (this._content) { - this._content.remove(); - this._content = undefined; + if (this._element) { + this._element.remove(); + this._element = undefined; } super.dispose(); } - public showFind() { - if (this.webview) { - this.webview.showFind(); - this._findWidgetVisible.set(true); - } - } - - public hideFind() { - this._findWidgetVisible.reset(); - this.webview?.hideFind(); - } - - public find(previous: boolean) { - this.webview?.runFindAction(previous); - } - - public selectAll() { - this.webview?.selectAll(); - } - - public reload() { - this.webview?.reload(); - } - public layout(dimension: DOM.Dimension): void { this._dimension = dimension; if (this.webview) { @@ -106,7 +75,7 @@ export class WebviewEditor extends BaseEditor { if (!this._onFocusWindowHandler.value && !isWeb) { // Make sure we restore focus when switching back to a VS Code window this._onFocusWindowHandler.value = this._hostService.onDidChangeFocus(focused => { - if (focused && this._editorService.activeControl === this) { + if (focused && this._editorService.activeEditorPane === this) { this.focus(); } }); @@ -114,10 +83,6 @@ export class WebviewEditor extends BaseEditor { this.webview?.focus(); } - public get webview(): WebviewEditorOverlay | undefined { - return this.input instanceof WebviewInput ? this.input.webview : undefined; - } - protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { if (this.input instanceof WebviewInput && this.webview) { if (visible) { @@ -150,6 +115,7 @@ export class WebviewEditor extends BaseEditor { await super.setInput(input, options, token); await input.resolve(); + if (token.isCancellationRequested) { return; } @@ -169,13 +135,8 @@ export class WebviewEditor extends BaseEditor { private claimWebview(input: WebviewInput): void { input.webview.claim(this); - if (input.webview.options.enableFindWidget) { - this._scopedContextKeyService.value = this._contextKeyService.createScoped(input.webview.container); - this._findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(this._scopedContextKeyService.value); - } - - if (this._content) { - this._content.setAttribute('aria-flowto', input.webview.container.id); + if (this._element) { + this._element.setAttribute('aria-flowto', input.webview.container.id); } this._webviewVisibleDisposables.clear(); @@ -205,13 +166,13 @@ export class WebviewEditor extends BaseEditor { this._webviewVisibleDisposables.add(this.trackFocus(input.webview)); } - private synchronizeWebviewContainerDimensions(webview: WebviewEditorOverlay, dimension?: DOM.Dimension) { - if (this._editorFrame) { - webview.layoutWebviewOverElement(this._editorFrame, dimension); + private synchronizeWebviewContainerDimensions(webview: WebviewOverlay, dimension?: DOM.Dimension) { + if (this._element) { + webview.layoutWebviewOverElement(this._element.parentElement!, dimension); } } - private trackFocus(webview: WebviewEditorOverlay): IDisposable { + private trackFocus(webview: WebviewOverlay): IDisposable { const store = new DisposableStore(); // Track focus in webview content diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index 0307159015..1bdaff4ae7 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -3,12 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { URI } from 'vs/base/common/uri'; -import { IEditorModel } from 'vs/platform/editor/common/editor'; -import { EditorInput, EditorModel, GroupIdentifier, IEditorInput, Verbosity } from 'vs/workbench/common/editor'; -import { IWebviewService, WebviewEditorOverlay, WebviewIcons } from 'vs/workbench/contrib/webview/browser/webview'; +import { EditorInput, GroupIdentifier, IEditorInput, Verbosity } from 'vs/workbench/common/editor'; +import { IWebviewService, WebviewIcons, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; const WebviewPanelResourceScheme = 'webview-panel'; @@ -20,12 +18,9 @@ export class WebviewInput extends EditorInput { private _iconPath?: WebviewIcons; private _group?: GroupIdentifier; - private readonly _webview: Lazy; + private readonly _webview: Lazy; private _didSomeoneTakeMyWebview = false; - private readonly _onDisposeWebview = this._register(new Emitter()); - readonly onDisposeWebview = this._onDisposeWebview.event; - get resource() { return URI.from({ scheme: WebviewPanelResourceScheme, @@ -37,7 +32,7 @@ export class WebviewInput extends EditorInput { public readonly id: string, public readonly viewType: string, name: string, - webview: Lazy, + webview: Lazy, @IWebviewService private readonly _webviewService: IWebviewService, ) { super(); @@ -49,7 +44,6 @@ export class WebviewInput extends EditorInput { if (!this.isDisposed()) { if (!this._didSomeoneTakeMyWebview) { this._webview?.rawValue?.dispose(); - this._onDisposeWebview.fire(); } } super.dispose(); @@ -76,7 +70,7 @@ export class WebviewInput extends EditorInput { this._onDidChangeLabel.fire(); } - public get webview(): WebviewEditorOverlay { + public get webview(): WebviewOverlay { return this._webview.getValue(); } @@ -105,15 +99,15 @@ export class WebviewInput extends EditorInput { this._group = group; } - public async resolve(): Promise { - return new EditorModel(); + public async resolve(): Promise { + return null; } public supportsSplitEditor() { return false; } - protected takeOwnershipOfWebview(): WebviewEditorOverlay | undefined { + protected takeOwnershipOfWebview(): WebviewOverlay | undefined { if (this._didSomeoneTakeMyWebview) { return undefined; } diff --git a/src/vs/workbench/contrib/webview/browser/webviewIconManager.ts b/src/vs/workbench/contrib/webview/browser/webviewIconManager.ts index 3c43d02b8e..e36c792918 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewIconManager.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewIconManager.ts @@ -5,10 +5,9 @@ import * as dom from 'vs/base/browser/dom'; import { memoize } from 'vs/base/common/decorators'; -import { URI } from 'vs/base/common/uri'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { WebviewIcons } from 'vs/workbench/contrib/webview/browser/webview'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class WebviewIconManager { @@ -48,22 +47,20 @@ export class WebviewIconManager { private async updateStyleSheet() { await this._lifecycleService.when(LifecyclePhase.Starting); - try { - const cssRules: string[] = []; - if (this._configService.getValue('workbench.iconTheme') !== null) { - this._icons.forEach((value, key) => { - const webviewSelector = `.show-file-icons .webview-${key}-name-file-icon::before`; - if (URI.isUri(value)) { - cssRules.push(`${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value)}; }`); - } else { - cssRules.push(`.vs ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.light)}; }`); - cssRules.push(`.vs-dark ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.dark)}; }`); - } - }); + const cssRules: string[] = []; + if (this._configService.getValue('workbench.iconTheme') !== null) { + for (const [key, value] of this._icons) { + const webviewSelector = `.show-file-icons .webview-${key}-name-file-icon::before`; + try { + cssRules.push( + `.vs ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.light)}; }`, + `.vs-dark ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.dark)}; }` + ); + } catch { + // noop + } } - this._styleElement.innerHTML = cssRules.join('\n'); - } catch { - // noop } + this._styleElement.innerHTML = cssRules.join('\n'); } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewService.ts b/src/vs/workbench/contrib/webview/browser/webviewService.ts index 4764b44583..9a54b3227d 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewService.ts @@ -5,7 +5,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewIcons, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, WebviewContentOptions, WebviewOverlay, WebviewElement, WebviewIcons, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/themeing'; import { DynamicWebviewEditorOverlay } from './dynamicWebviewEditorOverlay'; @@ -24,7 +24,7 @@ export class WebviewService implements IWebviewService { this._iconManager = this._instantiationService.createInstance(WebviewIconManager); } - createWebview( + createWebviewElement( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions @@ -32,11 +32,11 @@ export class WebviewService implements IWebviewService { return this._instantiationService.createInstance(IFrameWebview, id, options, contentOptions, this._webviewThemeDataProvider); } - createWebviewEditorOverlay( + createWebviewOverlay( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, - ): WebviewEditorOverlay { + ): WebviewOverlay { return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions); } diff --git a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts index 5f704f6d80..13e3d87af3 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts @@ -5,15 +5,15 @@ import { equals } from 'vs/base/common/arrays'; import { memoize } from 'vs/base/common/decorators'; +import { Iterable } from 'vs/base/common/iterator'; import { Lazy } from 'vs/base/common/lazy'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { values } from 'vs/base/common/map'; import { isEqual } from 'vs/base/common/resources'; -import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; +import { EditorActivation } from 'vs/platform/editor/common/editor'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { GroupIdentifier } from 'vs/workbench/common/editor'; -import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewExtensionDescription, WebviewIcons, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, WebviewContentOptions, WebviewExtensionDescription, WebviewIcons, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { WebviewInput } from './webviewEditorInput'; @@ -106,7 +106,7 @@ export class LazilyResolvedWebviewEditorInput extends WebviewInput { id: string, viewType: string, name: string, - webview: Lazy, + webview: Lazy, @IWebviewService webviewService: IWebviewService, @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, ) { @@ -114,7 +114,7 @@ export class LazilyResolvedWebviewEditorInput extends WebviewInput { } @memoize - public async resolve(): Promise { + public async resolve() { await this._webviewWorkbenchService.resolveWebview(this); return super.resolve(); } @@ -160,7 +160,7 @@ export class WebviewEditorService implements IWebviewWorkbenchService { options: WebviewInputOptions, extension: WebviewExtensionDescription | undefined, ): WebviewInput { - const webview = new Lazy(() => this.createWebiew(id, extension, options)); + const webview = new Lazy(() => this.createWebviewElement(id, extension, options)); const webviewInput = this._instantiationService.createInstance(WebviewInput, id, viewType, title, webview); this._editorService.openEditor(webviewInput, { pinned: true, @@ -203,7 +203,7 @@ export class WebviewEditorService implements IWebviewWorkbenchService { group: number | undefined, ): WebviewInput { const webview = new Lazy(() => { - const webview = this.createWebiew(id, extension, options); + const webview = this.createWebviewElement(id, extension, options); webview.state = state; return webview; }); @@ -231,7 +231,7 @@ export class WebviewEditorService implements IWebviewWorkbenchService { public shouldPersist( webview: WebviewInput ): boolean { - if (values(this._revivers).some(reviver => canRevive(reviver, webview))) { + if (Iterable.some(this._revivers.values(), reviver => canRevive(reviver, webview))) { return true; } @@ -243,7 +243,7 @@ export class WebviewEditorService implements IWebviewWorkbenchService { private async tryRevive( webview: WebviewInput ): Promise { - for (const reviver of values(this._revivers)) { + for (const reviver of this._revivers.values()) { if (canRevive(reviver, webview)) { await reviver.resolveWebview(webview); return true; @@ -265,8 +265,12 @@ export class WebviewEditorService implements IWebviewWorkbenchService { } } - private createWebiew(id: string, extension: WebviewExtensionDescription | undefined, options: WebviewInputOptions) { - const webview = this._webviewService.createWebviewEditorOverlay(id, { + private createWebviewElement( + id: string, + extension: WebviewExtensionDescription | undefined, + options: WebviewInputOptions + ) { + const webview = this._webviewService.createWebviewOverlay(id, { enableFindWidget: options.enableFindWidget, retainContextWhenHidden: options.retainContextWhenHidden }, options); diff --git a/src/vs/workbench/contrib/webview/common/resourceLoader.ts b/src/vs/workbench/contrib/webview/common/resourceLoader.ts index 996be332e3..95769f7918 100644 --- a/src/vs/workbench/contrib/webview/common/resourceLoader.ts +++ b/src/vs/workbench/contrib/webview/common/resourceLoader.ts @@ -5,7 +5,6 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { sep } from 'vs/base/common/path'; -import { startsWith, endsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; @@ -96,7 +95,7 @@ function normalizeRequestPath(requestUri: URI) { } function containsResource(root: URI, resource: URI): boolean { - let rootPath = root.fsPath + (endsWith(root.fsPath, sep) ? '' : sep); + let rootPath = root.fsPath + (root.fsPath.endsWith(sep) ? '' : sep); let resourceFsPath = resource.fsPath; if (isUNC(root.fsPath) && isUNC(resource.fsPath)) { @@ -104,5 +103,5 @@ function containsResource(root: URI, resource: URI): boolean { resourceFsPath = resourceFsPath.toLowerCase(); } - return startsWith(resourceFsPath, rootPath); + return resourceFsPath.startsWith(rootPath); } diff --git a/src/vs/workbench/contrib/webview/common/themeing.ts b/src/vs/workbench/contrib/webview/common/themeing.ts index cee8218caf..cb36b0a656 100644 --- a/src/vs/workbench/contrib/webview/common/themeing.ts +++ b/src/vs/workbench/contrib/webview/common/themeing.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; -import { DARK, ITheme, IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; +import { DARK, IColorTheme, IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; import { Emitter } from 'vs/base/common/event'; interface WebviewThemeData { @@ -30,7 +30,7 @@ export class WebviewThemeDataProvider extends Disposable { ) { super(); - this._register(this._themeService.onThemeChange(() => { + this._register(this._themeService.onDidColorThemeChange(() => { this.reset(); })); @@ -42,8 +42,8 @@ export class WebviewThemeDataProvider extends Disposable { })); } - public getTheme(): ITheme { - return this._themeService.getTheme(); + public getTheme(): IColorTheme { + return this._themeService.getColorTheme(); } @WebviewThemeDataProvider.MEMOIZER @@ -53,7 +53,7 @@ export class WebviewThemeDataProvider extends Disposable { const editorFontWeight = configuration.fontWeight || EDITOR_FONT_DEFAULTS.fontWeight; const editorFontSize = configuration.fontSize || EDITOR_FONT_DEFAULTS.fontSize; - const theme = this._themeService.getTheme(); + const theme = this._themeService.getColorTheme(); const exportedColors = colorRegistry.getColorRegistry().getColors().reduce((colors, entry) => { const color = theme.getColor(entry.id); if (color) { @@ -89,7 +89,7 @@ enum ApiThemeClassName { } namespace ApiThemeClassName { - export function fromTheme(theme: ITheme): ApiThemeClassName { + export function fromTheme(theme: IColorTheme): ApiThemeClassName { switch (theme.type) { case LIGHT: return ApiThemeClassName.light; case DARK: return ApiThemeClassName.dark; diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts index d4aa1c6c74..4d6eb47862 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts @@ -9,10 +9,10 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import * as nls from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { WebviewEditorOverlay, webviewHasOwnEditFunctionsContextKey } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewOverlay, webviewHasOwnEditFunctionsContextKey } from 'vs/workbench/contrib/webview/browser/webview'; import { getActiveWebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewCommands'; import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; @@ -25,7 +25,7 @@ export class OpenWebviewDeveloperToolsAction extends Action { super(id, label); } - public run(): Promise { + public async run(): Promise { const elements = document.querySelectorAll('webview.ready'); for (let i = 0; i < elements.length; i++) { try { @@ -34,7 +34,7 @@ export class OpenWebviewDeveloperToolsAction extends Action { console.error(e); } } - return Promise.resolve(true); + return true; } } @@ -42,7 +42,7 @@ export class CopyWebviewEditorCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.copy'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.copy', "Copy2"); - constructor(contextKeyExpr: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression) { super({ id: CopyWebviewEditorCommand.ID, title: CopyWebviewEditorCommand.LABEL, @@ -55,7 +55,7 @@ export class CopyWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewBasedWebview(accessor)?.copy(); + getActiveElectronBasedWebview(accessor)?.copy(); } } @@ -63,7 +63,7 @@ export class PasteWebviewEditorCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.paste'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.paste', 'Paste'); - constructor(contextKeyExpr: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression) { super({ id: PasteWebviewEditorCommand.ID, title: PasteWebviewEditorCommand.LABEL, @@ -76,7 +76,7 @@ export class PasteWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewBasedWebview(accessor)?.paste(); + getActiveElectronBasedWebview(accessor)?.paste(); } } @@ -84,7 +84,7 @@ export class CutWebviewEditorCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.cut'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.cut', 'Cut'); - constructor(contextKeyExpr: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression) { super({ id: CutWebviewEditorCommand.ID, title: CutWebviewEditorCommand.LABEL, @@ -97,7 +97,7 @@ export class CutWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - getActiveWebviewBasedWebview(accessor)?.cut(); + getActiveElectronBasedWebview(accessor)?.cut(); } } @@ -105,7 +105,7 @@ export class UndoWebviewEditorCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.undo'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.undo', "Undo"); - constructor(contextKeyExpr: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression) { super({ id: UndoWebviewEditorCommand.ID, title: UndoWebviewEditorCommand.LABEL, @@ -117,8 +117,8 @@ export class UndoWebviewEditorCommand extends Action2 { }); } - public run(accessor: ServicesAccessor, args: any): void { - getActiveWebviewBasedWebview(accessor)?.undo(); + public run(accessor: ServicesAccessor): void { + getActiveElectronBasedWebview(accessor)?.undo(); } } @@ -126,7 +126,7 @@ export class RedoWebviewEditorCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.redo'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.redo', "Redo"); - constructor(contextKeyExpr: ContextKeyExpr) { + constructor(contextKeyExpr: ContextKeyExpression) { super({ id: RedoWebviewEditorCommand.ID, title: RedoWebviewEditorCommand.LABEL, @@ -140,21 +140,21 @@ export class RedoWebviewEditorCommand extends Action2 { }); } - public run(accessor: ServicesAccessor, args: any): void { - getActiveWebviewBasedWebview(accessor)?.redo(); + public run(accessor: ServicesAccessor): void { + getActiveElectronBasedWebview(accessor)?.redo(); } } -function getActiveWebviewBasedWebview(accessor: ServicesAccessor): ElectronWebviewBasedWebview | undefined { - const webview = getActiveWebviewEditor(accessor)?.webview; +function getActiveElectronBasedWebview(accessor: ServicesAccessor): ElectronWebviewBasedWebview | undefined { + const webview = getActiveWebviewEditor(accessor); if (!webview) { return undefined; } if (webview instanceof ElectronWebviewBasedWebview) { return webview; - } else if ((webview as WebviewEditorOverlay).getInnerWebview) { - const innerWebview = (webview as WebviewEditorOverlay).getInnerWebview(); + } else if ((webview as WebviewOverlay).getInnerWebview) { + const innerWebview = (webview as WebviewOverlay).getInnerWebview(); if (innerWebview instanceof ElectronWebviewBasedWebview) { return innerWebview; } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 191b00697a..bf588d5eb3 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -113,23 +113,33 @@ class WebviewSession extends Disposable { } class WebviewProtocolProvider extends Disposable { + + private _resolve!: () => void; + private _reject!: () => void; + + public readonly ready: Promise; + constructor( handle: WebviewTagHandle, - private readonly _getExtensionLocation: () => URI | undefined, - private readonly _getLocalResourceRoots: () => ReadonlyArray, - private readonly _fileService: IFileService, + getExtensionLocation: () => URI | undefined, + getLocalResourceRoots: () => ReadonlyArray, + fileService: IFileService, ) { super(); - this._register(handle.onFirstLoad(contents => { - this.registerProtocols(contents); - })); - } + this.ready = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + }); - private registerProtocols(contents: WebContents) { - registerFileProtocol(contents, WebviewResourceScheme, this._fileService, this._getExtensionLocation(), () => - this._getLocalResourceRoots() - ); + this._register(handle.onFirstLoad(contents => { + try { + registerFileProtocol(contents, WebviewResourceScheme, fileService, getExtensionLocation(), getLocalResourceRoots); + this._resolve(); + } catch { + this._reject(); + } + })); } } @@ -204,6 +214,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme private _findStarted: boolean = false; public extension: WebviewExtensionDescription | undefined; + private readonly _protocolProvider: WebviewProtocolProvider; constructor( id: string, @@ -222,11 +233,12 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme const webviewAndContents = this._register(new WebviewTagHandle(this.element!)); const session = this._register(new WebviewSession(webviewAndContents)); - this._register(new WebviewProtocolProvider( - webviewAndContents, - () => this.extension ? this.extension.location : undefined, - () => (this.content.options.localResourceRoots || []), - fileService)); + this._protocolProvider = new WebviewProtocolProvider + (webviewAndContents, + () => this.extension ? this.extension.location : undefined, + () => (this.content.options.localResourceRoots || []), + fileService); + this._register(this._protocolProvider); this._register(new WebviewPortMappingProvider( session, @@ -322,7 +334,8 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme parent.appendChild(this.element); } - protected postMessage(channel: string, data?: any): void { + protected async postMessage(channel: string, data?: any): Promise { + await this._protocolProvider.ready; this.element?.send(channel, data); } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts index 06927eafee..74680ecae0 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts @@ -6,7 +6,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DynamicWebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay'; -import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewIcons, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, WebviewContentOptions, WebviewOverlay, WebviewElement, WebviewIcons, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { WebviewIconManager } from 'vs/workbench/contrib/webview/browser/webviewIconManager'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/themeing'; @@ -26,7 +26,7 @@ export class ElectronWebviewService implements IWebviewService { this._iconManager = this._instantiationService.createInstance(WebviewIconManager); } - createWebview( + createWebviewElement( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions @@ -39,11 +39,11 @@ export class ElectronWebviewService implements IWebviewService { } } - createWebviewEditorOverlay( + createWebviewOverlay( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, - ): WebviewEditorOverlay { + ): WebviewOverlay { return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions); } diff --git a/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts index 2cb7762086..5c88dc1943 100644 --- a/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts +++ b/src/vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint.ts @@ -22,7 +22,7 @@ export type ViewsWelcomeExtensionPoint = ViewWelcome[]; export const ViewIdentifierMap: { [key: string]: string } = { 'explorer': 'workbench.explorer.emptyView', - 'debug': 'workbench.debug.startView', + 'debug': 'workbench.debug.welcome', 'scm': 'workbench.scm', }; @@ -40,7 +40,6 @@ const viewsWelcomeExtensionPointSchema = Object.freeze**Tip:** You can also enable the checks workspace or application wide by adding |"javascript.implicitProjectConfig.checkJs": true| to your workspace or user settings and explicitly ignoring files or lines using |// @ts-nocheck| and |// @ts-ignore|. Check out the docs on [JavaScript in VS Code](https://code.visualstudio.com/docs/languages/javascript) to learn more. +>**Tip:** You can also enable the checks workspace or application wide by adding |"javascript.implicitProjectConfig.checkJs": true| to your workspace or user settings and explicitly ignoring files or lines using |// @ts-nocheck| and |// @ts-expect-error|. Check out the docs on [JavaScript in VS Code](https://code.visualstudio.com/docs/languages/javascript) to learn more. ## Thanks! diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughActions.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughActions.ts index db8f51aeab..598d15b373 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughActions.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughActions.ts @@ -17,9 +17,9 @@ export const WalkThroughArrowUp: ICommandAndKeybindingRule = { primary: KeyCode.UpArrow, handler: accessor => { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl; - if (activeControl instanceof WalkThroughPart) { - activeControl.arrowUp(); + const activeEditorPane = editorService.activeEditorPane; + if (activeEditorPane instanceof WalkThroughPart) { + activeEditorPane.arrowUp(); } } }; @@ -31,9 +31,9 @@ export const WalkThroughArrowDown: ICommandAndKeybindingRule = { primary: KeyCode.DownArrow, handler: accessor => { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl; - if (activeControl instanceof WalkThroughPart) { - activeControl.arrowDown(); + const activeEditorPane = editorService.activeEditorPane; + if (activeEditorPane instanceof WalkThroughPart) { + activeEditorPane.arrowDown(); } } }; @@ -45,9 +45,9 @@ export const WalkThroughPageUp: ICommandAndKeybindingRule = { primary: KeyCode.PageUp, handler: accessor => { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl; - if (activeControl instanceof WalkThroughPart) { - activeControl.pageUp(); + const activeEditorPane = editorService.activeEditorPane; + if (activeEditorPane instanceof WalkThroughPart) { + activeEditorPane.pageUp(); } } }; @@ -59,9 +59,9 @@ export const WalkThroughPageDown: ICommandAndKeybindingRule = { primary: KeyCode.PageDown, handler: accessor => { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl; - if (activeControl instanceof WalkThroughPart) { - activeControl.pageDown(); + const activeEditorPane = editorService.activeEditorPane; + if (activeEditorPane instanceof WalkThroughPart) { + activeEditorPane.pageDown(); } } -}; \ No newline at end of file +}; diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index 6262b6cbf7..646ff229ee 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -426,7 +426,7 @@ export class WalkThroughPart extends BaseEditor { alwaysConsumeMouseWheel: false }, overviewRulerLanes: 3, - fixedOverflowWidgets: true, + fixedOverflowWidgets: false, lineNumbersMinChars: 1, minimap: { enabled: false }, }; diff --git a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils.ts b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils.ts index 14571376a1..ab10bbdd09 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils.ts @@ -3,10 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITheme } from 'vs/platform/theme/common/themeService'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { editorBackground, ColorDefaults, ColorValue } from 'vs/platform/theme/common/colorRegistry'; -export function getExtraColor(theme: ITheme, colorId: string, defaults: ColorDefaults & { extra_dark: string }): ColorValue | null { +export function getExtraColor(theme: IColorTheme, colorId: string, defaults: ColorDefaults & { extra_dark: string }): ColorValue | null { const color = theme.getColor(colorId); if (color) { return color; @@ -20,4 +20,4 @@ export function getExtraColor(theme: ITheme, colorId: string, defaults: ColorDef } return defaults[theme.type]; -} \ No newline at end of file +} diff --git a/src/vs/workbench/electron-browser/actions/windowActions.ts b/src/vs/workbench/electron-browser/actions/windowActions.ts index b7b261525c..703b731ffd 100644 --- a/src/vs/workbench/electron-browser/actions/windowActions.ts +++ b/src/vs/workbench/electron-browser/actions/windowActions.ts @@ -18,7 +18,8 @@ import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IElectronService } from 'vs/platform/electron/node/electron'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export class CloseCurrentWindowAction extends Action { @@ -167,7 +168,7 @@ export abstract class BaseSwitchWindow extends Action { constructor( id: string, label: string, - private electronEnvironmentService: IElectronEnvironmentService, + private environmentService: INativeWorkbenchEnvironmentService, private quickInputService: IQuickInputService, private keybindingService: IKeybindingService, private modelService: IModelService, @@ -180,7 +181,7 @@ export abstract class BaseSwitchWindow extends Action { protected abstract isQuickNavigate(): boolean; async run(): Promise { - const currentWindowId = this.electronEnvironmentService.windowId; + const currentWindowId = this.environmentService.configuration.windowId; const windows = await this.electronService.getWindows(); const placeHolder = nls.localize('switchWindowPlaceHolder', "Select a window to switch to"); @@ -222,14 +223,14 @@ export class SwitchWindow extends BaseSwitchWindow { constructor( id: string, label: string, - @IElectronEnvironmentService electronEnvironmentService: IElectronEnvironmentService, + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService, @IModelService modelService: IModelService, @IModeService modeService: IModeService, @IElectronService electronService: IElectronService ) { - super(id, label, electronEnvironmentService, quickInputService, keybindingService, modelService, modeService, electronService); + super(id, label, environmentService, quickInputService, keybindingService, modelService, modeService, electronService); } protected isQuickNavigate(): boolean { @@ -245,14 +246,14 @@ export class QuickSwitchWindow extends BaseSwitchWindow { constructor( id: string, label: string, - @IElectronEnvironmentService electronEnvironmentService: IElectronEnvironmentService, + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService, @IModelService modelService: IModelService, @IModeService modeService: IModeService, @IElectronService electronService: IElectronService ) { - super(id, label, electronEnvironmentService, quickInputService, keybindingService, modelService, modeService, electronService); + super(id, label, environmentService, quickInputService, keybindingService, modelService, modeService, electronService); } protected isQuickNavigate(): boolean { diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index 4ffbcf5ce9..2f490ea4bb 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -17,7 +17,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IsMacContext, HasMacNativeTabsContext } from 'vs/workbench/browser/contextkeys'; // {{SQL CARBON EDIT}} remove import +import { IsMacContext } from 'vs/platform/contextkey/common/contextkeys'; import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; @@ -84,7 +84,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command, - when: HasMacNativeTabsContext + when: ContextKeyExpr.equals('config.window.nativeTabs', 'true') }); }); } diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index cc61064ca2..cf173fbd7b 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -6,9 +6,10 @@ import * as fs from 'fs'; import * as gracefulFs from 'graceful-fs'; import { createHash } from 'crypto'; +import { webFrame } from 'electron'; import { importEntries, mark } from 'vs/base/common/performance'; import { Workbench } from 'vs/workbench/browser/workbench'; -import { ElectronWindow } from 'vs/workbench/electron-browser/window'; +import { NativeWindow } from 'vs/workbench/electron-browser/window'; import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser'; import { domContentLoaded, addDisposableListener, EventType, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -20,8 +21,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { stat } from 'vs/base/node/pfs'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; -import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { webFrame } from 'electron'; +import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ConsoleLogService, MultiplexLogService, ILogService, ConsoleLogInMainService } from 'vs/platform/log/common/log'; import { NativeStorageService } from 'vs/platform/storage/node/storageService'; @@ -51,16 +51,15 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file import { basename } from 'vs/base/common/resources'; import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; -import { ElectronEnvironmentService, IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; class DesktopMain extends Disposable { private readonly environmentService: NativeWorkbenchEnvironmentService; - constructor(private configuration: IWindowConfiguration) { + constructor(private configuration: INativeWindowConfiguration) { super(); - this.environmentService = new NativeWorkbenchEnvironmentService(configuration, configuration.execPath, configuration.windowId); + this.environmentService = new NativeWorkbenchEnvironmentService(configuration, configuration.execPath); this.init(); } @@ -127,7 +126,7 @@ class DesktopMain extends Disposable { const instantiationService = workbench.startup(); // Window - this._register(instantiationService.createInstance(ElectronWindow)); + this._register(instantiationService.createInstance(NativeWindow)); // Driver if (this.environmentService.configuration.driver) { @@ -180,11 +179,6 @@ class DesktopMain extends Disposable { // Environment serviceCollection.set(IWorkbenchEnvironmentService, this.environmentService); - serviceCollection.set(IElectronEnvironmentService, new ElectronEnvironmentService( - this.configuration.windowId, - this.environmentService.sharedIPCHandle, - this.environmentService - )); // Product serviceCollection.set(IProductService, { _serviceBrand: undefined, ...product }); @@ -373,7 +367,7 @@ class DesktopMain extends Disposable { } } -export function main(configuration: IWindowConfiguration): Promise { +export function main(configuration: INativeWindowConfiguration): Promise { const renderer = new DesktopMain(configuration); return renderer.open(); diff --git a/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts new file mode 100644 index 0000000000..def2d3d73d --- /dev/null +++ b/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts @@ -0,0 +1,242 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as browser from 'vs/base/browser/browser'; +import * as DOM from 'vs/base/browser/dom'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { isMacintosh, isWindows, isLinux } from 'vs/base/common/platform'; +import { IMenuService } from 'vs/platform/actions/common/actions'; +import { TitlebarPart as BrowserTitleBarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +export class TitlebarPart extends BrowserTitleBarPart { + private appIcon: HTMLElement | undefined; + private windowControls: HTMLElement | undefined; + private maxRestoreControl: HTMLElement | undefined; + private dragRegion: HTMLElement | undefined; + private resizer: HTMLElement | undefined; + + constructor( + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService protected readonly configurationService: IConfigurationService, + @IEditorService editorService: IEditorService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @ILabelService labelService: ILabelService, + @IStorageService storageService: IStorageService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService, + @IHostService hostService: IHostService, + @IProductService productService: IProductService, + @IElectronService private readonly electronService: IElectronService + ) { + super(contextMenuService, configurationService, editorService, environmentService, contextService, instantiationService, themeService, labelService, storageService, layoutService, menuService, contextKeyService, hostService, productService); + } + + private onUpdateAppIconDragBehavior() { + const setting = this.configurationService.getValue('window.doubleClickIconToClose'); + if (setting && this.appIcon) { + (this.appIcon.style as any)['-webkit-app-region'] = 'no-drag'; + } else if (this.appIcon) { + (this.appIcon.style as any)['-webkit-app-region'] = 'drag'; + } + } + + private onDidChangeMaximized(maximized: boolean) { + if (this.maxRestoreControl) { + if (maximized) { + DOM.removeClass(this.maxRestoreControl, 'codicon-chrome-maximize'); + DOM.addClass(this.maxRestoreControl, 'codicon-chrome-restore'); + } else { + DOM.removeClass(this.maxRestoreControl, 'codicon-chrome-restore'); + DOM.addClass(this.maxRestoreControl, 'codicon-chrome-maximize'); + } + } + + if (this.resizer) { + if (maximized) { + DOM.hide(this.resizer); + } else { + DOM.show(this.resizer); + } + } + + this.adjustTitleMarginToCenter(); + } + + private onMenubarFocusChanged(focused: boolean) { + if ((isWindows || isLinux) && this.currentMenubarVisibility !== 'compact' && this.dragRegion) { + if (focused) { + DOM.hide(this.dragRegion); + } else { + DOM.show(this.dragRegion); + } + } + } + + protected onMenubarVisibilityChanged(visible: boolean) { + // Hide title when toggling menu bar + if ((isWindows || isLinux) && this.currentMenubarVisibility === 'toggle' && visible) { + // Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor + if (this.dragRegion) { + DOM.hide(this.dragRegion); + setTimeout(() => DOM.show(this.dragRegion!), 50); + } + } + + super.onMenubarVisibilityChanged(visible); + } + + protected onConfigurationChanged(event: IConfigurationChangeEvent): void { + + super.onConfigurationChanged(event); + + if (event.affectsConfiguration('window.doubleClickIconToClose')) { + if (this.appIcon) { + this.onUpdateAppIconDragBehavior(); + } + } + } + + protected adjustTitleMarginToCenter(): void { + if (this.customMenubar && this.menubar) { + const leftMarker = (this.appIcon ? this.appIcon.clientWidth : 0) + this.menubar.clientWidth + 10; + const rightMarker = this.element.clientWidth - (this.windowControls ? this.windowControls.clientWidth : 0) - 10; + + // Not enough space to center the titlebar within window, + // Center between menu and window controls + if (leftMarker > (this.element.clientWidth - this.title.clientWidth) / 2 || + rightMarker < (this.element.clientWidth + this.title.clientWidth) / 2) { + this.title.style.position = ''; + this.title.style.left = ''; + this.title.style.transform = ''; + return; + } + } + + this.title.style.position = 'absolute'; + this.title.style.left = '50%'; + this.title.style.transform = 'translate(-50%, 0)'; + } + + protected installMenubar(): void { + super.installMenubar(); + + if (this.menubar) { + return; + } + + if (this.customMenubar) { + this._register(this.customMenubar.onFocusStateChange(e => this.onMenubarFocusChanged(e))); + } + } + + createContentArea(parent: HTMLElement): HTMLElement { + const ret = super.createContentArea(parent); + + // App Icon (Native Windows/Linux) + if (!isMacintosh) { + this.appIcon = DOM.prepend(this.element, DOM.$('div.window-appicon')); + this.onUpdateAppIconDragBehavior(); + + this._register(DOM.addDisposableListener(this.appIcon, DOM.EventType.DBLCLICK, (e => { + this.electronService.closeWindow(); + }))); + } + + // Draggable region that we can manipulate for #52522 + this.dragRegion = DOM.prepend(this.element, DOM.$('div.titlebar-drag-region')); + + // Window Controls (Native Windows/Linux) + if (!isMacintosh) { + this.windowControls = DOM.append(this.element, DOM.$('div.window-controls-container')); + + // Minimize + const minimizeIcon = DOM.append(this.windowControls, DOM.$('div.window-icon.window-minimize.codicon.codicon-chrome-minimize')); + this._register(DOM.addDisposableListener(minimizeIcon, DOM.EventType.CLICK, e => { + this.electronService.minimizeWindow(); + })); + + // Restore + this.maxRestoreControl = DOM.append(this.windowControls, DOM.$('div.window-icon.window-max-restore.codicon')); + this._register(DOM.addDisposableListener(this.maxRestoreControl, DOM.EventType.CLICK, async e => { + const maximized = await this.electronService.isMaximized(); + if (maximized) { + return this.electronService.unmaximizeWindow(); + } + + return this.electronService.maximizeWindow(); + })); + + // Close + const closeIcon = DOM.append(this.windowControls, DOM.$('div.window-icon.window-close.codicon.codicon-chrome-close')); + this._register(DOM.addDisposableListener(closeIcon, DOM.EventType.CLICK, e => { + this.electronService.closeWindow(); + })); + + // Resizer + this.resizer = DOM.append(this.element, DOM.$('div.resizer')); + + this._register(this.layoutService.onMaximizeChange(maximized => this.onDidChangeMaximized(maximized))); + this.onDidChangeMaximized(this.layoutService.isWindowMaximized()); + } + + return ret; + } + + updateLayout(dimension: DOM.Dimension): void { + this.lastLayoutDimensions = dimension; + + if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { + // Only prevent zooming behavior on macOS or when the menubar is not visible + if (isMacintosh || this.currentMenubarVisibility === 'hidden') { + this.title.style.zoom = `${1 / browser.getZoomFactor()}`; + if (isWindows || isLinux) { + if (this.appIcon) { + this.appIcon.style.zoom = `${1 / browser.getZoomFactor()}`; + } + + if (this.windowControls) { + this.windowControls.style.zoom = `${1 / browser.getZoomFactor()}`; + } + } + } else { + this.title.style.zoom = null; + if (isWindows || isLinux) { + if (this.appIcon) { + this.appIcon.style.zoom = null; + } + + if (this.windowControls) { + this.windowControls.style.zoom = null; + } + } + } + + DOM.runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter()); + + if (this.customMenubar) { + const menubarDimension = new DOM.Dimension(0, dimension.height); + this.customMenubar.layout(menubarDimension); + } + } + } +} diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 87ec3556f9..c3c73be63f 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -6,20 +6,20 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import * as errors from 'vs/base/common/errors'; -import { equals, deepClone, assign } from 'vs/base/common/objects'; +import { equals, deepClone } from 'vs/base/common/objects'; import * as DOM from 'vs/base/browser/dom'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; -import { toResource, IUntitledTextResourceInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor'; -import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { toResource, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor'; +import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; +import { ITelemetryService, crashReporterIdStorageKey } from 'vs/platform/telemetry/common/telemetry'; import { IWindowSettings, IOpenFileRequest, IWindowsConfiguration, IAddFoldersRequest, IRunActionInWindowRequest, IRunKeybindingInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { IWorkbenchThemeService, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as browser from 'vs/base/browser/browser'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import { ipcRenderer as ipc, webFrame, crashReporter, CrashReporterStartOptions, Event as IpcEvent } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; @@ -46,7 +46,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { MenubarControl } from '../browser/parts/titlebar/menubarControl'; import { ILabelService } from 'vs/platform/label/common/label'; import { IUpdateService } from 'vs/platform/update/common/update'; -import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IPreferencesService } from '../services/preferences/common/preferences'; import { IMenubarService, IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenubarMenuItemSubmenu, IMenubarMenuItemAction, MenubarMenuItem } from 'vs/platform/menubar/node/menubar'; import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; @@ -58,12 +58,13 @@ import { getBaseLabel } from 'vs/base/common/labels'; import { ITunnelService, extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/remote/common/tunnel'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { Event } from 'vs/base/common/event'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { clearAllFontInfos } from 'vs/editor/browser/config/configuration'; -export class ElectronWindow extends Disposable { +export class NativeWindow extends Disposable { private touchBarMenu: IMenu | undefined; private readonly touchBarDisposables = this._register(new DisposableStore()); @@ -94,7 +95,7 @@ export class ElectronWindow extends Disposable { @IMenuService private readonly menuService: IMenuService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IIntegrityService private readonly integrityService: IIntegrityService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -102,9 +103,9 @@ export class ElectronWindow extends Disposable { @IElectronService private readonly electronService: IElectronService, @ITunnelService private readonly tunnelService: ITunnelService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, - @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IStorageService private readonly storageService: IStorageService, ) { super(); @@ -180,6 +181,10 @@ export class ElectronWindow extends Disposable { this.notificationService.info(message); }); + ipc.on('vscode:displayChanged', (event: IpcEvent) => { + clearAllFontInfos(); + }); + // Fullscreen Events ipc.on('vscode:enterFullScreen', async () => { await this.lifecycleService.when(LifecyclePhase.Ready); @@ -280,8 +285,8 @@ export class ElectronWindow extends Disposable { // Detect minimize / maximize this._register(Event.any( - Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), () => true), - Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), () => false) + Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.environmentService.configuration.windowId), () => true), + Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.environmentService.configuration.windowId), () => false) )(e => this.onDidChangeMaximized(e))); this.onDidChangeMaximized(this.environmentService.configuration.maximized ?? false); @@ -304,8 +309,8 @@ export class ElectronWindow extends Disposable { // Close when empty: check if we should close the window based on the setting // Overruled by: window has a workspace opened or this window is for extension development // or setting is disabled. Also enabled when running with --wait from the command line. - const visibleEditors = this.editorService.visibleControls; - if (visibleEditors.length === 0 && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && !this.environmentService.isExtensionDevelopment) { + const visibleEditorPanes = this.editorService.visibleEditorPanes; + if (visibleEditorPanes.length === 0 && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && !this.environmentService.isExtensionDevelopment) { const closeWhenEmpty = this.configurationService.getValue('window.closeWhenEmpty'); if (closeWhenEmpty || this.environmentService.args.wait) { this.closeEmptyWindowScheduler.schedule(); @@ -314,8 +319,8 @@ export class ElectronWindow extends Disposable { } private onAllEditorsClosed(): void { - const visibleEditors = this.editorService.visibleControls.length; - if (visibleEditors === 0) { + const visibleEditorPanes = this.editorService.visibleEditorPanes.length; + if (visibleEditorPanes === 0) { this.electronService.closeWindow(); } } @@ -395,7 +400,7 @@ export class ElectronWindow extends Disposable { this.setupOpenHandlers(); // Emit event when vscode is ready - this.lifecycleService.when(LifecyclePhase.Ready).then(() => ipc.send('vscode:workbenchReady', this.electronEnvironmentService.windowId)); + this.lifecycleService.when(LifecyclePhase.Ready).then(() => ipc.send('vscode:workbenchReady', this.environmentService.configuration.windowId)); // Integrity warning this.integrityService.isPure().then(res => this.titleService.updateProperties({ isPure: res.isPure })); @@ -422,8 +427,8 @@ export class ElectronWindow extends Disposable { this.updateTouchbarMenu(); // Crash reporter (if enabled) - if (!this.environmentService.disableCrashReporter && product.crashReporter && product.hockeyApp && this.configurationService.getValue('telemetry.enableCrashReporter')) { - this.setupCrashReporter(product.crashReporter.companyName, product.crashReporter.productName, product.hockeyApp); + if (!this.environmentService.disableCrashReporter && product.crashReporter && product.appCenter && this.configurationService.getValue('telemetry.enableCrashReporter')) { + this.setupCrashReporter(product.crashReporter.companyName, product.crashReporter.productName, product.appCenter); } } @@ -536,31 +541,36 @@ export class ElectronWindow extends Disposable { } } - private async setupCrashReporter(companyName: string, productName: string, hockeyAppConfig: typeof product.hockeyApp): Promise { - if (!hockeyAppConfig) { + private async setupCrashReporter(companyName: string, productName: string, appCenterConfig: typeof product.appCenter): Promise { + if (!appCenterConfig) { return; } + const appCenterURL = isWindows ? appCenterConfig[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] + : isLinux ? appCenterConfig[`linux-x64`] : appCenterConfig.darwin; + const info = await this.telemetryService.getTelemetryInfo(); + const crashReporterId = this.storageService.get(crashReporterIdStorageKey, StorageScope.GLOBAL)!; + // base options with product info const options: CrashReporterStartOptions = { companyName, productName, - submitURL: isWindows ? hockeyAppConfig[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] : isLinux ? hockeyAppConfig[`linux-x64`] : hockeyAppConfig.darwin, + submitURL: appCenterURL.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', info.sessionId), extra: { vscode_version: product.version, vscode_commit: product.commit || '' } }; - // mixin telemetry info - const info = await this.telemetryService.getTelemetryInfo(); - assign(options.extra, { vscode_sessionId: info.sessionId }); + // start crash reporter in the main process first. + // On windows crashpad excepts a name pipe for the client to connect, + // this pipe is created by crash reporter initialization from the main process, + // changing this order of initialization will cause issues. + // For more info: https://chromium.googlesource.com/crashpad/crashpad/+/HEAD/doc/overview_design.md#normal-registration + await this.electronService.startCrashReporter(options); // start crash reporter right here crashReporter.start(deepClone(options)); - - // start crash reporter in the main process - return this.electronService.startCrashReporter(options); } private onAddFoldersRequest(request: IAddFoldersRequest): void { @@ -587,7 +597,7 @@ export class ElectronWindow extends Disposable { } private async onOpenFiles(request: IOpenFileRequest): Promise { - const inputs: IResourceEditor[] = []; + const inputs: IResourceEditorInputType[] = []; const diffMode = !!(request.filesToDiff && (request.filesToDiff.length === 2)); if (!diffMode && request.filesToOpenOrCreate) { @@ -667,7 +677,7 @@ export class ElectronWindow extends Disposable { }); } - private async openResources(resources: Array, diffMode: boolean): Promise { + private async openResources(resources: Array, diffMode: boolean): Promise { await this.lifecycleService.when(LifecyclePhase.Ready); // In diffMode we open 2 resources as diff @@ -697,11 +707,10 @@ class NativeMenubarControl extends MenubarControl { @IStorageService storageService: IStorageService, @INotificationService notificationService: INotificationService, @IPreferencesService preferencesService: IPreferencesService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService protected readonly environmentService: INativeWorkbenchEnvironmentService, @IAccessibilityService accessibilityService: IAccessibilityService, @IMenubarService private readonly menubarService: IMenubarService, @IHostService hostService: IHostService, - @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService ) { super( menuService, @@ -750,7 +759,7 @@ class NativeMenubarControl extends MenubarControl { // Send menus to main process to be rendered by Electron const menubarData = { menus: {}, keybindings: {} }; if (this.getMenubarMenus(menubarData)) { - this.menubarService.updateMenubar(this.electronEnvironmentService.windowId, menubarData); + this.menubarService.updateMenubar(this.environmentService.configuration.windowId, menubarData); } } diff --git a/src/vs/workbench/services/accessibility/node/accessibilityService.ts b/src/vs/workbench/services/accessibility/electron-browser/accessibilityService.ts similarity index 91% rename from src/vs/workbench/services/accessibility/node/accessibilityService.ts rename to src/vs/workbench/services/accessibility/electron-browser/accessibilityService.ts index 8911b52f0b..6d9d3894c8 100644 --- a/src/vs/workbench/services/accessibility/node/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/electron-browser/accessibilityService.ts @@ -16,6 +16,7 @@ import { IJSONEditingService } from 'vs/workbench/services/configuration/common/ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; interface AccessibilityMetrics { enabled: boolean; @@ -24,14 +25,14 @@ type AccessibilityMetricsClassification = { enabled: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; -export class NodeAccessibilityService extends AccessibilityService implements IAccessibilityService { +export class NativeAccessibilityService extends AccessibilityService implements IAccessibilityService { _serviceBrand: undefined; private didSendTelemetry = false; constructor( - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly _telemetryService: ITelemetryService @@ -69,7 +70,7 @@ export class NodeAccessibilityService extends AccessibilityService implements IA } } -registerSingleton(IAccessibilityService, NodeAccessibilityService, true); +registerSingleton(IAccessibilityService, NativeAccessibilityService, true); // On linux we do not automatically detect that a screen reader is detected, thus we have to implicitly notify the renderer to enable accessibility when user configures it in settings class LinuxAccessibilityContribution implements IWorkbenchContribution { diff --git a/src/vs/workbench/services/activity/common/activity.ts b/src/vs/workbench/services/activity/common/activity.ts index 4bd41bca7e..aa0e64f4b6 100644 --- a/src/vs/workbench/services/activity/common/activity.ts +++ b/src/vs/workbench/services/activity/common/activity.ts @@ -6,14 +6,25 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +export const IActivityService = createDecorator('activityService'); + +export interface IActivityService { + + _serviceBrand: undefined; + + /** + * Show activity in the panel for the given panel or in the activitybar for the given viewlet or global action. + */ + showActivity(compositeOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable; +} + export interface IBadge { getDescription(): string; } -export class BaseBadge implements IBadge { - descriptorFn: (args: any) => string; +class BaseBadge implements IBadge { - constructor(descriptorFn: (args: any) => string) { + constructor(public readonly descriptorFn: (arg: any) => string) { this.descriptorFn = descriptorFn; } @@ -23,9 +34,8 @@ export class BaseBadge implements IBadge { } export class NumberBadge extends BaseBadge { - number: number; - constructor(number: number, descriptorFn: (args: any) => string) { + constructor(public readonly number: number, descriptorFn: (num: number) => string) { super(descriptorFn); this.number = number; @@ -37,31 +47,17 @@ export class NumberBadge extends BaseBadge { } export class TextBadge extends BaseBadge { - text: string; - constructor(text: string, descriptorFn: (args: any) => string) { + constructor(public readonly text: string, descriptorFn: () => string) { super(descriptorFn); - - this.text = text; } } export class IconBadge extends BaseBadge { - constructor(descriptorFn: (args: any) => string) { + constructor(descriptorFn: () => string) { super(descriptorFn); } } export class ProgressBadge extends BaseBadge { } - -export const IActivityService = createDecorator('activityService'); - -export interface IActivityService { - _serviceBrand: undefined; - - /** - * Show activity in the panel for the given panel or in the activitybar for the given viewlet or global action. - */ - showActivity(compositeOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable; -} diff --git a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts index 465d986550..5372ba47bc 100644 --- a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts @@ -48,7 +48,7 @@ const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(u class TestBackupEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(backupPath: string) { - super({ ...TestWindowConfiguration, backupPath, 'user-data-dir': userdataDir }, TestWindowConfiguration.execPath, TestWindowConfiguration.windowId); + super({ ...TestWindowConfiguration, backupPath, 'user-data-dir': userdataDir }, TestWindowConfiguration.execPath); } } diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index 32095a8c2c..acc8ba471c 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -433,7 +433,7 @@ export class BulkEditService implements IBulkEditService { // try to find code editor if (!codeEditor) { - let candidate = this._editorService.activeTextEditorWidget; + let candidate = this._editorService.activeTextEditorControl; if (isCodeEditor(candidate)) { codeEditor = candidate; } diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 136110b72b..b5e9ad27b3 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -40,3 +40,5 @@ export interface IConfigurationCache { remove(key: ConfigurationKey): Promise; } + +export const TASKS_DEFAULT = '{\n\t\"version\": \"2.0.0\",\n\t\"tasks\": []\n}'; diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 9fbe5237b2..fa0dd4a41e 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -20,7 +20,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService, IConfigurationOverrides, keyFromOverrideIdentifier } from 'vs/platform/configuration/common/configuration'; -import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, USER_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, USER_STANDALONE_CONFIGURATIONS, TASKS_DEFAULT } from 'vs/workbench/services/configuration/common/configuration'; import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { OVERRIDE_PROPERTY_PATTERN, IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; @@ -230,7 +230,7 @@ export class ConfigurationEditingService { } } - private onInvalidConfigurationError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, ): void { + private onInvalidConfigurationError(error: ConfigurationEditingError, operation: IConfigurationEditOperation,): void { const openStandAloneConfigurationActionLabel = operation.workspaceStandAloneConfigurationKey === TASKS_CONFIGURATION_KEY ? nls.localize('openTasksConfiguration', "Open Tasks Configuration") : operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY ? nls.localize('openLaunchConfiguration', "Open Launch Configuration") : null; @@ -434,10 +434,19 @@ export class ConfigurationEditingService { return setProperty(model.getValue(), jsonPath, value, { tabSize, insertSpaces, eol }); } + private defaultResourceValue(resource: URI): string { + const basename: string = resources.basename(resource); + const configurationValue: string = basename.substr(0, basename.length - resources.extname(resource).length); + switch (configurationValue) { + case TASKS_CONFIGURATION_KEY: return TASKS_DEFAULT; + default: return '{}'; + } + } + private async resolveModelReference(resource: URI): Promise> { const exists = await this.fileService.exists(resource); if (!exists) { - await this.textFileService.write(resource, '{}', { encoding: 'utf8' }); + await this.textFileService.write(resource, this.defaultResourceValue(resource), { encoding: 'utf8' }); } return this.textModelResolverService.createModelReference(resource); } diff --git a/src/vs/workbench/services/configuration/common/jsonEditingService.ts b/src/vs/workbench/services/configuration/common/jsonEditingService.ts index 08a52db254..5d7c14bdf0 100644 --- a/src/vs/workbench/services/configuration/common/jsonEditingService.ts +++ b/src/vs/workbench/services/configuration/common/jsonEditingService.ts @@ -41,18 +41,18 @@ export class JSONEditingService implements IJSONEditingService { private async doWriteConfiguration(resource: URI, values: IJSONValue[], save: boolean): Promise { const reference = await this.resolveAndValidate(resource, save); - await this.writeToBuffer(reference.object.textEditorModel, values); + await this.writeToBuffer(reference.object.textEditorModel, values, save); reference.dispose(); } - private async writeToBuffer(model: ITextModel, values: IJSONValue[]): Promise { + private async writeToBuffer(model: ITextModel, values: IJSONValue[], save: boolean): Promise { let hasEdits: boolean = false; for (const value of values) { const edit = this.getEdits(model, value)[0]; hasEdits = this.applyEditsToBuffer(edit, model); } - if (hasEdits) { + if (hasEdits && save) { return this.textFileService.save(model.uri); } } diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index e9538d7b99..c8fc46032b 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -45,7 +45,7 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file class TestEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { - super(TestWindowConfiguration, TestWindowConfiguration.execPath, TestWindowConfiguration.windowId); + super(TestWindowConfiguration, TestWindowConfiguration.execPath); } get appSettingsHome() { return this._appSettingsHome; } diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index d75711c04b..4ddc5ae96f 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -51,7 +51,7 @@ import { timeout } from 'vs/base/common/async'; class TestEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { - super(TestWindowConfiguration, TestWindowConfiguration.execPath, TestWindowConfiguration.windowId); + super(TestWindowConfiguration, TestWindowConfiguration.execPath); } get appSettingsHome() { return this._appSettingsHome; } diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index 3533c565e7..6d67562561 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -11,7 +11,7 @@ import { Schemas } from 'vs/base/common/network'; import { toResource } from 'vs/workbench/common/editor'; import { IStringDictionary, forEach, fromMap } from 'vs/base/common/collections'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IWorkspaceFolder, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -62,10 +62,10 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR return path.normalize(fileResource.fsPath); }, getSelectedText: (): string | undefined => { - const activeTextEditorWidget = editorService.activeTextEditorWidget; - if (isCodeEditor(activeTextEditorWidget)) { - const editorModel = activeTextEditorWidget.getModel(); - const editorSelection = activeTextEditorWidget.getSelection(); + const activeTextEditorControl = editorService.activeTextEditorControl; + if (isCodeEditor(activeTextEditorControl)) { + const editorModel = activeTextEditorControl.getModel(); + const editorSelection = activeTextEditorControl.getSelection(); if (editorModel && editorSelection) { return editorModel.getValueInRange(editorSelection); } @@ -73,9 +73,9 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR return undefined; }, getLineNumber: (): string | undefined => { - const activeTextEditorWidget = editorService.activeTextEditorWidget; - if (isCodeEditor(activeTextEditorWidget)) { - const selection = activeTextEditorWidget.getSelection(); + const activeTextEditorControl = editorService.activeTextEditorControl; + if (isCodeEditor(activeTextEditorControl)) { + const selection = activeTextEditorControl.getSelection(); if (selection) { const lineNumber = selection.positionLineNumber; return String(lineNumber); @@ -86,12 +86,12 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR }, envVariables); } - public async resolveWithInteractionReplace(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary): Promise { + public async resolveWithInteractionReplace(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise { // resolve any non-interactive variables and any contributed variables config = this.resolveAny(folder, config); // resolve input variables in the order in which they are encountered - return this.resolveWithInteraction(folder, config, section, variables).then(mapping => { + return this.resolveWithInteraction(folder, config, section, variables, target).then(mapping => { // finally substitute evaluated command variables (if there are any) if (!mapping) { return null; @@ -103,14 +103,14 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR }); } - public async resolveWithInteraction(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary): Promise | undefined> { + public async resolveWithInteraction(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise | undefined> { // resolve any non-interactive variables and any contributed variables const resolved = await this.resolveAnyMap(folder, config); config = resolved.newConfig; const allVariableMapping: Map = resolved.resolvedVariables; // resolve input and command variables in the order in which they are encountered - return this.resolveWithInputAndCommands(folder, config, variables, section).then(inputOrCommandMapping => { + return this.resolveWithInputAndCommands(folder, config, variables, section, target).then(inputOrCommandMapping => { if (this.updateMapping(inputOrCommandMapping, allVariableMapping)) { return allVariableMapping; } @@ -139,7 +139,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR * * @param variableToCommandMap Aliases for commands */ - private async resolveWithInputAndCommands(folder: IWorkspaceFolder | undefined, configuration: any, variableToCommandMap?: IStringDictionary, section?: string): Promise | undefined> { + private async resolveWithInputAndCommands(folder: IWorkspaceFolder | undefined, configuration: any, variableToCommandMap?: IStringDictionary, section?: string, target?: ConfigurationTarget): Promise | undefined> { if (!configuration) { return Promise.resolve(undefined); @@ -148,9 +148,18 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR // get all "inputs" let inputs: ConfiguredInput[] = []; if (folder && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY && section) { - let result = this.configurationService.getValue(section, { resource: folder.uri }); - if (result) { - inputs = result.inputs; + let result = this.configurationService.inspect(section, { resource: folder.uri }); + if (result && (result.userValue || result.workspaceValue || result.workspaceFolderValue)) { + switch (target) { + case ConfigurationTarget.USER: inputs = (result.userValue)?.inputs; break; + case ConfigurationTarget.WORKSPACE: inputs = (result.workspaceValue)?.inputs; break; + default: inputs = (result.workspaceFolderValue)?.inputs; + } + } else { + const valueResult = this.configurationService.getValue(section, { resource: folder.uri }); + if (valueResult) { + inputs = valueResult.inputs; + } } } @@ -340,7 +349,7 @@ export class ConfigurationResolverService extends BaseConfigurationResolverServi @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, @IQuickInputService quickInputService: IQuickInputService ) { - super(environmentService.configuration.userEnv, editorService, environmentService, configurationService, commandService, workspaceContextService, quickInputService); + super(Object.create(null), editorService, environmentService, configurationService, commandService, workspaceContextService, quickInputService); } } diff --git a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts index eff3ed2259..b691f97d8a 100644 --- a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts @@ -6,6 +6,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; export const IConfigurationResolverService = createDecorator('configurationResolverService'); @@ -29,13 +30,13 @@ export interface IConfigurationResolverService { * @param section For example, 'tasks' or 'debug'. Used for resolving inputs. * @param variables Aliases for commands. */ - resolveWithInteractionReplace(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary): Promise; + resolveWithInteractionReplace(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise; /** * Similar to resolveWithInteractionReplace, except without the replace. Returns a map of variables and their resolution. * Keys in the map will be of the format input:variableName or command:variableName. */ - resolveWithInteraction(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary): Promise | undefined>; + resolveWithInteraction(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary, target?: ConfigurationTarget): Promise | undefined>; /** * Contributes a variable that can be resolved later. Consumers that use resolveAny, resolveWithInteraction, diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index f32740f087..e58e31de39 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -4,14 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { Event } from 'vs/base/common/event'; import { URI as uri } from 'vs/base/common/uri'; import * as platform from 'vs/base/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; +import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { TestEditorService, TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestWindowConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -21,11 +22,11 @@ import * as Types from 'vs/base/common/types'; import { EditorType } from 'vs/editor/common/editorCommon'; import { Selection } from 'vs/editor/common/core/selection'; import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; const mockLineNumber = 10; class TestEditorServiceWithActiveEditor extends TestEditorService { - get activeTextEditorWidget(): any { + get activeTextEditorControl(): any { return { getEditorType() { return EditorType.ICodeEditor; @@ -37,10 +38,14 @@ class TestEditorServiceWithActiveEditor extends TestEditorService { } } +class TestConfigurationResolverService extends BaseConfigurationResolverService { + +} + suite('Configuration Resolver Service', () => { let configurationResolverService: IConfigurationResolverService | null; let envVariables: { [key: string]: string } = { key1: 'Value for key1', key2: 'Value for key2' }; - let environmentService: IWorkbenchEnvironmentService; + let environmentService: MockWorkbenchEnvironmentService; let mockCommandService: MockCommandService; let editorService: TestEditorServiceWithActiveEditor; let workspace: IWorkspaceFolder; @@ -57,7 +62,7 @@ suite('Configuration Resolver Service', () => { index: 0, toResource: (path: string) => uri.file(path) }; - configurationResolverService = new ConfigurationResolverService(editorService, environmentService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(), quickInputService); + configurationResolverService = new TestConfigurationResolverService(environmentService.userEnv, editorService, environmentService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(), quickInputService); }); teardown(() => { @@ -136,7 +141,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new TestConfigurationResolverService(environmentService.userEnv, new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz'); }); @@ -153,7 +158,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new TestConfigurationResolverService(environmentService.userEnv, new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} xyz'), 'abc foo bar xyz'); }); @@ -170,7 +175,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new TestConfigurationResolverService(environmentService.userEnv, new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); if (platform.isWindows) { assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo \\VSCode\\workspaceLocation Value for key1 xyz'); } else { @@ -191,7 +196,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new TestConfigurationResolverService(environmentService.userEnv, new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); if (platform.isWindows) { assert.strictEqual(service.resolve(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar \\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2'); } else { @@ -225,7 +230,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new TestConfigurationResolverService(environmentService.userEnv, new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${config:editor.lineNumbers} ${config:editor.insertSpaces} xyz'), 'abc foo 123 false xyz'); }); @@ -235,7 +240,7 @@ suite('Configuration Resolver Service', () => { editor: {} }); - let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new TestConfigurationResolverService(environmentService.userEnv, new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); assert.strictEqual(service.resolve(workspace, 'abc ${unknownVariable} xyz'), 'abc ${unknownVariable} xyz'); assert.strictEqual(service.resolve(workspace, 'abc ${env:unknownVariable} xyz'), 'abc xyz'); }); @@ -248,7 +253,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new TestConfigurationResolverService(environmentService.userEnv, new TestEditorServiceWithActiveEditor(), environmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); assert.throws(() => service.resolve(workspace, 'abc ${env} xyz')); assert.throws(() => service.resolve(workspace, 'abc ${env:} xyz')); @@ -527,6 +532,11 @@ class MockCommandService implements ICommandService { class MockQuickInputService implements IQuickInputService { _serviceBrand: undefined; + readonly onShow = Event.None; + readonly onHide = Event.None; + + readonly quickAccess = undefined!; + public pick(picks: Promise[]> | QuickPickInput[], options?: IPickOptions & { canPickMany: true }, token?: CancellationToken): Promise; public pick(picks: Promise[]> | QuickPickInput[], options?: IPickOptions & { canPickMany: false }, token?: CancellationToken): Promise; public pick(picks: Promise[]> | QuickPickInput[], options?: Omit, 'canPickMany'>, token?: CancellationToken): Promise { @@ -574,6 +584,10 @@ class MockQuickInputService implements IQuickInputService { cancel(): Promise { throw new Error('not implemented.'); } + + hide(): void { + throw new Error('not implemented.'); + } } class MockInputsConfigurationService extends TestConfigurationService { @@ -619,7 +633,7 @@ class MockInputsConfigurationService extends TestConfigurationService { class MockWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { - constructor(userEnv: platform.IProcessEnvironment) { - super({ ...TestWindowConfiguration, userEnv }, TestWindowConfiguration.execPath, TestWindowConfiguration.windowId); + constructor(public userEnv: platform.IProcessEnvironment) { + super({ ...TestWindowConfiguration, userEnv }, TestWindowConfiguration.execPath); } } diff --git a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts index a3498fee95..36fb7eb701 100644 --- a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts @@ -25,6 +25,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { ContextMenuService as HTMLContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { stripCodicons } from 'vs/base/common/codicons'; export class ContextMenuService extends Disposable implements IContextMenuService { @@ -138,7 +139,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService // Submenu if (entry instanceof ContextSubMenu) { return { - label: unmnemonicLabel(entry.label), + label: unmnemonicLabel(stripCodicons(entry.label)).trim(), submenu: this.createMenu(delegate, entry.entries, onHide) }; } @@ -155,7 +156,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService } const item: IContextMenuItem = { - label: unmnemonicLabel(entry.label), + label: unmnemonicLabel(stripCodicons(entry.label)).trim(), checked: !!entry.checked, type, enabled: !!entry.enabled, diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index e2d9ef5c10..1ee872a747 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -11,7 +11,7 @@ import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifec import { isThenable } from 'vs/base/common/async'; import { LinkedList } from 'vs/base/common/linkedList'; import { createStyleSheet, createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; @@ -56,7 +56,7 @@ class DecorationRule { return --this._refCounter === 0; } - appendCSSRules(element: HTMLStyleElement, theme: ITheme): void { + appendCSSRules(element: HTMLStyleElement, theme: IColorTheme): void { if (!Array.isArray(this.data)) { this._appendForOne(this.data, element, theme); } else { @@ -64,7 +64,7 @@ class DecorationRule { } } - private _appendForOne(data: IDecorationData, element: HTMLStyleElement, theme: ITheme): void { + private _appendForOne(data: IDecorationData, element: HTMLStyleElement, theme: IColorTheme): void { const { color, letter } = data; // label createCSSRule(`.${this.itemColorClassName}`, `color: ${getColor(theme, color)};`, element); @@ -74,7 +74,7 @@ class DecorationRule { } } - private _appendForMany(data: IDecorationData[], element: HTMLStyleElement, theme: ITheme): void { + private _appendForMany(data: IDecorationData[], element: HTMLStyleElement, theme: IColorTheme): void { // label const { color } = data[0]; createCSSRule(`.${this.itemColorClassName}`, `color: ${getColor(theme, color)};`, element); @@ -110,7 +110,7 @@ class DecorationStyles { constructor( private _themeService: IThemeService, ) { - this._themeService.onThemeChange(this._onThemeChange, this, this._dispoables); + this._themeService.onDidColorThemeChange(this._onThemeChange, this, this._dispoables); } dispose(): void { @@ -130,7 +130,7 @@ class DecorationStyles { // new css rule rule = new DecorationRule(data, key); this._decorationRules.set(key, rule); - rule.appendCSSRules(this._styleElement, this._themeService.getTheme()); + rule.appendCSSRules(this._styleElement, this._themeService.getColorTheme()); } rule.acquire(); @@ -162,7 +162,7 @@ class DecorationStyles { private _onThemeChange(): void { this._decorationRules.forEach(rule => { rule.removeCSSRules(this._styleElement); - rule.appendCSSRules(this._styleElement, this._themeService.getTheme()); + rule.appendCSSRules(this._styleElement, this._themeService.getColorTheme()); }); } } @@ -364,23 +364,21 @@ export class DecorationsService implements IDecorationsService { getDecoration(uri: URI, includeChildren: boolean): IDecoration | undefined { let data: IDecorationData[] = []; let containsChildren: boolean = false; - for (let iter = this._data.iterator(), next = iter.next(); !next.done; next = iter.next()) { - const { label } = next.value.provider; - next.value.getOrRetrieve(uri, includeChildren, (deco, isChild) => { + for (let wrapper of this._data) { + wrapper.getOrRetrieve(uri, includeChildren, (deco, isChild) => { if (!isChild || deco.bubble) { data.push(deco); containsChildren = isChild || containsChildren; - this._logService.trace('DecorationsService#getDecoration#getOrRetrieve', label, deco, isChild, uri); + this._logService.trace('DecorationsService#getDecoration#getOrRetrieve', wrapper.provider.label, deco, isChild, uri); } }); } - return data.length === 0 ? undefined : this._decorationStyles.asDecoration(data, containsChildren); } } -function getColor(theme: ITheme, color: string | undefined) { +function getColor(theme: IColorTheme, color: string | undefined) { if (color) { const foundColor = theme.getColor(color); if (foundColor) { diff --git a/src/vs/workbench/services/dialogs/browser/dialogService.ts b/src/vs/workbench/services/dialogs/browser/dialogService.ts index cbbce9ea73..1cf317ad13 100644 --- a/src/vs/workbench/services/dialogs/browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/dialogService.ts @@ -18,6 +18,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IProductService } from 'vs/platform/product/common/productService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { fromNow } from 'vs/base/common/date'; export class DialogService implements IDialogService { @@ -121,18 +122,24 @@ export class DialogService implements IDialogService { } async about(): Promise { - const detail = nls.localize('aboutDetail', - "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}", - this.productService.version || 'Unknown', - this.productService.commit || 'Unknown', - this.productService.date || 'Unknown', - navigator.userAgent - ); + const detailString = (useAgo: boolean): string => { + return nls.localize('aboutDetail', + "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}", + this.productService.version || 'Unknown', + this.productService.commit || 'Unknown', + this.productService.date ? `${this.productService.date}${useAgo ? ' (' + fromNow(new Date(this.productService.date), true) + ')' : ''}` : 'Unknown', + navigator.userAgent + ); + }; + + const detail = detailString(true); + const detailToCopy = detailString(false); + const { choice } = await this.show(Severity.Info, this.productService.nameLong, [nls.localize('copy', "Copy"), nls.localize('ok', "OK")], { detail, cancelId: 1 }); if (choice === 0) { - this.clipboardService.writeText(detail); + this.clipboardService.writeText(detailToCopy); } } } diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index 157b943244..6a777aa991 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -54,9 +54,9 @@ export namespace SaveLocalFileCommand { export function handler(): ICommandHandler { return accessor => { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl; - if (activeControl) { - return editorService.save({ groupId: activeControl.group.id, editor: activeControl.input }, { saveAs: true, availableFileSystems: [Schemas.file], reason: SaveReason.EXPLICIT }); + const activeEditorPane = editorService.activeEditorPane; + if (activeEditorPane) { + return editorService.save({ groupId: activeEditorPane.group.id, editor: activeEditorPane.input }, { saveAs: true, availableFileSystems: [Schemas.file], reason: SaveReason.EXPLICIT }); } return Promise.resolve(undefined); diff --git a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts index 7f72a51970..5b66948e61 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts @@ -23,6 +23,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { MessageBoxOptions } from 'electron'; +import { fromNow } from 'vs/base/common/date'; interface IMassagedMessageBoxOptions { @@ -216,18 +217,24 @@ class NativeDialogService implements IDialogService { } const isSnap = process.platform === 'linux' && process.env.SNAP && process.env.SNAP_REVISION; - const detail = nls.localize('aboutDetail', // {{SQL CARBON EDIT}} update about dialog - "Version: {0}\nCommit: {1}\nDate: {2}\nVS Code: {8}\nElectron: {3}\nChrome: {4}\nNode.js: {5}\nV8: {6}\nOS: {7}", - version, - product.commit || 'Unknown', - product.date || 'Unknown', - process.versions['electron'], - process.versions['chrome'], - process.versions['node'], - process.versions['v8'], - `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}`, - product.vscodeVersion - ); + + const detailString = (useAgo: boolean): string => { + return nls.localize('aboutDetail', + "Version: {0}\nCommit: {1}\nDate: {2}\nVS Code: {8}\nElectron: {3}\nChrome: {4}\nNode.js: {5}\nV8: {6}\nOS: {7}", + version, + product.commit || 'Unknown', + product.date ? `${product.date}${useAgo ? ' (' + fromNow(new Date(product.date), true) + ')' : ''}` : 'Unknown', + process.versions['electron'], + process.versions['chrome'], + process.versions['node'], + process.versions['v8'], + `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}`, + product.vscodeVersion + ); + }; + + const detail = detailString(true); + const detailToCopy = detailString(false); const ok = nls.localize('okButton', "OK"); const copy = mnemonicButtonLabel(nls.localize({ key: 'copy', comment: ['&& denotes a mnemonic'] }, "&&Copy")); @@ -250,7 +257,7 @@ class NativeDialogService implements IDialogService { }); if (buttons[result.response] === copy) { - this.clipboardService.writeText(detail); + this.clipboardService.writeText(detailToCopy); } } } diff --git a/src/vs/workbench/services/editor/browser/codeEditorService.ts b/src/vs/workbench/services/editor/browser/codeEditorService.ts index 76cd67b2c0..911ff821cc 100644 --- a/src/vs/workbench/services/editor/browser/codeEditorService.ts +++ b/src/vs/workbench/services/editor/browser/codeEditorService.ts @@ -6,7 +6,7 @@ import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { CodeEditorServiceImpl } from 'vs/editor/browser/services/codeEditorServiceImpl'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TextEditorOptions } from 'vs/workbench/common/editor'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; @@ -23,34 +23,34 @@ export class CodeEditorService extends CodeEditorServiceImpl { } getActiveCodeEditor(): ICodeEditor | null { - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - if (isCodeEditor(activeTextEditorWidget)) { - return activeTextEditorWidget; + const activeTextEditorControl = this.editorService.activeTextEditorControl; + if (isCodeEditor(activeTextEditorControl)) { + return activeTextEditorControl; } - if (isDiffEditor(activeTextEditorWidget)) { - return activeTextEditorWidget.getModifiedEditor(); + if (isDiffEditor(activeTextEditorControl)) { + return activeTextEditorControl.getModifiedEditor(); } return null; } - openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { // Special case: If the active editor is a diff editor and the request to open originates and // targets the modified side of it, we just apply the request there to prevent opening the modified // side as separate editor. - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; + const activeTextEditorControl = this.editorService.activeTextEditorControl; if ( !sideBySide && // we need the current active group to be the taret - isDiffEditor(activeTextEditorWidget) && // we only support this for active text diff editors + isDiffEditor(activeTextEditorControl) && // we only support this for active text diff editors input.options && // we need options to apply input.resource && // we need a request resource to compare with - activeTextEditorWidget.getModel() && // we need a target model to compare with - source === activeTextEditorWidget.getModifiedEditor() && // we need the source of this request to be the modified side of the diff editor - input.resource.toString() === activeTextEditorWidget.getModel()!.modified.uri.toString() // we need the input resources to match with modified side + activeTextEditorControl.getModel() && // we need a target model to compare with + source === activeTextEditorControl.getModifiedEditor() && // we need the source of this request to be the modified side of the diff editor + input.resource.toString() === activeTextEditorControl.getModel()!.modified.uri.toString() // we need the input resources to match with modified side ) { - const targetEditor = activeTextEditorWidget.getModifiedEditor(); + const targetEditor = activeTextEditorControl.getModifiedEditor(); const textOptions = TextEditorOptions.create(input.options); textOptions.apply(targetEditor, ScrollType.Smooth); @@ -62,7 +62,7 @@ export class CodeEditorService extends CodeEditorServiceImpl { return this.doOpenCodeEditor(input, source, sideBySide); } - private async doOpenCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + private async doOpenCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { const control = await this.editorService.openEditor(input, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); if (control) { const widget = control.getControl(); diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 402b972dcf..742a44fa32 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IResourceInput, ITextEditorOptions, IEditorOptions, EditorActivation } from 'vs/platform/editor/common/editor'; -import { SideBySideEditor as SideBySideEditorChoice, IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, IRevertOptions, SaveReason, EditorsOrder, isTextEditor, IWorkbenchEditorConfiguration, toResource } from 'vs/workbench/common/editor'; +import { IResourceEditorInput, ITextEditorOptions, IEditorOptions, EditorActivation } from 'vs/platform/editor/common/editor'; +import { SideBySideEditor, IEditorInput, IEditorPane, GroupIdentifier, IFileEditorInput, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditorPane, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, isTextEditorPane, IWorkbenchEditorConfiguration, toResource, IVisibleEditorPane } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { ResourceMap } from 'vs/base/common/map'; @@ -17,7 +17,7 @@ import { URI } from 'vs/base/common/uri'; import { basename, isEqualOrParent, joinPath } from 'vs/base/common/resources'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IResourceEditor, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IVisibleEditor, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions } from 'vs/workbench/services/editor/common/editorService'; +import { IResourceEditorInputType, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { coalesce, distinct } from 'vs/base/common/arrays'; @@ -84,7 +84,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { this.editorGroupService.whenRestored.then(() => this.onEditorsRestored()); this.editorGroupService.onDidActiveGroupChange(group => this.handleActiveEditorChange(group)); this.editorGroupService.onDidAddGroup(group => this.registerGroupListeners(group as IEditorGroupView)); - this.editorsObserver.onDidChange(() => this._onDidMostRecentlyActiveEditorsChange.fire()); + this.editorsObserver.onDidMostRecentlyActiveEditorsChange(() => this._onDidMostRecentlyActiveEditorsChange.fire()); // Out of workspace file watchers this._register(this.onDidVisibleEditorsChange(() => this.handleVisibleEditorsChange())); @@ -177,8 +177,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { for (const editor of this.visibleEditors) { const resources = distinct(coalesce([ - toResource(editor, { supportSideBySide: SideBySideEditorChoice.MASTER }), - toResource(editor, { supportSideBySide: SideBySideEditorChoice.DETAILS }) + toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }), + toResource(editor, { supportSideBySide: SideBySideEditor.DETAILS }) ]), resource => resource.toString()); for (const resource of resources) { @@ -293,7 +293,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { } private closeOnFileDelete: boolean = false; - private fileInputFactory = Registry.as(EditorExtensions.EditorInputFactories).getFileInputFactory(); + private fileEditorInputFactory = Registry.as(EditorExtensions.EditorInputFactories).getFileEditorInputFactory(); private onConfigurationUpdated(configuration: IWorkbenchEditorConfiguration): void { if (typeof configuration.workbench?.editor?.closeOnFileDelete === 'boolean') { this.closeOnFileDelete = configuration.workbench.editor.closeOnFileDelete; @@ -314,7 +314,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // - the user has not disabled the setting closeOnFileDelete // - the file change is local // - the input is a file that is not resolved (we need to dispose because we cannot restore otherwise since we do not have the contents) - if (this.closeOnFileDelete || !isExternal || (this.fileInputFactory.isFileInput(editor) && !editor.isResolved())) { + if (this.closeOnFileDelete || !isExternal || (this.fileEditorInputFactory.isFileEditorInput(editor) && !editor.isResolved())) { // Do NOT close any opened editor that matches the resource path (either equal or being parent) of the // resource we move to (movedTo). Otherwise we would close a resource that has been renamed to the same @@ -389,16 +389,16 @@ export class EditorService extends Disposable implements EditorServiceImpl { private readonly editorsObserver = this._register(this.instantiationService.createInstance(EditorsObserver)); - get activeControl(): IVisibleEditor | undefined { - return this.editorGroupService.activeGroup?.activeControl; + get activeEditorPane(): IVisibleEditorPane | undefined { + return this.editorGroupService.activeGroup?.activeEditorPane; } - get activeTextEditorWidget(): ICodeEditor | IDiffEditor | undefined { - const activeControl = this.activeControl; - if (activeControl) { - const activeControlWidget = activeControl.getControl(); - if (isCodeEditor(activeControlWidget) || isDiffEditor(activeControlWidget)) { - return activeControlWidget; + get activeTextEditorControl(): ICodeEditor | IDiffEditor | undefined { + const activeEditorPane = this.activeEditorPane; + if (activeEditorPane) { + const activeControl = activeEditorPane.getControl(); + if (isCodeEditor(activeControl) || isDiffEditor(activeControl)) { + return activeControl; } } @@ -408,11 +408,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { get activeTextEditorMode(): string | undefined { let activeCodeEditor: ICodeEditor | undefined = undefined; - const activeTextEditorWidget = this.activeTextEditorWidget; - if (isDiffEditor(activeTextEditorWidget)) { - activeCodeEditor = activeTextEditorWidget.getModifiedEditor(); + const activeTextEditorControl = this.activeTextEditorControl; + if (isDiffEditor(activeTextEditorControl)) { + activeCodeEditor = activeTextEditorControl.getModifiedEditor(); } else { - activeCodeEditor = activeTextEditorWidget; + activeCodeEditor = activeTextEditorControl; } return activeCodeEditor?.getModel()?.getLanguageIdentifier().language; @@ -446,12 +446,20 @@ export class EditorService extends Disposable implements EditorServiceImpl { return activeGroup ? withNullAsUndefined(activeGroup.activeEditor) : undefined; } - get visibleControls(): IVisibleEditor[] { - return coalesce(this.editorGroupService.groups.map(group => group.activeControl)); + get visibleEditorPanes(): IVisibleEditorPane[] { + return coalesce(this.editorGroupService.groups.map(group => group.activeEditorPane)); } - get visibleTextEditorWidgets(): Array { - return this.visibleControls.map(control => control.getControl() as ICodeEditor | IDiffEditor).filter(widget => isCodeEditor(widget) || isDiffEditor(widget)); + get visibleTextEditorControls(): Array { + const visibleTextEditorControls: Array = []; + for (const visibleEditorPane of this.visibleEditorPanes) { + const control = visibleEditorPane.getControl(); + if (isCodeEditor(control) || isDiffEditor(control)) { + visibleTextEditorControls.push(control); + } + } + + return visibleTextEditorControls; } get visibleEditors(): IEditorInput[] { @@ -494,11 +502,10 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region openEditor() - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise; - openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: OpenInEditorGroup): Promise; - openEditor(editor: IResourceDiffInput, group?: OpenInEditorGroup): Promise; - openEditor(editor: IResourceSideBySideInput, group?: OpenInEditorGroup): Promise; - async openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise { + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise; + openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: OpenInEditorGroup): Promise; + openEditor(editor: IResourceDiffEditorInput, group?: OpenInEditorGroup): Promise; + async openEditor(editor: IEditorInput | IResourceEditorInputType, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise { const result = this.doResolveEditorOpenRequest(editor, optionsOrGroup, group); if (result) { const [resolvedGroup, resolvedEditor, resolvedOptions] = result; @@ -509,7 +516,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { return undefined; } - doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): [IEditorGroup, EditorInput, EditorOptions | undefined] | undefined { + doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditorInputType, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): [IEditorGroup, EditorInput, EditorOptions | undefined] | undefined { let resolvedGroup: IEditorGroup | undefined; let candidateGroup: OpenInEditorGroup | undefined; @@ -527,8 +534,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Untyped Text Editor Support else { - const textInput = editor; - typedEditor = this.createInput(textInput); + const textInput = editor; + typedEditor = this.createEditorInput(textInput); if (typedEditor) { typedOptions = TextEditorOptions.from(textInput); @@ -660,9 +667,9 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region openEditors() - openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise; - openEditors(editors: IResourceEditor[], group?: OpenInEditorGroup): Promise; - async openEditors(editors: Array, group?: OpenInEditorGroup): Promise { + openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise; + openEditors(editors: IResourceEditorInputType[], group?: OpenInEditorGroup): Promise; + async openEditors(editors: Array, group?: OpenInEditorGroup): Promise { // Convert to typed editors and options const typedEditors: IEditorInputWithOptions[] = []; @@ -670,7 +677,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { if (isEditorInputWithOptions(editor)) { typedEditors.push(editor); } else { - typedEditors.push({ editor: this.createInput(editor), options: TextEditorOptions.from(editor) }); + typedEditors.push({ editor: this.createEditorInput(editor), options: TextEditorOptions.from(editor) }); } }); @@ -693,7 +700,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // Open in target groups - const result: Promise[] = []; + const result: Promise[] = []; mapGroupToEditors.forEach((editorsWithOptions, group) => { result.push(group.openEditors(editorsWithOptions)); }); @@ -705,8 +712,18 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region isOpen() - isOpen(editor: IEditorInput): boolean { - return this.editorGroupService.groups.some(group => group.isOpened(editor)); + isOpen(editor: IEditorInput): boolean; + isOpen(editor: IResourceEditorInput): boolean; + isOpen(editor: IEditorInput | IResourceEditorInput): boolean { + if (editor instanceof EditorInput) { + return this.editorGroupService.groups.some(group => group.isOpened(editor)); + } + + if (editor.resource) { + return this.editorsObserver.hasEditor(editor.resource); + } + + return false; } //#endregion @@ -731,8 +748,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { const replacementArg = replaceEditorArg as IResourceEditorReplacement; typedEditors.push({ - editor: this.createInput(replacementArg.editor), - replacement: this.createInput(replacementArg.replacement), + editor: this.createEditorInput(replacementArg.editor), + replacement: this.createEditorInput(replacementArg.replacement), options: this.toOptions(replacementArg.replacement.options) }); } @@ -751,9 +768,9 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region invokeWithinEditorContext() invokeWithinEditorContext(fn: (accessor: ServicesAccessor) => T): T { - const activeTextEditorWidget = this.activeTextEditorWidget; - if (isCodeEditor(activeTextEditorWidget)) { - return activeTextEditorWidget.invokeWithinContext(fn); + const activeTextEditorControl = this.activeTextEditorControl; + if (isCodeEditor(activeTextEditorControl)) { + return activeTextEditorControl.invokeWithinContext(fn); } const activeGroup = this.editorGroupService.activeGroup; @@ -766,11 +783,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#endregion - //#region createInput() + //#region createEditorInput() private readonly editorInputCache = new ResourceMap(); - createInput(input: IEditorInputWithOptions | IEditorInput | IResourceEditor): EditorInput { + createEditorInput(input: IEditorInputWithOptions | IEditorInput | IResourceEditorInputType): EditorInput { // Typed Editor Input Support (EditorInput) if (input instanceof EditorInput) { @@ -783,25 +800,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { return editorInputWithOptions.editor; } - // Side by Side Support - const resourceSideBySideInput = input as IResourceSideBySideInput; - if (resourceSideBySideInput.masterResource && resourceSideBySideInput.detailResource) { - const masterInput = this.createInput({ resource: resourceSideBySideInput.masterResource, forceFile: resourceSideBySideInput.forceFile }); - const detailInput = this.createInput({ resource: resourceSideBySideInput.detailResource, forceFile: resourceSideBySideInput.forceFile }); - - return new SideBySideEditorInput( - resourceSideBySideInput.label || this.toSideBySideLabel(detailInput, masterInput, '-'), - resourceSideBySideInput.description, - detailInput, - masterInput - ); - } - // Diff Editor Support - const resourceDiffInput = input as IResourceDiffInput; + const resourceDiffInput = input as IResourceDiffEditorInput; if (resourceDiffInput.leftResource && resourceDiffInput.rightResource) { - const leftInput = this.createInput({ resource: resourceDiffInput.leftResource, forceFile: resourceDiffInput.forceFile }); - const rightInput = this.createInput({ resource: resourceDiffInput.rightResource, forceFile: resourceDiffInput.forceFile }); + const leftInput = this.createEditorInput({ resource: resourceDiffInput.leftResource, forceFile: resourceDiffInput.forceFile }); + const rightInput = this.createEditorInput({ resource: resourceDiffInput.rightResource, forceFile: resourceDiffInput.forceFile }); return new DiffEditorInput( resourceDiffInput.label || this.toSideBySideLabel(leftInput, rightInput, '↔'), @@ -812,7 +815,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // Untitled file support - const untitledInput = input as IUntitledTextResourceInput; + const untitledInput = input as IUntitledTextResourceEditorInput; if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) { const untitledOptions = { mode: untitledInput.mode, @@ -848,22 +851,22 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // Resource Editor Support - const resourceInput = input as IResourceInput; - if (resourceInput.resource instanceof URI) { - let label = resourceInput.label; + const resourceEditorInput = input as IResourceEditorInput; + if (resourceEditorInput.resource instanceof URI) { + let label = resourceEditorInput.label; if (!label) { - label = basename(resourceInput.resource); // derive the label from the path + label = basename(resourceEditorInput.resource); // derive the label from the path } - return this.createOrGetCached(resourceInput.resource, () => { + return this.createOrGetCached(resourceEditorInput.resource, () => { // File - if (resourceInput.forceFile /* fix for https://github.com/Microsoft/vscode/issues/48275 */ || this.fileService.canHandleResource(resourceInput.resource)) { - return this.fileInputFactory.createFileInput(resourceInput.resource, resourceInput.encoding, resourceInput.mode, this.instantiationService); + if (resourceEditorInput.forceFile /* fix for https://github.com/Microsoft/vscode/issues/48275 */ || this.fileService.canHandleResource(resourceEditorInput.resource)) { + return this.fileEditorInputFactory.createFileEditorInput(resourceEditorInput.resource, resourceEditorInput.encoding, resourceEditorInput.mode, this.instantiationService); } // Resource - return this.instantiationService.createInstance(ResourceEditorInput, resourceInput.label, resourceInput.description, resourceInput.resource, resourceInput.mode); + return this.instantiationService.createInstance(ResourceEditorInput, resourceEditorInput.label, resourceEditorInput.description, resourceEditorInput.resource, resourceEditorInput.mode); }, cachedInput => { // Untitled @@ -873,12 +876,12 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Files else if (!(cachedInput instanceof ResourceEditorInput)) { - if (resourceInput.encoding) { - cachedInput.setPreferredEncoding(resourceInput.encoding); + if (resourceEditorInput.encoding) { + cachedInput.setPreferredEncoding(resourceEditorInput.encoding); } - if (resourceInput.mode) { - cachedInput.setPreferredMode(resourceInput.mode); + if (resourceEditorInput.mode) { + cachedInput.setPreferredMode(resourceEditorInput.mode); } } @@ -888,12 +891,12 @@ export class EditorService extends Disposable implements EditorServiceImpl { cachedInput.setName(label); } - if (resourceInput.description) { - cachedInput.setDescription(resourceInput.description); + if (resourceEditorInput.description) { + cachedInput.setDescription(resourceEditorInput.description); } - if (resourceInput.mode) { - cachedInput.setPreferredMode(resourceInput.mode); + if (resourceEditorInput.mode) { + cachedInput.setPreferredMode(resourceEditorInput.mode); } } }) as EditorInput; @@ -934,7 +937,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // If both editors are file inputs, we produce an optimized label // by adding the relative path of both inputs to the label. This // makes it easier to understand a file-based comparison. - if (this.fileInputFactory.isFileInput(leftInput) && this.fileInputFactory.isFileInput(rightInput)) { + if (this.fileEditorInputFactory.isFileEditorInput(leftInput) && this.fileEditorInputFactory.isFileEditorInput(rightInput)) { return `${this.labelService.getUriLabel(leftResource, { relative: true })} ${divider} ${this.labelService.getUriLabel(rightResource, { relative: true })}`; } @@ -953,6 +956,10 @@ export class EditorService extends Disposable implements EditorServiceImpl { editors = [editors]; } + // Make sure to not save the same editor multiple times + // by using the `matches()` method to find duplicates + const uniqueEditors = this.getUniqueEditors(editors); + // Split editors up into a bucket that is saved in parallel // and sequentially. Unless "Save As", all non-untitled editors // can be saved in parallel to speed up the operation. Remaining @@ -961,9 +968,9 @@ export class EditorService extends Disposable implements EditorServiceImpl { const editorsToSaveParallel: IEditorIdentifier[] = []; const editorsToSaveSequentially: IEditorIdentifier[] = []; if (options?.saveAs) { - editorsToSaveSequentially.push(...editors); + editorsToSaveSequentially.push(...uniqueEditors); } else { - for (const { groupId, editor } of editors) { + for (const { groupId, editor } of uniqueEditors) { if (editor.isUntitled()) { editorsToSaveSequentially.push({ groupId, editor }); } else { @@ -973,7 +980,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // Editors to save in parallel - await Promise.all(editorsToSaveParallel.map(({ groupId, editor }) => { + const saveResults = await Promise.all(editorsToSaveParallel.map(({ groupId, editor }) => { // Use save as a hint to pin the editor if used explicitly if (options?.reason === SaveReason.EXPLICIT) { @@ -994,14 +1001,16 @@ export class EditorService extends Disposable implements EditorServiceImpl { // is untitled or we "Save As". This also allows the user to review // the contents of the editor before making a decision. let viewState: IEditorViewState | undefined = undefined; - const control = await this.openEditor(editor, undefined, groupId); - if (isTextEditor(control)) { - viewState = control.getViewState(); + const editorPane = await this.openEditor(editor, undefined, groupId); + if (isTextEditorPane(editorPane)) { + viewState = editorPane.getViewState(); } const result = options?.saveAs ? await editor.saveAs(groupId, options) : await editor.save(groupId, options); + saveResults.push(result); + if (!result) { - return false; // failed or cancelled, abort + break; // failed or cancelled, abort } // Replace editor preserving viewstate (either across all groups or @@ -1015,35 +1024,34 @@ export class EditorService extends Disposable implements EditorServiceImpl { } } - return true; + return saveResults.every(result => !!result); } saveAll(options?: ISaveAllEditorsOptions): Promise { return this.save(this.getAllDirtyEditors(options), options); } - async revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise { + async revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise { // Convert to array if (!Array.isArray(editors)) { editors = [editors]; } - const result = await Promise.all(editors.map(async ({ groupId, editor }) => { - if (editor.isDisposed()) { - return true; // might have been disposed from from the revert already - } + // Make sure to not revert the same editor multiple times + // by using the `matches()` method to find duplicates + const uniqueEditors = this.getUniqueEditors(editors); + + await Promise.all(uniqueEditors.map(async ({ groupId, editor }) => { // Use revert as a hint to pin the editor this.editorGroupService.getGroup(groupId)?.pinEditor(editor); return editor.revert(groupId, options); })); - - return result.every(success => !!success); } - async revertAll(options?: IRevertAllEditorsOptions): Promise { + async revertAll(options?: IRevertAllEditorsOptions): Promise { return this.revert(this.getAllDirtyEditors(options), options); } @@ -1061,6 +1069,19 @@ export class EditorService extends Disposable implements EditorServiceImpl { return editors; } + private getUniqueEditors(editors: IEditorIdentifier[]): IEditorIdentifier[] { + const uniqueEditors: IEditorIdentifier[] = []; + for (const { editor, groupId } of editors) { + if (uniqueEditors.some(uniqueEditor => uniqueEditor.editor.matches(editor))) { + continue; + } + + uniqueEditors.push({ editor, groupId }); + } + + return uniqueEditors; + } + //#endregion dispose(): void { @@ -1074,11 +1095,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { export interface IEditorOpenHandler { ( - delegate: (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => Promise, + delegate: (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => Promise, group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions - ): Promise; + ): Promise; } /** @@ -1095,25 +1116,24 @@ export class DelegatingEditorService implements IEditorService { @IEditorService private editorService: EditorService ) { } - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise; - openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: OpenInEditorGroup): Promise; - openEditor(editor: IResourceDiffInput, group?: OpenInEditorGroup): Promise; - openEditor(editor: IResourceSideBySideInput, group?: OpenInEditorGroup): Promise; - async openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise { + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise; + openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: OpenInEditorGroup): Promise; + openEditor(editor: IResourceDiffEditorInput, group?: OpenInEditorGroup): Promise; + async openEditor(editor: IEditorInput | IResourceEditorInputType, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise { const result = this.editorService.doResolveEditorOpenRequest(editor, optionsOrGroup, group); if (result) { const [resolvedGroup, resolvedEditor, resolvedOptions] = result; // Pass on to editor open handler - const control = await this.editorOpenHandler( + const editorPane = await this.editorOpenHandler( (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => group.openEditor(editor, options), resolvedGroup, resolvedEditor, resolvedOptions ); - if (control) { - return control; // the opening was handled, so return early + if (editorPane) { + return editorPane; // the opening was handled, so return early } return withNullAsUndefined(await resolvedGroup.openEditor(resolvedEditor, resolvedOptions)); @@ -1128,20 +1148,20 @@ export class DelegatingEditorService implements IEditorService { get onDidVisibleEditorsChange(): Event { return this.editorService.onDidVisibleEditorsChange; } get activeEditor(): IEditorInput | undefined { return this.editorService.activeEditor; } - get activeControl(): IVisibleEditor | undefined { return this.editorService.activeControl; } - get activeTextEditorWidget(): ICodeEditor | IDiffEditor | undefined { return this.editorService.activeTextEditorWidget; } + get activeEditorPane(): IVisibleEditorPane | undefined { return this.editorService.activeEditorPane; } + get activeTextEditorControl(): ICodeEditor | IDiffEditor | undefined { return this.editorService.activeTextEditorControl; } get activeTextEditorMode(): string | undefined { return this.editorService.activeTextEditorMode; } get visibleEditors(): ReadonlyArray { return this.editorService.visibleEditors; } - get visibleControls(): ReadonlyArray { return this.editorService.visibleControls; } - get visibleTextEditorWidgets(): ReadonlyArray { return this.editorService.visibleTextEditorWidgets; } + get visibleEditorPanes(): ReadonlyArray { return this.editorService.visibleEditorPanes; } + get visibleTextEditorControls(): ReadonlyArray { return this.editorService.visibleTextEditorControls; } get editors(): ReadonlyArray { return this.editorService.editors; } get count(): number { return this.editorService.count; } getEditors(order: EditorsOrder): ReadonlyArray { return this.editorService.getEditors(order); } - openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise; - openEditors(editors: IResourceEditor[], group?: OpenInEditorGroup): Promise; - openEditors(editors: Array, group?: OpenInEditorGroup): Promise { + openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise; + openEditors(editors: IResourceEditorInputType[], group?: OpenInEditorGroup): Promise; + openEditors(editors: Array, group?: OpenInEditorGroup): Promise { return this.editorService.openEditors(editors, group); } @@ -1151,19 +1171,21 @@ export class DelegatingEditorService implements IEditorService { return this.editorService.replaceEditors(editors as IResourceEditorReplacement[] /* TS fail */, group); } - isOpen(editor: IEditorInput): boolean { return this.editorService.isOpen(editor); } + isOpen(editor: IEditorInput): boolean; + isOpen(editor: IResourceEditorInput): boolean; + isOpen(editor: IEditorInput | IResourceEditorInput): boolean { return this.editorService.isOpen(editor as IResourceEditorInput /* TS fail */); } overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable { return this.editorService.overrideOpenEditor(handler); } invokeWithinEditorContext(fn: (accessor: ServicesAccessor) => T): T { return this.editorService.invokeWithinEditorContext(fn); } - createInput(input: IResourceEditor): IEditorInput { return this.editorService.createInput(input); } + createEditorInput(input: IResourceEditorInputType): IEditorInput { return this.editorService.createEditorInput(input); } save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { return this.editorService.save(editors, options); } saveAll(options?: ISaveAllEditorsOptions): Promise { return this.editorService.saveAll(options); } - revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise { return this.editorService.revert(editors, options); } - revertAll(options?: IRevertAllEditorsOptions): Promise { return this.editorService.revertAll(options); } + revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise { return this.editorService.revert(editors, options); } + revertAll(options?: IRevertAllEditorsOptions): Promise { return this.editorService.revertAll(options); } //#endregion } diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 73ec90ff40..a0e0e6129f 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -5,10 +5,9 @@ import { Event } from 'vs/base/common/event'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditorPane, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane } from 'vs/workbench/common/editor'; import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; import { IDimension } from 'vs/editor/common/editorCommon'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -403,13 +402,13 @@ export interface IEditorGroup { readonly ariaLabel: string; /** - * The active control is the currently visible control of the group. + * The active editor pane is the currently visible editor pane of the group. */ - readonly activeControl: IVisibleEditor | undefined; + readonly activeEditorPane: IVisibleEditorPane | undefined; /** * The active editor is the currently visible editor of the group - * within the current active control. + * within the current active editor pane. */ readonly activeEditor: IEditorInput | null; @@ -452,7 +451,7 @@ export interface IEditorGroup { * @returns a promise that resolves around an IEditor instance unless * the call failed, or the editor was not opened as active editor. */ - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions): Promise; + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions): Promise; /** * Opens editors in this group. @@ -462,7 +461,7 @@ export interface IEditorGroup { * a group can only ever have one active editor, even if many editors are * opened, the result will only be one editor. */ - openEditors(editors: IEditorInputWithOptions[]): Promise; + openEditors(editors: IEditorInputWithOptions[]): Promise; /** * Find out if the provided editor is opened in the group. diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 884d409594..f8341a1b01 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -4,20 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IResourceInput, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, IEditorIdentifier, ISaveOptions, IRevertOptions, EditorsOrder } from 'vs/workbench/common/editor'; +import { IResourceEditorInput, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorInput, IEditorPane, GroupIdentifier, IEditorInputWithOptions, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, ITextEditorPane, ITextDiffEditorPane, IEditorIdentifier, ISaveOptions, IRevertOptions, EditorsOrder, IVisibleEditorPane } from 'vs/workbench/common/editor'; import { Event } from 'vs/base/common/event'; -import { IEditor as ICodeEditor, IDiffEditor } from 'vs/editor/common/editorCommon'; +import { IEditor, IDiffEditor } from 'vs/editor/common/editorCommon'; import { IEditorGroup, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; export const IEditorService = createDecorator('editorService'); -export type IResourceEditor = IResourceInput | IUntitledTextResourceInput | IResourceDiffInput | IResourceSideBySideInput; +export type IResourceEditorInputType = IResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput; export interface IResourceEditorReplacement { - editor: IResourceEditor; - replacement: IResourceEditor; + readonly editor: IResourceEditorInputType; + readonly replacement: IResourceEditorInputType; } export const ACTIVE_GROUP = -1; @@ -36,12 +36,7 @@ export interface IOpenEditorOverride { * If defined, will prevent the opening of an editor and replace the resulting * promise with the provided promise for the openEditor() call. */ - override?: Promise; -} - -export interface IVisibleEditor extends IEditor { - input: IEditorInput; - group: IEditorGroup; + override?: Promise; } export interface ISaveEditorsOptions extends ISaveOptions { @@ -49,7 +44,7 @@ export interface ISaveEditorsOptions extends ISaveOptions { /** * If true, will ask for a location of the editor to save to. */ - saveAs?: boolean; + readonly saveAs?: boolean; } export interface IBaseSaveRevertAllEditorOptions { @@ -57,7 +52,7 @@ export interface IBaseSaveRevertAllEditorOptions { /** * Whether to include untitled editors as well. */ - includeUntitled?: boolean; + readonly includeUntitled?: boolean; } export interface ISaveAllEditorsOptions extends ISaveEditorsOptions, IBaseSaveRevertAllEditorOptions { } @@ -71,17 +66,25 @@ export interface IEditorService { /** * Emitted when the currently active editor changes. * - * @see `IEditorService.activeEditor` + * @see `IEditorService.activeEditorPane` */ readonly onDidActiveEditorChange: Event; /** * Emitted when any of the current visible editors changes. * - * @see `IEditorService.visibleEditors` + * @see `IEditorService.visibleEditorPanes` */ readonly onDidVisibleEditorsChange: Event; + /** + * The currently active editor pane or `undefined` if none. The editor pane is + * the workbench container for editors of any kind. + * + * @see `IEditorService.activeEditor` for access to the active editor input + */ + readonly activeEditorPane: IVisibleEditorPane | undefined; + /** * The currently active editor or `undefined` if none. An editor is active when it is * located in the currently active editor group. It will be `undefined` if the active @@ -90,44 +93,38 @@ export interface IEditorService { readonly activeEditor: IEditorInput | undefined; /** - * The currently active editor control or `undefined` if none. The editor control is - * the workbench container for editors of any kind. - * - * @see `IEditorService.activeEditor` - */ - readonly activeControl: IVisibleEditor | undefined; - - /** - * The currently active text editor widget or `undefined` if there is currently no active + * The currently active text editor control or `undefined` if there is currently no active * editor or the active editor widget is neither a text nor a diff editor. * * @see `IEditorService.activeEditor` */ - readonly activeTextEditorWidget: ICodeEditor | IDiffEditor | undefined; + readonly activeTextEditorControl: IEditor | IDiffEditor | undefined; /** * The currently active text editor mode or `undefined` if there is currently no active - * editor or the active editor widget is neither a text nor a diff editor. If the active + * editor or the active editor control is neither a text nor a diff editor. If the active * editor is a diff editor, the modified side's mode will be taken. */ readonly activeTextEditorMode: string | undefined; + /** + * All editor panes that are currently visible across all editor groups. + * + * @see `IEditorService.visibleEditors` for access to the visible editor inputs + */ + readonly visibleEditorPanes: ReadonlyArray; + /** * All editors that are currently visible. An editor is visible when it is opened in an * editor group and active in that group. Multiple editor groups can be opened at the same time. */ readonly visibleEditors: ReadonlyArray; - /** - * All editor controls that are currently visible across all editor groups. - */ - readonly visibleControls: ReadonlyArray; - /** * All text editor widgets that are currently visible across all editor groups. A text editor * widget is either a text or a diff editor. */ - readonly visibleTextEditorWidgets: ReadonlyArray; + readonly visibleTextEditorControls: ReadonlyArray; /** * All editors that are opened across all editor groups in sequential order @@ -162,10 +159,9 @@ export interface IEditorService { * @returns the editor that opened or `undefined` if the operation failed or the editor was not * opened to be active. */ - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceDiffEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; /** * Open editors in an editor group. @@ -178,8 +174,8 @@ export interface IEditorService { * @returns the editors that opened. The array can be empty or have less elements for editors * that failed to open or were instructed to open as inactive. */ - openEditors(editors: IEditorInputWithOptions[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise>; - openEditors(editors: IResourceEditor[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise>; + openEditors(editors: IEditorInputWithOptions[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise>; + openEditors(editors: IResourceEditorInputType[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise>; /** * Replaces editors in an editor group with the provided replacement. @@ -196,7 +192,13 @@ export interface IEditorService { * Find out if the provided editor is opened in any editor group. * * Note: An editor can be opened but not actively visible. + * + * @param editor the editor to check for being opened. If a + * `IResourceEditorInput` is passed in, the resource is checked on + * all opened editors. In case of a side by side editor, the + * right hand side resource is considered only. */ + isOpen(editor: IResourceEditorInput): boolean; isOpen(editor: IEditorInput): boolean; /** @@ -214,25 +216,29 @@ export interface IEditorService { /** * Converts a lightweight input to a workbench editor input. */ - createInput(input: IResourceEditor): IEditorInput; + createEditorInput(input: IResourceEditorInputType): IEditorInput; /** * Save the provided list of editors. + * + * @returns `true` if all editors saved and `false` otherwise. */ save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise; /** * Save all editors. + * + * @returns `true` if all editors saved and `false` otherwise. */ saveAll(options?: ISaveAllEditorsOptions): Promise; /** * Reverts the provided list of editors. */ - revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise; + revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise; /** * Reverts all editors. */ - revertAll(options?: IRevertAllEditorsOptions): Promise; + revertAll(options?: IRevertAllEditorsOptions): Promise; } diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index 65aa59d685..6cfec270f6 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; -import { workbenchInstantiationService, registerTestEditor, TestFileEditorInput } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, registerTestEditor, TestFileEditorInput, TestEditorPart, ITestInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupChangeKind, GroupLocation } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EditorOptions, CloseDirection, IEditorPartOptions, EditorsOrder } from 'vs/workbench/common/editor'; @@ -29,18 +28,16 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite disposables = []; }); - function createPart(): EditorPart { - const instantiationService = workbenchInstantiationService(); - - const part = instantiationService.createInstance(EditorPart); + function createPart(instantiationService = workbenchInstantiationService()): [TestEditorPart, ITestInstantiationService] { + const part = instantiationService.createInstance(TestEditorPart); part.create(document.createElement('div')); part.layout(400, 300); - return part; + return [part, instantiationService]; } test('groups basics', async function () { - const part = createPart(); + const [part] = createPart(); let activeGroupChangeCounter = 0; const activeGroupChangeListener = part.onDidActiveGroupChange(() => { @@ -126,7 +123,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite assert.equal(groupAddedCounter, 2); assert.equal(part.groups.length, 3); assert.ok(part.activeGroup === rightGroup); - assert.ok(!downGroup.activeControl); + assert.ok(!downGroup.activeEditorPane); assert.equal(rootGroup.label, 'Group 1'); assert.equal(rightGroup.label, 'Group 2'); assert.equal(downGroup.label, 'Group 3'); @@ -201,8 +198,37 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite part.dispose(); }); + test('save & restore state', async function () { + let [part, instantiationService] = createPart(); + + const rootGroup = part.groups[0]; + const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); + const downGroup = part.addGroup(rightGroup, GroupDirection.DOWN); + + const rootGroupInput = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + await rootGroup.openEditor(rootGroupInput, EditorOptions.create({ pinned: true })); + + const rightGroupInput = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + await rightGroup.openEditor(rightGroupInput, EditorOptions.create({ pinned: true })); + + assert.equal(part.groups.length, 3); + + part.saveState(); + part.dispose(); + + let [restoredPart] = createPart(instantiationService); + + assert.equal(restoredPart.groups.length, 3); + assert.ok(restoredPart.getGroup(rootGroup.id)); + assert.ok(restoredPart.getGroup(rightGroup.id)); + assert.ok(restoredPart.getGroup(downGroup.id)); + + restoredPart.clearState(); + restoredPart.dispose(); + }); + test('groups index / labels', function () { - const part = createPart(); + const [part] = createPart(); const rootGroup = part.groups[0]; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); @@ -260,7 +286,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('copy/merge groups', async () => { - const part = createPart(); + const [part] = createPart(); let groupAddedCounter = 0; const groupAddedListener = part.onDidAddGroup(() => { @@ -301,7 +327,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('whenRestored', async () => { - const part = createPart(); + const [part] = createPart(); await part.whenRestored; assert.ok(true); @@ -309,7 +335,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('options', () => { - const part = createPart(); + const [part] = createPart(); let oldOptions!: IEditorPartOptions; let newOptions!: IEditorPartOptions; @@ -330,7 +356,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('editor basics', async function () { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); @@ -399,7 +425,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite assert.ok(!group.previewEditor); assert.equal(group.activeEditor, input); - assert.equal(group.activeControl?.getId(), TEST_EDITOR_ID); + assert.equal(group.activeEditorPane?.getId(), TEST_EDITOR_ID); assert.equal(group.count, 2); const mru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); @@ -428,7 +454,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('openEditors / closeEditors', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); @@ -446,7 +472,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('closeEditors (except one)', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); @@ -467,7 +493,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('closeEditors (saved only)', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); @@ -487,7 +513,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('closeEditors (direction: right)', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); @@ -509,7 +535,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('closeEditors (direction: left)', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); @@ -531,7 +557,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('closeAllEditors', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); @@ -549,7 +575,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('moveEditor (same group)', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); @@ -577,7 +603,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('moveEditor (across groups)', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); @@ -599,7 +625,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('copyEditor (across groups)', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); @@ -622,7 +648,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('replaceEditors', async () => { - const part = createPart(); + const [part] = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty, true); @@ -640,7 +666,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('find neighbour group (left/right)', function () { - const part = createPart(); + const [part] = createPart(); const rootGroup = part.activeGroup; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); @@ -651,7 +677,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('find neighbour group (up/down)', function () { - const part = createPart(); + const [part] = createPart(); const rootGroup = part.activeGroup; const downGroup = part.addGroup(rootGroup, GroupDirection.DOWN); @@ -662,7 +688,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite }); test('find group by location (left/right)', function () { - const part = createPart(); + const [part] = createPart(); const rootGroup = part.activeGroup; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); const downGroup = part.addGroup(rightGroup, GroupDirection.DOWN); @@ -678,4 +704,24 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite part.dispose(); }); + + test('applyLayout (2x2)', function () { + const [part] = createPart(); + + part.applyLayout({ groups: [{ groups: [{}, {}] }, { groups: [{}, {}] }], orientation: GroupOrientation.HORIZONTAL }); + + assert.equal(part.groups.length, 4); + + part.dispose(); + }); + + test('centeredLayout', function () { + const [part] = createPart(); + + part.centerLayout(true); + + assert.equal(part.isLayoutCentered(), true); + + part.dispose(); + }); }); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 7b906172b4..5854031c02 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -8,8 +8,8 @@ import { EditorActivation } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { EditorInput, EditorsOrder } from 'vs/workbench/common/editor'; -import { workbenchInstantiationService, TestStorageService, TestServiceAccessor, registerTestEditor, TestFileEditorInput } from 'vs/workbench/test/browser/workbenchTestServices'; +import { EditorInput, EditorsOrder, SideBySideEditorInput } from 'vs/workbench/common/editor'; +import { workbenchInstantiationService, TestServiceAccessor, registerTestEditor, TestFileEditorInput } from 'vs/workbench/test/browser/workbenchTestServices'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { EditorService, DelegatingEditorService } from 'vs/workbench/services/editor/browser/editorService'; @@ -26,6 +26,8 @@ import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { NullFileSystemProvider } from 'vs/platform/files/test/common/nullFileSystemProvider'; +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; const TEST_EDITOR_ID = 'MyTestEditorForEditorService'; const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorService'; @@ -93,17 +95,18 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite let editor = await service.openEditor(input, { pinned: true }); assert.equal(editor?.getId(), TEST_EDITOR_ID); - assert.equal(editor, service.activeControl); + assert.equal(editor, service.activeEditorPane); assert.equal(1, service.count); assert.equal(input, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].editor); assert.equal(input, service.getEditors(EditorsOrder.SEQUENTIAL)[0].editor); assert.equal(input, service.activeEditor); - assert.equal(service.visibleControls.length, 1); - assert.equal(service.visibleControls[0], editor); - assert.ok(!service.activeTextEditorWidget); + assert.equal(service.visibleEditorPanes.length, 1); + assert.equal(service.visibleEditorPanes[0], editor); + assert.ok(!service.activeTextEditorControl); assert.ok(!service.activeTextEditorMode); - assert.equal(service.visibleTextEditorWidgets.length, 0); + assert.equal(service.visibleTextEditorControls.length, 0); assert.equal(service.isOpen(input), true); + assert.equal(service.isOpen({ resource: input.resource }), true); assert.equal(activeEditorChangeEventCounter, 1); assert.equal(visibleEditorChangeEventCounter, 1); @@ -134,9 +137,11 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite assert.equal(input, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[1].editor); assert.equal(input, service.getEditors(EditorsOrder.SEQUENTIAL)[0].editor); assert.equal(otherInput, service.getEditors(EditorsOrder.SEQUENTIAL)[1].editor); - assert.equal(service.visibleControls.length, 1); + assert.equal(service.visibleEditorPanes.length, 1); assert.equal(service.isOpen(input), true); + assert.equal(service.isOpen({ resource: input.resource }), true); assert.equal(service.isOpen(otherInput), true); + assert.equal(service.isOpen({ resource: otherInput.resource }), true); assert.equal(activeEditorChangeEventCounter, 4); assert.equal(visibleEditorChangeEventCounter, 4); @@ -148,6 +153,53 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite part.dispose(); }); + test('isOpen() with side by side editor', async () => { + const [part, service] = createEditorService(); + + const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); + const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); + const sideBySideInput = new SideBySideEditorInput('sideBySide', '', input, otherInput); + + await part.whenRestored; + + const editor1 = await service.openEditor(sideBySideInput, { pinned: true }); + assert.equal(part.activeGroup.count, 1); + + assert.equal(service.isOpen(input), false); + assert.equal(service.isOpen(otherInput), false); + assert.equal(service.isOpen(sideBySideInput), true); + assert.equal(service.isOpen({ resource: input.resource }), false); + assert.equal(service.isOpen({ resource: otherInput.resource }), true); + + const editor2 = await service.openEditor(input, { pinned: true }); + assert.equal(part.activeGroup.count, 2); + + assert.equal(service.isOpen(input), true); + assert.equal(service.isOpen(otherInput), false); + assert.equal(service.isOpen(sideBySideInput), true); + assert.equal(service.isOpen({ resource: input.resource }), true); + assert.equal(service.isOpen({ resource: otherInput.resource }), true); + + await editor2?.group?.closeEditor(input); + assert.equal(part.activeGroup.count, 1); + + assert.equal(service.isOpen(input), false); + assert.equal(service.isOpen(otherInput), false); + assert.equal(service.isOpen(sideBySideInput), true); + assert.equal(service.isOpen({ resource: input.resource }), false); + assert.equal(service.isOpen({ resource: otherInput.resource }), true); + + await editor1?.group?.closeEditor(sideBySideInput); + + assert.equal(service.isOpen(input), false); + assert.equal(service.isOpen(otherInput), false); + assert.equal(service.isOpen(sideBySideInput), false); + assert.equal(service.isOpen({ resource: input.resource }), false); + assert.equal(service.isOpen({ resource: otherInput.resource }), false); + + part.dispose(); + }); + test('openEditors() / replaceEditors()', async () => { const [part, service] = createEditorService(); @@ -175,50 +227,50 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite // Cached Input (Files) const fileResource1 = toResource.call(this, '/foo/bar/cache1.js'); - const fileInput1 = service.createInput({ resource: fileResource1 }); - assert.ok(fileInput1); + const fileEditorInput1 = service.createEditorInput({ resource: fileResource1 }); + assert.ok(fileEditorInput1); const fileResource2 = toResource.call(this, '/foo/bar/cache2.js'); - const fileInput2 = service.createInput({ resource: fileResource2 }); - assert.ok(fileInput2); + const fileEditorInput2 = service.createEditorInput({ resource: fileResource2 }); + assert.ok(fileEditorInput2); - assert.notEqual(fileInput1, fileInput2); + assert.notEqual(fileEditorInput1, fileEditorInput2); - const fileInput1Again = service.createInput({ resource: fileResource1 }); - assert.equal(fileInput1Again, fileInput1); + const fileEditorInput1Again = service.createEditorInput({ resource: fileResource1 }); + assert.equal(fileEditorInput1Again, fileEditorInput1); - fileInput1Again!.dispose(); + fileEditorInput1Again!.dispose(); - assert.ok(fileInput1!.isDisposed()); + assert.ok(fileEditorInput1!.isDisposed()); - const fileInput1AgainAndAgain = service.createInput({ resource: fileResource1 }); - assert.notEqual(fileInput1AgainAndAgain, fileInput1); - assert.ok(!fileInput1AgainAndAgain!.isDisposed()); + const fileEditorInput1AgainAndAgain = service.createEditorInput({ resource: fileResource1 }); + assert.notEqual(fileEditorInput1AgainAndAgain, fileEditorInput1); + assert.ok(!fileEditorInput1AgainAndAgain!.isDisposed()); // Cached Input (Resource) const resource1 = URI.from({ scheme: 'custom', path: '/foo/bar/cache1.js' }); - const input1 = service.createInput({ resource: resource1 }); + const input1 = service.createEditorInput({ resource: resource1 }); assert.ok(input1); const resource2 = URI.from({ scheme: 'custom', path: '/foo/bar/cache2.js' }); - const input2 = service.createInput({ resource: resource2 }); + const input2 = service.createEditorInput({ resource: resource2 }); assert.ok(input2); assert.notEqual(input1, input2); - const input1Again = service.createInput({ resource: resource1 }); + const input1Again = service.createEditorInput({ resource: resource1 }); assert.equal(input1Again, input1); input1Again!.dispose(); assert.ok(input1!.isDisposed()); - const input1AgainAndAgain = service.createInput({ resource: resource1 }); + const input1AgainAndAgain = service.createEditorInput({ resource: resource1 }); assert.notEqual(input1AgainAndAgain, input1); assert.ok(!input1AgainAndAgain!.isDisposed()); }); - test('createInput', async function () { + test('createEditorInput', async function () { const instantiationService = workbenchInstantiationService(); const service = instantiationService.createInstance(EditorService); @@ -228,67 +280,78 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite }); // Untyped Input (file) - let input = service.createInput({ resource: toResource.call(this, '/index.html'), options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + let input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof FileEditorInput); let contentInput = input; assert.strictEqual(contentInput.resource.fsPath, toResource.call(this, '/index.html').fsPath); + // Typed Input + assert.equal(service.createEditorInput(input), input); + assert.equal(service.createEditorInput({ editor: input }), input); + // Untyped Input (file, encoding) - input = service.createInput({ resource: toResource.call(this, '/index.html'), encoding: 'utf16le', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), encoding: 'utf16le', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof FileEditorInput); contentInput = input; assert.equal(contentInput.getPreferredEncoding(), 'utf16le'); // Untyped Input (file, mode) - input = service.createInput({ resource: toResource.call(this, '/index.html'), mode }); + input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), mode }); assert(input instanceof FileEditorInput); contentInput = input; assert.equal(contentInput.getPreferredMode(), mode); // Untyped Input (file, different mode) - input = service.createInput({ resource: toResource.call(this, '/index.html'), mode: 'text' }); + input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), mode: 'text' }); assert(input instanceof FileEditorInput); contentInput = input; assert.equal(contentInput.getPreferredMode(), 'text'); // Untyped Input (untitled) - input = service.createInput({ options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createEditorInput({ options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof UntitledTextEditorInput); // Untyped Input (untitled with contents) - input = service.createInput({ contents: 'Hello Untitled', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createEditorInput({ contents: 'Hello Untitled', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof UntitledTextEditorInput); let model = await input.resolve() as UntitledTextEditorModel; assert.equal(model.textEditorModel!.getValue(), 'Hello Untitled'); // Untyped Input (untitled with mode) - input = service.createInput({ mode, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createEditorInput({ mode, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof UntitledTextEditorInput); model = await input.resolve() as UntitledTextEditorModel; assert.equal(model.getMode(), mode); // Untyped Input (untitled with file path) - input = service.createInput({ resource: URI.file('/some/path.txt'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createEditorInput({ resource: URI.file('/some/path.txt'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof UntitledTextEditorInput); assert.ok((input as UntitledTextEditorInput).model.hasAssociatedFilePath); // Untyped Input (untitled with untitled resource) - input = service.createInput({ resource: URI.parse('untitled://Untitled-1'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createEditorInput({ resource: URI.parse('untitled://Untitled-1'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof UntitledTextEditorInput); assert.ok(!(input as UntitledTextEditorInput).model.hasAssociatedFilePath); // Untyped Input (untitled with custom resource) const provider = instantiationService.createInstance(FileServiceProvider, 'untitled-custom'); - input = service.createInput({ resource: URI.parse('untitled-custom://some/path'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createEditorInput({ resource: URI.parse('untitled-custom://some/path'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof UntitledTextEditorInput); assert.ok((input as UntitledTextEditorInput).model.hasAssociatedFilePath); provider.dispose(); // Untyped Input (resource) - input = service.createInput({ resource: URI.parse('custom:resource') }); + input = service.createEditorInput({ resource: URI.parse('custom:resource') }); assert(input instanceof ResourceEditorInput); + + // Untyped Input (diff) + input = service.createEditorInput({ + leftResource: toResource.call(this, '/master.html'), + rightResource: toResource.call(this, '/detail.html') + }); + assert(input instanceof DiffEditorInput); }); test('delegate', function (done) { @@ -306,7 +369,7 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite layout(): void { } - createEditor(): any { } + createEditor(): void { } } const ed = instantiationService.createInstance(MyEditor, 'my.editor'); @@ -670,7 +733,7 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite part.dispose(); }); - test('activeTextEditorWidget / activeTextEditorMode', async () => { + test('activeTextEditorControl / activeTextEditorMode', async () => { const [part, service] = createEditorService(); await part.whenRestored; @@ -678,8 +741,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite // Open untitled input let editor = await service.openEditor({}); - assert.equal(service.activeControl, editor); - assert.equal(service.activeTextEditorWidget, editor?.getControl()); + assert.equal(service.activeEditorPane, editor); + assert.equal(service.activeTextEditorControl, editor?.getControl()); assert.equal(service.activeTextEditorMode, 'plaintext'); part.dispose(); @@ -710,10 +773,12 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite test('save, saveAll, revertAll', async function () { const [part, service] = createEditorService(); - const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); + const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); input1.dirty = true; - const input2 = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); input2.dirty = true; + const sameInput1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); + sameInput1.dirty = true; const rootGroup = part.activeGroup; @@ -721,24 +786,50 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite await service.openEditor(input1, { pinned: true }); await service.openEditor(input2, { pinned: true }); + await service.openEditor(sameInput1, { pinned: true }, SIDE_GROUP); await service.save({ groupId: rootGroup.id, editor: input1 }); assert.equal(input1.gotSaved, true); + input1.gotSaved = false; + input1.gotSavedAs = false; + input1.gotReverted = false; + await service.save({ groupId: rootGroup.id, editor: input1 }, { saveAs: true }); assert.equal(input1.gotSavedAs, true); + input1.gotSaved = false; + input1.gotSavedAs = false; + input1.gotReverted = false; + await service.revertAll(); assert.equal(input1.gotReverted, true); + input1.gotSaved = false; + input1.gotSavedAs = false; + input1.gotReverted = false; + await service.saveAll(); assert.equal(input1.gotSaved, true); assert.equal(input2.gotSaved, true); + input1.gotSaved = false; + input1.gotSavedAs = false; + input1.gotReverted = false; + input2.gotSaved = false; + input2.gotSavedAs = false; + input2.gotReverted = false; + await service.saveAll({ saveAs: true }); + assert.equal(input1.gotSavedAs, true); assert.equal(input2.gotSavedAs, true); + // services dedupes inputs automatically + assert.equal(sameInput1.gotSaved, false); + assert.equal(sameInput1.gotSavedAs, false); + assert.equal(sameInput1.gotReverted, false); + part.dispose(); }); @@ -753,9 +844,9 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite async function testFileDeleteEditorClose(dirty: boolean): Promise { const [part, service, accessor] = createEditorService(); - const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); + const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); input1.dirty = dirty; - const input2 = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); input2.dirty = dirty; const rootGroup = part.activeGroup; @@ -785,8 +876,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite test('file move asks input to move', async function () { const [part, service, accessor] = createEditorService(); - const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); - const movedInput = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); + const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); + const movedInput = new TestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); input1.movedEditor = { editor: movedInput }; const rootGroup = part.activeGroup; @@ -803,7 +894,7 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite isDirectory: false, isFile: true, mtime: 0, - name: 'resource2-openside', + name: 'resource2', size: 0, isSymbolicLink: false })); @@ -823,8 +914,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite test('file watcher gets installed for out of workspace files', async function () { const [part, service, accessor] = createEditorService(); - const input1 = new TestFileEditorInput(URI.parse('file://resource1-openside'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.parse('file://resource2-openside'), TEST_EDITOR_INPUT_ID); + const input1 = new TestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID); await part.whenRestored; @@ -841,4 +932,53 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite part.dispose(); }); + + test('invokeWithinEditorContext', async function () { + const [part, service] = createEditorService(); + + const input1 = new TestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID); + new TestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID); + + await part.whenRestored; + + await service.openEditor(input1, { pinned: true }); + + let hasAccessor = false; + service.invokeWithinEditorContext(accessor => { + hasAccessor = true; + }); + + assert.ok(hasAccessor); + + part.dispose(); + }); + + test('overrideOpenEditor', async function () { + const [part, service] = createEditorService(); + + const input1 = new TestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID); + + await part.whenRestored; + + let overrideCalled = false; + + const handler = service.overrideOpenEditor(editor => { + if (editor === input1) { + overrideCalled = true; + + return { override: service.openEditor(input2, { pinned: true }) }; + } + + return undefined; + }); + + await service.openEditor(input1, { pinned: true }); + + assert.ok(overrideCalled); + assert.equal(service.activeEditor, input2); + + handler.dispose(); + part.dispose(); + }); }); diff --git a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts index 742a962880..10bca9f3e9 100644 --- a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { EditorOptions, IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { workbenchInstantiationService, TestStorageService, TestFileEditorInput, registerTestEditor } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestFileEditorInput, registerTestEditor, TestEditorPart } from 'vs/workbench/test/browser/workbenchTestServices'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -16,6 +16,7 @@ import { WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver'; import { timeout } from 'vs/base/common/async'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; const TEST_EDITOR_ID = 'MyTestEditorForEditorsObserver'; const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorsObserver'; @@ -34,11 +35,11 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin disposables = []; }); - async function createPart(): Promise { + async function createPart(): Promise { const instantiationService = workbenchInstantiationService(); instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - const part = instantiationService.createInstance(EditorPart); + const part = instantiationService.createInstance(TestEditorPart); part.create(document.createElement('div')); part.layout(400, 300); @@ -58,14 +59,14 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin test('basics (single group)', async () => { const [part, observer] = await createEditorObserver(); - let observerChangeListenerCalled = false; - const listener = observer.onDidChange(() => { - observerChangeListenerCalled = true; + let onDidMostRecentlyActiveEditorsChangeCalled = false; + const listener = observer.onDidMostRecentlyActiveEditorsChange(() => { + onDidMostRecentlyActiveEditorsChangeCalled = true; }); let currentEditorsMRU = observer.editors; assert.equal(currentEditorsMRU.length, 0); - assert.equal(observerChangeListenerCalled, false); + assert.equal(onDidMostRecentlyActiveEditorsChangeCalled, false); const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); @@ -75,7 +76,8 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU.length, 1); assert.equal(currentEditorsMRU[0].groupId, part.activeGroup.id); assert.equal(currentEditorsMRU[0].editor, input1); - assert.equal(observerChangeListenerCalled, true); + assert.equal(onDidMostRecentlyActiveEditorsChangeCalled, true); + assert.equal(observer.hasEditor(input1.resource), true); const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); @@ -91,6 +93,8 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU[1].editor, input2); assert.equal(currentEditorsMRU[2].groupId, part.activeGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); @@ -102,8 +106,11 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU[1].editor, input3); assert.equal(currentEditorsMRU[2].groupId, part.activeGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); - observerChangeListenerCalled = false; + onDidMostRecentlyActiveEditorsChangeCalled = false; await part.activeGroup.closeEditor(input1); currentEditorsMRU = observer.editors; @@ -112,11 +119,17 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU[0].editor, input2); assert.equal(currentEditorsMRU[1].groupId, part.activeGroup.id); assert.equal(currentEditorsMRU[1].editor, input3); - assert.equal(observerChangeListenerCalled, true); + assert.equal(onDidMostRecentlyActiveEditorsChangeCalled, true); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); await part.activeGroup.closeAllEditors(); currentEditorsMRU = observer.editors; assert.equal(currentEditorsMRU.length, 0); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), false); + assert.equal(observer.hasEditor(input3.resource), false); part.dispose(); listener.dispose(); @@ -143,6 +156,7 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU[0].editor, input1); assert.equal(currentEditorsMRU[1].groupId, rootGroup.id); assert.equal(currentEditorsMRU[1].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); @@ -152,6 +166,7 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU[0].editor, input1); assert.equal(currentEditorsMRU[1].groupId, sideGroup.id); assert.equal(currentEditorsMRU[1].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); // Opening an editor inactive should not change // the most recent editor, but rather put it behind @@ -167,6 +182,8 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU[1].editor, input2); assert.equal(currentEditorsMRU[2].groupId, sideGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); await rootGroup.closeAllEditors(); @@ -174,11 +191,15 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU.length, 1); assert.equal(currentEditorsMRU[0].groupId, sideGroup.id); assert.equal(currentEditorsMRU[0].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), false); await sideGroup.closeAllEditors(); currentEditorsMRU = observer.editors; assert.equal(currentEditorsMRU.length, 0); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), false); part.dispose(); }); @@ -204,6 +225,9 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU[1].editor, input2); assert.equal(currentEditorsMRU[2].groupId, rootGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); const copiedGroup = part.copyGroup(rootGroup, rootGroup, GroupDirection.RIGHT); copiedGroup.setActive(true); @@ -222,6 +246,21 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU[4].editor, input2); assert.equal(currentEditorsMRU[5].groupId, rootGroup.id); assert.equal(currentEditorsMRU[5].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); + + await rootGroup.closeAllEditors(); + + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); + + await copiedGroup.closeAllEditors(); + + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), false); + assert.equal(observer.hasEditor(input3.resource), false); part.dispose(); }); @@ -251,6 +290,9 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU[1].editor, input2); assert.equal(currentEditorsMRU[2].groupId, rootGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); @@ -265,7 +307,11 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU[1].editor, input2); assert.equal(currentEditorsMRU[2].groupId, rootGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); + part.clearState(); part.dispose(); }); @@ -296,6 +342,9 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU[1].editor, input2); assert.equal(currentEditorsMRU[2].groupId, rootGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); @@ -310,7 +359,11 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU[1].editor, input2); assert.equal(currentEditorsMRU[2].groupId, rootGroup.id); assert.equal(currentEditorsMRU[2].editor, input1); + assert.equal(restoredObserver.hasEditor(input1.resource), true); + assert.equal(restoredObserver.hasEditor(input2.resource), true); + assert.equal(restoredObserver.hasEditor(input3.resource), true); + part.clearState(); part.dispose(); }); @@ -331,6 +384,7 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU.length, 1); assert.equal(currentEditorsMRU[0].groupId, rootGroup.id); assert.equal(currentEditorsMRU[0].editor, input1); + assert.equal(observer.hasEditor(input1.resource), true); storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); @@ -339,7 +393,9 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin currentEditorsMRU = restoredObserver.editors; assert.equal(currentEditorsMRU.length, 0); + assert.equal(restoredObserver.hasEditor(input1.resource), false); + part.clearState(); part.dispose(); }); @@ -368,6 +424,10 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(rootGroup.isOpened(input2), true); assert.equal(rootGroup.isOpened(input3), true); assert.equal(rootGroup.isOpened(input4), true); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); + assert.equal(observer.hasEditor(input4.resource), true); input2.setDirty(); part.enforcePartOptions({ limit: { enabled: true, value: 1 } }); @@ -379,6 +439,10 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(rootGroup.isOpened(input2), true); // dirty assert.equal(rootGroup.isOpened(input3), false); assert.equal(rootGroup.isOpened(input4), true); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), false); + assert.equal(observer.hasEditor(input4.resource), true); const input5 = new TestFileEditorInput(URI.parse('foo://bar5'), TEST_EDITOR_INPUT_ID); await sideGroup.openEditor(input5, EditorOptions.create({ pinned: true })); @@ -388,8 +452,12 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(rootGroup.isOpened(input2), true); // dirty assert.equal(rootGroup.isOpened(input3), false); assert.equal(rootGroup.isOpened(input4), false); - assert.equal(sideGroup.isOpened(input5), true); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), false); + assert.equal(observer.hasEditor(input4.resource), false); + assert.equal(observer.hasEditor(input5.resource), true); observer.dispose(); part.dispose(); @@ -415,11 +483,15 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true })); - assert.equal(rootGroup.count, 3); + assert.equal(rootGroup.count, 3); // 1 editor got closed due to our limit! assert.equal(rootGroup.isOpened(input1), false); assert.equal(rootGroup.isOpened(input2), true); assert.equal(rootGroup.isOpened(input3), true); assert.equal(rootGroup.isOpened(input4), true); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); + assert.equal(observer.hasEditor(input4.resource), true); await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true })); await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true })); @@ -431,6 +503,10 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(sideGroup.isOpened(input2), true); assert.equal(sideGroup.isOpened(input3), true); assert.equal(sideGroup.isOpened(input4), true); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), true); + assert.equal(observer.hasEditor(input3.resource), true); + assert.equal(observer.hasEditor(input4.resource), true); part.enforcePartOptions({ limit: { enabled: true, value: 1, perEditorGroup: true } }); @@ -448,6 +524,11 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(sideGroup.isOpened(input3), false); assert.equal(sideGroup.isOpened(input4), true); + assert.equal(observer.hasEditor(input1.resource), false); + assert.equal(observer.hasEditor(input2.resource), false); + assert.equal(observer.hasEditor(input3.resource), false); + assert.equal(observer.hasEditor(input4.resource), true); + observer.dispose(); part.dispose(); }); diff --git a/src/vs/workbench/services/electron/electron-browser/electronEnvironmentService.ts b/src/vs/workbench/services/electron/electron-browser/electronEnvironmentService.ts deleted file mode 100644 index 54c3e86a16..0000000000 --- a/src/vs/workbench/services/electron/electron-browser/electronEnvironmentService.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { URI } from 'vs/base/common/uri'; -import { memoize } from 'vs/base/common/decorators'; -import { join } from 'vs/base/common/path'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; - -export const IElectronEnvironmentService = createDecorator('electronEnvironmentService'); - -export interface IElectronEnvironmentService { - - _serviceBrand: undefined; - - readonly windowId: number; - - readonly sharedIPCHandle: string; - - readonly extHostLogsPath: URI; -} - -export class ElectronEnvironmentService implements IElectronEnvironmentService { - - _serviceBrand: undefined; - - constructor( - public readonly windowId: number, - public readonly sharedIPCHandle: string, - private readonly environmentService: IEnvironmentService - ) { } - - @memoize - get extHostLogsPath(): URI { return URI.file(join(this.environmentService.logsPath, `exthost${this.windowId}`)); } -} diff --git a/src/vs/workbench/services/electron/electron-browser/electronService.ts b/src/vs/workbench/services/electron/electron-browser/electronService.ts index 46387199e4..8410dd35e1 100644 --- a/src/vs/workbench/services/electron/electron-browser/electronService.ts +++ b/src/vs/workbench/services/electron/electron-browser/electronService.ts @@ -6,8 +6,9 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { createChannelSender } from 'vs/base/parts/ipc/node/ipc'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export class ElectronService { @@ -15,9 +16,9 @@ export class ElectronService { constructor( @IMainProcessService mainProcessService: IMainProcessService, - @IElectronEnvironmentService electronEnvironmentService: IElectronEnvironmentService + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService ) { - return createChannelSender(mainProcessService.getChannel('electron'), { context: electronEnvironmentService.windowId }); + return createChannelSender(mainProcessService.getChannel('electron'), { context: environmentService.configuration.windowId }); } } diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index b9b7743edb..ad0acd352c 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -4,22 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { Schemas } from 'vs/base/common/network'; -import { ExportData } from 'vs/base/common/performance'; -import { IProcessEnvironment } from 'vs/base/common/platform'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { BACKUPS, IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; -import { LogLevel } from 'vs/platform/log/common/log'; -import { IPath, IPathsToWaitFor, IWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IPath, IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; import product from 'vs/platform/product/common/product'; import { serializableToMap } from 'vs/base/common/map'; import { memoize } from 'vs/base/common/decorators'; -// TODO@ben remove properties that are node/electron only export class BrowserWindowConfiguration implements IWindowConfiguration { constructor( @@ -28,8 +23,6 @@ export class BrowserWindowConfiguration implements IWindowConfiguration { private readonly environment: IWorkbenchEnvironmentService ) { } - //#region PROPERLY CONFIGURED IN DESKTOP + WEB - @memoize get sessionId(): string { return generateUuid(); } @@ -54,44 +47,10 @@ export class BrowserWindowConfiguration implements IWindowConfiguration { return undefined; } - // Currently unsupported in web + // Currently unsupported in web but should look into support get filesToDiff(): IPath[] | undefined { return undefined; } - - //#endregion - - - //#region TODO MOVE TO NODE LAYER - - _!: any[]; - - windowId!: number; - mainPid!: number; - - logLevel!: LogLevel; - - appRoot!: string; - execPath!: string; - backupPath?: string; - nodeCachedDataDir?: string; - - userEnv!: IProcessEnvironment; - - workspace?: IWorkspaceIdentifier; - folderUri?: ISingleFolderWorkspaceIdentifier; - - zoomLevel?: number; - fullscreen?: boolean; - maximized?: boolean; - highContrast?: boolean; - accessibilitySupport?: boolean; - partsSplashPath?: string; - - isInitialStartup?: boolean; - perfEntries!: ExportData; - - filesToWait?: IPathsToWaitFor; - - //#endregion + highContrast = false; + _ = []; private getCookieValue(name: string): string | undefined { const m = document.cookie.match('(^|[^;]+)\\s*' + name + '\\s*=\\s*([^;]+)'); // See https://stackoverflow.com/a/25490531 @@ -116,7 +75,14 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment _serviceBrand: undefined; - //#region PROPERLY CONFIGURED IN DESKTOP + WEB + private _configuration: IWindowConfiguration | undefined = undefined; + get configuration(): IWindowConfiguration { + if (!this._configuration) { + this._configuration = new BrowserWindowConfiguration(this.options, this.payload, this); + } + + return this._configuration; + } @memoize get isBuilt(): boolean { return !!product.commit; } @@ -209,71 +175,14 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment return this.webviewExternalEndpoint.replace('{{uuid}}', '*'); } - // Currently not configurable in web + // Currently unsupported in web but should look into support get disableExtensions() { return false; } get extensionsPath(): string | undefined { return undefined; } get verbose(): boolean { return false; } get disableUpdates(): boolean { return false; } get logExtensionHostCommunication(): boolean { return false; } - - //#endregion - - - //#region TODO MOVE TO NODE LAYER - - private _configuration: IWindowConfiguration | undefined = undefined; - get configuration(): IWindowConfiguration { - if (!this._configuration) { - this._configuration = new BrowserWindowConfiguration(this.options, this.payload, this); - } - - return this._configuration; - } - - args = { _: [] }; - - wait!: boolean; - status!: boolean; - log?: string; - - mainIPCHandle!: string; - sharedIPCHandle!: string; - - nodeCachedDataDir?: string; - - disableCrashReporter!: boolean; - - driverHandle?: string; - driverVerbose!: boolean; - - installSourcePath!: string; - - builtinExtensionsPath!: string; - - globalStorageHome!: string; - workspaceStorageHome!: string; - - backupWorkspacesPath!: string; - - machineSettingsHome!: URI; - machineSettingsResource!: URI; - - userHome!: string; - userDataPath!: string; - appRoot!: string; - appSettingsHome!: URI; - execPath!: string; - cliPath!: string; - - //#endregion - - - //#region TODO ENABLE IN WEB - galleryMachineIdResource?: URI; - //#endregion - private payload: Map | undefined; constructor(readonly options: IBrowserWorkbenchEnvironmentConstructionOptions) { @@ -316,4 +225,35 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment return extensionHostDebugEnvironment; } + + //#region TODO MOVE TO NODE LAYER + + args = { _: [] }; + + mainIPCHandle!: string; + sharedIPCHandle!: string; + + nodeCachedDataDir?: string; + + driverHandle?: string; + driverVerbose!: boolean; + + installSourcePath!: string; + + builtinExtensionsPath!: string; + + globalStorageHome!: string; + workspaceStorageHome!: string; + + backupWorkspacesPath!: string; + + machineSettingsResource!: URI; + + userHome!: string; + userDataPath!: string; + appRoot!: string; + appSettingsHome!: URI; + execPath!: string; + + //#endregion } diff --git a/src/vs/workbench/services/environment/electron-browser/environmentService.ts b/src/vs/workbench/services/environment/electron-browser/environmentService.ts index 022c35fd22..2b1808a3e6 100644 --- a/src/vs/workbench/services/environment/electron-browser/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-browser/environmentService.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { memoize } from 'vs/base/common/decorators'; import { URI } from 'vs/base/common/uri'; @@ -12,8 +11,21 @@ import { Schemas } from 'vs/base/common/network'; import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/electron-browser/backup'; import { join } from 'vs/base/common/path'; import product from 'vs/platform/product/common/product'; +import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; -export class NativeWorkbenchEnvironmentService extends EnvironmentService implements IWorkbenchEnvironmentService { +export interface INativeWorkbenchEnvironmentService extends IWorkbenchEnvironmentService { + + readonly configuration: INativeWindowConfiguration; + + readonly disableCrashReporter: boolean; + + readonly cliPath: string; + + readonly log?: string; + readonly extHostLogsPath: URI; +} + +export class NativeWorkbenchEnvironmentService extends EnvironmentService implements INativeWorkbenchEnvironmentService { _serviceBrand: undefined; @@ -34,12 +46,14 @@ export class NativeWorkbenchEnvironmentService extends EnvironmentService implem get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); } @memoize - get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.windowId}.log`)); } + get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.configuration.windowId}.log`)); } + + @memoize + get extHostLogsPath(): URI { return URI.file(join(this.logsPath, `exthost${this.configuration.windowId}`)); } constructor( - readonly configuration: IWindowConfiguration, - execPath: string, - private readonly windowId: number + readonly configuration: INativeWindowConfiguration, + execPath: string ) { super(configuration, execPath); diff --git a/src/vs/workbench/services/extensions/common/extensionHostMain.ts b/src/vs/workbench/services/extensions/common/extensionHostMain.ts index 18056116ee..91276cd131 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostMain.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostMain.ts @@ -76,7 +76,7 @@ export class ExtensionHostMain { // error forwarding and stack trace scanning Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API) - const extensionErrors = new WeakMap(); + const extensionErrors = new WeakMap(); this._extensionService.getExtensionPathIndex().then(map => { (Error).prepareStackTrace = (error: Error, stackTrace: errors.V8CallSite[]) => { let stackTraceMessage = ''; diff --git a/src/vs/workbench/services/extensions/common/rpcProtocol.ts b/src/vs/workbench/services/extensions/common/rpcProtocol.ts index 4152909779..5631263fed 100644 --- a/src/vs/workbench/services/extensions/common/rpcProtocol.ts +++ b/src/vs/workbench/services/extensions/common/rpcProtocol.ts @@ -27,6 +27,10 @@ function safeStringify(obj: any, replacer: JSONStringifyReplacer | null): string } } +function stringify(obj: any, replacer: JSONStringifyReplacer | null): string { + return JSON.stringify(obj, <(key: string, value: any) => any>replacer); +} + function createURIReplacer(transformer: IURITransformer | null): JSONStringifyReplacer | null { if (!transformer) { return null; @@ -412,6 +416,8 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { return Promise.reject(errors.canceled()); } + const serializedRequestArguments = MessageIO.serializeRequestArguments(args, this._uriReplacer); + const req = ++this._lastMessageId; const callId = String(req); const result = new LazyPromise(); @@ -428,7 +434,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { this._pendingRPCReplies[callId] = result; this._onWillSendRequest(req); - const msg = MessageIO.serializeRequest(req, rpcId, methodName, args, !!cancellationToken, this._uriReplacer); + const msg = MessageIO.serializeRequest(req, rpcId, methodName, serializedRequestArguments, !!cancellationToken); if (this._logger) { this._logger.logOutgoing(msg.byteLength, req, RequestInitiator.LocalSide, `request: ${getStringIdentifierForProxy(rpcId)}.${methodName}(`, args); } @@ -600,6 +606,8 @@ class MessageBuffer { } } +type SerializedRequestArguments = { type: 'mixed'; args: VSBuffer[]; argsType: ArgType[]; } | { type: 'simple'; args: string; }; + class MessageIO { private static _arrayContainsBufferOrUndefined(arr: any[]): boolean { @@ -614,7 +622,7 @@ class MessageIO { return false; } - public static serializeRequest(req: number, rpcId: number, method: string, args: any[], usesCancellationToken: boolean, replacer: JSONStringifyReplacer | null): VSBuffer { + public static serializeRequestArguments(args: any[], replacer: JSONStringifyReplacer | null): SerializedRequestArguments { if (this._arrayContainsBufferOrUndefined(args)) { let massagedArgs: VSBuffer[] = []; let massagedArgsType: ArgType[] = []; @@ -627,13 +635,27 @@ class MessageIO { massagedArgs[i] = VSBuffer.alloc(0); massagedArgsType[i] = ArgType.Undefined; } else { - massagedArgs[i] = VSBuffer.fromString(safeStringify(arg, replacer)); + massagedArgs[i] = VSBuffer.fromString(stringify(arg, replacer)); massagedArgsType[i] = ArgType.String; } } - return this._requestMixedArgs(req, rpcId, method, massagedArgs, massagedArgsType, usesCancellationToken); + return { + type: 'mixed', + args: massagedArgs, + argsType: massagedArgsType + }; } - return this._requestJSONArgs(req, rpcId, method, safeStringify(args, replacer), usesCancellationToken); + return { + type: 'simple', + args: stringify(args, replacer) + }; + } + + public static serializeRequest(req: number, rpcId: number, method: string, serializedArgs: SerializedRequestArguments, usesCancellationToken: boolean): VSBuffer { + if (serializedArgs.type === 'mixed') { + return this._requestMixedArgs(req, rpcId, method, serializedArgs.args, serializedArgs.argsType, usesCancellationToken); + } + return this._requestJSONArgs(req, rpcId, method, serializedArgs.args, usesCancellationToken); } private static _requestJSONArgs(req: number, rpcId: number, method: string, args: string, usesCancellationToken: boolean): VSBuffer { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 151105bbb0..556499ee5e 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -42,6 +42,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { joinPath } from 'vs/base/common/resources'; import { Registry } from 'vs/platform/registry/common/platform'; import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class ExtensionHostProcessWorker implements IExtensionHostStarter { @@ -78,7 +79,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { @INotificationService private readonly _notificationService: INotificationService, @IElectronService private readonly _electronService: IElectronService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, - @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILogService private readonly _logService: ILogService, @ILabelService private readonly _labelService: ILabelService, diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index f0463a5d58..81e22eedd2 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -35,7 +35,7 @@ import { Logger } from 'vs/workbench/services/extensions/common/extensionPoints' import { flatten } from 'vs/base/common/arrays'; import { IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions'; import { IElectronService } from 'vs/platform/electron/node/electron'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { Action } from 'vs/base/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; @@ -60,7 +60,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten constructor( @IInstantiationService instantiationService: IInstantiationService, @INotificationService notificationService: INotificationService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService protected readonly _environmentService: INativeWorkbenchEnvironmentService, @ITelemetryService telemetryService: ITelemetryService, @IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService, @IFileService fileService: IFileService, @@ -73,14 +73,13 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IStaticExtensionsService private readonly _staticExtensions: IStaticExtensionsService, @IElectronService private readonly _electronService: IElectronService, @IHostService private readonly _hostService: IHostService, - @IElectronEnvironmentService private readonly _electronEnvironmentService: IElectronEnvironmentService, @IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService, @IExtensionGalleryService private readonly _extensionGalleryService: IExtensionGalleryService, ) { super( instantiationService, notificationService, - environmentService, + _environmentService, telemetryService, extensionEnablementService, fileService, @@ -366,7 +365,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten const result: ExtensionHostProcessManager[] = []; - const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._electronEnvironmentService.extHostLogsPath); + const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._environmentService.extHostLogsPath); const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, true, extHostProcessWorker, null, initialActivationEvents); result.push(extHostProcessManager); @@ -464,16 +463,13 @@ export class ExtensionService extends AbstractExtensionService implements IExten } catch (err) { const remoteName = getRemoteName(remoteAuthority); if (RemoteAuthorityResolverError.isNoResolverFound(err)) { - this._handleNoResolverFound(remoteName, allExtensions); + err.isHandled = await this._handleNoResolverFound(remoteName, allExtensions); } else { console.log(err); - if (RemoteAuthorityResolverError.isHandledNotAvailable(err)) { - console.log(`Not showing a notification for the error`); - } else { - this._notificationService.notify({ severity: Severity.Error, message: nls.localize('resolveAuthorityFailure', "Resolving the authority `{0}` failed", remoteName) }); + if (RemoteAuthorityResolverError.isHandled(err)) { + console.log(`Error handled: Not showing a notification for the error`); } } - this._remoteAuthorityResolverService.setResolvedAuthorityError(remoteAuthority, err); // Proceed with the local extension host @@ -589,10 +585,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten } } - private async _handleNoResolverFound(remoteName: string, allExtensions: IExtensionDescription[]): Promise { + private async _handleNoResolverFound(remoteName: string, allExtensions: IExtensionDescription[]): Promise { const recommendation = this._productService.remoteExtensionTips?.[remoteName]; if (!recommendation) { - return; + return false; } const sendTelemetry = (userReaction: 'install' | 'enable' | 'cancel') => { /* __GDPR__ @@ -608,7 +604,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten const extension = allExtensions.filter(e => e.identifier.value === resolverExtensionId)[0]; if (extension) { if (this._isDisabled(extension)) { - const message = nls.localize('enableResolver', "Extension '{0}' is required to open the remote window.\nOk to enable?", recommendation.friendlyName); + const message = nls.localize('enableResolver', "Extension '{0}' is required to open the remote window.\nOK to enable?", recommendation.friendlyName); this._notificationService.prompt(Severity.Info, message, [{ label: nls.localize('enable', 'Enable and Reload'), @@ -623,7 +619,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten } } else { // Install the Extension and reload the window to handle. - const message = nls.localize('installResolver', "Extension '{0}' is required to open the remote window.\nOk to install?", recommendation.friendlyName); + const message = nls.localize('installResolver', "Extension '{0}' is required to open the remote window.\nnOK to install?", recommendation.friendlyName); this._notificationService.prompt(Severity.Info, message, [{ label: nls.localize('install', 'Install and Reload'), @@ -646,6 +642,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten ); } + return true; } } diff --git a/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts b/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts index f0c47b1ea1..3f65da9035 100644 --- a/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts +++ b/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts @@ -212,4 +212,13 @@ suite('RPCProtocol', () => { assert.equal(res, 7); }); }); + + test('issue #81424: SerializeRequest should throw if an argument can not be serialized', () => { + let badObject = {}; + (badObject).loop = badObject; + + assert.throws(() => { + bProxy.$m(badObject, '2'); + }); + }); }); diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 5e3e1b0c17..88849460c8 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -5,8 +5,8 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { IEditor } from 'vs/editor/common/editorCommon'; -import { ITextEditorOptions, IResourceInput, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor as IBaseEditor, Extensions as EditorExtensions, EditorInput, IEditorCloseEvent, IEditorInputFactoryRegistry, toResource, IEditorIdentifier, GroupIdentifier, EditorsOrder } from 'vs/workbench/common/editor'; +import { ITextEditorOptions, IResourceEditorInput, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; +import { IEditorInput, IEditorPane, Extensions as EditorExtensions, EditorInput, IEditorCloseEvent, IEditorInputFactoryRegistry, toResource, IEditorIdentifier, GroupIdentifier, EditorsOrder } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { FileChangesEvent, IFileService, FileChangeType, FILES_EXCLUDE_CONFIG } from 'vs/platform/files/common/files'; @@ -82,7 +82,7 @@ interface ISerializedEditorHistoryEntry { } interface IStackEntry { - input: IEditorInput | IResourceInput; + input: IEditorInput | IResourceEditorInput; selection?: Selection; } @@ -130,7 +130,7 @@ export class HistoryService extends Disposable implements IHistoryService { // if the service is created late enough that an editor is already opened // make sure to trigger the onActiveEditorChanged() to track the editor // properly (fixes https://github.com/Microsoft/vscode/issues/59908) - if (this.editorService.activeControl) { + if (this.editorService.activeEditorPane) { this.onActiveEditorChanged(); } @@ -140,7 +140,7 @@ export class HistoryService extends Disposable implements IHistoryService { mouseBackForwardSupportListener.clear(); if (this.configurationService.getValue('workbench.editor.mouseBackForwardToNavigate')) { - mouseBackForwardSupportListener.add(addDisposableListener(this.layoutService.getWorkbenchElement(), EventType.MOUSE_DOWN, e => this.onMouseDown(e))); + mouseBackForwardSupportListener.add(addDisposableListener(this.layoutService.container, EventType.MOUSE_DOWN, e => this.onMouseDown(e))); } }; @@ -169,44 +169,44 @@ export class HistoryService extends Disposable implements IHistoryService { } private onActiveEditorChanged(): void { - const activeControl = this.editorService.activeControl; - if (this.lastActiveEditor && this.matchesEditor(this.lastActiveEditor, activeControl)) { + const activeEditorPane = this.editorService.activeEditorPane; + if (this.lastActiveEditor && this.matchesEditor(this.lastActiveEditor, activeEditorPane)) { return; // return if the active editor is still the same } // Remember as last active editor (can be undefined if none opened) - this.lastActiveEditor = activeControl?.input && activeControl.group ? { editor: activeControl.input, groupId: activeControl.group.id } : undefined; + this.lastActiveEditor = activeEditorPane?.input && activeEditorPane.group ? { editor: activeEditorPane.input, groupId: activeEditorPane.group.id } : undefined; // Dispose old listeners this.activeEditorListeners.clear(); // Propagate to history - this.handleActiveEditorChange(activeControl); + this.handleActiveEditorChange(activeEditorPane); // Apply listener for selection changes if this is a text editor - const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget); + const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); const activeEditor = this.editorService.activeEditor; - if (activeTextEditorWidget) { + if (activeTextEditorControl) { // Debounce the event with a timeout of 0ms so that multiple calls to // editor.setSelection() are folded into one. We do not want to record // subsequent history navigations for such API calls. - this.activeEditorListeners.add(Event.debounce(activeTextEditorWidget.onDidChangeCursorPosition, (last, event) => event, 0)((event => { - this.handleEditorSelectionChangeEvent(activeControl, event); + this.activeEditorListeners.add(Event.debounce(activeTextEditorControl.onDidChangeCursorPosition, (last, event) => event, 0)((event => { + this.handleEditorSelectionChangeEvent(activeEditorPane, event); }))); // Track the last edit location by tracking model content change events // Use a debouncer to make sure to capture the correct cursor position // after the model content has changed. - this.activeEditorListeners.add(Event.debounce(activeTextEditorWidget.onDidChangeModelContent, (last, event) => event, 0)((event => { + this.activeEditorListeners.add(Event.debounce(activeTextEditorControl.onDidChangeModelContent, (last, event) => event, 0)((event => { if (activeEditor) { - this.rememberLastEditLocation(activeEditor, activeTextEditorWidget); + this.rememberLastEditLocation(activeEditor, activeTextEditorControl); } }))); } } - private matchesEditor(identifier: IEditorIdentifier, editor?: IBaseEditor): boolean { + private matchesEditor(identifier: IEditorIdentifier, editor?: IEditorPane): boolean { if (!editor || !editor.group) { return false; } @@ -224,11 +224,11 @@ export class HistoryService extends Disposable implements IHistoryService { } } - private handleEditorSelectionChangeEvent(editor?: IBaseEditor, event?: ICursorPositionChangedEvent): void { + private handleEditorSelectionChangeEvent(editor?: IEditorPane, event?: ICursorPositionChangedEvent): void { this.handleEditorEventInNavigationStack(editor, event); } - private handleActiveEditorChange(editor?: IBaseEditor): void { + private handleActiveEditorChange(editor?: IEditorPane): void { this.handleEditorEventInHistory(editor); this.handleEditorEventInNavigationStack(editor); } @@ -245,7 +245,7 @@ export class HistoryService extends Disposable implements IHistoryService { disposables.add(toDispose); } - private clearOnEditorDispose(editor: IEditorInput | IResourceInput | FileChangesEvent, mapEditorToDispose: Map): void { + private clearOnEditorDispose(editor: IEditorInput | IResourceEditorInput | FileChangesEvent, mapEditorToDispose: Map): void { if (editor instanceof EditorInput) { const disposables = mapEditorToDispose.get(editor); if (disposables) { @@ -255,21 +255,21 @@ export class HistoryService extends Disposable implements IHistoryService { } } - remove(input: IEditorInput | IResourceInput): void; + remove(input: IEditorInput | IResourceEditorInput): void; remove(input: FileChangesEvent): void; - remove(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { + remove(arg1: IEditorInput | IResourceEditorInput | FileChangesEvent): void { this.removeFromHistory(arg1); this.removeFromNavigationStack(arg1); this.removeFromRecentlyClosedFiles(arg1); this.removeFromRecentlyOpened(arg1); } - private removeFromRecentlyOpened(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { + private removeFromRecentlyOpened(arg1: IEditorInput | IResourceEditorInput | FileChangesEvent): void { if (arg1 instanceof EditorInput || arg1 instanceof FileChangesEvent) { return; // for now do not delete from file events since recently open are likely out of workspace files for which there are no delete events } - const input = arg1 as IResourceInput; + const input = arg1 as IResourceEditorInput; this.workspacesService.removeRecentlyOpened([input.resource]); } @@ -344,7 +344,7 @@ export class HistoryService extends Disposable implements IHistoryService { this.doNavigate(navigateToStackEntry).finally(() => this.navigatingInStack = false); } - private doNavigate(location: IStackEntry): Promise { + private doNavigate(location: IStackEntry): Promise { const options: ITextEditorOptions = { revealIfOpened: true, // support to navigate across editor groups, selection: location.selection, @@ -355,10 +355,10 @@ export class HistoryService extends Disposable implements IHistoryService { return this.editorService.openEditor(location.input, options); } - return this.editorService.openEditor({ resource: (location.input as IResourceInput).resource, options }); + return this.editorService.openEditor({ resource: (location.input as IResourceEditorInput).resource, options }); } - private handleEditorEventInNavigationStack(control: IBaseEditor | undefined, event?: ICursorPositionChangedEvent): void { + private handleEditorEventInNavigationStack(control: IEditorPane | undefined, event?: ICursorPositionChangedEvent): void { const codeEditor = control ? getCodeEditor(control.getControl()) : undefined; // treat editor changes that happen as part of stack navigation specially @@ -392,7 +392,7 @@ export class HistoryService extends Disposable implements IHistoryService { } } - private handleTextEditorEventInNavigationStack(editor: IBaseEditor, editorControl: IEditor, event?: ICursorPositionChangedEvent): void { + private handleTextEditorEventInNavigationStack(editor: IEditorPane, editorControl: IEditor, event?: ICursorPositionChangedEvent): void { if (!editor.input) { return; } @@ -413,7 +413,7 @@ export class HistoryService extends Disposable implements IHistoryService { this.currentTextEditorState = stateCandidate; } - private handleNonTextEditorEventInNavigationStack(editor: IBaseEditor): void { + private handleNonTextEditorEventInNavigationStack(editor: IEditorPane): void { if (!editor.input) { return; } @@ -458,8 +458,8 @@ export class HistoryService extends Disposable implements IHistoryService { } } - const stackInput = this.preferResourceInput(input); - const entry = { input: stackInput, selection }; + const stackEditorInput = this.preferResourceEditorInput(input); + const entry = { input: stackEditorInput, selection }; // Replace at current position let removedEntries: IStackEntry[] = []; @@ -499,15 +499,15 @@ export class HistoryService extends Disposable implements IHistoryService { // Remove this from the stack unless the stack input is a resource // that can easily be restored even when the input gets disposed - if (stackInput instanceof EditorInput) { - this.onEditorDispose(stackInput, () => this.removeFromNavigationStack(stackInput), this.editorStackListeners); + if (stackEditorInput instanceof EditorInput) { + this.onEditorDispose(stackEditorInput, () => this.removeFromNavigationStack(stackEditorInput), this.editorStackListeners); } // Context Keys this.updateContextKeys(); } - private preferResourceInput(input: IEditorInput): IEditorInput | IResourceInput { + private preferResourceEditorInput(input: IEditorInput): IEditorInput | IResourceEditorInput { const resource = input.resource; if (resource && (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData)) { // for now, only prefer well known schemes that we control to prevent @@ -530,7 +530,7 @@ export class HistoryService extends Disposable implements IHistoryService { return selectionA.startLineNumber === selectionB.startLineNumber; // we consider the history entry same if we are on the same line } - private removeFromNavigationStack(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { + private removeFromNavigationStack(arg1: IEditorInput | IResourceEditorInput | FileChangesEvent): void { this.navigationStack = this.navigationStack.filter(e => { const matches = this.matches(arg1, e.input); @@ -548,15 +548,15 @@ export class HistoryService extends Disposable implements IHistoryService { this.updateContextKeys(); } - private matches(arg1: IEditorInput | IResourceInput | FileChangesEvent, inputB: IEditorInput | IResourceInput): boolean { + private matches(arg1: IEditorInput | IResourceEditorInput | FileChangesEvent, inputB: IEditorInput | IResourceEditorInput): boolean { if (arg1 instanceof FileChangesEvent) { if (inputB instanceof EditorInput) { - return false; // we only support this for IResourceInput + return false; // we only support this for IResourceEditorInput } - const resourceInputB = inputB as IResourceInput; + const resourceEditorInputB = inputB as IResourceEditorInput; - return arg1.contains(resourceInputB.resource, FileChangeType.DELETED); + return arg1.contains(resourceEditorInputB.resource, FileChangeType.DELETED); } if (arg1 instanceof EditorInput && inputB instanceof EditorInput) { @@ -564,20 +564,20 @@ export class HistoryService extends Disposable implements IHistoryService { } if (arg1 instanceof EditorInput) { - return this.matchesFile((inputB as IResourceInput).resource, arg1); + return this.matchesFile((inputB as IResourceEditorInput).resource, arg1); } if (inputB instanceof EditorInput) { - return this.matchesFile((arg1 as IResourceInput).resource, inputB); + return this.matchesFile((arg1 as IResourceEditorInput).resource, inputB); } - const resourceInputA = arg1 as IResourceInput; - const resourceInputB = inputB as IResourceInput; + const resourceEditorInputA = arg1 as IResourceEditorInput; + const resourceEditorInputB = inputB as IResourceEditorInput; - return resourceInputA && resourceInputB && resourceInputA.resource.toString() === resourceInputB.resource.toString(); + return resourceEditorInputA && resourceEditorInputB && resourceEditorInputA.resource.toString() === resourceEditorInputB.resource.toString(); } - private matchesFile(resource: URI, arg2: IEditorInput | IResourceInput | FileChangesEvent): boolean { + private matchesFile(resource: URI, arg2: IEditorInput | IResourceEditorInput | FileChangesEvent): boolean { if (arg2 instanceof FileChangesEvent) { return arg2.contains(resource, FileChangeType.DELETED); } @@ -595,9 +595,9 @@ export class HistoryService extends Disposable implements IHistoryService { return inputResource.toString() === resource.toString(); } - const resourceInput = arg2 as IResourceInput; + const resourceEditorInput = arg2 as IResourceEditorInput; - return resourceInput?.resource.toString() === resource.toString(); + return resourceEditorInput?.resource.toString() === resource.toString(); } //#endregion @@ -666,7 +666,7 @@ export class HistoryService extends Disposable implements IHistoryService { return false; } - private removeFromRecentlyClosedFiles(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { + private removeFromRecentlyClosedFiles(arg1: IEditorInput | IResourceEditorInput | FileChangesEvent): void { this.recentlyClosedFiles = this.recentlyClosedFiles.filter(e => !this.matchesFile(e.resource, arg1)); this.canReopenClosedEditorContextKey.set(this.recentlyClosedFiles.length > 0); } @@ -677,11 +677,11 @@ export class HistoryService extends Disposable implements IHistoryService { private lastEditLocation: IStackEntry | undefined; - private rememberLastEditLocation(activeEditor: IEditorInput, activeTextEditorWidget: ICodeEditor): void { + private rememberLastEditLocation(activeEditor: IEditorInput, activeTextEditorControl: ICodeEditor): void { this.lastEditLocation = { input: activeEditor }; this.canNavigateToLastEditLocationContextKey.set(true); - const position = activeTextEditorWidget.getPosition(); + const position = activeTextEditorControl.getPosition(); if (position) { this.lastEditLocation.selection = new Selection(position.lineNumber, position.column, position.lineNumber, position.column); } @@ -716,7 +716,7 @@ export class HistoryService extends Disposable implements IHistoryService { private static readonly MAX_HISTORY_ITEMS = 200; private static readonly HISTORY_STORAGE_KEY = 'history.entries'; - private history: Array | undefined = undefined; + private history: Array | undefined = undefined; private readonly resourceFilter = this._register(this.instantiationService.createInstance( ResourceGlobMatcher, @@ -730,7 +730,7 @@ export class HistoryService extends Disposable implements IHistoryService { return getExcludes(scope ? this.configurationService.getValue(scope) : this.configurationService.getValue())!; } - private handleEditorEventInHistory(editor?: IBaseEditor): void { + private handleEditorEventInHistory(editor?: IEditorPane): void { // Ensure we have not configured to exclude input and don't track invalid inputs const input = editor?.input; @@ -738,7 +738,7 @@ export class HistoryService extends Disposable implements IHistoryService { return; } - const historyInput = this.preferResourceInput(input); + const historyInput = this.preferResourceEditorInput(input); // Remove any existing entry and add to the beginning this.ensureHistoryLoaded(this.history); @@ -757,14 +757,14 @@ export class HistoryService extends Disposable implements IHistoryService { } } - private include(input: IEditorInput | IResourceInput): boolean { + private include(input: IEditorInput | IResourceEditorInput): boolean { if (input instanceof EditorInput) { return true; // include any non files } - const resourceInput = input as IResourceInput; + const resourceEditorInput = input as IResourceEditorInput; - return !this.resourceFilter.matches(resourceInput.resource); + return !this.resourceFilter.matches(resourceEditorInput.resource); } private removeExcludedFromHistory(): void { @@ -782,7 +782,7 @@ export class HistoryService extends Disposable implements IHistoryService { }); } - private removeFromHistory(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { + private removeFromHistory(arg1: IEditorInput | IResourceEditorInput | FileChangesEvent): void { this.ensureHistoryLoaded(this.history); this.history = this.history.filter(e => { @@ -804,19 +804,19 @@ export class HistoryService extends Disposable implements IHistoryService { this.editorHistoryListeners.clear(); } - getHistory(): ReadonlyArray { + getHistory(): ReadonlyArray { this.ensureHistoryLoaded(this.history); return this.history.slice(0); } - private ensureHistoryLoaded(history: Array | undefined): asserts history { + private ensureHistoryLoaded(history: Array | undefined): asserts history { if (!this.history) { this.history = this.loadHistory(); } } - private loadHistory(): Array { + private loadHistory(): Array { let entries: ISerializedEditorHistoryEntry[] = []; const entriesRaw = this.storageService.get(HistoryService.HISTORY_STORAGE_KEY, StorageScope.WORKSPACE); @@ -835,7 +835,7 @@ export class HistoryService extends Disposable implements IHistoryService { })); } - private safeLoadHistoryEntry(registry: IEditorInputFactoryRegistry, entry: ISerializedEditorHistoryEntry): IEditorInput | IResourceInput | undefined { + private safeLoadHistoryEntry(registry: IEditorInputFactoryRegistry, entry: ISerializedEditorHistoryEntry): IEditorInput | IResourceEditorInput | undefined { const serializedEditorHistoryEntry = entry; // File resource: via URI.revive() @@ -882,7 +882,7 @@ export class HistoryService extends Disposable implements IHistoryService { // File resource: via URI.toJSON() else { - return { resourceJSON: (input as IResourceInput).resource.toJSON() }; + return { resourceJSON: (input as IResourceEditorInput).resource.toJSON() }; } return undefined; @@ -919,12 +919,12 @@ export class HistoryService extends Disposable implements IHistoryService { continue; } - const resourceInput = input as IResourceInput; - if (schemeFilter && resourceInput.resource.scheme !== schemeFilter) { + const resourceEditorInput = input as IResourceEditorInput; + if (schemeFilter && resourceEditorInput.resource.scheme !== schemeFilter) { continue; } - const resourceWorkspace = this.contextService.getWorkspaceFolder(resourceInput.resource); + const resourceWorkspace = this.contextService.getWorkspaceFolder(resourceEditorInput.resource); if (resourceWorkspace) { return resourceWorkspace.uri; } @@ -947,7 +947,7 @@ export class HistoryService extends Disposable implements IHistoryService { if (input instanceof EditorInput) { resource = toResource(input, { filterByScheme }); } else { - resource = (input as IResourceInput).resource; + resource = (input as IResourceEditorInput).resource; } if (resource?.scheme === filterByScheme) { diff --git a/src/vs/workbench/services/history/common/history.ts b/src/vs/workbench/services/history/common/history.ts index d05ed54d1a..73f405b3a4 100644 --- a/src/vs/workbench/services/history/common/history.ts +++ b/src/vs/workbench/services/history/common/history.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IEditorInput, GroupIdentifier } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; @@ -42,7 +42,7 @@ export interface IHistoryService { /** * Removes an entry from history. */ - remove(input: IEditorInput | IResourceInput): void; + remove(input: IEditorInput | IResourceEditorInput): void; /** * Clears all history. @@ -57,7 +57,7 @@ export interface IHistoryService { /** * Get the entire history of editors that were opened. */ - getHistory(): ReadonlyArray; + getHistory(): ReadonlyArray; /** * Looking at the editor history, returns the workspace root of the last file that was diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index da94e91e2e..be78cb390e 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -6,8 +6,8 @@ import { Event } from 'vs/base/common/event'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IResourceEditor, IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { IResourceEditorInputType, IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWindowSettings, IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, isFileToOpen, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; import { pathsToEditors } from 'vs/workbench/common/editor'; @@ -59,7 +59,7 @@ export class BrowserHostService extends Disposable implements IHostService { private workspaceProvider: IWorkspaceProvider; constructor( - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @ILayoutService private readonly layoutService: ILayoutService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @IFileService private readonly fileService: IFileService, @@ -133,7 +133,7 @@ export class BrowserHostService extends Disposable implements IHostService { // Same Window: open via editor service in current window if (this.shouldReuse(options, true /* file */)) { - const inputs: IResourceEditor[] = await pathsToEditors([openable], this.fileService); + const inputs: IResourceEditorInputType[] = await pathsToEditors([openable], this.fileService); this.editorService.openEditors(inputs); } @@ -177,7 +177,7 @@ export class BrowserHostService extends Disposable implements IHostService { } async toggleFullScreen(): Promise { - const target = this.layoutService.getWorkbenchElement(); + const target = this.layoutService.container; // Chromium if (document.fullscreen !== undefined) { diff --git a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts index 0316a9b248..20804a0d2e 100644 --- a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts +++ b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts @@ -11,7 +11,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class DesktopHostService extends Disposable implements IHostService { @@ -20,16 +20,15 @@ export class DesktopHostService extends Disposable implements IHostService { constructor( @IElectronService private readonly electronService: IElectronService, @ILabelService private readonly labelService: ILabelService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService ) { super(); } get onDidChangeFocus(): Event { return this._onDidChangeFocus; } private _onDidChangeFocus: Event = Event.any( - Event.map(Event.filter(this.electronService.onWindowFocus, id => id === this.electronEnvironmentService.windowId), () => this.hasFocus), - Event.map(Event.filter(this.electronService.onWindowBlur, id => id === this.electronEnvironmentService.windowId), () => this.hasFocus) + Event.map(Event.filter(this.electronService.onWindowFocus, id => id === this.environmentService.configuration.windowId), () => this.hasFocus), + Event.map(Event.filter(this.electronService.onWindowBlur, id => id === this.environmentService.configuration.windowId), () => this.hasFocus) ); get hasFocus(): boolean { @@ -43,7 +42,7 @@ export class DesktopHostService extends Disposable implements IHostService { return false; } - return activeWindowId === this.electronEnvironmentService.windowId; + return activeWindowId === this.environmentService.configuration.windowId; } openWindow(options?: IOpenEmptyWindowOptions): Promise; diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 2e22787ca5..8070b6e716 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -11,11 +11,11 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Keybinding, ResolvedKeybinding, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; -import { OS, OperatingSystem } from 'vs/base/common/platform'; +import { OS, OperatingSystem, isMacintosh } from 'vs/base/common/platform'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService'; @@ -161,6 +161,18 @@ const NUMPAD_PRINTABLE_SCANCODES = [ ScanCode.NumpadDecimal ]; +const otherMacNumpadMapping = new Map(); +otherMacNumpadMapping.set(ScanCode.Numpad1, KeyCode.KEY_1); +otherMacNumpadMapping.set(ScanCode.Numpad2, KeyCode.KEY_2); +otherMacNumpadMapping.set(ScanCode.Numpad3, KeyCode.KEY_3); +otherMacNumpadMapping.set(ScanCode.Numpad4, KeyCode.KEY_4); +otherMacNumpadMapping.set(ScanCode.Numpad5, KeyCode.KEY_5); +otherMacNumpadMapping.set(ScanCode.Numpad6, KeyCode.KEY_6); +otherMacNumpadMapping.set(ScanCode.Numpad7, KeyCode.KEY_7); +otherMacNumpadMapping.set(ScanCode.Numpad8, KeyCode.KEY_8); +otherMacNumpadMapping.set(ScanCode.Numpad9, KeyCode.KEY_9); +otherMacNumpadMapping.set(ScanCode.Numpad0, KeyCode.KEY_0); + export class WorkbenchKeybindingService extends AbstractKeybindingService { private _keyboardMapper: IKeyboardMapper; @@ -510,7 +522,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { let commandAction = MenuRegistry.getCommand(command); let precondition = commandAction && commandAction.precondition; - let fullWhen: ContextKeyExpr | undefined; + let fullWhen: ContextKeyExpression | undefined; if (when && precondition) { fullWhen = ContextKeyExpr.and(precondition, ContextKeyExpr.deserialize(when)); } else if (when) { @@ -589,6 +601,10 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { // NumLock is on or this is /, *, -, + on the numpad return true; } + if (isMacintosh && event.keyCode === otherMacNumpadMapping.get(code)) { + // on macOS, the numpad keys can also map to keys 1 - 0. + return true; + } return false; } diff --git a/src/vs/workbench/services/keybinding/common/keybindingIO.ts b/src/vs/workbench/services/keybinding/common/keybindingIO.ts index b7e206b7bf..00022acb86 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingIO.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingIO.ts @@ -6,7 +6,7 @@ import { SimpleKeybinding } from 'vs/base/common/keyCodes'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { ScanCodeBinding } from 'vs/base/common/scanCode'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; @@ -14,7 +14,7 @@ export interface IUserKeybindingItem { parts: (SimpleKeybinding | ScanCodeBinding)[]; command: string | null; commandArgs?: any; - when: ContextKeyExpr | undefined; + when: ContextKeyExpression | undefined; } export class KeybindingIO { diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/browserKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/browserKeyboardMapper.test.ts index ab08e20475..91523f45bb 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/browserKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/browserKeyboardMapper.test.ts @@ -12,8 +12,8 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { INotificationService } from 'vs/platform/notification/common/notification'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; class TestKeyboardMapperFactory extends BrowserKeyboardMapperFactoryBase { constructor(notificationService: INotificationService, storageService: IStorageService, commandService: ICommandService) { diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index 6191377f35..f0f6fefc74 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -38,7 +38,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; -import { TestBackupFileService, TestContextService, TestEditorGroupsService, TestEditorService, TestLifecycleService, TestTextResourcePropertiesService, TestWorkingCopyService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestBackupFileService, TestEditorGroupsService, TestEditorService, TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; import { FileService } from 'vs/platform/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; @@ -54,11 +54,12 @@ import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbe import { WorkingCopyFileService, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { TestTextResourcePropertiesService, TestContextService, TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestServices'; class TestEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { - super(TestWindowConfiguration, TestWindowConfiguration.execPath, TestWindowConfiguration.windowId); + super(TestWindowConfiguration, TestWindowConfiguration.execPath); } get appSettingsHome() { return this._appSettingsHome; } diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 0d1d0bc776..0ee91b2af7 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -5,9 +5,9 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import * as paths from 'vs/base/common/path'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -16,7 +16,7 @@ import { isEqual, basenameOrAuthority, basename, joinPath, dirname } from 'vs/ba import { tildify, getPathLabel } from 'vs/base/common/labels'; import { ltrim, endsWith } from 'vs/base/common/strings'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; -import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting } from 'vs/platform/label/common/label'; +import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting, IFormatterChangeEvent } from 'vs/platform/label/common/label'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { match } from 'vs/base/common/glob'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -89,19 +89,20 @@ class ResourceLabelFormattersHandler implements IWorkbenchContribution { } Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ResourceLabelFormattersHandler, LifecyclePhase.Restored); -export class LabelService implements ILabelService { +export class LabelService extends Disposable implements ILabelService { + _serviceBrand: undefined; private formatters: ResourceLabelFormatter[] = []; - private readonly _onDidChangeFormatters = new Emitter(); + + private readonly _onDidChangeFormatters = this._register(new Emitter()); + readonly onDidChangeFormatters = this._onDidChangeFormatters.event; constructor( @IEnvironmentService private readonly environmentService: IEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - ) { } - - get onDidChangeFormatters(): Event { - return this._onDidChangeFormatters.event; + ) { + super(); } findFormatting(resource: URI): ResourceLabelFormatting | undefined { @@ -226,12 +227,12 @@ export class LabelService implements ILabelService { registerFormatter(formatter: ResourceLabelFormatter): IDisposable { this.formatters.push(formatter); - this._onDidChangeFormatters.fire(); + this._onDidChangeFormatters.fire({ scheme: formatter.scheme }); return { dispose: () => { this.formatters = this.formatters.filter(f => f !== formatter); - this._onDidChangeFormatters.fire(); + this._onDidChangeFormatters.fire({ scheme: formatter.scheme }); } }; } diff --git a/src/vs/workbench/services/label/test/browser/label.test.ts b/src/vs/workbench/services/label/test/browser/label.test.ts index 8355b123e9..89544a96cb 100644 --- a/src/vs/workbench/services/label/test/browser/label.test.ts +++ b/src/vs/workbench/services/label/test/browser/label.test.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { TestEnvironmentService, TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { URI } from 'vs/base/common/uri'; import { sep } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; suite('URI Label', () => { diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 2ece2af83d..65a89319dc 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -113,11 +113,6 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ setActivityBarHidden(hidden: boolean): void; - /** - * Number of pixels (adjusted for zooming) that the title bar (if visible) pushes down the workbench contents. - */ - getTitleBarOffset(): number; - /** * * Set editor area hidden or not @@ -185,11 +180,6 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ getWorkbenchContainer(): HTMLElement; - /** - * Returns the element that contains the workbench. - */ - getWorkbenchElement(): HTMLElement; - /** * Toggles the workbench in and out of zen mode - parts get hidden and window goes fullscreen. */ @@ -215,7 +205,6 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ registerPart(part: Part): void; - /** * Returns whether the window is maximized. */ diff --git a/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts index b6de83548c..2903f76c40 100644 --- a/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts @@ -7,7 +7,6 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ShutdownReason, StartupKind, handleVetos, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { ipcRenderer as ipc } from 'electron'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -15,6 +14,8 @@ import { AbstractLifecycleService } from 'vs/platform/lifecycle/common/lifecycle import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import Severity from 'vs/base/common/severity'; import { localize } from 'vs/nls'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class NativeLifecycleService extends AbstractLifecycleService { @@ -26,7 +27,7 @@ export class NativeLifecycleService extends AbstractLifecycleService { constructor( @INotificationService private readonly notificationService: INotificationService, - @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, @IStorageService readonly storageService: IStorageService, @ILogService readonly logService: ILogService ) { @@ -56,7 +57,7 @@ export class NativeLifecycleService extends AbstractLifecycleService { } private registerListeners(): void { - const windowId = this.electronEnvironmentService.windowId; + const windowId = this.environmentService.configuration.windowId; // Main side indicates that window is about to unload, check for vetos ipc.on('vscode:onBeforeUnload', (_event: unknown, reply: { okChannel: string, cancelChannel: string, reason: ShutdownReason }) => { diff --git a/src/vs/workbench/services/log/common/keyValueLogProvider.ts b/src/vs/workbench/services/log/common/keyValueLogProvider.ts index cb24dd77f1..28fb2753ba 100644 --- a/src/vs/workbench/services/log/common/keyValueLogProvider.ts +++ b/src/vs/workbench/services/log/common/keyValueLogProvider.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { IFileSystemProviderWithFileReadWriteCapability, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileDeleteOptions, FileWriteOptions, FileChangeType, FileSystemProviderErrorCode } from 'vs/platform/files/common/files'; +import { IFileSystemProviderWithFileReadWriteCapability, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileDeleteOptions, FileWriteOptions, FileChangeType, createFileSystemProviderError, FileSystemProviderErrorCode } from 'vs/platform/files/common/files'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { VSBuffer } from 'vs/base/common/buffer'; -import { FileSystemError } from 'vs/workbench/api/common/extHostTypes'; import { isEqualOrParent, joinPath, relativePath } from 'vs/base/common/resources'; import { values } from 'vs/base/common/map'; +import { localize } from 'vs/nls'; export abstract class KeyValueLogProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability { @@ -53,13 +53,13 @@ export abstract class KeyValueLogProvider extends Disposable implements IFileSys size: 0 }; } - return Promise.reject(new FileSystemError(resource, FileSystemProviderErrorCode.FileNotFound)); + throw createFileSystemProviderError(localize('fileNotExists', "File does not exist"), FileSystemProviderErrorCode.FileNotFound); } async readdir(resource: URI): Promise<[string, FileType][]> { const hasKey = await this.hasKey(resource.path); if (hasKey) { - return Promise.reject(new FileSystemError(resource, FileSystemProviderErrorCode.FileNotADirectory)); + throw createFileSystemProviderError(localize('fileNotDirectory', "File is not a directory"), FileSystemProviderErrorCode.FileNotADirectory); } const keys = await this.getAllKeys(); const files: Map = new Map(); @@ -79,7 +79,7 @@ export abstract class KeyValueLogProvider extends Disposable implements IFileSys async readFile(resource: URI): Promise { const hasKey = await this.hasKey(resource.path); if (!hasKey) { - return Promise.reject(new FileSystemError(resource, FileSystemProviderErrorCode.FileNotFound)); + throw createFileSystemProviderError(localize('fileNotFound', "File not found"), FileSystemProviderErrorCode.FileNotFound); } const value = await this.getValue(resource.path); return VSBuffer.fromString(value).buffer; @@ -90,7 +90,7 @@ export abstract class KeyValueLogProvider extends Disposable implements IFileSys if (!hasKey) { const files = await this.readdir(resource); if (files.length) { - return Promise.reject(new FileSystemError(resource, FileSystemProviderErrorCode.FileIsADirectory)); + throw createFileSystemProviderError(localize('fileIsDirectory', "File is Directory"), FileSystemProviderErrorCode.FileIsADirectory); } } await this.setValue(resource.path, VSBuffer.wrap(content).toString()); diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index 8e9e698eda..3b100d796b 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -87,11 +87,14 @@ export class NotificationService extends Disposable implements INotificationServ })); // Insert as primary or secondary action - const actions = notification.actions || { primary: [], secondary: [] }; + const actions = { + primary: notification.actions?.primary || [], + secondary: notification.actions?.secondary || [] + }; if (!notification.neverShowAgain.isSecondary) { - actions.primary = [neverShowAgainAction, ...(actions.primary || [])]; // action comes first + actions.primary = [neverShowAgainAction, ...actions.primary]; // action comes first } else { - actions.secondary = [...(actions.secondary || []), neverShowAgainAction]; // actions comes last + actions.secondary = [...actions.secondary, neverShowAgainAction]; // actions comes last } notification.actions = actions; diff --git a/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts b/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts index 1317262f22..8912e28977 100644 --- a/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts +++ b/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts @@ -21,7 +21,7 @@ import { toLocalISOString } from 'vs/base/common/date'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Emitter, Event } from 'vs/base/common/event'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implements IOutputChannelModel { @@ -203,8 +203,7 @@ export class OutputChannelModelService extends AsbtractOutputChannelModelService constructor( @IInstantiationService instantiationService: IInstantiationService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, @IFileService private readonly fileService: IFileService ) { super(instantiationService); @@ -218,7 +217,7 @@ export class OutputChannelModelService extends AsbtractOutputChannelModelService private _outputDir: Promise | null = null; private get outputDir(): Promise { if (!this._outputDir) { - const outputDir = URI.file(join(this.environmentService.logsPath, `output_${this.electronEnvironmentService.windowId}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`)); + const outputDir = URI.file(join(this.environmentService.logsPath, `output_${this.environmentService.configuration.windowId}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`)); this._outputDir = this.fileService.createFolder(outputDir).then(() => outputDir); } return this._outputDir; diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 6dab8c3f5b..42d541df73 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -8,11 +8,9 @@ import { parse } from 'vs/base/common/json'; import { Disposable } from 'vs/base/common/lifecycle'; import * as network from 'vs/base/common/network'; import { assign } from 'vs/base/common/objects'; -import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditOperation } from 'vs/editor/common/core/editOperation'; -import { IPosition, Position } from 'vs/editor/common/core/position'; +import { IPosition } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -28,7 +26,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { EditorInput, IEditor } from 'vs/workbench/common/editor'; +import { EditorInput, IEditorPane } from 'vs/workbench/common/editor'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { GroupDirection, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -39,6 +37,10 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { getDefaultValue, IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; const emptyEditableSettingsContent = '{\n}'; @@ -73,7 +75,8 @@ export class PreferencesService extends Disposable implements IPreferencesServic @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, @IModeService private readonly modeService: IModeService, @ILabelService private readonly labelService: ILabelService, - @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @ICommandService private readonly commandService: ICommandService, ) { super(); // The default keybindings.json updates based on keyboard layouts, so here we make sure @@ -189,15 +192,15 @@ export class PreferencesService extends Disposable implements IPreferencesServic return null; } - openRawDefaultSettings(): Promise { + openRawDefaultSettings(): Promise { return this.editorService.openEditor({ resource: this.defaultSettingsRawResource }); } - openRawUserSettings(): Promise { + openRawUserSettings(): Promise { return this.editorService.openEditor({ resource: this.userSettingsResource }); } - openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise { + openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -212,13 +215,13 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.openOrSwitchSettings(target, resource, { query: query }); } - private openSettings2(options?: ISettingsEditorOptions): Promise { + private openSettings2(options?: ISettingsEditorOptions): Promise { const input = this.settingsEditor2Input; return this.editorService.openEditor(input, options ? SettingsEditorOptions.create(options) : undefined) - .then(() => this.editorGroupService.activeGroup.activeControl!); + .then(() => this.editorGroupService.activeGroup.activeEditorPane!); } - openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -228,7 +231,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic this.openOrSwitchSettings2(ConfigurationTarget.USER_LOCAL, undefined, options, group); } - async openRemoteSettings(): Promise { + async openRemoteSettings(): Promise { const environment = await this.remoteAgentService.getEnvironment(); if (environment) { await this.createIfNotExists(environment.settingsPath, emptyEditableSettingsContent); @@ -237,7 +240,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return undefined; } - openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -252,7 +255,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic this.openOrSwitchSettings2(ConfigurationTarget.WORKSPACE, undefined, options, group); } - async openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + async openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -271,9 +274,9 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.doOpenSettings2(target, resource).then(() => undefined); } - const activeControl = this.editorService.activeControl; - if (activeControl && activeControl.input instanceof PreferencesEditorInput) { - return this.doSwitchSettings(target, resource, activeControl.input, activeControl.group).then(() => undefined); + const activeEditorPane = this.editorService.activeEditorPane; + if (activeEditorPane?.input instanceof PreferencesEditorInput) { + return this.doSwitchSettings(target, resource, activeEditorPane.input, activeEditorPane.group).then(() => undefined); } else { return this.doOpenSettings(target, resource).then(() => undefined); } @@ -307,29 +310,11 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.editorService.openEditor(this.instantiationService.createInstance(KeybindingsEditorInput), { pinned: true, revealIfOpened: true }).then(() => undefined); } - openDefaultKeybindingsFile(): Promise { + openDefaultKeybindingsFile(): Promise { return this.editorService.openEditor({ resource: this.defaultKeybindingsResource, label: nls.localize('defaultKeybindings', "Default Keybindings") }); } - configureSettingsForLanguage(language: string): void { - this.openGlobalSettings(true) - .then(editor => this.createPreferencesEditorModel(this.userSettingsResource) - .then((settingsModel: IPreferencesEditorModel | null) => { - const codeEditor = editor ? getCodeEditor(editor.getControl()) : null; - if (codeEditor && settingsModel) { - this.addLanguageOverrideEntry(language, settingsModel, codeEditor) - .then(position => { - if (codeEditor && position) { - codeEditor.setPosition(position); - codeEditor.revealLine(position.lineNumber); - codeEditor.focus(); - } - }); - } - })); - } - - private openOrSwitchSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { + private async openOrSwitchSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { const editorInput = this.getActiveSettingsEditorInput(group); if (editorInput) { const editorInputResource = editorInput.master.resource; @@ -337,14 +322,18 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.doSwitchSettings(configurationTarget, resource, editorInput, group, options); } } - return this.doOpenSettings(configurationTarget, resource, options, group); + const editor = await this.doOpenSettings(configurationTarget, resource, options, group); + if (editor && options?.editSetting) { + await this.editSetting(options?.editSetting, editor, resource); + } + return editor; } - private openOrSwitchSettings2(configurationTarget: ConfigurationTarget, folderUri?: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { + private openOrSwitchSettings2(configurationTarget: ConfigurationTarget, folderUri?: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { return this.doOpenSettings2(configurationTarget, folderUri, options, group); } - private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { const openSplitJSON = !!this.configurationService.getValue(USE_SPLIT_JSON_SETTING); if (openSplitJSON) { return this.doOpenSplitJSON(configurationTarget, resource, options, group); @@ -373,7 +362,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic }); } - private doOpenSplitJSON(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + private doOpenSplitJSON(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { return this.getOrCreateEditableSettingsEditorInput(configurationTarget, resource) .then(editableSettingsEditorInput => { if (!options) { @@ -393,7 +382,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.instantiationService.createInstance(Settings2EditorModel, this.getDefaultSettings(ConfigurationTarget.USER_LOCAL)); } - private doOpenSettings2(target: ConfigurationTarget, folderUri: URI | undefined, options?: IEditorOptions, group?: IEditorGroup): Promise { + private doOpenSettings2(target: ConfigurationTarget, folderUri: URI | undefined, options?: IEditorOptions, group?: IEditorGroup): Promise { const input = this.settingsEditor2Input; const settingsOptions: ISettingsEditorOptions = { ...options, @@ -404,7 +393,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.editorService.openEditor(input, SettingsEditorOptions.create(settingsOptions), group); } - private async doSwitchSettings(target: ConfigurationTarget, resource: URI, input: PreferencesEditorInput, group: IEditorGroup, options?: ISettingsEditorOptions): Promise { + private async doSwitchSettings(target: ConfigurationTarget, resource: URI, input: PreferencesEditorInput, group: IEditorGroup, options?: ISettingsEditorOptions): Promise { const settingsURI = await this.getEditableSettingsURI(target, resource); if (!settingsURI) { return Promise.reject(`Invalid settings URI - ${resource.toString()}`); @@ -420,7 +409,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic options: options ? SettingsEditorOptions.create(options) : undefined }]).then(() => { this.lastOpenedSettingsInput = replaceWith; - return group.activeControl!; + return group.activeEditorPane!; }); }); }); @@ -489,7 +478,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic private getOrCreateEditableSettingsEditorInput(target: ConfigurationTarget, resource: URI): Promise { return this.createSettingsIfNotExists(target, resource) - .then(() => this.editorService.createInput({ resource })); + .then(() => this.editorService.createEditorInput({ resource })); } private createEditableSettingsEditorModel(configurationTarget: ConfigurationTarget, settingsUri: URI): Promise { @@ -593,39 +582,62 @@ export class PreferencesService extends Disposable implements IPreferencesServic ]; } - private addLanguageOverrideEntry(language: string, settingsModel: IPreferencesEditorModel, codeEditor: ICodeEditor): Promise { - const languageKey = `[${language}]`; - let setting = settingsModel.getPreference(languageKey); - const model = codeEditor.getModel(); - if (model) { - const configuration = this.configurationService.getValue<{ editor: { tabSize: number; insertSpaces: boolean } }>(); - const eol = model.getEOL(); - if (setting) { - if (setting.overrides && setting.overrides.length) { - const lastSetting = setting.overrides[setting.overrides.length - 1]; - return Promise.resolve({ lineNumber: lastSetting.valueRange.endLineNumber, column: model.getLineMaxColumn(lastSetting.valueRange.endLineNumber) }); - } - return Promise.resolve({ lineNumber: setting.valueRange.startLineNumber, column: setting.valueRange.startColumn + 1 }); - } - return this.configurationService.updateValue(languageKey, {}, ConfigurationTarget.USER) - .then(() => { - setting = settingsModel.getPreference(languageKey); - if (setting) { - let content = eol + this.spaces(2, configuration.editor) + eol + this.spaces(1, configuration.editor); - let editOperation = EditOperation.insert(new Position(setting.valueRange.endLineNumber, setting.valueRange.endColumn - 1), content); - model.pushEditOperations([], [editOperation], () => []); - let lineNumber = setting.valueRange.endLineNumber + 1; - settingsModel.dispose(); - return { lineNumber, column: model.getLineMaxColumn(lineNumber) }; - } - return null; - }); + private async editSetting(settingKey: string, editor: IEditorPane, settingsResource: URI): Promise { + const codeEditor = editor ? getCodeEditor(editor.getControl()) : null; + if (!codeEditor) { + return; + } + const settingsModel = await this.createPreferencesEditorModel(settingsResource); + if (!settingsModel) { + return; + } + const position = await this.getPositionToEdit(settingKey, settingsModel, codeEditor); + if (position) { + codeEditor.setPosition(position); + codeEditor.revealPositionNearTop(position); + codeEditor.focus(); + await this.commandService.executeCommand('editor.action.triggerSuggest'); } - return Promise.resolve(null); } - private spaces(count: number, { tabSize, insertSpaces }: { tabSize: number; insertSpaces: boolean }): string { - return insertSpaces ? strings.repeat(' ', tabSize * count) : strings.repeat('\t', count); + private async getPositionToEdit(settingKey: string, settingsModel: IPreferencesEditorModel, codeEditor: ICodeEditor): Promise { + const model = codeEditor.getModel(); + if (!model) { + return null; + } + const schema = Registry.as(Extensions.Configuration).getConfigurationProperties()[settingKey]; + if (!schema && !OVERRIDE_PROPERTY_PATTERN.test(settingKey)) { + return null; + } + + let position = null; + const type = schema ? schema.type : 'object' /* Override Identifier */; + let setting = settingsModel.getPreference(settingKey); + if (!setting) { + const defaultValue = type === 'array' ? this.configurationService.inspect(settingKey).defaultValue : getDefaultValue(type); + if (defaultValue !== undefined) { + await this.jsonEditingService.write(settingsModel.uri!, [{ key: settingKey, value: defaultValue }], false); + setting = settingsModel.getPreference(settingKey); + } + } + + if (setting) { + position = { lineNumber: setting.valueRange.startLineNumber, column: setting.valueRange.startColumn + 1 }; + if (type === 'object' || type === 'array') { + codeEditor.setPosition(position); + await CoreEditingCommands.LineBreakInsert.runEditorCommand(null, codeEditor, null); + position = { lineNumber: position.lineNumber + 1, column: model.getLineMaxColumn(position.lineNumber + 1) }; + const firstNonWhiteSpaceColumn = model.getLineFirstNonWhitespaceColumn(position.lineNumber); + if (firstNonWhiteSpaceColumn) { + // Line has some text. Insert another new line. + codeEditor.setPosition({ lineNumber: position.lineNumber, column: firstNonWhiteSpaceColumn }); + await CoreEditingCommands.LineBreakInsert.runEditorCommand(null, codeEditor, null); + position = { lineNumber: position.lineNumber, column: model.getLineMaxColumn(position.lineNumber) }; + } + } + } + + return position; } public dispose(): void { diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 0f05f4ae6a..0282ecb737 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -14,7 +14,7 @@ import { ConfigurationScope, IConfigurationExtensionInfo } from 'vs/platform/con import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { EditorOptions, IEditor } from 'vs/workbench/common/editor'; +import { EditorOptions, IEditorPane } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; @@ -155,6 +155,7 @@ export interface ISettingsEditorOptions extends IEditorOptions { target?: ConfigurationTarget; folderUri?: URI; query?: string; + editSetting?: string; } /** @@ -165,6 +166,7 @@ export class SettingsEditorOptions extends EditorOptions implements ISettingsEdi target?: ConfigurationTarget; folderUri?: URI; query?: string; + editSetting?: string; static create(settings: ISettingsEditorOptions): SettingsEditorOptions { const options = new SettingsEditorOptions(); @@ -173,6 +175,7 @@ export class SettingsEditorOptions extends EditorOptions implements ISettingsEdi options.target = settings.target; options.folderUri = settings.folderUri; options.query = settings.query; + options.editSetting = settings.editSetting; return options; } @@ -194,17 +197,15 @@ export interface IPreferencesService { createPreferencesEditorModel(uri: URI): Promise | null>; createSettings2EditorModel(): Settings2EditorModel; // TODO - openRawDefaultSettings(): Promise; - openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise; - openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; - openRemoteSettings(): Promise; - openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; - openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; + openRawDefaultSettings(): Promise; + openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise; + openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; + openRemoteSettings(): Promise; + openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; + openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; switchSettings(target: ConfigurationTarget, resource: URI, jsonEditor?: boolean): Promise; openGlobalKeybindingSettings(textual: boolean): Promise; - openDefaultKeybindingsFile(): Promise; - - configureSettingsForLanguage(language: string | null): void; + openDefaultKeybindingsFile(): Promise; } export function getSettingsTargetName(target: ConfigurationTarget, resource: URI, workspaceContextService: IWorkspaceContextService): string { diff --git a/src/vs/workbench/services/progress/browser/progressIndicator.ts b/src/vs/workbench/services/progress/browser/progressIndicator.ts index 6782047344..f5f1129881 100644 --- a/src/vs/workbench/services/progress/browser/progressIndicator.ts +++ b/src/vs/workbench/services/progress/browser/progressIndicator.ts @@ -10,6 +10,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IProgressRunner, IProgressIndicator, emptyProgressRunner } from 'vs/platform/progress/common/progress'; import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; +import { IViewsService } from 'vs/workbench/common/views'; export class ProgressBarIndicator extends Disposable implements IProgressIndicator { @@ -45,7 +46,7 @@ export class ProgressBarIndicator extends Disposable implements IProgressIndicat }; } - async showWhile(promise: Promise, delay?: number): Promise { + async showWhile(promise: Promise, delay?: number): Promise { try { this.progressbar.infinite().show(delay); @@ -92,7 +93,7 @@ export class EditorProgressIndicator extends ProgressBarIndicator { return super.show(infiniteOrTotal, delay); } - async showWhile(promise: Promise, delay?: number): Promise { + async showWhile(promise: Promise, delay?: number): Promise { // No editor open: ignore any progress reporting if (this.group.isEmpty) { @@ -125,7 +126,7 @@ namespace ProgressIndicatorState { readonly type = Type.While; constructor( - readonly whilePromise: Promise, + readonly whilePromise: Promise, readonly whileStart: number, readonly whileDelay: number, ) { } @@ -153,6 +154,7 @@ export abstract class CompositeScope extends Disposable { constructor( private viewletService: IViewletService, private panelService: IPanelService, + private viewsService: IViewsService, private scopeId: string ) { super(); @@ -161,6 +163,8 @@ export abstract class CompositeScope extends Disposable { } registerListeners(): void { + this._register(this.viewsService.onDidChangeViewVisibility(e => e.visible ? this.onScopeOpened(e.id) : this.onScopeClosed(e.id))); + this._register(this.viewletService.onDidViewletOpen(viewlet => this.onScopeOpened(viewlet.getId()))); this._register(this.panelService.onDidPanelOpen(({ panel }) => this.onScopeOpened(panel.getId()))); @@ -194,10 +198,12 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr progressbar: ProgressBar, scopeId: string, isActive: boolean, + private readonly options: { exclusiveProgressBar?: boolean } | undefined, @IViewletService viewletService: IViewletService, - @IPanelService panelService: IPanelService + @IPanelService panelService: IPanelService, + @IViewsService viewsService: IViewsService ) { - super(viewletService, panelService, scopeId); + super(viewletService, panelService, viewsService, scopeId); this.progressbar = progressbar; this.isActive = isActive || isUndefinedOrNull(scopeId); // If service is unscoped, enable by default @@ -205,6 +211,10 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr onScopeDeactivated(): void { this.isActive = false; + + if (this.options?.exclusiveProgressBar) { + this.progressbar.stop().hide(); + } } onScopeActivated(): void { @@ -304,14 +314,14 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr done: () => { this.progressState = ProgressIndicatorState.Done; - if (this.isActive) { + if (this.isActive || this.options?.exclusiveProgressBar) { this.progressbar.stop().hide(); } } }; } - async showWhile(promise: Promise, delay?: number): Promise { + async showWhile(promise: Promise, delay?: number): Promise { // Join with existing running promise to ensure progress is accurate if (this.progressState.type === ProgressIndicatorState.Type.While) { @@ -335,7 +345,7 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr // The while promise is either null or equal the promise we last hooked on this.progressState = ProgressIndicatorState.None; - if (this.isActive) { + if (this.isActive || this.options?.exclusiveProgressBar) { this.progressbar.stop().hide(); } } diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 7e1b563f6a..867940b13a 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -6,10 +6,10 @@ import 'vs/css!./media/progressService'; import { localize } from 'vs/nls'; -import { IDisposable, dispose, DisposableStore, MutableDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, DisposableStore, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions, IProgressRunner, IProgressIndicator, IProgressWindowOptions } from 'vs/platform/progress/common/progress'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { StatusbarAlignment, IStatusbarService } from 'vs/workbench/services/statusbar/common/statusbar'; +import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { timeout } from 'vs/base/common/async'; import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity'; import { INotificationService, Severity, INotificationHandle } from 'vs/platform/notification/common/notification'; @@ -25,17 +25,17 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventHelper } from 'vs/base/browser/dom'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { parseLinkedText } from 'vs/base/common/linkedText'; +import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; export class ProgressService extends Disposable implements IProgressService { _serviceBrand: undefined; - private readonly stack: [IProgressOptions, Progress][] = []; - private readonly globalStatusEntry = this._register(new MutableDisposable()); - constructor( @IActivityService private readonly activityService: IActivityService, @IViewletService private readonly viewletService: IViewletService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IViewsService private readonly viewsService: IViewsService, @IPanelService private readonly panelService: IPanelService, @INotificationService private readonly notificationService: INotificationService, @IStatusbarService private readonly statusbarService: IStatusbarService, @@ -57,6 +57,10 @@ export class ProgressService extends Disposable implements IProgressService { return this.withPanelProgress(location, task, { ...options, location }); } + if (this.viewsService.getProgressIndicator(location)) { + return this.withViewProgress(location, task, { ...options, location }); + } + throw new Error(`Bad progress location: ${location}`); } @@ -78,6 +82,9 @@ export class ProgressService extends Disposable implements IProgressService { } } + private readonly windowProgressStack: [IProgressOptions, Progress][] = []; + private windowProgressStatusEntry: IStatusbarEntryAccessor | undefined = undefined; + private withWindowProgress(options: IProgressWindowOptions, callback: (progress: IProgress<{ message?: string }>) => Promise): Promise { const task: [IProgressWindowOptions, Progress] = [options, new Progress(() => this.updateWindowProgress())]; @@ -85,7 +92,7 @@ export class ProgressService extends Disposable implements IProgressService { let delayHandle: any = setTimeout(() => { delayHandle = undefined; - this.stack.unshift(task); + this.windowProgressStack.unshift(task); this.updateWindowProgress(); // show progress for at least 150ms @@ -93,8 +100,8 @@ export class ProgressService extends Disposable implements IProgressService { timeout(150), promise ]).finally(() => { - const idx = this.stack.indexOf(task); - this.stack.splice(idx, 1); + const idx = this.windowProgressStack.indexOf(task); + this.windowProgressStack.splice(idx, 1); this.updateWindowProgress(); }); }, 150); @@ -104,10 +111,10 @@ export class ProgressService extends Disposable implements IProgressService { } private updateWindowProgress(idx: number = 0) { - this.globalStatusEntry.clear(); - if (idx < this.stack.length) { - const [options, progress] = this.stack[idx]; + // We still have progress to show + if (idx < this.windowProgressStack.length) { + const [options, progress] = this.windowProgressStack[idx]; let progressTitle = options.title; let progressMessage = progress.value && progress.value.message; @@ -136,11 +143,23 @@ export class ProgressService extends Disposable implements IProgressService { return; } - this.globalStatusEntry.value = this.statusbarService.addEntry({ + const statusEntryProperties: IStatusbarEntry = { text: `$(sync~spin) ${text}`, tooltip: title, command: progressCommand - }, 'status.progress', localize('status.progress', "Progress Message"), StatusbarAlignment.LEFT); + }; + + if (this.windowProgressStatusEntry) { + this.windowProgressStatusEntry.update(statusEntryProperties); + } else { + this.windowProgressStatusEntry = this.statusbarService.addEntry(statusEntryProperties, 'status.progress', localize('status.progress', "Progress Message"), StatusbarAlignment.LEFT); + } + } + + // Progress is done so we remove the status entry + else { + this.windowProgressStatusEntry?.dispose(); + this.windowProgressStatusEntry = undefined; } } @@ -245,7 +264,7 @@ export class ProgressService extends Disposable implements IProgressService { super(`progress.button.${button}`, button, undefined, true); } - async run(): Promise { + async run(): Promise { progressStateModel.cancel(index); } }; @@ -261,7 +280,7 @@ export class ProgressService extends Disposable implements IProgressService { super('progress.cancel', localize('cancel', "Cancel"), undefined, true); } - async run(): Promise { + async run(): Promise { progressStateModel.cancel(); } }; @@ -364,18 +383,38 @@ export class ProgressService extends Disposable implements IProgressService { // show in viewlet const promise = this.withCompositeProgress(this.viewletService.getProgressIndicator(viewletId), task, options); - // show activity bar + // show on activity bar + this.showOnActivityBar(viewletId, options, promise); + + return promise; + } + + private withViewProgress

, R = unknown>(viewId: string, task: (progress: IProgress) => P, options: IProgressCompositeOptions): P { + + // show in viewlet + const promise = this.withCompositeProgress(this.viewsService.getProgressIndicator(viewId), task, options); + + const location = this.viewDescriptorService.getViewLocation(viewId); + if (location !== ViewContainerLocation.Sidebar) { + return promise; + } + + const viewletId = this.viewDescriptorService.getViewContainer(viewId)?.id; + if (viewletId === undefined) { + return promise; + } + + // show on activity bar + this.showOnActivityBar(viewletId, options, promise); + + return promise; + } + + private showOnActivityBar

, R = unknown>(viewletId: string, options: IProgressCompositeOptions, promise: P) { let activityProgress: IDisposable; let delayHandle: any = setTimeout(() => { delayHandle = undefined; - - const handle = this.activityService.showActivity( - viewletId, - new ProgressBadge(() => ''), - 'progress-badge', - 100 - ); - + const handle = this.activityService.showActivity(viewletId, new ProgressBadge(() => ''), 'progress-badge', 100); const startTimeVisible = Date.now(); const minTimeVisible = 300; activityProgress = { @@ -391,13 +430,10 @@ export class ProgressService extends Disposable implements IProgressService { } }; }, options.delay || 300); - promise.finally(() => { clearTimeout(delayHandle); dispose(activityProgress); }); - - return promise; } private withPanelProgress

, R = unknown>(panelid: string, task: (progress: IProgress) => P, options: IProgressCompositeOptions): P { diff --git a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts index a87ac95e3c..c68f6fec71 100644 --- a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts @@ -10,9 +10,9 @@ import { CompositeScope, CompositeProgressIndicator } from 'vs/workbench/service import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewlet } from 'vs/workbench/common/viewlet'; -import { TestViewletService, TestPanelService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestViewletService, TestPanelService, TestViewsService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Event } from 'vs/base/common/event'; -import { IView, IViewPaneContainer } from 'vs/workbench/common/views'; +import { IView, IViewPaneContainer, IViewsService } from 'vs/workbench/common/views'; class TestViewlet implements IViewlet { @@ -38,8 +38,8 @@ class TestViewlet implements IViewlet { class TestCompositeScope extends CompositeScope { isActive: boolean = false; - constructor(viewletService: IViewletService, panelService: IPanelService, scopeId: string) { - super(viewletService, panelService, scopeId); + constructor(viewletService: IViewletService, panelService: IPanelService, viewsService: IViewsService, scopeId: string) { + super(viewletService, panelService, viewsService, scopeId); } onScopeActivated() { this.isActive = true; } @@ -106,7 +106,8 @@ suite('Progress Indicator', () => { test('CompositeScope', () => { let viewletService = new TestViewletService(); let panelService = new TestPanelService(); - let service = new TestCompositeScope(viewletService, panelService, 'test.scopeId'); + let viewsService = new TestViewsService(); + let service = new TestCompositeScope(viewletService, panelService, viewsService, 'test.scopeId'); const testViewlet = new TestViewlet('test.scopeId'); assert(!service.isActive); @@ -116,13 +117,19 @@ suite('Progress Indicator', () => { viewletService.onDidViewletCloseEmitter.fire(testViewlet); assert(!service.isActive); + viewsService.onDidChangeViewVisibilityEmitter.fire({ id: 'test.scopeId', visible: true }); + assert(service.isActive); + + viewsService.onDidChangeViewVisibilityEmitter.fire({ id: 'test.scopeId', visible: false }); + assert(!service.isActive); }); test('CompositeProgressIndicator', async () => { let testProgressBar = new TestProgressBar(); let viewletService = new TestViewletService(); let panelService = new TestPanelService(); - let service = new CompositeProgressIndicator((testProgressBar), 'test.scopeId', true, viewletService, panelService); + let viewsService = new TestViewsService(); + let service = new CompositeProgressIndicator((testProgressBar), 'test.scopeId', true, undefined, viewletService, panelService, viewsService); // Active: Show (Infinite) let fn = service.show(true); @@ -169,5 +176,19 @@ suite('Progress Indicator', () => { assert.strictEqual(true, testProgressBar.fDone); viewletService.onDidViewletOpenEmitter.fire(testViewlet); assert.strictEqual(true, testProgressBar.fDone); + + // Visible view: Show (Infinite) + viewsService.onDidChangeViewVisibilityEmitter.fire({ id: 'test.scopeId', visible: true }); + fn = service.show(true); + assert.strictEqual(true, testProgressBar.fInfinite); + fn.done(); + assert.strictEqual(true, testProgressBar.fDone); + + // Hidden view: Show (Infinite) + viewsService.onDidChangeViewVisibilityEmitter.fire({ id: 'test.scopeId', visible: false }); + service.show(true); + assert.strictEqual(false, !!testProgressBar.fInfinite); + viewsService.onDidChangeViewVisibilityEmitter.fire({ id: 'test.scopeId', visible: true }); + assert.strictEqual(true, testProgressBar.fInfinite); }); }); diff --git a/src/vs/workbench/services/quickinput/browser/quickInputService.ts b/src/vs/workbench/services/quickinput/browser/quickInputService.ts new file mode 100644 index 0000000000..015be57bdd --- /dev/null +++ b/src/vs/workbench/services/quickinput/browser/quickInputService.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { QuickInputController } from 'vs/base/parts/quickinput/browser/quickInput'; +import { QuickInputService as BaseQuickInputService } from 'vs/platform/quickinput/browser/quickInput'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; + +export class QuickInputService extends BaseQuickInputService { + + constructor( + @IEnvironmentService private environmentService: IEnvironmentService, + @IConfigurationService private configurationService: IConfigurationService, + @IInstantiationService instantiationService: IInstantiationService, + @IKeybindingService private keybindingService: IKeybindingService, + @IContextKeyService contextKeyService: IContextKeyService, + @IThemeService themeService: IThemeService, + @IAccessibilityService accessibilityService: IAccessibilityService, + @ILayoutService protected layoutService: ILayoutService + ) { + super(instantiationService, contextKeyService, themeService, accessibilityService, layoutService); + } + + protected createController(): QuickInputController { + return super.createController(this.layoutService, { + ignoreFocusOut: () => this.environmentService.args['sticky-quickopen'] || !this.configurationService.getValue('workbench.quickOpen.closeOnFocusLost'), + backKeybindingLabel: () => this.keybindingService.lookupKeybinding('workbench.action.quickInputBack')?.getLabel() || undefined, + }); + } +} + +registerSingleton(IQuickInputService, QuickInputService, true); diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index 64c4e05d0d..3c2d467a5a 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -164,7 +164,7 @@ class RemoteConnectionFailureNotificationContribution implements IWorkbenchContr // Let's cover the case where connecting to fetch the remote extension info fails remoteAgentService.getEnvironment(true) .then(undefined, err => { - if (!RemoteAuthorityResolverError.isHandledNotAvailable(err)) { + if (!RemoteAuthorityResolverError.isHandled(err)) { notificationService.error(nls.localize('connectionError', "Failed to connect to the remote extension host server (Error: {0})", err ? err.message : '')); } }); diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index f214f5b5ff..961d8a180c 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -29,6 +29,7 @@ export interface ITunnelItem { remoteHost: string; remotePort: number; localAddress?: string; + localPort?: number; name?: string; closeable?: boolean; description?: string; @@ -114,11 +115,9 @@ export class TunnelModel extends Disposable { this._onClosePort.fire(address); } })); - - this.restoreForwarded(); } - private async restoreForwarded() { + async restoreForwarded() { if (this.configurationService.getValue('remote.restoreForwardedPorts')) { const tunnelsString = this.storageService.get(TUNNELS_TO_RESTORE, StorageScope.WORKSPACE); if (tunnelsString) { @@ -181,7 +180,7 @@ export class TunnelModel extends Disposable { this.detected.set(MakeAddress(tunnel.remoteAddress.host, tunnel.remoteAddress.port), { remoteHost: tunnel.remoteAddress.host, remotePort: tunnel.remoteAddress.port, - localAddress: tunnel.localAddress, + localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : MakeAddress(tunnel.localAddress.host, tunnel.localAddress.port), closeable: false }); }); @@ -238,6 +237,7 @@ export interface IRemoteExplorerService { registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void; setCandidateFilter(filter: ((candidates: { host: string, port: number, detail: string }[]) => Promise<{ host: string, port: number, detail: string }[]>) | undefined): IDisposable; refresh(): Promise; + restore(): Promise; } class RemoteExplorerService implements IRemoteExplorerService { @@ -328,6 +328,10 @@ class RemoteExplorerService implements IRemoteExplorerService { refresh(): Promise { return this.tunnelModel.refresh(); } + + restore(): Promise { + return this.tunnelModel.restoreForwarded(); + } } registerSingleton(IRemoteExplorerService, RemoteExplorerService, true); diff --git a/src/vs/workbench/services/remote/common/tunnelService.ts b/src/vs/workbench/services/remote/common/tunnelService.ts index d558a6f625..d6d15895e6 100644 --- a/src/vs/workbench/services/remote/common/tunnelService.ts +++ b/src/vs/workbench/services/remote/common/tunnelService.ts @@ -101,7 +101,7 @@ export abstract class AbstractTunnelService implements ITunnelService { private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise }): Promise { if (tunnel.refcount <= 0) { const disposePromise: Promise = tunnel.value.then(tunnel => { - tunnel.dispose(); + tunnel.dispose(true); this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }); }); if (this._tunnels.has(remoteHost)) { diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 26c04cb55f..fed5288272 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -145,7 +145,7 @@ export class TunnelService extends AbstractTunnelService { } if (this._tunnelProvider) { - const tunnel = this._tunnelProvider.forwardPort({ remoteAddress: { host: remoteHost, port: remotePort } }); + const tunnel = this._tunnelProvider.forwardPort({ remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort }); if (tunnel) { this.addTunnelToMap(remoteHost, remotePort, tunnel); } diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 69c5f32a93..76c3e0f191 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -9,7 +9,7 @@ import * as glob from 'vs/base/common/glob'; import { IDisposable } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import * as extpath from 'vs/base/common/extpath'; -import { getNLines } from 'vs/base/common/strings'; +import { fuzzyContains, getNLines } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IFilesConfiguration } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -50,6 +50,7 @@ export interface ISearchResultProvider { export interface IFolderQuery { folder: U; + folderName?: string; excludePattern?: glob.IExpression; includePattern?: glob.IExpression; fileEncoding?: string; @@ -437,9 +438,19 @@ export interface IRawSearchService { export interface IRawFileMatch { base?: string; + /** + * The path of the file relative to the containing `base` folder. + * This path is exactly as it appears on the filesystem. + */ relativePath: string; - basename: string; - size?: number; + /** + * This path is transformed for search purposes. For example, this could be + * the `relativePath` with the workspace folder name prepended. This way the + * search algorithm would also match against the name of the containing folder. + * + * If not given, the search algorithm should use `relativePath`. + */ + searchPath?: string; } export interface ISearchEngine { @@ -486,6 +497,11 @@ export function isSerializedFileMatch(arg: ISerializedSearchProgressItem): arg i return !!(arg).path; } +export function isFilePatternMatch(candidate: IRawFileMatch, normalizedFilePatternLowercase: string): boolean { + const pathToMatch = candidate.searchPath ? candidate.searchPath : candidate.relativePath; + return fuzzyContains(pathToMatch, normalizedFilePatternLowercase); +} + export interface ISerializedFileMatch { path: string; results?: ITextSearchResult[]; diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index 72e7dc410b..502ad3132e 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -20,6 +20,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType, isFileMatch, isProgressMessage } from 'vs/workbench/services/search/common/search'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { DeferredPromise } from 'vs/base/test/common/utils'; export class SearchService extends Disposable implements ISearchService { @@ -29,6 +30,9 @@ export class SearchService extends Disposable implements ISearchService { private readonly fileSearchProviders = new Map(); private readonly textSearchProviders = new Map(); + private deferredFileSearchesByScheme = new Map>(); + private deferredTextSearchesByScheme = new Map>(); + constructor( private readonly modelService: IModelService, private readonly editorService: IEditorService, @@ -42,16 +46,24 @@ export class SearchService extends Disposable implements ISearchService { registerSearchResultProvider(scheme: string, type: SearchProviderType, provider: ISearchResultProvider): IDisposable { let list: Map; + let deferredMap: Map>; if (type === SearchProviderType.file) { list = this.fileSearchProviders; + deferredMap = this.deferredFileSearchesByScheme; } else if (type === SearchProviderType.text) { list = this.textSearchProviders; + deferredMap = this.deferredTextSearchesByScheme; } else { throw new Error('Unknown SearchProviderType'); } list.set(scheme, provider); + if (deferredMap.has(scheme)) { + deferredMap.get(scheme)!.complete(provider); + deferredMap.delete(scheme); + } + return toDisposable(() => { list.delete(scheme); }); @@ -62,13 +74,13 @@ export class SearchService extends Disposable implements ISearchService { const localResults = this.getLocalResults(query); if (onProgress) { - arrays.coalesce(localResults.values()).forEach(onProgress); + arrays.coalesce(localResults.results.values()).forEach(onProgress); } const onProviderProgress = (progress: ISearchProgressItem) => { if (isFileMatch(progress)) { // Match - if (!localResults.has(progress.resource) && onProgress) { // don't override local results + if (!localResults.results.has(progress.resource) && onProgress) { // don't override local results onProgress(progress); } } else if (onProgress) { @@ -84,7 +96,10 @@ export class SearchService extends Disposable implements ISearchService { const otherResults = await this.doSearch(query, token, onProviderProgress); return { ...otherResults, - results: [...otherResults.results, ...arrays.coalesce(localResults.values())] + ...{ + limitHit: otherResults.limitHit || localResults.limitHit + }, + results: [...otherResults.results, ...arrays.coalesce(localResults.results.values())] }; } @@ -161,24 +176,41 @@ export class SearchService extends Disposable implements ISearchService { return schemes; } - private searchWithProviders(query: ISearchQuery, onProviderProgress: (progress: ISearchProgressItem) => void, token?: CancellationToken) { + private async waitForProvider(queryType: QueryType, scheme: string): Promise { + let deferredMap: Map> = queryType === QueryType.File ? + this.deferredFileSearchesByScheme : + this.deferredTextSearchesByScheme; + + if (deferredMap.has(scheme)) { + return deferredMap.get(scheme)!.p; + } else { + const deferred = new DeferredPromise(); + deferredMap.set(scheme, deferred); + return deferred.p; + } + } + + private async searchWithProviders(query: ISearchQuery, onProviderProgress: (progress: ISearchProgressItem) => void, token?: CancellationToken) { const e2eSW = StopWatch.create(false); const diskSearchQueries: IFolderQuery[] = []; const searchPs: Promise[] = []; const fqs = this.groupFolderQueriesByScheme(query); - keys(fqs).forEach(scheme => { + await Promise.all(keys(fqs).map(async scheme => { const schemeFQs = fqs.get(scheme)!; - const provider = query.type === QueryType.File ? + let provider = query.type === QueryType.File ? this.fileSearchProviders.get(scheme) : this.textSearchProviders.get(scheme); if (!provider && scheme === 'file') { diskSearchQueries.push(...schemeFQs); - } else if (!provider) { - console.warn('No search provider registered for scheme: ' + scheme); } else { + if (!provider) { + console.warn(`No search provider registered for scheme: ${scheme}, waiting`); + provider = await this.waitForProvider(query.type, scheme); + } + const oneSchemeQuery: ISearchQuery = { ...query, ...{ @@ -190,7 +222,7 @@ export class SearchService extends Disposable implements ISearchService { provider.fileSearch(oneSchemeQuery, token) : provider.textSearch(oneSchemeQuery, onProviderProgress, token)); } - }); + })); const diskSearchExtraFileResources = query.extraFileResources && query.extraFileResources.filter(res => res.scheme === Schemas.file); @@ -378,8 +410,9 @@ export class SearchService extends Disposable implements ISearchService { } } - private getLocalResults(query: ITextQuery): ResourceMap { + private getLocalResults(query: ITextQuery): { results: ResourceMap; limitHit: boolean } { const localResults = new ResourceMap(); + let limitHit = false; if (query.type === QueryType.Text) { const models = this.modelService.getModels(); @@ -389,8 +422,12 @@ export class SearchService extends Disposable implements ISearchService { return; } + if (limitHit) { + return; + } + // Skip files that are not opened as text file - if (!this.editorService.isOpen(this.editorService.createInput({ resource, forceFile: resource.scheme !== Schemas.untitled, forceUntitled: resource.scheme === Schemas.untitled }))) { + if (!this.editorService.isOpen({ resource })) { return; } @@ -415,8 +452,14 @@ export class SearchService extends Disposable implements ISearchService { } // Use editor API to find matches - const matches = model.findMatches(query.contentPattern.pattern, false, !!query.contentPattern.isRegExp, !!query.contentPattern.isCaseSensitive, query.contentPattern.isWordMatch ? query.contentPattern.wordSeparators! : null, false, query.maxResults); + const askMax = typeof query.maxResults === 'number' ? query.maxResults + 1 : undefined; + let matches = model.findMatches(query.contentPattern.pattern, false, !!query.contentPattern.isRegExp, !!query.contentPattern.isCaseSensitive, query.contentPattern.isWordMatch ? query.contentPattern.wordSeparators! : null, false, askMax); if (matches.length) { + if (askMax && matches.length >= askMax) { + limitHit = true; + matches = matches.slice(0, askMax - 1); + } + const fileMatch = new FileMatch(resource); localResults.set(resource, fileMatch); @@ -428,7 +471,10 @@ export class SearchService extends Disposable implements ISearchService { }); } - return localResults; + return { + results: localResults, + limitHit + }; } private matches(resource: uri, query: ITextQuery): boolean { diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index a02b4f9d9b..7ff67feb55 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -20,9 +20,9 @@ import * as strings from 'vs/base/common/strings'; import * as types from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { readdir } from 'vs/base/node/pfs'; -import { IFileQuery, IFolderQuery, IProgressMessage, ISearchEngineStats, IRawFileMatch, ISearchEngine, ISearchEngineSuccess } from 'vs/workbench/services/search/common/search'; +import { IFileQuery, IFolderQuery, IProgressMessage, ISearchEngineStats, IRawFileMatch, ISearchEngine, ISearchEngineSuccess, isFilePatternMatch } from 'vs/workbench/services/search/common/search'; import { spawnRipgrepCmd } from './ripgrepFileSearch'; -import { prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; +import { prepareQuery } from 'vs/base/common/fuzzyScorer'; interface IDirectoryEntry { base: string; @@ -122,7 +122,7 @@ export class FileWalker { } // File: Check for match on file pattern and include pattern - this.matchFile(onResult, { relativePath: extraFilePath.fsPath /* no workspace relative path */, basename }); + this.matchFile(onResult, { relativePath: extraFilePath.fsPath /* no workspace relative path */ }); }); this.cmdSW = StopWatch.create(false); @@ -246,8 +246,7 @@ export class FileWalker { if (noSiblingsClauses) { for (const relativePath of relativeFiles) { - const basename = path.basename(relativePath); - this.matchFile(onResult, { base: rootFolder, relativePath, basename }); + this.matchFile(onResult, { base: rootFolder, relativePath, searchPath: this.getSearchPath(folderQuery, relativePath) }); if (this.isLimitHit) { killCmd(); break; @@ -393,8 +392,7 @@ export class FileWalker { private addDirectoryEntries({ pathToEntries }: IDirectoryTree, base: string, relativeFiles: string[], onResult: (result: IRawFileMatch) => void) { // Support relative paths to files from a root resource (ignores excludes) if (relativeFiles.indexOf(this.filePattern) !== -1) { - const basename = path.basename(this.filePattern); - this.matchFile(onResult, { base: base, relativePath: this.filePattern, basename }); + this.matchFile(onResult, { base: base, relativePath: this.filePattern }); } function add(relativePath: string) { @@ -540,7 +538,11 @@ export class FileWalker { return clb(null, undefined); // ignore file if max file size is hit } - this.matchFile(onResult, { base: rootFolder.fsPath, relativePath: currentRelativePath, basename: file, size: stat.size }); + this.matchFile(onResult, { + base: rootFolder.fsPath, + relativePath: currentRelativePath, + searchPath: this.getSearchPath(folderQuery, currentRelativePath), + }); } // Unwind @@ -554,7 +556,7 @@ export class FileWalker { } private matchFile(onResult: (result: IRawFileMatch) => void, candidate: IRawFileMatch): void { - if (this.isFilePatternMatch(candidate.relativePath) && (!this.includePattern || this.includePattern(candidate.relativePath, candidate.basename))) { + if (this.isFileMatch(candidate) && (!this.includePattern || this.includePattern(candidate.relativePath, path.basename(candidate.relativePath)))) { this.resultCount++; if (this.exists || (this.maxResults && this.resultCount > this.maxResults)) { @@ -567,8 +569,7 @@ export class FileWalker { } } - private isFilePatternMatch(path: string): boolean { - + private isFileMatch(candidate: IRawFileMatch): boolean { // Check for search pattern if (this.filePattern) { if (this.filePattern === '*') { @@ -576,7 +577,7 @@ export class FileWalker { } if (this.normalizedFilePatternLowercase) { - return strings.fuzzyContains(path, this.normalizedFilePatternLowercase); + return isFilePatternMatch(candidate, this.normalizedFilePatternLowercase); } } @@ -605,6 +606,19 @@ export class FileWalker { return clb(null, path); } + + /** + * If we're searching for files in multiple workspace folders, then better prepend the + * name of the workspace folder to the path of the file. This way we'll be able to + * better filter files that are all on the top of a workspace folder and have all the + * same name. A typical example are `package.json` or `README.md` files. + */ + private getSearchPath(folderQuery: IFolderQuery, relativePath: string): string { + if (folderQuery.folderName) { + return path.join(folderQuery.folderName, relativePath); + } + return relativePath; + } } export class Engine implements ISearchEngine { diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 5b3814f287..75c6d82996 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -5,7 +5,7 @@ import * as fs from 'fs'; import * as gracefulFs from 'graceful-fs'; -import { join, sep } from 'vs/base/common/path'; +import { basename, dirname, join, sep } from 'vs/base/common/path'; import * as arrays from 'vs/base/common/arrays'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -15,9 +15,9 @@ import * as objects from 'vs/base/common/objects'; import { StopWatch } from 'vs/base/common/stopwatch'; import * as strings from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer'; +import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from 'vs/base/common/fuzzyScorer'; import { MAX_FILE_SIZE } from 'vs/base/node/pfs'; -import { ICachedSearchStats, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, IRawFileQuery, IRawQuery, IRawTextQuery, ITextQuery, IFileSearchProgressItem, IRawFileMatch, IRawSearchService, ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess } from 'vs/workbench/services/search/common/search'; +import { ICachedSearchStats, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, IRawFileQuery, IRawQuery, IRawTextQuery, ITextQuery, IFileSearchProgressItem, IRawFileMatch, IRawSearchService, ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess, isFilePatternMatch } from 'vs/workbench/services/search/common/search'; import { Engine as FileSearchEngine } from 'vs/workbench/services/search/node/fileSearch'; import { TextSearchEngineAdapter } from 'vs/workbench/services/search/node/textSearchAdapter'; @@ -316,7 +316,7 @@ export class SearchService implements IRawSearchService { for (const entry of cachedEntries) { // Check if this entry is a match for the search value - if (!strings.fuzzyContains(entry.relativePath, normalizedSearchValueLowercase)) { + if (!isFilePatternMatch(entry, normalizedSearchValueLowercase)) { continue; } @@ -414,11 +414,11 @@ class Cache { const FileMatchItemAccessor = new class implements IItemAccessor { getItemLabel(match: IRawFileMatch): string { - return match.basename; // e.g. myFile.txt + return basename(match.relativePath); // e.g. myFile.txt } getItemDescription(match: IRawFileMatch): string { - return match.relativePath.substr(0, match.relativePath.length - match.basename.length - 1); // e.g. some/path/to/file + return dirname(match.relativePath); // e.g. some/path/to/file } getItemPath(match: IRawFileMatch): string { diff --git a/src/vs/workbench/services/search/test/node/rawSearchService.test.ts b/src/vs/workbench/services/search/test/node/rawSearchService.test.ts index 641462e16c..3f7cd0d90a 100644 --- a/src/vs/workbench/services/search/test/node/rawSearchService.test.ts +++ b/src/vs/workbench/services/search/test/node/rawSearchService.test.ts @@ -83,8 +83,6 @@ suite('RawSearchService', () => { const rawMatch: IRawFileMatch = { base: path.normalize('/some'), relativePath: 'where', - basename: 'where', - size: 123 }; const match: ISerializedFileMatch = { @@ -342,8 +340,6 @@ suite('RawSearchService', () => { matches.push({ base: path.normalize('/some/where'), relativePath: 'bc', - basename: 'bc', - size: 3 }); const results: any[] = []; const cb: IProgressCallback = value => { diff --git a/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts b/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts index 9ff7f39100..c058c66de3 100644 --- a/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts +++ b/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts @@ -5,11 +5,12 @@ import { Client } from 'vs/base/parts/ipc/common/ipc.net'; import { connect } from 'vs/base/parts/ipc/node/ipc.net'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; import { IChannel, IServerChannel, getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class SharedProcessService implements ISharedProcessService { @@ -20,12 +21,12 @@ export class SharedProcessService implements ISharedProcessService { constructor( @IMainProcessService mainProcessService: IMainProcessService, - @IElectronEnvironmentService environmentService: IElectronEnvironmentService + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService ) { this.sharedProcessMainChannel = mainProcessService.getChannel('sharedProcess'); this.withSharedProcessConnection = this.whenSharedProcessReady() - .then(() => connect(environmentService.sharedIPCHandle, `window:${environmentService.windowId}`)); + .then(() => connect(environmentService.sharedIPCHandle, `window:${environmentService.configuration.windowId}`)); } whenSharedProcessReady(): Promise { diff --git a/src/vs/workbench/services/statusbar/common/statusbar.ts b/src/vs/workbench/services/statusbar/common/statusbar.ts index 13af0f4c03..661c422b29 100644 --- a/src/vs/workbench/services/statusbar/common/statusbar.ts +++ b/src/vs/workbench/services/statusbar/common/statusbar.ts @@ -7,6 +7,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IDisposable } from 'vs/base/common/lifecycle'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { Event } from 'vs/base/common/event'; +import { Command } from 'vs/editor/common/modes'; export const IStatusbarService = createDecorator('statusbarService'); @@ -45,12 +46,7 @@ export interface IStatusbarEntry { /** * An optional id of a command that is known to the workbench to execute on click */ - readonly command?: string; - - /** - * Optional arguments for the command. - */ - readonly arguments?: any[]; + readonly command?: string | Command; /** * Whether to show a beak above the status bar entry. diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index 99f3eb0b60..5a87c25908 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -17,6 +17,7 @@ import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/wor import { TelemetryService as BaseTelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class TelemetryService extends Disposable implements ITelemetryService { @@ -25,7 +26,7 @@ export class TelemetryService extends Disposable implements ITelemetryService { private impl: ITelemetryService; constructor( - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IProductService productService: IProductService, @ISharedProcessService sharedProcessService: ISharedProcessService, @ILogService logService: ILogService, @@ -38,7 +39,7 @@ export class TelemetryService extends Disposable implements ITelemetryService { const channel = sharedProcessService.getChannel('telemetryAppender'); const config: ITelemetryServiceConfig = { appender: combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService)), - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId!, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), piiPaths: environmentService.extensionsPath ? [environmentService.appRoot, environmentService.extensionsPath] : [environmentService.appRoot] }; diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index 6371467a32..2a5da6fc6e 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -23,7 +23,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ITMSyntaxExtensionPoint, grammarsExtPoint } from 'vs/workbench/services/textMate/common/TMGrammars'; import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; -import { ITextMateThemingRule, IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ITextMateThemingRule, IWorkbenchThemeService, IWorkbenchColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IGrammar, StackElement, IOnigLib, IRawTheme } from 'vscode-textmate'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -247,7 +247,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex return result; } - private _updateTheme(grammarFactory: TMGrammarFactory, colorTheme: IColorTheme, forceUpdate: boolean): void { + private _updateTheme(grammarFactory: TMGrammarFactory, colorTheme: IWorkbenchColorTheme, forceUpdate: boolean): void { if (!forceUpdate && this._currentTheme && this._currentTokenColorMap && AbstractTextMateService.equalsTokenRules(this._currentTheme.settings, colorTheme.tokenColors) && equalArray(this._currentTokenColorMap, colorTheme.tokenColorMap)) { return; } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 2796f1ad44..938940246a 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -442,7 +442,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#region revert - async revert(resource: URI, options?: IRevertOptions): Promise { + async revert(resource: URI, options?: IRevertOptions): Promise { // Untitled if (resource.scheme === Schemas.untitled) { @@ -450,17 +450,15 @@ export abstract class AbstractTextFileService extends Disposable implements ITex if (model) { return model.revert(options); } - - return false; } // File - const model = this.files.get(resource); - if (model && (model.isDirty() || options?.force)) { - return model.revert(options); + else { + const model = this.files.get(resource); + if (model && (model.isDirty() || options?.force)) { + return model.revert(options); + } } - - return false; } //#endregion diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index cdb6f4e798..4a6e9cf79c 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -206,9 +206,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil //#region Revert - async revert(options?: IRevertOptions): Promise { + async revert(options?: IRevertOptions): Promise { if (!this.isResolved()) { - return false; + return; } // Unset flags @@ -240,8 +240,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (wasDirty) { this._onDidChangeDirty.fire(); } - - return true; } //#endregion diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index f88f0a3d13..c4af89b890 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -78,7 +78,7 @@ export interface ITextFileService extends IDisposable { * @param resource the resource of the file to revert. * @param force to force revert even when the file is not dirty */ - revert(resource: URI, options?: IRevertOptions): Promise; + revert(resource: URI, options?: IRevertOptions): Promise; /** * Read the contents of a file identified by the resource. @@ -411,7 +411,7 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport updatePreferredEncoding(encoding: string | undefined): void; save(options?: ITextFileSaveOptions): Promise; - revert(options?: IRevertOptions): Promise; + revert(options?: IRevertOptions): Promise; load(options?: ITextFileLoadOptions): Promise; diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index 98e4713ebf..4ef7ad3df1 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -39,6 +39,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class NativeTextFileService extends AbstractTextFileService { @@ -48,7 +49,7 @@ export class NativeTextFileService extends AbstractTextFileService { @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService instantiationService: IInstantiationService, @IModelService modelService: IModelService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService protected environmentService: INativeWorkbenchEnvironmentService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts index a689c35763..74bcbb662c 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -8,7 +8,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { EncodingMode } from 'vs/workbench/common/editor'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { TextFileEditorModelState, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; -import { createFileInput, workbenchInstantiationService, TestServiceAccessor, TestReadonlyTextFileEditorModel } from 'vs/workbench/test/browser/workbenchTestServices'; +import { createFileEditorInput, workbenchInstantiationService, TestServiceAccessor, TestReadonlyTextFileEditorModel } from 'vs/workbench/test/browser/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; @@ -80,6 +80,12 @@ suite('Files - TextFileEditorModel', () => { assert.equal(accessor.workingCopyService.dirtyCount, 0); + let savedEvent = false; + model.onDidSave(() => savedEvent = true); + + await model.save(); + assert.ok(!savedEvent); + model.updateTextEditorModel(createTextBufferFactory('bar')); assert.ok(getLastModifiedTime(model) <= Date.now()); assert.ok(model.hasState(TextFileEditorModelState.DIRTY)); @@ -87,9 +93,6 @@ suite('Files - TextFileEditorModel', () => { assert.equal(accessor.workingCopyService.dirtyCount, 1); assert.equal(accessor.workingCopyService.isDirty(model.resource), true); - let savedEvent = false; - model.onDidSave(() => savedEvent = true); - let workingCopyEvent = false; accessor.workingCopyService.onDidChangeDirty(e => { if (e.resource.toString() === model.resource.toString()) { @@ -110,6 +113,11 @@ suite('Files - TextFileEditorModel', () => { assert.equal(accessor.workingCopyService.dirtyCount, 0); assert.equal(accessor.workingCopyService.isDirty(model.resource), false); + savedEvent = false; + + await model.save({ force: true }); + assert.ok(savedEvent); + model.dispose(); assert.ok(!accessor.modelService.getModel(model.resource)); }); @@ -404,7 +412,7 @@ suite('Files - TextFileEditorModel', () => { model.dispose(); }); - test('No Dirty for readonly models', async function () { + test('No Dirty or saving for readonly models', async function () { let workingCopyEvent = false; accessor.workingCopyService.onDidChangeDirty(e => { if (e.resource.toString() === model.resource.toString()) { @@ -414,10 +422,18 @@ suite('Files - TextFileEditorModel', () => { const model = instantiationService.createInstance(TestReadonlyTextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + let saveEvent = false; + model.onDidSave(() => { + saveEvent = true; + }); + await model.load(); model.updateTextEditorModel(createTextBufferFactory('foo')); assert.ok(!model.isDirty()); + await model.save({ force: true }); + assert.equal(saveEvent, false); + await model.revert({ soft: true }); assert.ok(!model.isDirty()); @@ -453,8 +469,8 @@ suite('Files - TextFileEditorModel', () => { }); test('save() and isDirty() - proper with check for mtimes', async function () { - const input1 = createFileInput(instantiationService, toResource.call(this, '/path/index_async2.txt')); - const input2 = createFileInput(instantiationService, toResource.call(this, '/path/index_async.txt')); + const input1 = createFileEditorInput(instantiationService, toResource.call(this, '/path/index_async2.txt')); + const input2 = createFileEditorInput(instantiationService, toResource.call(this, '/path/index_async.txt')); const model1 = await input1.resolve() as TextFileEditorModel; const model2 = await input2.resolve() as TextFileEditorModel; diff --git a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts index 877579abb3..7aa9a50e36 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts @@ -97,8 +97,7 @@ suite('Files - TextFileService', () => { model!.textEditorModel!.setValue('foo'); assert.ok(accessor.textFileService.isDirty(model.resource)); - const res = await accessor.textFileService.revert(model.resource); - assert.ok(res); + await accessor.textFileService.revert(model.resource); assert.ok(!accessor.textFileService.isDirty(model.resource)); }); diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index 8cf07a66eb..cb44a38f8b 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -8,12 +8,15 @@ import * as nls from 'vs/nls'; import * as Paths from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import * as Json from 'vs/base/common/json'; -import { ExtensionData, IThemeExtensionPoint, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ExtensionData, IThemeExtensionPoint, IWorkbenchFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IFileService } from 'vs/platform/files/common/files'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { asCSSUrl } from 'vs/base/browser/dom'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -export class FileIconThemeData implements IFileIconTheme { +const PERSISTED_FILE_ICON_THEME_STORAGE_KEY = 'iconThemeData'; + +export class FileIconThemeData implements IWorkbenchFileIconTheme { id: string; label: string; settingsId: string | null; @@ -78,7 +81,7 @@ export class FileIconThemeData implements IFileIconTheme { private static _noIconTheme: FileIconThemeData | null = null; - static noIconTheme(): FileIconThemeData { + static get noIconTheme(): FileIconThemeData { let themeData = FileIconThemeData._noIconTheme; if (!themeData) { themeData = FileIconThemeData._noIconTheme = new FileIconThemeData('', '', null); @@ -103,7 +106,12 @@ export class FileIconThemeData implements IFileIconTheme { return themeData; } - static fromStorageData(input: string): FileIconThemeData | null { + + static fromStorageData(storageService: IStorageService): FileIconThemeData | undefined { + const input = storageService.get(PERSISTED_FILE_ICON_THEME_STORAGE_KEY, StorageScope.GLOBAL); + if (!input) { + return undefined; + } try { let data = JSON.parse(input); const theme = new FileIconThemeData('', '', null); @@ -128,12 +136,12 @@ export class FileIconThemeData implements IFileIconTheme { } return theme; } catch (e) { - return null; + return undefined; } } - toStorageData() { - return JSON.stringify({ + toStorage(storageService: IStorageService) { + const data = JSON.stringify({ id: this.id, label: this.label, description: this.description, @@ -145,6 +153,7 @@ export class FileIconThemeData implements IFileIconTheme { hidesExplorerArrows: this.hidesExplorerArrows, watch: this.watch }); + storageService.store(PERSISTED_FILE_ICON_THEME_STORAGE_KEY, data, StorageScope.GLOBAL); } } diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeStore.ts b/src/vs/workbench/services/themes/browser/fileIconThemeStore.ts deleted file mode 100644 index 931c7409f3..0000000000 --- a/src/vs/workbench/services/themes/browser/fileIconThemeStore.ts +++ /dev/null @@ -1,163 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; - -import * as types from 'vs/base/common/types'; -import * as resources from 'vs/base/common/resources'; -import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ExtensionData, IThemeExtensionPoint } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { Event, Emitter } from 'vs/base/common/event'; -import { FileIconThemeData } from 'vs/workbench/services/themes/browser/fileIconThemeData'; -import { URI } from 'vs/base/common/uri'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { find } from 'vs/base/common/arrays'; - -const iconThemeExtPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'iconThemes', - jsonSchema: { - description: nls.localize('vscode.extension.contributes.iconThemes', 'Contributes file icon themes.'), - type: 'array', - items: { - type: 'object', - defaultSnippets: [{ body: { id: '${1:id}', label: '${2:label}', path: './fileicons/${3:id}-icon-theme.json' } }], - properties: { - id: { - description: nls.localize('vscode.extension.contributes.iconThemes.id', 'Id of the icon theme as used in the user settings.'), - type: 'string' - }, - label: { - description: nls.localize('vscode.extension.contributes.iconThemes.label', 'Label of the icon theme as shown in the UI.'), - type: 'string' - }, - path: { - description: nls.localize('vscode.extension.contributes.iconThemes.path', 'Path of the icon theme definition file. The path is relative to the extension folder and is typically \'./icons/awesome-icon-theme.json\'.'), - type: 'string' - } - }, - required: ['path', 'id'] - } - } -}); - -export interface FileIconThemeChangeEvent { - themes: FileIconThemeData[]; - added: FileIconThemeData[]; -} - -export class FileIconThemeStore extends Disposable { - - private knownIconThemes: FileIconThemeData[]; - - private readonly onDidChangeEmitter = this._register(new Emitter()); - readonly onDidChange: Event = this.onDidChangeEmitter.event; - - constructor(@IExtensionService private readonly extensionService: IExtensionService) { - super(); - this.knownIconThemes = []; - this.initialize(); - } - - private initialize() { - iconThemeExtPoint.setHandler((extensions) => { - const previousIds: { [key: string]: boolean; } = {}; - const added: FileIconThemeData[] = []; - for (const theme of this.knownIconThemes) { - previousIds[theme.id] = true; - } - this.knownIconThemes.length = 0; - for (let ext of extensions) { - let extensionData = { - extensionId: ext.description.identifier.value, - extensionPublisher: ext.description.publisher, - extensionName: ext.description.name, - extensionIsBuiltin: ext.description.isBuiltin, - extensionLocation: ext.description.extensionLocation - }; - this.onIconThemes(extensionData, ext.value, ext.collector); - } - for (const theme of this.knownIconThemes) { - if (!previousIds[theme.id]) { - added.push(theme); - } - } - this.onDidChangeEmitter.fire({ themes: this.knownIconThemes, added }); - }); - } - - private onIconThemes(extensionData: ExtensionData, iconThemes: IThemeExtensionPoint[], collector: ExtensionMessageCollector): void { - if (!Array.isArray(iconThemes)) { - collector.error(nls.localize( - 'reqarray', - "Extension point `{0}` must be an array.", - iconThemeExtPoint.name - )); - return; - } - iconThemes.forEach(iconTheme => { - if (!iconTheme.path || !types.isString(iconTheme.path)) { - collector.error(nls.localize( - 'reqpath', - "Expected string in `contributes.{0}.path`. Provided value: {1}", - iconThemeExtPoint.name, - String(iconTheme.path) - )); - return; - } - if (!iconTheme.id || !types.isString(iconTheme.id)) { - collector.error(nls.localize( - 'reqid', - "Expected string in `contributes.{0}.id`. Provided value: {1}", - iconThemeExtPoint.name, - String(iconTheme.path) - )); - return; - } - - const iconThemeLocation = resources.joinPath(extensionData.extensionLocation, iconTheme.path); - if (!resources.isEqualOrParent(iconThemeLocation, extensionData.extensionLocation)) { - collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", iconThemeExtPoint.name, iconThemeLocation.path, extensionData.extensionLocation.path)); - } - - let themeData = FileIconThemeData.fromExtensionTheme(iconTheme, iconThemeLocation, extensionData); - this.knownIconThemes.push(themeData); - }); - - } - - public findThemeData(iconTheme: string): Promise { - if (iconTheme.length === 0) { - return Promise.resolve(FileIconThemeData.noIconTheme()); - } - return this.getFileIconThemes().then(allIconSets => { - return find(allIconSets, iconSet => iconSet.id === iconTheme); - }); - } - - public findThemeBySettingsId(settingsId: string | null): Promise { - if (!settingsId) { - return Promise.resolve(FileIconThemeData.noIconTheme()); - } - return this.getFileIconThemes().then(allIconSets => { - return find(allIconSets, iconSet => iconSet.settingsId === settingsId); - }); - } - - public findThemeDataByExtensionLocation(extLocation: URI | undefined): Promise { - if (extLocation) { - return this.getFileIconThemes().then(allThemes => { - return allThemes.filter(t => t.extensionData && resources.isEqualOrParent(t.extensionData.extensionLocation, extLocation)); - }); - } - return Promise.resolve([]); - } - - public getFileIconThemes(): Promise { - return this.extensionService.whenInstalledExtensionsRegistered().then(isReady => { - return this.knownIconThemes; - }); - } -} diff --git a/src/vs/workbench/services/themes/browser/productIconThemeData.ts b/src/vs/workbench/services/themes/browser/productIconThemeData.ts new file mode 100644 index 0000000000..98985fcd99 --- /dev/null +++ b/src/vs/workbench/services/themes/browser/productIconThemeData.ts @@ -0,0 +1,205 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import * as nls from 'vs/nls'; +import * as Paths from 'vs/base/common/path'; +import * as resources from 'vs/base/common/resources'; +import * as Json from 'vs/base/common/json'; +import { ExtensionData, IThemeExtensionPoint, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; +import { asCSSUrl } from 'vs/base/browser/dom'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE } from 'vs/workbench/services/themes/common/themeConfiguration'; + +const PERSISTED_PRODUCT_ICON_THEME_STORAGE_KEY = 'productIconThemeData'; + +export const DEFAULT_PRODUCT_ICON_THEME_ID = ''; // TODO + +export class ProductIconThemeData implements IWorkbenchProductIconTheme { + id: string; + label: string; + settingsId: string; + description?: string; + isLoaded: boolean; + location?: URI; + extensionData?: ExtensionData; + watch?: boolean; + + styleSheetContent?: string; + + private constructor(id: string, label: string, settingsId: string) { + this.id = id; + this.label = label; + this.settingsId = settingsId; + this.isLoaded = false; + } + + public ensureLoaded(fileService: IFileService): Promise { + return !this.isLoaded ? this.load(fileService) : Promise.resolve(this.styleSheetContent); + } + + public reload(fileService: IFileService): Promise { + return this.load(fileService); + } + + private load(fileService: IFileService): Promise { + if (!this.location) { + return Promise.resolve(this.styleSheetContent); + } + return _loadProductIconThemeDocument(fileService, this.location).then(iconThemeDocument => { + const result = _processIconThemeDocument(this.id, this.location!, iconThemeDocument); + this.styleSheetContent = result.content; + this.isLoaded = true; + return this.styleSheetContent; + }); + } + + static fromExtensionTheme(iconTheme: IThemeExtensionPoint, iconThemeLocation: URI, extensionData: ExtensionData): ProductIconThemeData { + const id = extensionData.extensionId + '-' + iconTheme.id; + const label = iconTheme.label || Paths.basename(iconTheme.path); + const settingsId = iconTheme.id; + + const themeData = new ProductIconThemeData(id, label, settingsId); + + themeData.description = iconTheme.description; + themeData.location = iconThemeLocation; + themeData.extensionData = extensionData; + themeData.watch = iconTheme._watch; + themeData.isLoaded = false; + return themeData; + } + + static createUnloadedTheme(id: string): ProductIconThemeData { + const themeData = new ProductIconThemeData(id, '', '__' + id); + themeData.isLoaded = false; + themeData.extensionData = undefined; + themeData.watch = false; + return themeData; + } + + private static _defaultProductIconTheme: ProductIconThemeData | null = null; + + static get defaultTheme(): ProductIconThemeData { + let themeData = ProductIconThemeData._defaultProductIconTheme; + if (!themeData) { + themeData = ProductIconThemeData._defaultProductIconTheme = new ProductIconThemeData(DEFAULT_PRODUCT_ICON_THEME_ID, nls.localize('defaultTheme', 'Default theme'), DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE); + themeData.isLoaded = true; + themeData.extensionData = undefined; + themeData.watch = false; + } + return themeData; + } + + static fromStorageData(storageService: IStorageService): ProductIconThemeData | undefined { + const input = storageService.get(PERSISTED_PRODUCT_ICON_THEME_STORAGE_KEY, StorageScope.GLOBAL); + if (!input) { + return undefined; + } + try { + let data = JSON.parse(input); + const theme = new ProductIconThemeData('', '', ''); + for (let key in data) { + switch (key) { + case 'id': + case 'label': + case 'description': + case 'settingsId': + case 'extensionData': + case 'styleSheetContent': + case 'watch': + (theme as any)[key] = data[key]; + break; + case 'location': + theme.location = URI.revive(data.location); + break; + } + } + return theme; + } catch (e) { + return undefined; + } + } + + toStorage(storageService: IStorageService) { + const data = JSON.stringify({ + id: this.id, + label: this.label, + description: this.description, + settingsId: this.settingsId, + location: this.location, + styleSheetContent: this.styleSheetContent, + watch: this.watch + }); + storageService.store(PERSISTED_PRODUCT_ICON_THEME_STORAGE_KEY, data, StorageScope.GLOBAL); + } +} + +interface IconDefinition { + fontCharacter: string; + fontId: string; +} + +interface FontDefinition { + id: string; + weight: string; + style: string; + size: string; + src: { path: string; format: string; }[]; +} + +interface ProductIconThemeDocument { + iconDefinitions: { [key: string]: IconDefinition }; + fonts: FontDefinition[]; +} + +function _loadProductIconThemeDocument(fileService: IFileService, location: URI): Promise { + return fileService.readFile(location).then((content) => { + let errors: Json.ParseError[] = []; + let contentValue = Json.parse(content.value.toString(), errors); + if (errors.length > 0) { + return Promise.reject(new Error(nls.localize('error.cannotparseicontheme', "Problems parsing product icons file: {0}", errors.map(e => getParseErrorMessage(e.error)).join(', ')))); + } else if (Json.getNodeType(contentValue) !== 'object') { + return Promise.reject(new Error(nls.localize('error.invalidformat', "Invalid format for product icons theme file: Object expected."))); + } else if (!contentValue.iconDefinitions || !Array.isArray(contentValue.fonts) || !contentValue.fonts.length) { + return Promise.reject(new Error(nls.localize('error.missingProperties', "Invalid format for product icons theme file: Must contain iconDefinitions and fonts."))); + } + return Promise.resolve(contentValue); + }); +} + +function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, iconThemeDocument: ProductIconThemeDocument): { content: string; } { + + const result = { content: '' }; + + if (!iconThemeDocument.iconDefinitions || !Array.isArray(iconThemeDocument.fonts) || !iconThemeDocument.fonts.length) { + return result; + } + + const iconThemeDocumentLocationDirname = resources.dirname(iconThemeDocumentLocation); + function resolvePath(path: string) { + return resources.joinPath(iconThemeDocumentLocationDirname, path); + } + + let cssRules: string[] = []; + + let fonts = iconThemeDocument.fonts; + for (const font of fonts) { + const src = font.src.map(l => `${asCSSUrl(resolvePath(l.path))} format('${l.format}')`).join(', '); + cssRules.push(`@font-face { src: ${src}; font-family: '${font.id}'; font-weight: ${font.weight}; font-style: ${font.style}; }`); + } + + let primaryFontId = fonts[0].id; + let iconDefinitions = iconThemeDocument.iconDefinitions; + for (const iconId in iconThemeDocument.iconDefinitions) { + const definition = iconDefinitions[iconId]; + if (definition && definition.fontCharacter) { + cssRules.push(`.codicon-${iconId}:before { content: '${definition.fontCharacter}' !important; font-family: ${definition.fontId || primaryFontId} !important; }`); + } + } + result.content = cssRules.join('\n'); + return result; +} diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 1715f504e9..668f47bfb9 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -6,65 +6,49 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IWorkbenchColorTheme, IWorkbenchFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, ThemeSettings, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Registry } from 'vs/platform/registry/common/platform'; import * as errors from 'vs/base/common/errors'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData'; -import { ITheme, Extensions as ThemingExtensions, IThemingRegistry, ThemeType, LIGHT, DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, Extensions as ThemingExtensions, IThemingRegistry, ThemeType, LIGHT, DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; import { registerFileIconThemeSchemas } from 'vs/workbench/services/themes/common/fileIconThemeSchema'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ColorThemeStore } from 'vs/workbench/services/themes/common/colorThemeStore'; -import { FileIconThemeStore } from 'vs/workbench/services/themes/browser/fileIconThemeStore'; import { FileIconThemeData } from 'vs/workbench/services/themes/browser/fileIconThemeData'; import { removeClasses, addClasses } from 'vs/base/browser/dom'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { textmateColorsSchemaId, registerColorThemeSchemas, textmateColorGroupSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; -import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry'; -import { tokenStylingSchemaId } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { registerColorThemeSchemas } from 'vs/workbench/services/themes/common/colorThemeSchema'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; - -// settings - -const PREFERRED_DARK_THEME_SETTING = 'workbench.preferredDarkColorTheme'; -const PREFERRED_LIGHT_THEME_SETTING = 'workbench.preferredLightColorTheme'; -const PREFERRED_HC_THEME_SETTING = 'workbench.preferredHighContrastColorTheme'; -const DETECT_COLOR_SCHEME_SETTING = 'window.autoDetectColorScheme'; -const DETECT_HC_SETTING = 'window.autoDetectHighContrast'; +import { ThemeRegistry, registerColorThemeExtensionPoint, registerFileIconThemeExtensionPoint, registerProductIconThemeExtensionPoint } from 'vs/workbench/services/themes/common/themeExtensionPoints'; +import { updateColorThemeConfigurationSchemas, updateFileIconThemeConfigurationSchemas, ThemeConfiguration, updateProductIconThemeConfigurationSchemas } from 'vs/workbench/services/themes/common/themeConfiguration'; +import { ProductIconThemeData, DEFAULT_PRODUCT_ICON_THEME_ID } from 'vs/workbench/services/themes/browser/productIconThemeData'; +import { registerProductIconThemeSchemas } from 'vs/workbench/services/themes/common/productIconThemeSchema'; // implementation -const DEFAULT_THEME_ID = 'vs sql-theme-carbon-themes-light_carbon-json'; -const DEFAULT_THEME_SETTING_VALUE = 'Default Light Azure Data Studio'; -const DEFAULT_THEME_DARK_SETTING_VALUE = 'Default Dark Azure Data Studio'; -const DEFAULT_THEME_LIGHT_SETTING_VALUE = 'Default Light Azure Data Studio'; -const DEFAULT_THEME_HC_SETTING_VALUE = 'Default High Contrast Azure Data Studio'; +const DEFAULT_COLOR_THEME_ID = 'vs sql-theme-carbon-themes-light_carbon-json'; -const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData'; -const PERSISTED_ICON_THEME_STORAGE_KEY = 'iconThemeData'; const PERSISTED_OS_COLOR_SCHEME = 'osColorScheme'; // {{SQL CARBON EDIT}} const defaultThemeExtensionId = 'sql-theme-carbon'; const oldDefaultThemeExtensionId = 'vscode-theme-colorful-defaults'; -const DEFAULT_ICON_THEME_SETTING_VALUE = 'vs-seti'; -const DEFAULT_ICON_THEME_ID = 'vscode.vscode-theme-seti-vs-seti'; +const DEFAULT_FILE_ICON_THEME_ID = 'vscode.vscode-theme-seti-vs-seti'; const fileIconsEnabledClass = 'file-icons-enabled'; const colorThemeRulesClassName = 'contributedColorTheme'; -const iconThemeRulesClassName = 'contributedIconTheme'; +const fileIconThemeRulesClassName = 'contributedFileIconTheme'; +const productIconThemeRulesClassName = 'contributedProductIconTheme'; const themingRegistry = Registry.as(ThemingExtensions.ThemingContribution); @@ -81,35 +65,31 @@ function validateThemeId(theme: string): string { return theme; } +const colorThemesExtPoint = registerColorThemeExtensionPoint(); +const fileIconThemesExtPoint = registerFileIconThemeExtensionPoint(); +const productIconThemesExtPoint = registerProductIconThemeExtensionPoint(); + export class WorkbenchThemeService implements IWorkbenchThemeService { _serviceBrand: undefined; - private colorThemeStore: ColorThemeStore; + private readonly container: HTMLElement; + private settings: ThemeConfiguration; + + private readonly colorThemeRegistry: ThemeRegistry; private currentColorTheme: ColorThemeData; - private container: HTMLElement; - private readonly onColorThemeChange: Emitter; - private watchedColorThemeLocation: URI | undefined; - private watchedColorThemeDisposable: IDisposable | undefined; + private readonly onColorThemeChange: Emitter; + private readonly colorThemeWatcher: ThemeFileWatcher; + private colorThemingParticipantChangeListener: IDisposable | undefined; - private iconThemeStore: FileIconThemeStore; - private currentIconTheme: FileIconThemeData; - private readonly onFileIconThemeChange: Emitter; - private watchedIconThemeLocation: URI | undefined; - private watchedIconThemeDisposable: IDisposable | undefined; + private readonly fileIconThemeRegistry: ThemeRegistry; + private currentFileIconTheme: FileIconThemeData; + private readonly onFileIconThemeChange: Emitter; + private readonly fileIconThemeWatcher: ThemeFileWatcher; - private themingParticipantChangeListener: IDisposable | undefined; - - private get colorCustomizations(): IColorCustomizations { - return this.configurationService.getValue(CUSTOM_WORKBENCH_COLORS_SETTING) || {}; - } - - private get tokenColorCustomizations(): ITokenColorCustomizations { - return this.configurationService.getValue(CUSTOM_EDITOR_COLORS_SETTING) || {}; - } - - private get tokenStylesCustomizations(): IExperimentalTokenStyleCustomizations { - return this.configurationService.getValue(CUSTOM_EDITOR_TOKENSTYLES_SETTING) || {}; - } + private readonly productIconThemeRegistry: ThemeRegistry; + private currentProductIconTheme: ProductIconThemeData; + private readonly onProductIconThemeChange: Emitter; + private readonly productIconThemeWatcher: ThemeFileWatcher; constructor( @IExtensionService extensionService: IExtensionService, @@ -121,43 +101,43 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, @IWorkbenchLayoutService readonly layoutService: IWorkbenchLayoutService ) { - this.container = layoutService.getWorkbenchContainer(); - this.colorThemeStore = new ColorThemeStore(extensionService); - this.onFileIconThemeChange = new Emitter(); - this.iconThemeStore = new FileIconThemeStore(extensionService); - this.onColorThemeChange = new Emitter({ leakWarningThreshold: 400 }); + this.settings = new ThemeConfiguration(configurationService); + this.colorThemeRegistry = new ThemeRegistry(extensionService, colorThemesExtPoint, ColorThemeData.fromExtensionTheme); + this.colorThemeWatcher = new ThemeFileWatcher(fileService, environmentService, this.reloadCurrentColorTheme.bind(this)); + this.onColorThemeChange = new Emitter({ leakWarningThreshold: 400 }); this.currentColorTheme = ColorThemeData.createUnloadedTheme(''); - this.currentIconTheme = FileIconThemeData.createUnloadedTheme(''); + + this.fileIconThemeWatcher = new ThemeFileWatcher(fileService, environmentService, this.reloadCurrentFileIconTheme.bind(this)); + this.fileIconThemeRegistry = new ThemeRegistry(extensionService, fileIconThemesExtPoint, FileIconThemeData.fromExtensionTheme, true, FileIconThemeData.noIconTheme); + this.onFileIconThemeChange = new Emitter(); + this.currentFileIconTheme = FileIconThemeData.createUnloadedTheme(''); + + this.productIconThemeWatcher = new ThemeFileWatcher(fileService, environmentService, this.reloadCurrentProductIconTheme.bind(this)); + this.productIconThemeRegistry = new ThemeRegistry(extensionService, productIconThemesExtPoint, ProductIconThemeData.fromExtensionTheme, true, ProductIconThemeData.defaultTheme); + this.onProductIconThemeChange = new Emitter(); + this.currentProductIconTheme = ProductIconThemeData.createUnloadedTheme(''); // In order to avoid paint flashing for tokens, because // themes are loaded asynchronously, we need to initialize // a color theme document with good defaults until the theme is loaded - let themeData: ColorThemeData | undefined = undefined; - let persistedThemeData = this.storageService.get(PERSISTED_THEME_STORAGE_KEY, StorageScope.GLOBAL); - if (persistedThemeData) { - themeData = ColorThemeData.fromStorageData(persistedThemeData); - } - let containerBaseTheme = this.getBaseThemeFromContainer(); + let themeData: ColorThemeData | undefined = ColorThemeData.fromStorageData(this.storageService); + const containerBaseTheme = this.getBaseThemeFromContainer(); if (!themeData || themeData.baseTheme !== containerBaseTheme) { themeData = ColorThemeData.createUnloadedTheme(containerBaseTheme); } - themeData.setCustomColors(this.colorCustomizations); - themeData.setCustomTokenColors(this.tokenColorCustomizations); - themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); - this.updateDynamicCSSRules(themeData); + themeData.setCustomizations(this.settings); this.applyTheme(themeData, undefined, true); - let persistedIconThemeData = this.storageService.get(PERSISTED_ICON_THEME_STORAGE_KEY, StorageScope.GLOBAL); - if (persistedIconThemeData) { - const iconData = FileIconThemeData.fromStorageData(persistedIconThemeData); - if (iconData) { - _applyIconTheme(iconData, () => { - this.doSetFileIconTheme(iconData); - return Promise.resolve(iconData); - }); - } + const fileIconData = FileIconThemeData.fromStorageData(this.storageService); + if (fileIconData) { + this.applyAndSetFileIconTheme(fileIconData); + } + + const productIconData = ProductIconThemeData.fromStorageData(this.storageService); + if (productIconData) { + this.applyAndSetProductIconTheme(productIconData); } this.initialize().then(undefined, errors.onUnexpectedError).then(_ => { @@ -168,35 +148,12 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { let prevColorId: string | undefined = undefined; // update settings schema setting for theme specific settings - this.colorThemeStore.onDidChange(async event => { - // updates enum for the 'workbench.colorTheme` setting - colorThemeSettingEnum.splice(0, colorThemeSettingEnum.length, ...event.themes.map(t => t.settingsId)); - colorThemeSettingEnumDescriptions.splice(0, colorThemeSettingEnumDescriptions.length, ...event.themes.map(t => t.description || '')); + this.colorThemeRegistry.onDidChange(async event => { + updateColorThemeConfigurationSchemas(event.themes); - const themeSpecificWorkbenchColors: IJSONSchema = { properties: {} }; - const themeSpecificTokenColors: IJSONSchema = { properties: {} }; - const themeSpecificTokenStyling: IJSONSchema = { properties: {} }; - - const workbenchColors = { $ref: workbenchColorsSchemaId, additionalProperties: false }; - const tokenColors = { properties: tokenColorSchema.properties, additionalProperties: false }; - const tokenStyling = { $ref: tokenStylingSchemaId, additionalProperties: false }; - for (let t of event.themes) { - // add theme specific color customization ("[Abyss]":{ ... }) - const themeId = `[${t.settingsId}]`; - themeSpecificWorkbenchColors.properties![themeId] = workbenchColors; - themeSpecificTokenColors.properties![themeId] = tokenColors; - themeSpecificTokenStyling.properties![themeId] = tokenStyling; - } - - colorCustomizationsSchema.allOf![1] = themeSpecificWorkbenchColors; - tokenColorCustomizationSchema.allOf![1] = themeSpecificTokenColors; - experimentalTokenStylingCustomizationSchema.allOf![1] = themeSpecificTokenStyling; - - configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration, tokenColorCustomizationConfiguration); - - let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); + const colorThemeSetting = this.settings.colorTheme; if (colorThemeSetting !== this.currentColorTheme.settingsId) { - const theme = await this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined); + const theme = await this.colorThemeRegistry.findThemeBySettingsId(colorThemeSetting, undefined); if (theme) { this.setColorTheme(theme.id, undefined); return; @@ -204,17 +161,17 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } if (this.currentColorTheme.isLoaded) { - const themeData = await this.colorThemeStore.findThemeData(this.currentColorTheme.id); + const themeData = await this.colorThemeRegistry.findThemeById(this.currentColorTheme.id); if (!themeData) { // current theme is no longer available prevColorId = this.currentColorTheme.id; - this.setColorTheme(DEFAULT_THEME_ID, 'auto'); + this.setColorTheme(DEFAULT_COLOR_THEME_ID, 'auto'); } else { - if (this.currentColorTheme.id === DEFAULT_THEME_ID && !types.isUndefined(prevColorId) && await this.colorThemeStore.findThemeData(prevColorId)) { - // restore color + if (this.currentColorTheme.id === DEFAULT_COLOR_THEME_ID && !types.isUndefined(prevColorId) && await this.colorThemeRegistry.findThemeById(prevColorId)) { + // restore theme this.setColorTheme(prevColorId, 'auto'); prevColorId = undefined; - } else { + } else if (event.added.some(t => t.settingsId === this.currentColorTheme.settingsId)) { this.reloadCurrentColorTheme(); } } @@ -222,78 +179,57 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { }); let prevFileIconId: string | undefined = undefined; - this.iconThemeStore.onDidChange(async event => { - iconThemeSettingSchema.enum = [null, ...event.themes.map(t => t.settingsId)]; - iconThemeSettingSchema.enumDescriptions = [iconThemeSettingSchema.enumDescriptions![0], ...event.themes.map(t => t.description || '')]; - configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration); - - let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); - if (iconThemeSetting !== this.currentIconTheme.settingsId) { - const theme = await this.iconThemeStore.findThemeBySettingsId(iconThemeSetting); - if (theme) { - this.setFileIconTheme(theme.id, undefined); - return; + this.fileIconThemeRegistry.onDidChange(async event => { + updateFileIconThemeConfigurationSchemas(event.themes); + if (await this.restoreFileIconTheme()) { // checks if theme from settings exists and is set + // restore theme + if (this.currentFileIconTheme.id === DEFAULT_FILE_ICON_THEME_ID && !types.isUndefined(prevFileIconId) && await this.fileIconThemeRegistry.findThemeById(prevFileIconId)) { + this.setFileIconTheme(prevFileIconId, 'auto'); + prevFileIconId = undefined; + } else if (event.added.some(t => t.settingsId === this.currentFileIconTheme.settingsId)) { + this.reloadCurrentFileIconTheme(); } + } else { + // current theme is no longer available + prevFileIconId = this.currentFileIconTheme.id; + this.setFileIconTheme(DEFAULT_FILE_ICON_THEME_ID, 'auto'); } - if (this.currentIconTheme.isLoaded) { - const theme = await this.iconThemeStore.findThemeData(this.currentIconTheme.id); - if (!theme) { - // current theme is no longer available - prevFileIconId = this.currentIconTheme.id; - this.setFileIconTheme(DEFAULT_ICON_THEME_ID, 'auto'); - } else { - // restore color - if (this.currentIconTheme.id === DEFAULT_ICON_THEME_ID && !types.isUndefined(prevFileIconId) && await this.iconThemeStore.findThemeData(prevFileIconId)) { - this.setFileIconTheme(prevFileIconId, 'auto'); - prevFileIconId = undefined; - } else { - this.reloadCurrentFileIconTheme(); - } - } - } }); - this.fileService.onDidFilesChange(async e => { - if (this.watchedColorThemeLocation && this.currentColorTheme && e.contains(this.watchedColorThemeLocation, FileChangeType.UPDATED)) { - this.reloadCurrentColorTheme(); - } - if (this.watchedIconThemeLocation && this.currentIconTheme && e.contains(this.watchedIconThemeLocation, FileChangeType.UPDATED)) { - this.reloadCurrentFileIconTheme(); + let prevProductIconId: string | undefined = undefined; + this.productIconThemeRegistry.onDidChange(async event => { + updateProductIconThemeConfigurationSchemas(event.themes); + if (await this.restoreProductIconTheme()) { // checks if theme from settings exists and is set + // restore theme + if (this.currentProductIconTheme.id === DEFAULT_PRODUCT_ICON_THEME_ID && !types.isUndefined(prevProductIconId) && await this.productIconThemeRegistry.findThemeById(prevProductIconId)) { + this.setProductIconTheme(prevProductIconId, 'auto'); + prevProductIconId = undefined; + } else if (event.added.some(t => t.settingsId === this.currentProductIconTheme.settingsId)) { + this.reloadCurrentProductIconTheme(); + } + } else { + // current theme is no longer available + prevProductIconId = this.currentProductIconTheme.id; + this.setProductIconTheme(DEFAULT_PRODUCT_ICON_THEME_ID, 'auto'); } }); } - public get onDidColorThemeChange(): Event { + public get onDidColorThemeChange(): Event { return this.onColorThemeChange.event; } - public get onDidFileIconThemeChange(): Event { - return this.onFileIconThemeChange.event; - } - - public get onIconThemeChange(): Event { - return this.onFileIconThemeChange.event; - } - - public get onThemeChange(): Event { - return this.onColorThemeChange.event; - } - - private initialize(): Promise<[IColorTheme | null, IFileIconTheme | null]> { - const colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); - const iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); - + private initialize(): Promise<[IWorkbenchColorTheme | null, IWorkbenchFileIconTheme | null, IWorkbenchProductIconTheme | null]> { const extDevLocs = this.environmentService.extensionDevelopmentLocationURI; + const extDevLoc = extDevLocs && extDevLocs.length === 1 ? extDevLocs[0] : undefined; // in dev mode, switch to a theme provided by the extension under dev. const initializeColorTheme = async () => { - if (extDevLocs && extDevLocs.length === 1) { // in dev mode, switch to a theme provided by the extension under dev. - const devThemes = await this.colorThemeStore.findThemeDataByExtensionLocation(extDevLocs[0]); - if (devThemes.length) { - return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); - } + const devThemes = await this.colorThemeRegistry.findThemeByExtensionLocation(extDevLoc); + if (devThemes.length) { + return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); } - let theme = await this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID); + const theme = await this.colorThemeRegistry.findThemeBySettingsId(this.settings.colorTheme, DEFAULT_COLOR_THEME_ID); const persistedColorScheme = this.storageService.get(PERSISTED_OS_COLOR_SCHEME, StorageScope.GLOBAL); const preferredColorScheme = this.getPreferredColorScheme(); @@ -303,64 +239,62 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.setColorTheme(theme && theme.id, undefined); }; - const initializeIconTheme = async () => { - if (extDevLocs && extDevLocs.length === 1) { // in dev mode, switch to a theme provided by the extension under dev. - const devThemes = await this.iconThemeStore.findThemeDataByExtensionLocation(extDevLocs[0]); - if (devThemes.length) { - return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY); - } + const initializeFileIconTheme = async () => { + const devThemes = await this.fileIconThemeRegistry.findThemeByExtensionLocation(extDevLoc); + if (devThemes.length) { + return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY); } - const theme = await this.iconThemeStore.findThemeBySettingsId(iconThemeSetting); - return this.setFileIconTheme(theme ? theme.id : DEFAULT_ICON_THEME_ID, undefined); + const theme = await this.fileIconThemeRegistry.findThemeBySettingsId(this.settings.fileIconTheme); + return this.setFileIconTheme(theme ? theme.id : DEFAULT_FILE_ICON_THEME_ID, undefined); }; - return Promise.all([initializeColorTheme(), initializeIconTheme()]); + const initializeProductIconTheme = async () => { + const devThemes = await this.productIconThemeRegistry.findThemeByExtensionLocation(extDevLoc); + if (devThemes.length) { + return this.setProductIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY); + } + const theme = await this.productIconThemeRegistry.findThemeBySettingsId(this.settings.productIconTheme); + return this.setProductIconTheme(theme ? theme.id : DEFAULT_PRODUCT_ICON_THEME_ID, undefined); + }; + + return Promise.all([initializeColorTheme(), initializeFileIconTheme(), initializeProductIconTheme()]) as Promise<[IWorkbenchColorTheme | null, IWorkbenchFileIconTheme | null, IWorkbenchProductIconTheme | null]>; // {{SQL CARBON EDIT}} strict-null-checks maybe? } private installConfigurationListener() { this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(COLOR_THEME_SETTING)) { - let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); - if (colorThemeSetting !== this.currentColorTheme.settingsId) { - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined).then(theme => { - if (theme) { - this.setColorTheme(theme.id, undefined); - } - }); - } + if (e.affectsConfiguration(ThemeSettings.COLOR_THEME)) { + this.restoreColorTheme(); } - if (e.affectsConfiguration(DETECT_COLOR_SCHEME_SETTING)) { + if (e.affectsConfiguration(ThemeSettings.DETECT_COLOR_SCHEME)) { this.handlePreferredSchemeUpdated(); } - if (e.affectsConfiguration(PREFERRED_DARK_THEME_SETTING) && this.getPreferredColorScheme() === DARK) { + if (e.affectsConfiguration(ThemeSettings.PREFERRED_DARK_THEME) && this.getPreferredColorScheme() === DARK) { this.applyPreferredColorTheme(DARK); } - if (e.affectsConfiguration(PREFERRED_LIGHT_THEME_SETTING) && this.getPreferredColorScheme() === LIGHT) { + if (e.affectsConfiguration(ThemeSettings.PREFERRED_LIGHT_THEME) && this.getPreferredColorScheme() === LIGHT) { this.applyPreferredColorTheme(LIGHT); } - if (e.affectsConfiguration(PREFERRED_HC_THEME_SETTING) && this.getPreferredColorScheme() === HIGH_CONTRAST) { + if (e.affectsConfiguration(ThemeSettings.PREFERRED_HC_THEME) && this.getPreferredColorScheme() === HIGH_CONTRAST) { this.applyPreferredColorTheme(HIGH_CONTRAST); } - if (e.affectsConfiguration(ICON_THEME_SETTING)) { - let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); - if (iconThemeSetting !== this.currentIconTheme.settingsId) { - this.iconThemeStore.findThemeBySettingsId(iconThemeSetting).then(theme => { - this.setFileIconTheme(theme ? theme.id : DEFAULT_ICON_THEME_ID, undefined); - }); - } + if (e.affectsConfiguration(ThemeSettings.ICON_THEME)) { + this.restoreFileIconTheme(); + } + if (e.affectsConfiguration(ThemeSettings.PRODUCT_ICON_THEME)) { + this.restoreProductIconTheme(); } if (this.currentColorTheme) { let hasColorChanges = false; - if (e.affectsConfiguration(CUSTOM_WORKBENCH_COLORS_SETTING)) { - this.currentColorTheme.setCustomColors(this.colorCustomizations); + if (e.affectsConfiguration(ThemeSettings.COLOR_CUSTOMIZATIONS)) { + this.currentColorTheme.setCustomColors(this.settings.colorCustomizations); hasColorChanges = true; } - if (e.affectsConfiguration(CUSTOM_EDITOR_COLORS_SETTING)) { - this.currentColorTheme.setCustomTokenColors(this.tokenColorCustomizations); + if (e.affectsConfiguration(ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS)) { + this.currentColorTheme.setCustomTokenColors(this.settings.tokenColorCustomizations); hasColorChanges = true; } - if (e.affectsConfiguration(CUSTOM_EDITOR_TOKENSTYLES_SETTING)) { - this.currentColorTheme.setCustomTokenStyleRules(this.tokenStylesCustomizations); + if (e.affectsConfiguration(ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL)) { + this.currentColorTheme.setCustomTokenStyleRules(this.settings.tokenStylesCustomizations); hasColorChanges = true; } if (hasColorChanges) { @@ -387,11 +321,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private getPreferredColorScheme(): ThemeType | undefined { - let detectHCThemeSetting = this.configurationService.getValue(DETECT_HC_SETTING); + const detectHCThemeSetting = this.configurationService.getValue(ThemeSettings.DETECT_HC); if (this.environmentService.configuration.highContrast && detectHCThemeSetting) { return HIGH_CONTRAST; } - if (this.configurationService.getValue(DETECT_COLOR_SCHEME_SETTING)) { + if (this.configurationService.getValue(ThemeSettings.DETECT_COLOR_SCHEME)) { if (window.matchMedia(`(prefers-color-scheme: light)`).matches) { return LIGHT; } else if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) { @@ -401,11 +335,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return undefined; } - private async applyPreferredColorTheme(type: ThemeType): Promise { - const settingId = type === DARK ? PREFERRED_DARK_THEME_SETTING : type === LIGHT ? PREFERRED_LIGHT_THEME_SETTING : PREFERRED_HC_THEME_SETTING; + private async applyPreferredColorTheme(type: ThemeType): Promise { + const settingId = type === DARK ? ThemeSettings.PREFERRED_DARK_THEME : type === LIGHT ? ThemeSettings.PREFERRED_LIGHT_THEME : ThemeSettings.PREFERRED_HC_THEME; const themeSettingId = this.configurationService.getValue(settingId); if (themeSettingId) { - const theme = await this.colorThemeStore.findThemeDataBySettingsId(themeSettingId, undefined); + const theme = await this.colorThemeRegistry.findThemeBySettingsId(themeSettingId, undefined); if (theme) { return this.setColorTheme(theme.id, 'auto'); } @@ -413,29 +347,25 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return null; } - public getColorTheme(): IColorTheme { + public getColorTheme(): IWorkbenchColorTheme { return this.currentColorTheme; } - public getColorThemes(): Promise { - return this.colorThemeStore.getColorThemes(); + public getColorThemes(): Promise { + return this.colorThemeRegistry.getThemes(); } - public getTheme(): ITheme { - return this.getColorTheme(); - } - - public setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { + public setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { if (!themeId) { return Promise.resolve(null); } if (themeId === this.currentColorTheme.id && this.currentColorTheme.isLoaded) { - return this.writeColorThemeConfiguration(settingsTarget); + return this.settings.setColorTheme(this.currentColorTheme, settingsTarget); } themeId = validateThemeId(themeId); // migrate theme ids - return this.colorThemeStore.findThemeData(themeId, DEFAULT_THEME_ID).then(themeData => { + return this.colorThemeRegistry.findThemeById(themeId, DEFAULT_COLOR_THEME_ID).then(themeData => { if (!themeData) { return null; } @@ -444,15 +374,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.currentColorTheme.clearCaches(); // the loaded theme is identical to the perisisted theme. Don't need to send an event. this.currentColorTheme = themeData; - themeData.setCustomColors(this.colorCustomizations); - themeData.setCustomTokenColors(this.tokenColorCustomizations); - themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); + themeData.setCustomizations(this.settings); return Promise.resolve(themeData); } - themeData.setCustomColors(this.colorCustomizations); - themeData.setCustomTokenColors(this.tokenColorCustomizations); - themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); - this.updateDynamicCSSRules(themeData); + themeData.setCustomizations(this.settings); return this.applyTheme(themeData, settingsTarget); }, error => { return Promise.reject(new Error(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.location!.toString(), error.message))); @@ -462,17 +387,14 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private async reloadCurrentColorTheme() { await this.currentColorTheme.reload(this.extensionResourceLoaderService); - this.currentColorTheme.setCustomColors(this.colorCustomizations); - this.currentColorTheme.setCustomTokenColors(this.tokenColorCustomizations); - this.currentColorTheme.setCustomTokenStyleRules(this.tokenStylesCustomizations); - this.updateDynamicCSSRules(this.currentColorTheme); + this.currentColorTheme.setCustomizations(this.settings); this.applyTheme(this.currentColorTheme, undefined, false); } public restoreColorTheme() { - let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); + const colorThemeSetting = this.settings.colorTheme; if (colorThemeSetting !== this.currentColorTheme.settingsId) { - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined).then(theme => { + this.colorThemeRegistry.findThemeBySettingsId(colorThemeSetting, undefined).then(theme => { if (theme) { this.setColorTheme(theme.id, undefined); } @@ -480,7 +402,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } } - private updateDynamicCSSRules(themeData: ITheme) { + private updateDynamicCSSRules(themeData: IColorTheme) { const cssRules = new Set(); const ruleCollector = { addRule: (rule: string) => { @@ -493,7 +415,9 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { _applyRules([...cssRules].join('\n'), colorThemeRulesClassName); } - private applyTheme(newTheme: ColorThemeData, settingsTarget: ConfigurationTarget | undefined | 'auto', silent = false): Promise { + private applyTheme(newTheme: ColorThemeData, settingsTarget: ConfigurationTarget | undefined | 'auto', silent = false): Promise { + this.updateDynamicCSSRules(newTheme); + if (this.currentColorTheme.id) { removeClasses(this.container, this.currentColorTheme.id); } else { @@ -503,19 +427,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.currentColorTheme.clearCaches(); this.currentColorTheme = newTheme; - if (!this.themingParticipantChangeListener) { - this.themingParticipantChangeListener = themingRegistry.onThemingParticipantAdded(_ => this.updateDynamicCSSRules(this.currentColorTheme)); + if (!this.colorThemingParticipantChangeListener) { + this.colorThemingParticipantChangeListener = themingRegistry.onThemingParticipantAdded(_ => this.updateDynamicCSSRules(this.currentColorTheme)); } - if (this.fileService && !resources.isEqual(newTheme.location, this.watchedColorThemeLocation)) { - dispose(this.watchedColorThemeDisposable); - this.watchedColorThemeLocation = undefined; - - if (newTheme.location && (newTheme.watch || !!this.environmentService.extensionDevelopmentLocationURI)) { - this.watchedColorThemeLocation = newTheme.location; - this.watchedColorThemeDisposable = this.fileService.watch(newTheme.location); - } - } + this.colorThemeWatcher.update(newTheme); this.sendTelemetry(newTheme.id, newTheme.extensionData, 'color'); @@ -527,23 +443,17 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { // remember theme data for a quick restore if (newTheme.isLoaded) { - this.storageService.store(PERSISTED_THEME_STORAGE_KEY, newTheme.toStorageData(), StorageScope.GLOBAL); + newTheme.toStorage(this.storageService); } - return this.writeColorThemeConfiguration(settingsTarget); + return this.settings.setColorTheme(this.currentColorTheme, settingsTarget); } - private writeColorThemeConfiguration(settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { - if (!types.isUndefinedOrNull(settingsTarget)) { - return this.writeConfiguration(COLOR_THEME_SETTING, this.currentColorTheme.settingsId, settingsTarget).then(_ => this.currentColorTheme); - } - return Promise.resolve(this.currentColorTheme); - } private themeExtensionsActivated = new Map(); private sendTelemetry(themeId: string, themeData: ExtensionData | undefined, themeType: string) { if (themeData) { - let key = themeType + themeData.extensionId; + const key = themeType + themeData.extensionId; if (!this.themeExtensionsActivated.get(key)) { type ActivatePluginClassification = { id: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; @@ -571,63 +481,61 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } } - public getFileIconThemes(): Promise { - return this.iconThemeStore.getFileIconThemes(); + public getFileIconThemes(): Promise { + return this.fileIconThemeRegistry.getThemes(); } public getFileIconTheme() { - return this.currentIconTheme; + return this.currentFileIconTheme; } - public getIconTheme() { - return this.currentIconTheme; + public get onDidFileIconThemeChange(): Event { + return this.onFileIconThemeChange.event; } - public setFileIconTheme(iconTheme: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { + + public async setFileIconTheme(iconTheme: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { iconTheme = iconTheme || ''; - if (iconTheme === this.currentIconTheme.id && this.currentIconTheme.isLoaded) { - return this.writeFileIconConfiguration(settingsTarget); + if (iconTheme === this.currentFileIconTheme.id && this.currentFileIconTheme.isLoaded) { + await this.settings.setFileIconTheme(this.currentFileIconTheme, settingsTarget); + return this.currentFileIconTheme; } - let onApply = (newIconTheme: FileIconThemeData) => { - this.doSetFileIconTheme(newIconTheme); - // remember theme data for a quick restore - if (newIconTheme.isLoaded && (!newIconTheme.location || !getRemoteAuthority(newIconTheme.location))) { - this.storageService.store(PERSISTED_ICON_THEME_STORAGE_KEY, newIconTheme.toStorageData(), StorageScope.GLOBAL); - } + const newThemeData = (await this.fileIconThemeRegistry.findThemeById(iconTheme)) || FileIconThemeData.noIconTheme; + await newThemeData.ensureLoaded(this.fileService); - return this.writeFileIconConfiguration(settingsTarget); - }; + this.applyAndSetFileIconTheme(newThemeData); - return this.iconThemeStore.findThemeData(iconTheme).then(data => { - const iconThemeData = data || FileIconThemeData.noIconTheme(); - return iconThemeData.ensureLoaded(this.fileService).then(_ => { - return _applyIconTheme(iconThemeData, onApply); - }); - }); + // remember theme data for a quick restore + if (newThemeData.isLoaded && (!newThemeData.location || !getRemoteAuthority(newThemeData.location))) { + newThemeData.toStorage(this.storageService); + } + await this.settings.setFileIconTheme(this.currentFileIconTheme, settingsTarget); + + return newThemeData; } private async reloadCurrentFileIconTheme() { - await this.currentIconTheme.reload(this.fileService); - _applyIconTheme(this.currentIconTheme, () => { - this.doSetFileIconTheme(this.currentIconTheme); - return Promise.resolve(this.currentIconTheme); - }); + await this.currentFileIconTheme.reload(this.fileService); + this.applyAndSetFileIconTheme(this.currentFileIconTheme); } - public restoreFileIconTheme() { - let fileIconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); - if (fileIconThemeSetting !== this.currentIconTheme.settingsId) { - this.iconThemeStore.findThemeBySettingsId(fileIconThemeSetting).then(theme => { - if (theme) { - this.setFileIconTheme(theme.id, undefined); - } - }); + public async restoreFileIconTheme(): Promise { + const settingId = this.settings.fileIconTheme; + const theme = await this.fileIconThemeRegistry.findThemeBySettingsId(settingId); + if (theme) { + if (settingId !== this.currentFileIconTheme.settingsId) { + await this.setFileIconTheme(theme.id, undefined); + } + return true; } + return false; } - private doSetFileIconTheme(iconThemeData: FileIconThemeData): void { - this.currentIconTheme = iconThemeData; + private applyAndSetFileIconTheme(iconThemeData: FileIconThemeData): void { + this.currentFileIconTheme = iconThemeData; + + _applyRules(iconThemeData.styleSheetContent!, fileIconThemeRulesClassName); if (iconThemeData.id) { addClasses(this.container, fileIconsEnabledClass); @@ -635,57 +543,78 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { removeClasses(this.container, fileIconsEnabledClass); } - if (this.fileService && !resources.isEqual(iconThemeData.location, this.watchedIconThemeLocation)) { - dispose(this.watchedIconThemeDisposable); - this.watchedIconThemeLocation = undefined; - - if (iconThemeData.location && (iconThemeData.watch || !!this.environmentService.extensionDevelopmentLocationURI)) { - this.watchedIconThemeLocation = iconThemeData.location; - this.watchedIconThemeDisposable = this.fileService.watch(iconThemeData.location); - } - } + this.fileIconThemeWatcher.update(iconThemeData); if (iconThemeData.id) { this.sendTelemetry(iconThemeData.id, iconThemeData.extensionData, 'fileIcon'); } - this.onFileIconThemeChange.fire(this.currentIconTheme); + this.onFileIconThemeChange.fire(this.currentFileIconTheme); } - private writeFileIconConfiguration(settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { - if (!types.isUndefinedOrNull(settingsTarget)) { - return this.writeConfiguration(ICON_THEME_SETTING, this.currentIconTheme.settingsId, settingsTarget).then(_ => this.currentIconTheme); - } - return Promise.resolve(this.currentIconTheme); + public getProductIconThemes(): Promise { + return this.productIconThemeRegistry.getThemes(); } - public writeConfiguration(key: string, value: any, settingsTarget: ConfigurationTarget | 'auto'): Promise { - let settings = this.configurationService.inspect(key); - if (settingsTarget === 'auto') { - if (!types.isUndefined(settings.workspaceFolderValue)) { - settingsTarget = ConfigurationTarget.WORKSPACE_FOLDER; - } else if (!types.isUndefined(settings.workspaceValue)) { - settingsTarget = ConfigurationTarget.WORKSPACE; - } else { - settingsTarget = ConfigurationTarget.USER; - } + public getProductIconTheme() { + return this.currentProductIconTheme; + } + + public get onDidProductIconThemeChange(): Event { + return this.onProductIconThemeChange.event; + } + + public async setProductIconTheme(iconTheme: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { + iconTheme = iconTheme || ''; + if (iconTheme === this.currentProductIconTheme.id && this.currentProductIconTheme.isLoaded) { + await this.settings.setProductIconTheme(this.currentProductIconTheme, settingsTarget); + return this.currentProductIconTheme; } - if (settingsTarget === ConfigurationTarget.USER) { - if (value === settings.userValue) { - return Promise.resolve(undefined); // nothing to do - } else if (value === settings.defaultValue) { - if (types.isUndefined(settings.userValue)) { - return Promise.resolve(undefined); // nothing to do - } - value = undefined; // remove configuration from user settings - } - } else if (settingsTarget === ConfigurationTarget.WORKSPACE || settingsTarget === ConfigurationTarget.WORKSPACE_FOLDER) { - if (value === settings.value) { - return Promise.resolve(undefined); // nothing to do - } + const newThemeData = await this.productIconThemeRegistry.findThemeById(iconTheme) || ProductIconThemeData.defaultTheme; + await newThemeData.ensureLoaded(this.fileService); + + this.applyAndSetProductIconTheme(newThemeData); + + // remember theme data for a quick restore + if (newThemeData.isLoaded && (!newThemeData.location || !getRemoteAuthority(newThemeData.location))) { + newThemeData.toStorage(this.storageService); } - return this.configurationService.updateValue(key, value, settingsTarget); + await this.settings.setProductIconTheme(this.currentProductIconTheme, settingsTarget); + + return newThemeData; + } + + private async reloadCurrentProductIconTheme() { + await this.currentProductIconTheme.reload(this.fileService); + this.applyAndSetProductIconTheme(this.currentProductIconTheme); + } + + public async restoreProductIconTheme(): Promise { + const settingId = this.settings.productIconTheme; + const theme = await this.productIconThemeRegistry.findThemeBySettingsId(settingId); + if (theme) { + if (settingId !== this.currentProductIconTheme.settingsId) { + await this.setProductIconTheme(theme.id, undefined); + } + return true; + } + return false; + } + + private applyAndSetProductIconTheme(iconThemeData: ProductIconThemeData): void { + + this.currentProductIconTheme = iconThemeData; + + _applyRules(iconThemeData.styleSheetContent!, productIconThemeRulesClassName); + + this.productIconThemeWatcher.update(iconThemeData); + + if (iconThemeData.id) { + this.sendTelemetry(iconThemeData.id, iconThemeData.extensionData, 'productIcon'); + } + this.onProductIconThemeChange.fire(this.currentProductIconTheme); + } private getBaseThemeFromContainer() { @@ -699,15 +628,43 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } } -function _applyIconTheme(data: FileIconThemeData, onApply: (theme: FileIconThemeData) => Promise): Promise { - _applyRules(data.styleSheetContent!, iconThemeRulesClassName); - return onApply(data); +class ThemeFileWatcher { + + private inExtensionDevelopment: boolean; + private watchedLocation: URI | undefined; + private watcherDisposable: IDisposable | undefined; + private fileChangeListener: IDisposable | undefined; + + constructor(private fileService: IFileService, environmentService: IWorkbenchEnvironmentService, private onUpdate: () => void) { + this.inExtensionDevelopment = !!environmentService.extensionDevelopmentLocationURI; + } + + update(theme: { location?: URI, watch?: boolean; }) { + if (!resources.isEqual(theme.location, this.watchedLocation)) { + this.dispose(); + if (theme.location && (theme.watch || this.inExtensionDevelopment)) { + this.watchedLocation = theme.location; + this.watcherDisposable = this.fileService.watch(theme.location); + this.fileService.onDidFilesChange(e => { + if (this.watchedLocation && e.contains(this.watchedLocation, FileChangeType.UPDATED)) { + this.onUpdate(); + } + }); + } + } + } + + dispose() { + this.watcherDisposable = dispose(this.watcherDisposable); + this.fileChangeListener = dispose(this.fileChangeListener); + this.watchedLocation = undefined; + } } function _applyRules(styleSheetContent: string, rulesClassName: string) { - let themeStyles = document.head.getElementsByClassName(rulesClassName); + const themeStyles = document.head.getElementsByClassName(rulesClassName); if (themeStyles.length === 0) { - let elStyle = document.createElement('style'); + const elStyle = document.createElement('style'); elStyle.type = 'text/css'; elStyle.className = rulesClassName; elStyle.innerHTML = styleSheetContent; @@ -719,127 +676,6 @@ function _applyRules(styleSheetContent: string, rulesClassName: string) { registerColorThemeSchemas(); registerFileIconThemeSchemas(); - -// Configuration: Themes -const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); - -const colorThemeSettingEnum: string[] = []; -const colorThemeSettingEnumDescriptions: string[] = []; - -const colorThemeSettingSchema: IConfigurationPropertySchema = { - type: 'string', - description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."), - default: DEFAULT_THEME_SETTING_VALUE, - enum: colorThemeSettingEnum, - enumDescriptions: colorThemeSettingEnumDescriptions, - errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), -}; -const preferredDarkThemeSettingSchema: IConfigurationPropertySchema = { - type: 'string', - description: nls.localize('preferredDarkColorTheme', 'Specifies the preferred color theme for dark OS appearance when \'{0}\' is enabled.', DETECT_COLOR_SCHEME_SETTING), - default: DEFAULT_THEME_DARK_SETTING_VALUE, - enum: colorThemeSettingEnum, - enumDescriptions: colorThemeSettingEnumDescriptions, - errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), -}; -const preferredLightThemeSettingSchema: IConfigurationPropertySchema = { - type: 'string', - description: nls.localize('preferredLightColorTheme', 'Specifies the preferred color theme for light OS appearance when \'{0}\' is enabled.', DETECT_COLOR_SCHEME_SETTING), - default: DEFAULT_THEME_LIGHT_SETTING_VALUE, - enum: colorThemeSettingEnum, - enumDescriptions: colorThemeSettingEnumDescriptions, - errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), -}; -const preferredHCThemeSettingSchema: IConfigurationPropertySchema = { - type: 'string', - description: nls.localize('preferredHCColorTheme', 'Specifies the preferred color theme used in high contrast mode when \'{0}\' is enabled.', DETECT_HC_SETTING), - default: DEFAULT_THEME_HC_SETTING_VALUE, - enum: colorThemeSettingEnum, - enumDescriptions: colorThemeSettingEnumDescriptions, - errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), -}; -const detectColorSchemeSettingSchema: IConfigurationPropertySchema = { - type: 'boolean', - description: nls.localize('detectColorScheme', 'If set, automatically switch to the preferred color theme based on the OS appearance.'), - default: false -}; - -const iconThemeSettingSchema: IConfigurationPropertySchema = { - type: ['string', 'null'], - default: DEFAULT_ICON_THEME_SETTING_VALUE, - description: nls.localize('iconTheme', "Specifies the icon theme used in the workbench or 'null' to not show any file icons."), - enum: [null], - enumDescriptions: [nls.localize('noIconThemeDesc', 'No file icons')], - errorMessage: nls.localize('iconThemeError', "File icon theme is unknown or not installed.") -}; -const colorCustomizationsSchema: IConfigurationPropertySchema = { - type: 'object', - description: nls.localize('workbenchColors', "Overrides colors from the currently selected color theme."), - allOf: [{ $ref: workbenchColorsSchemaId }], - default: {}, - defaultSnippets: [{ - body: { - } - }] -}; - -const themeSettingsConfiguration: IConfigurationNode = { - id: 'workbench', - order: 7.1, - type: 'object', - properties: { - [COLOR_THEME_SETTING]: colorThemeSettingSchema, - [PREFERRED_DARK_THEME_SETTING]: preferredDarkThemeSettingSchema, - [PREFERRED_LIGHT_THEME_SETTING]: preferredLightThemeSettingSchema, - [PREFERRED_HC_THEME_SETTING]: preferredHCThemeSettingSchema, - [DETECT_COLOR_SCHEME_SETTING]: detectColorSchemeSettingSchema, - [ICON_THEME_SETTING]: iconThemeSettingSchema, - [CUSTOM_WORKBENCH_COLORS_SETTING]: colorCustomizationsSchema - } -}; -configurationRegistry.registerConfiguration(themeSettingsConfiguration); - -function tokenGroupSettings(description: string): IJSONSchema { - return { - description, - $ref: textmateColorGroupSchemaId - }; -} - -const tokenColorSchema: IJSONSchema = { - properties: { - comments: tokenGroupSettings(nls.localize('editorColors.comments', "Sets the colors and styles for comments")), - strings: tokenGroupSettings(nls.localize('editorColors.strings', "Sets the colors and styles for strings literals.")), - keywords: tokenGroupSettings(nls.localize('editorColors.keywords', "Sets the colors and styles for keywords.")), - numbers: tokenGroupSettings(nls.localize('editorColors.numbers', "Sets the colors and styles for number literals.")), - types: tokenGroupSettings(nls.localize('editorColors.types', "Sets the colors and styles for type declarations and references.")), - functions: tokenGroupSettings(nls.localize('editorColors.functions', "Sets the colors and styles for functions declarations and references.")), - variables: tokenGroupSettings(nls.localize('editorColors.variables', "Sets the colors and styles for variables declarations and references.")), - textMateRules: { - description: nls.localize('editorColors.textMateRules', 'Sets colors and styles using textmate theming rules (advanced).'), - $ref: textmateColorsSchemaId - } - } -}; -const tokenColorCustomizationSchema: IConfigurationPropertySchema = { - description: nls.localize('editorColors', "Overrides editor colors and font style from the currently selected color theme."), - default: {}, - allOf: [tokenColorSchema] -}; -const experimentalTokenStylingCustomizationSchema: IConfigurationPropertySchema = { - description: nls.localize('editorColorsTokenStyles', "Overrides token color and styles from the currently selected color theme."), - default: {}, - allOf: [{ $ref: tokenStylingSchemaId }] -}; -const tokenColorCustomizationConfiguration: IConfigurationNode = { - id: 'editor', - order: 7.2, - type: 'object', - properties: { - [CUSTOM_EDITOR_COLORS_SETTING]: tokenColorCustomizationSchema, - [CUSTOM_EDITOR_TOKENSTYLES_SETTING]: experimentalTokenStylingCustomizationSchema - } -}; -configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration); +registerProductIconThemeSchemas(); registerSingleton(IWorkbenchThemeService, WorkbenchThemeService); diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 240f1043ae..7253ed84b5 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -6,7 +6,7 @@ import { basename } from 'vs/base/common/path'; import * as Json from 'vs/base/common/json'; import { Color } from 'vs/base/common/color'; -import { ExtensionData, ITokenColorCustomizations, ITextMateThemingRule, IColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations, IExperimentalTokenStyleCustomizations, ITokenColorizationSetting } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ExtensionData, ITokenColorCustomizations, ITextMateThemingRule, IWorkbenchColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations, IExperimentalTokenStyleCustomizations, ITokenColorizationSetting } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { convertSettings } from 'vs/workbench/services/themes/common/themeCompatibility'; import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; @@ -23,6 +23,8 @@ import { TokenStyle, ProbeScope, TokenStylingRule, getTokenClassificationRegistr import { MatcherWithPriority, Matcher, createMatchers } from 'vs/workbench/services/themes/common/textMateScopeMatcher'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; import { CharCode } from 'vs/base/common/charCode'; +import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; +import { ThemeConfiguration } from 'vs/workbench/services/themes/common/themeConfiguration'; let colorRegistry = Registry.as(ColorRegistryExtensions.ColorContribution); @@ -42,7 +44,9 @@ const tokenGroupToScopesMap = { export type TokenStyleDefinition = TokenStylingRule | ProbeScope[] | TokenStyleValue; export type TokenStyleDefinitions = { [P in keyof TokenStyleData]?: TokenStyleDefinition | undefined }; -export class ColorThemeData implements IColorTheme { +const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData'; + +export class ColorThemeData implements IWorkbenchColorTheme { id: string; label: string; @@ -309,6 +313,12 @@ export class ColorThemeData implements IColorTheme { return this.customColorMap.hasOwnProperty(colorId) || this.colorMap.hasOwnProperty(colorId); } + public setCustomizations(settings: ThemeConfiguration) { + this.setCustomColors(settings.colorCustomizations); + this.setCustomTokenColors(settings.tokenColorCustomizations); + this.setCustomTokenStyleRules(settings.tokenStylesCustomizations); + } + public setCustomColors(colors: IColorCustomizations) { this.customColorMap = {}; this.overwriteCustomColors(colors); @@ -422,13 +432,13 @@ export class ColorThemeData implements IColorTheme { this.customTokenScopeMatchers = undefined; } - toStorageData() { + toStorage(storageService: IStorageService) { let colorMapData: { [key: string]: string } = {}; for (let key in this.colorMap) { colorMapData[key] = Color.Format.CSS.formatHexA(this.colorMap[key], true); } // no need to persist custom colors, they will be taken from the settings - return JSON.stringify({ + const value = JSON.stringify({ id: this.id, label: this.label, settingsId: this.settingsId, @@ -438,6 +448,7 @@ export class ColorThemeData implements IColorTheme { colorMap: colorMapData, watch: this.watch }); + storageService.store(PERSISTED_THEME_STORAGE_KEY, value, StorageScope.GLOBAL); } hasEqualData(other: ColorThemeData) { @@ -474,7 +485,11 @@ export class ColorThemeData implements IColorTheme { return themeData; } - static fromStorageData(input: string): ColorThemeData | undefined { + static fromStorageData(storageService: IStorageService): ColorThemeData | undefined { + const input = storageService.get(PERSISTED_THEME_STORAGE_KEY, StorageScope.GLOBAL); + if (!input) { + return undefined; + } try { let data = JSON.parse(input); let theme = new ColorThemeData('', '', ''); diff --git a/src/vs/workbench/services/themes/common/colorThemeStore.ts b/src/vs/workbench/services/themes/common/colorThemeStore.ts deleted file mode 100644 index 71b42cb6ee..0000000000 --- a/src/vs/workbench/services/themes/common/colorThemeStore.ts +++ /dev/null @@ -1,168 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; - -import * as types from 'vs/base/common/types'; -import * as resources from 'vs/base/common/resources'; -import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ExtensionData, IThemeExtensionPoint, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { Event, Emitter } from 'vs/base/common/event'; -import { URI } from 'vs/base/common/uri'; - -const themesExtPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'themes', - jsonSchema: { - description: nls.localize('vscode.extension.contributes.themes', 'Contributes textmate color themes.'), - type: 'array', - items: { - type: 'object', - defaultSnippets: [{ body: { label: '${1:label}', id: '${2:id}', uiTheme: VS_DARK_THEME, path: './themes/${3:id}.tmTheme.' } }], - properties: { - id: { - description: nls.localize('vscode.extension.contributes.themes.id', 'Id of the icon theme as used in the user settings.'), - type: 'string' - }, - label: { - description: nls.localize('vscode.extension.contributes.themes.label', 'Label of the color theme as shown in the UI.'), - type: 'string' - }, - uiTheme: { - description: nls.localize('vscode.extension.contributes.themes.uiTheme', 'Base theme defining the colors around the editor: \'vs\' is the light color theme, \'vs-dark\' is the dark color theme. \'hc-black\' is the dark high contrast theme.'), - enum: [VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME] - }, - path: { - description: nls.localize('vscode.extension.contributes.themes.path', 'Path of the tmTheme file. The path is relative to the extension folder and is typically \'./themes/themeFile.tmTheme\'.'), - type: 'string' - } - }, - required: ['path', 'uiTheme'] - } - } -}); - -export interface ColorThemeChangeEvent { - themes: ColorThemeData[]; - added: ColorThemeData[]; -} - -export class ColorThemeStore { - - private extensionsColorThemes: ColorThemeData[]; - - private readonly onDidChangeEmitter = new Emitter(); - public readonly onDidChange: Event = this.onDidChangeEmitter.event; - - constructor(@IExtensionService private readonly extensionService: IExtensionService) { - this.extensionsColorThemes = []; - this.initialize(); - } - - private initialize() { - themesExtPoint.setHandler((extensions, delta) => { - const previousIds: { [key: string]: boolean } = {}; - const added: ColorThemeData[] = []; - for (const theme of this.extensionsColorThemes) { - previousIds[theme.id] = true; - } - this.extensionsColorThemes.length = 0; - for (let ext of extensions) { - let extensionData: ExtensionData = { - extensionId: ext.description.identifier.value, - extensionPublisher: ext.description.publisher, - extensionName: ext.description.name, - extensionIsBuiltin: ext.description.isBuiltin, - extensionLocation: ext.description.extensionLocation - }; - this.onThemes(extensionData, ext.value, ext.collector); - } - for (const theme of this.extensionsColorThemes) { - if (!previousIds[theme.id]) { - added.push(theme); - } - } - this.onDidChangeEmitter.fire({ themes: this.extensionsColorThemes, added }); - }); - } - - private onThemes(extensionData: ExtensionData, themes: IThemeExtensionPoint[], collector: ExtensionMessageCollector): void { - if (!Array.isArray(themes)) { - collector.error(nls.localize( - 'reqarray', - "Extension point `{0}` must be an array.", - themesExtPoint.name - )); - return; - } - themes.forEach(theme => { - if (!theme.path || !types.isString(theme.path)) { - collector.error(nls.localize( - 'reqpath', - "Expected string in `contributes.{0}.path`. Provided value: {1}", - themesExtPoint.name, - String(theme.path) - )); - return; - } - - const colorThemeLocation = resources.joinPath(extensionData.extensionLocation, theme.path); - if (!resources.isEqualOrParent(colorThemeLocation, extensionData.extensionLocation)) { - collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", themesExtPoint.name, colorThemeLocation.path, extensionData.extensionLocation.path)); - } - - let themeData = ColorThemeData.fromExtensionTheme(theme, colorThemeLocation, extensionData); - this.extensionsColorThemes.push(themeData); - }); - } - - public findThemeData(themeId: string, defaultId?: string): Promise { - return this.getColorThemes().then(allThemes => { - let defaultTheme: ColorThemeData | undefined = undefined; - for (let t of allThemes) { - if (t.id === themeId) { - return t; - } - if (t.id === defaultId) { - defaultTheme = t; - } - } - return defaultTheme; - }); - } - - public findThemeDataBySettingsId(settingsId: string, defaultId: string | undefined): Promise { - return this.getColorThemes().then(allThemes => { - let defaultTheme: ColorThemeData | undefined = undefined; - for (let t of allThemes) { - if (t.settingsId === settingsId) { - return t; - } - if (t.id === defaultId) { - defaultTheme = t; - } - } - return defaultTheme; - }); - } - - public findThemeDataByExtensionLocation(extLocation: URI | undefined): Promise { - if (extLocation) { - return this.getColorThemes().then(allThemes => { - return allThemes.filter(t => t.extensionData && resources.isEqual(t.extensionData.extensionLocation, extLocation)); - }); - } - return Promise.resolve([]); - - } - - public getColorThemes(): Promise { - return this.extensionService.whenInstalledExtensionsRegistered().then(_ => { - return this.extensionsColorThemes; - }); - } - -} diff --git a/src/vs/workbench/services/themes/common/productIconThemeSchema.ts b/src/vs/workbench/services/themes/common/productIconThemeSchema.ts new file mode 100644 index 0000000000..a372ea34e3 --- /dev/null +++ b/src/vs/workbench/services/themes/common/productIconThemeSchema.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; + +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; + + +const schemaId = 'vscode://schemas/product-icon-theme'; +const schema: IJSONSchema = { + type: 'object', + allowComments: true, + allowTrailingCommas: true, + properties: { + fonts: { + type: 'array', + description: nls.localize('schema.fonts', 'Fonts that are used in the icon definitions.'), + items: { + type: 'object', + properties: { + id: { + type: 'string', + description: nls.localize('schema.id', 'The ID of the font.') + }, + src: { + type: 'array', + description: nls.localize('schema.src', 'The location of the font.'), + items: { + type: 'object', + properties: { + path: { + type: 'string', + description: nls.localize('schema.font-path', 'The font path, relative to the current workbench icon theme file.'), + }, + format: { + type: 'string', + description: nls.localize('schema.font-format', 'The format of the font.') + } + }, + required: [ + 'path', + 'format' + ] + } + }, + weight: { + type: 'string', + description: nls.localize('schema.font-weight', 'The weight of the font.') + }, + style: { + type: 'string', + description: nls.localize('schema.font-sstyle', 'The style of the font.') + }, + size: { + type: 'string', + description: nls.localize('schema.font-size', 'The default size of the font.') + } + }, + required: [ + 'id', + 'src' + ] + } + }, + iconDefinitions: { + type: 'object', + description: nls.localize('schema.iconDefinitions', 'Assocation of icon name to a font character.'), + properties: getIconRegistry().getIconSchema().properties, + additionalProperties: false + } + } +}; + +export function registerProductIconThemeSchemas() { + let schemaRegistry = Registry.as(JSONExtensions.JSONContribution); + schemaRegistry.registerSchema(schemaId, schema); +} diff --git a/src/vs/workbench/services/themes/common/themeConfiguration.ts b/src/vs/workbench/services/themes/common/themeConfiguration.ts new file mode 100644 index 0000000000..f682b6afc3 --- /dev/null +++ b/src/vs/workbench/services/themes/common/themeConfiguration.ts @@ -0,0 +1,277 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as types from 'vs/base/common/types'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; + +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { textmateColorsSchemaId, textmateColorGroupSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; +import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry'; +import { tokenStylingSchemaId } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { ThemeSettings, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IColorCustomizations, ITokenColorCustomizations, IExperimentalTokenStyleCustomizations, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; + +const DEFAULT_THEME_SETTING_VALUE = 'Default Light Azure Data Studio'; // {{SQL CARBON EDIT}} replace default theme +const DEFAULT_THEME_DARK_SETTING_VALUE = 'Default Dark Azure Data Studio'; // {{SQL CARBON EDIT}} replace default theme +const DEFAULT_THEME_LIGHT_SETTING_VALUE = 'Default Light Azure Data Studio'; // {{SQL CARBON EDIT}} replace default theme +const DEFAULT_THEME_HC_SETTING_VALUE = 'Default High Contrast Azure Data Studio'; // {{SQL CARBON EDIT}} replace default theme + +const DEFAULT_FILE_ICON_THEME_SETTING_VALUE = 'vs-seti'; + +export const DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE = 'Default'; + +// Configuration: Themes +const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + +const colorThemeSettingEnum: string[] = []; +const colorThemeSettingEnumDescriptions: string[] = []; + +const colorThemeSettingSchema: IConfigurationPropertySchema = { + type: 'string', + description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."), + default: DEFAULT_THEME_SETTING_VALUE, + enum: colorThemeSettingEnum, + enumDescriptions: colorThemeSettingEnumDescriptions, + errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), +}; +const preferredDarkThemeSettingSchema: IConfigurationPropertySchema = { + type: 'string', + description: nls.localize('preferredDarkColorTheme', 'Specifies the preferred color theme for dark OS appearance when \'{0}\' is enabled.', ThemeSettings.DETECT_COLOR_SCHEME), + default: DEFAULT_THEME_DARK_SETTING_VALUE, + enum: colorThemeSettingEnum, + enumDescriptions: colorThemeSettingEnumDescriptions, + errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), +}; +const preferredLightThemeSettingSchema: IConfigurationPropertySchema = { + type: 'string', + description: nls.localize('preferredLightColorTheme', 'Specifies the preferred color theme for light OS appearance when \'{0}\' is enabled.', ThemeSettings.DETECT_COLOR_SCHEME), + default: DEFAULT_THEME_LIGHT_SETTING_VALUE, + enum: colorThemeSettingEnum, + enumDescriptions: colorThemeSettingEnumDescriptions, + errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), +}; +const preferredHCThemeSettingSchema: IConfigurationPropertySchema = { + type: 'string', + description: nls.localize('preferredHCColorTheme', 'Specifies the preferred color theme used in high contrast mode when \'{0}\' is enabled.', ThemeSettings.DETECT_HC), + default: DEFAULT_THEME_HC_SETTING_VALUE, + enum: colorThemeSettingEnum, + enumDescriptions: colorThemeSettingEnumDescriptions, + errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), +}; +const detectColorSchemeSettingSchema: IConfigurationPropertySchema = { + type: 'boolean', + description: nls.localize('detectColorScheme', 'If set, automatically switch to the preferred color theme based on the OS appearance.'), + default: false +}; + +const colorCustomizationsSchema: IConfigurationPropertySchema = { + type: 'object', + description: nls.localize('workbenchColors', "Overrides colors from the currently selected color theme."), + allOf: [{ $ref: workbenchColorsSchemaId }], + default: {}, + defaultSnippets: [{ + body: { + } + }] +}; + +const fileIconThemeSettingSchema: IConfigurationPropertySchema = { + type: ['string', 'null'], + default: DEFAULT_FILE_ICON_THEME_SETTING_VALUE, + description: nls.localize('iconTheme', "Specifies the icon theme used in the workbench or 'null' to not show any file icons."), + enum: [null], + enumDescriptions: [nls.localize('noIconThemeDesc', 'No file icons')], + errorMessage: nls.localize('iconThemeError', "File icon theme is unknown or not installed.") +}; +const productIconThemeSettingSchema: IConfigurationPropertySchema = { + type: ['string', 'null'], + default: DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE, + description: nls.localize('workbenchIconTheme', "Specifies the workbench icon theme used."), + enum: [DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE], + enumDescriptions: [nls.localize('defaultWorkbenchIconThemeDesc', 'Default')], + errorMessage: nls.localize('workbenchIconThemeError', "Workbench icon theme is unknown or not installed.") +}; + +const themeSettingsConfiguration: IConfigurationNode = { + id: 'workbench', + order: 7.1, + type: 'object', + properties: { + [ThemeSettings.COLOR_THEME]: colorThemeSettingSchema, + [ThemeSettings.PREFERRED_DARK_THEME]: preferredDarkThemeSettingSchema, + [ThemeSettings.PREFERRED_LIGHT_THEME]: preferredLightThemeSettingSchema, + [ThemeSettings.PREFERRED_HC_THEME]: preferredHCThemeSettingSchema, + [ThemeSettings.DETECT_COLOR_SCHEME]: detectColorSchemeSettingSchema, + [ThemeSettings.ICON_THEME]: fileIconThemeSettingSchema, + [ThemeSettings.COLOR_CUSTOMIZATIONS]: colorCustomizationsSchema, + [ThemeSettings.PRODUCT_ICON_THEME]: productIconThemeSettingSchema + } +}; +configurationRegistry.registerConfiguration(themeSettingsConfiguration); + +function tokenGroupSettings(description: string): IJSONSchema { + return { + description, + $ref: textmateColorGroupSchemaId + }; +} + +const tokenColorSchema: IJSONSchema = { + properties: { + comments: tokenGroupSettings(nls.localize('editorColors.comments', "Sets the colors and styles for comments")), + strings: tokenGroupSettings(nls.localize('editorColors.strings', "Sets the colors and styles for strings literals.")), + keywords: tokenGroupSettings(nls.localize('editorColors.keywords', "Sets the colors and styles for keywords.")), + numbers: tokenGroupSettings(nls.localize('editorColors.numbers', "Sets the colors and styles for number literals.")), + types: tokenGroupSettings(nls.localize('editorColors.types', "Sets the colors and styles for type declarations and references.")), + functions: tokenGroupSettings(nls.localize('editorColors.functions', "Sets the colors and styles for functions declarations and references.")), + variables: tokenGroupSettings(nls.localize('editorColors.variables', "Sets the colors and styles for variables declarations and references.")), + textMateRules: { + description: nls.localize('editorColors.textMateRules', 'Sets colors and styles using textmate theming rules (advanced).'), + $ref: textmateColorsSchemaId + } + } +}; +const tokenColorCustomizationSchema: IConfigurationPropertySchema = { + description: nls.localize('editorColors', "Overrides editor colors and font style from the currently selected color theme."), + default: {}, + allOf: [tokenColorSchema] +}; +const experimentalTokenStylingCustomizationSchema: IConfigurationPropertySchema = { + description: nls.localize('editorColorsTokenStyles', "Overrides token color and styles from the currently selected color theme."), + default: {}, + allOf: [{ $ref: tokenStylingSchemaId }] +}; +const tokenColorCustomizationConfiguration: IConfigurationNode = { + id: 'editor', + order: 7.2, + type: 'object', + properties: { + [ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS]: tokenColorCustomizationSchema, + [ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL]: experimentalTokenStylingCustomizationSchema + } +}; +configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration); + +export function updateColorThemeConfigurationSchemas(themes: IWorkbenchColorTheme[]) { + // updates enum for the 'workbench.colorTheme` setting + colorThemeSettingEnum.splice(0, colorThemeSettingEnum.length, ...themes.map(t => t.settingsId)); + colorThemeSettingEnumDescriptions.splice(0, colorThemeSettingEnumDescriptions.length, ...themes.map(t => t.description || '')); + + const themeSpecificWorkbenchColors: IJSONSchema = { properties: {} }; + const themeSpecificTokenColors: IJSONSchema = { properties: {} }; + const themeSpecificTokenStyling: IJSONSchema = { properties: {} }; + + const workbenchColors = { $ref: workbenchColorsSchemaId, additionalProperties: false }; + const tokenColors = { properties: tokenColorSchema.properties, additionalProperties: false }; + const tokenStyling = { $ref: tokenStylingSchemaId, additionalProperties: false }; + for (let t of themes) { + // add theme specific color customization ("[Abyss]":{ ... }) + const themeId = `[${t.settingsId}]`; + themeSpecificWorkbenchColors.properties![themeId] = workbenchColors; + themeSpecificTokenColors.properties![themeId] = tokenColors; + themeSpecificTokenStyling.properties![themeId] = tokenStyling; + } + + colorCustomizationsSchema.allOf![1] = themeSpecificWorkbenchColors; + tokenColorCustomizationSchema.allOf![1] = themeSpecificTokenColors; + experimentalTokenStylingCustomizationSchema.allOf![1] = themeSpecificTokenStyling; + + configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration, tokenColorCustomizationConfiguration); +} + +export function updateFileIconThemeConfigurationSchemas(themes: IWorkbenchFileIconTheme[]) { + fileIconThemeSettingSchema.enum!.splice(1, Number.MAX_VALUE, ...themes.map(t => t.settingsId)); + fileIconThemeSettingSchema.enumDescriptions!.splice(1, Number.MAX_VALUE, ...themes.map(t => t.description || '')); + + configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration); +} + +export function updateProductIconThemeConfigurationSchemas(themes: IWorkbenchProductIconTheme[]) { + productIconThemeSettingSchema.enum!.splice(1, Number.MAX_VALUE, ...themes.map(t => t.settingsId)); + productIconThemeSettingSchema.enumDescriptions!.splice(1, Number.MAX_VALUE, ...themes.map(t => t.description || '')); + + configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration); +} + + +export class ThemeConfiguration { + constructor(private configurationService: IConfigurationService) { + } + + public get colorTheme(): string { + return this.configurationService.getValue(ThemeSettings.COLOR_THEME); + } + + public get fileIconTheme(): string | null { + return this.configurationService.getValue(ThemeSettings.ICON_THEME); + } + + public get productIconTheme(): string { + return this.configurationService.getValue(ThemeSettings.PRODUCT_ICON_THEME); + } + + public get colorCustomizations(): IColorCustomizations { + return this.configurationService.getValue(ThemeSettings.COLOR_CUSTOMIZATIONS) || {}; + } + + public get tokenColorCustomizations(): ITokenColorCustomizations { + return this.configurationService.getValue(ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS) || {}; + } + + public get tokenStylesCustomizations(): IExperimentalTokenStyleCustomizations { + return this.configurationService.getValue(ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL) || {}; + } + + public async setColorTheme(theme: IWorkbenchColorTheme, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { + await this.writeConfiguration(ThemeSettings.COLOR_THEME, theme.settingsId, settingsTarget); + return theme; + } + + public async setFileIconTheme(theme: IWorkbenchFileIconTheme, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { + await this.writeConfiguration(ThemeSettings.ICON_THEME, theme.settingsId, settingsTarget); + return theme; + } + + public async setProductIconTheme(theme: IWorkbenchProductIconTheme, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { + await this.writeConfiguration(ThemeSettings.PRODUCT_ICON_THEME, theme.settingsId, settingsTarget); + return theme; + } + + private async writeConfiguration(key: string, value: any, settingsTarget: ConfigurationTarget | 'auto' | undefined): Promise { + if (settingsTarget === undefined) { + return; + } + + let settings = this.configurationService.inspect(key); + if (settingsTarget === 'auto') { + if (!types.isUndefined(settings.workspaceFolderValue)) { + settingsTarget = ConfigurationTarget.WORKSPACE_FOLDER; + } else if (!types.isUndefined(settings.workspaceValue)) { + settingsTarget = ConfigurationTarget.WORKSPACE; + } else { + settingsTarget = ConfigurationTarget.USER; + } + } + + if (settingsTarget === ConfigurationTarget.USER) { + if (value === settings.userValue) { + return Promise.resolve(undefined); // nothing to do + } else if (value === settings.defaultValue) { + if (types.isUndefined(settings.userValue)) { + return Promise.resolve(undefined); // nothing to do + } + value = undefined; // remove configuration from user settings + } + } else if (settingsTarget === ConfigurationTarget.WORKSPACE || settingsTarget === ConfigurationTarget.WORKSPACE_FOLDER) { + if (value === settings.value) { + return Promise.resolve(undefined); // nothing to do + } + } + return this.configurationService.updateValue(key, value, settingsTarget); + } + +} diff --git a/src/vs/workbench/services/themes/common/themeExtensionPoints.ts b/src/vs/workbench/services/themes/common/themeExtensionPoints.ts new file mode 100644 index 0000000000..eccf5d0b7e --- /dev/null +++ b/src/vs/workbench/services/themes/common/themeExtensionPoints.ts @@ -0,0 +1,252 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; + +import * as types from 'vs/base/common/types'; +import * as resources from 'vs/base/common/resources'; +import { ExtensionMessageCollector, IExtensionPoint, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { ExtensionData, IThemeExtensionPoint, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; + +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { Event, Emitter } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; + +export function registerColorThemeExtensionPoint() { + return ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'themes', + jsonSchema: { + description: nls.localize('vscode.extension.contributes.themes', 'Contributes textmate color themes.'), + type: 'array', + items: { + type: 'object', + defaultSnippets: [{ body: { label: '${1:label}', id: '${2:id}', uiTheme: VS_DARK_THEME, path: './themes/${3:id}.tmTheme.' } }], + properties: { + id: { + description: nls.localize('vscode.extension.contributes.themes.id', 'Id of the color theme as used in the user settings.'), + type: 'string' + }, + label: { + description: nls.localize('vscode.extension.contributes.themes.label', 'Label of the color theme as shown in the UI.'), + type: 'string' + }, + uiTheme: { + description: nls.localize('vscode.extension.contributes.themes.uiTheme', 'Base theme defining the colors around the editor: \'vs\' is the light color theme, \'vs-dark\' is the dark color theme. \'hc-black\' is the dark high contrast theme.'), + enum: [VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME] + }, + path: { + description: nls.localize('vscode.extension.contributes.themes.path', 'Path of the tmTheme file. The path is relative to the extension folder and is typically \'./colorthemes/awesome-color-theme.json\'.'), + type: 'string' + } + }, + required: ['path', 'uiTheme'] + } + } + }); +} +export function registerFileIconThemeExtensionPoint() { + return ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'iconThemes', + jsonSchema: { + description: nls.localize('vscode.extension.contributes.iconThemes', 'Contributes file icon themes.'), + type: 'array', + items: { + type: 'object', + defaultSnippets: [{ body: { id: '${1:id}', label: '${2:label}', path: './fileicons/${3:id}-icon-theme.json' } }], + properties: { + id: { + description: nls.localize('vscode.extension.contributes.iconThemes.id', 'Id of the file icon theme as used in the user settings.'), + type: 'string' + }, + label: { + description: nls.localize('vscode.extension.contributes.iconThemes.label', 'Label of the file icon theme as shown in the UI.'), + type: 'string' + }, + path: { + description: nls.localize('vscode.extension.contributes.iconThemes.path', 'Path of the file icon theme definition file. The path is relative to the extension folder and is typically \'./fileicons/awesome-icon-theme.json\'.'), + type: 'string' + } + }, + required: ['path', 'id'] + } + } + }); +} + +export function registerProductIconThemeExtensionPoint() { + return ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'productIconThemes', + jsonSchema: { + description: nls.localize('vscode.extension.contributes.productIconThemes', 'Contributes product icon themes.'), + type: 'array', + items: { + type: 'object', + defaultSnippets: [{ body: { id: '${1:id}', label: '${2:label}', path: './producticons/${3:id}-product-icon-theme.json' } }], + properties: { + id: { + description: nls.localize('vscode.extension.contributes.productIconThemes.id', 'Id of the product icon theme as used in the user settings.'), + type: 'string' + }, + label: { + description: nls.localize('vscode.extension.contributes.productIconThemes.label', 'Label of the product icon theme as shown in the UI.'), + type: 'string' + }, + path: { + description: nls.localize('vscode.extension.contributes.productIconThemes.path', 'Path of the product icon theme definition file. The path is relative to the extension folder and is typically \'./producticons/awesome-product-icon-theme.json\'.'), + type: 'string' + } + }, + required: ['path', 'id'] + } + } + }); +} + +export interface ThemeChangeEvent { + themes: T[]; + added: T[]; +} + +export interface IThemeData { + id: string; + settingsId: string | null; + extensionData?: ExtensionData; +} + +export class ThemeRegistry { + + private extensionThemes: T[]; + + private readonly onDidChangeEmitter = new Emitter>(); + public readonly onDidChange: Event> = this.onDidChangeEmitter.event; + + constructor( + @IExtensionService private readonly extensionService: IExtensionService, + private readonly themesExtPoint: IExtensionPoint, + private create: (theme: IThemeExtensionPoint, themeLocation: URI, extensionData: ExtensionData) => T, + private idRequired = false, + private builtInTheme: T | undefined = undefined + ) { + this.extensionThemes = []; + this.initialize(); + } + + private initialize() { + this.themesExtPoint.setHandler((extensions, delta) => { + const previousIds: { [key: string]: boolean } = {}; + const added: T[] = []; + for (const theme of this.extensionThemes) { + previousIds[theme.id] = true; + } + this.extensionThemes.length = 0; + for (let ext of extensions) { + let extensionData: ExtensionData = { + extensionId: ext.description.identifier.value, + extensionPublisher: ext.description.publisher, + extensionName: ext.description.name, + extensionIsBuiltin: ext.description.isBuiltin, + extensionLocation: ext.description.extensionLocation + }; + this.onThemes(extensionData, ext.value, ext.collector); + } + for (const theme of this.extensionThemes) { + if (!previousIds[theme.id]) { + added.push(theme); + } + } + this.onDidChangeEmitter.fire({ themes: this.extensionThemes, added }); + }); + } + + private onThemes(extensionData: ExtensionData, themes: IThemeExtensionPoint[], collector: ExtensionMessageCollector): void { + if (!Array.isArray(themes)) { + collector.error(nls.localize( + 'reqarray', + "Extension point `{0}` must be an array.", + this.themesExtPoint.name + )); + return; + } + themes.forEach(theme => { + if (!theme.path || !types.isString(theme.path)) { + collector.error(nls.localize( + 'reqpath', + "Expected string in `contributes.{0}.path`. Provided value: {1}", + this.themesExtPoint.name, + String(theme.path) + )); + return; + } + if (this.idRequired && (!theme.id || !types.isString(theme.id))) { + collector.error(nls.localize( + 'reqid', + "Expected string in `contributes.{0}.id`. Provided value: {1}", + this.themesExtPoint.name, + String(theme.id) + )); + return; + } + + const themeLocation = resources.joinPath(extensionData.extensionLocation, theme.path); + if (!resources.isEqualOrParent(themeLocation, extensionData.extensionLocation)) { + collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", this.themesExtPoint.name, themeLocation.path, extensionData.extensionLocation.path)); + } + + let themeData = this.create(theme, themeLocation, extensionData); + this.extensionThemes.push(themeData); + }); + } + + public async findThemeById(themeId: string, defaultId?: string): Promise { + if (this.builtInTheme && this.builtInTheme.id === themeId) { + return this.builtInTheme; + } + const allThemes = await this.getThemes(); + let defaultTheme: T | undefined = undefined; + for (let t of allThemes) { + if (t.id === themeId) { + return t; + } + if (t.id === defaultId) { + defaultTheme = t; + } + } + return defaultTheme; + } + + public async findThemeBySettingsId(settingsId: string | null, defaultId?: string): Promise { + if (this.builtInTheme && this.builtInTheme.settingsId === settingsId) { + return this.builtInTheme; + } + const allThemes = await this.getThemes(); + let defaultTheme: T | undefined = undefined; + for (let t of allThemes) { + if (t.settingsId === settingsId) { + return t; + } + if (t.id === defaultId) { + defaultTheme = t; + } + } + return defaultTheme; + } + + public findThemeByExtensionLocation(extLocation: URI | undefined): Promise { + if (extLocation) { + return this.getThemes().then(allThemes => { + return allThemes.filter(t => t.extensionData && resources.isEqual(t.extensionData.extensionLocation, extLocation)); + }); + } + return Promise.resolve([]); + + } + + public getThemes(): Promise { + return this.extensionService.whenInstalledExtensionsRegistered().then(_ => { + return this.extensionThemes; + }); + } + +} diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index 08835cf300..d48db67132 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -6,7 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { Color } from 'vs/base/common/color'; -import { ITheme, IThemeService, IIconTheme } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; @@ -18,13 +18,23 @@ export const VS_HC_THEME = 'hc-black'; export const HC_THEME_ID = 'Default High Contrast'; -export const COLOR_THEME_SETTING = 'workbench.colorTheme'; -export const ICON_THEME_SETTING = 'workbench.iconTheme'; -export const CUSTOM_WORKBENCH_COLORS_SETTING = 'workbench.colorCustomizations'; -export const CUSTOM_EDITOR_COLORS_SETTING = 'editor.tokenColorCustomizations'; -export const CUSTOM_EDITOR_TOKENSTYLES_SETTING = 'editor.tokenColorCustomizationsExperimental'; +export enum ThemeSettings { + COLOR_THEME = 'workbench.colorTheme', + ICON_THEME = 'workbench.iconTheme', + COLOR_CUSTOMIZATIONS = 'workbench.colorCustomizations', + TOKEN_COLOR_CUSTOMIZATIONS = 'editor.tokenColorCustomizations', + TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL = 'editor.tokenColorCustomizationsExperimental', -export interface IColorTheme extends ITheme { + PREFERRED_DARK_THEME = 'workbench.preferredDarkColorTheme', + PREFERRED_LIGHT_THEME = 'workbench.preferredLightColorTheme', + PREFERRED_HC_THEME = 'workbench.preferredHighContrastColorTheme', + DETECT_COLOR_SCHEME = 'window.autoDetectColorScheme', + DETECT_HC = 'window.autoDetectHighContrast', + + PRODUCT_ICON_THEME = 'workbench.productIconTheme' +} + +export interface IWorkbenchColorTheme extends IColorTheme { readonly id: string; readonly label: string; readonly settingsId: string; @@ -38,7 +48,7 @@ export interface IColorMap { [id: string]: Color; } -export interface IFileIconTheme extends IIconTheme { +export interface IWorkbenchFileIconTheme extends IFileIconTheme { readonly id: string; readonly label: string; readonly settingsId: string | null; @@ -51,18 +61,35 @@ export interface IFileIconTheme extends IIconTheme { readonly hidesExplorerArrows: boolean; } +export interface IWorkbenchProductIconTheme { + readonly id: string; + readonly label: string; + readonly settingsId: string; + readonly description?: string; + readonly extensionData?: ExtensionData; + + readonly isLoaded: boolean; +} + + export interface IWorkbenchThemeService extends IThemeService { _serviceBrand: undefined; - setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined): Promise; - getColorTheme(): IColorTheme; - getColorThemes(): Promise; - onDidColorThemeChange: Event; + setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined): Promise; + getColorTheme(): IWorkbenchColorTheme; + getColorThemes(): Promise; + onDidColorThemeChange: Event; restoreColorTheme(): void; - setFileIconTheme(iconThemeId: string | undefined, settingsTarget: ConfigurationTarget | undefined): Promise; - getFileIconTheme(): IFileIconTheme; - getFileIconThemes(): Promise; - onDidFileIconThemeChange: Event; + setFileIconTheme(iconThemeId: string | undefined, settingsTarget: ConfigurationTarget | undefined): Promise; + getFileIconTheme(): IWorkbenchFileIconTheme; + getFileIconThemes(): Promise; + onDidFileIconThemeChange: Event; + + setProductIconTheme(iconThemeId: string | undefined, settingsTarget: ConfigurationTarget | undefined): Promise; + getProductIconTheme(): IWorkbenchProductIconTheme; + getProductIconThemes(): Promise; + onDidProductIconThemeChange: Event; + } export interface IColorCustomizations { diff --git a/src/vs/workbench/services/timer/electron-browser/timerService.ts b/src/vs/workbench/services/timer/electron-browser/timerService.ts index d33ebbfbf8..c40b8d677b 100644 --- a/src/vs/workbench/services/timer/electron-browser/timerService.ts +++ b/src/vs/workbench/services/timer/electron-browser/timerService.ts @@ -18,6 +18,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; /* __GDPR__FRAGMENT__ "IMemoryInfo" : { @@ -303,7 +304,7 @@ class TimerService implements ITimerService { constructor( @IElectronService private readonly _electronService: IElectronService, - @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @IExtensionService private readonly _extensionService: IExtensionService, diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts index d0a3390319..47dc6bc6f5 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts @@ -255,7 +255,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt return !!target; } - async revert(): Promise { + async revert(): Promise { this.setDirty(false); // Emit as event @@ -265,8 +265,6 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt // no actual source on disk to revert to. As such we // dispose the model. this.dispose(); - - return true; } async backup(): Promise { diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts index 60b53299e5..7a7bec6027 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -82,7 +82,7 @@ suite('Untitled text editors', () => { assert.ok(!workingCopyService.isDirty(input2.resource)); assert.equal(workingCopyService.dirtyCount, 0); - assert.equal(await input1.revert(0), false); + await input1.revert(0); assert.ok(input1.isDisposed()); assert.ok(!service.get(input1.resource)); diff --git a/src/vs/workbench/services/url/electron-browser/urlService.ts b/src/vs/workbench/services/url/electron-browser/urlService.ts index ab882a96f8..1e10dd21cf 100644 --- a/src/vs/workbench/services/url/electron-browser/urlService.ts +++ b/src/vs/workbench/services/url/electron-browser/urlService.ts @@ -11,9 +11,10 @@ import { URLService } from 'vs/platform/url/node/urlService'; import { IOpenerService, IOpener, matchesScheme } from 'vs/platform/opener/common/opener'; import product from 'vs/platform/product/common/product'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; import { createChannelSender } from 'vs/base/parts/ipc/node/ipc'; import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export interface IRelayOpenURLOptions extends IOpenURLOptions { openToSide?: boolean; @@ -27,7 +28,7 @@ export class RelayURLService extends URLService implements IURLHandler, IOpener constructor( @IMainProcessService mainProcessService: IMainProcessService, @IOpenerService openerService: IOpenerService, - @IElectronEnvironmentService private electronEnvironmentService: IElectronEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, @IElectronService private electronService: IElectronService ) { super(); @@ -43,9 +44,9 @@ export class RelayURLService extends URLService implements IURLHandler, IOpener let query = uri.query; if (!query) { - query = `windowId=${encodeURIComponent(this.electronEnvironmentService.windowId)}`; + query = `windowId=${encodeURIComponent(this.environmentService.configuration.windowId)}`; } else { - query += `&windowId=${encodeURIComponent(this.electronEnvironmentService.windowId)}`; + query += `&windowId=${encodeURIComponent(this.environmentService.configuration.windowId)}`; } return uri.with({ query }); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts index c3f78516ef..0e7c666285 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts @@ -84,8 +84,16 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ return this.channel.call('resolveConflicts', [conflicts]); } - getRemoteContent(preview?: boolean): Promise { - return this.channel.call('getRemoteContent', [!!preview]); + getRemoteContent(ref?: string, fragment?: string): Promise { + return this.channel.call('getRemoteContent', [ref, fragment]); + } + + getLocalBackupContent(ref?: string, fragment?: string): Promise { + return this.channel.call('getLocalBackupContent', [ref, fragment]); + } + + getRemoteContentFromPreview(): Promise { + return this.channel.call('getRemoteContentFromPreview', []); } private async updateStatus(status: SyncStatus): Promise { diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService.ts new file mode 100644 index 0000000000..b95859314b --- /dev/null +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ResourceKey, IResourceRefHandle, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class UserDataSyncBackupStoreService implements IUserDataSyncBackupStoreService { + + _serviceBrand: undefined; + private readonly channel: IChannel; + + constructor( + @ISharedProcessService sharedProcessService: ISharedProcessService, + ) { + this.channel = sharedProcessService.getChannel('userDataSyncBackupStoreService'); + } + + backup(key: ResourceKey, content: string): Promise { + return this.channel.call('backup', [key, content]); + } + + + getAllRefs(key: ResourceKey): Promise { + return this.channel.call('getAllRefs', [key]); + } + + resolveContent(key: ResourceKey, ref: string): Promise { + return this.channel.call('resolveContent', [key, ref]); + } + +} + +registerSingleton(IUserDataSyncBackupStoreService, UserDataSyncBackupStoreService); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index dbf22bf30f..bdf1ecc387 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -10,6 +10,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { URI } from 'vs/base/common/uri'; export class UserDataSyncService extends Disposable implements IUserDataSyncService { @@ -88,8 +89,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.channel.call('stop'); } - getRemoteContent(source: SyncSource, preview: boolean): Promise { - return this.channel.call('getRemoteContent', [source, preview]); + resolveContent(resource: URI): Promise { + return this.channel.call('resolveContent', [resource]); } isFirstTimeSyncWithMerge(): Promise { diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreService.ts new file mode 100644 index 0000000000..30172ec831 --- /dev/null +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreService.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { SyncSource, IUserDataSyncStoreService, IUserDataSyncStore, getUserDataSyncStore, ResourceKey, IUserData, IUserDataManifest, IResourceRefHandle } from 'vs/platform/userDataSync/common/userDataSync'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IProductService } from 'vs/platform/product/common/productService'; + +export class UserDataSyncStoreService implements IUserDataSyncStoreService { + + _serviceBrand: undefined; + private readonly channel: IChannel; + readonly userDataSyncStore: IUserDataSyncStore | undefined; + + constructor( + @ISharedProcessService sharedProcessService: ISharedProcessService, + @IProductService productService: IProductService, + @IConfigurationService configurationService: IConfigurationService + ) { + this.channel = sharedProcessService.getChannel('userDataSyncStoreService'); + this.userDataSyncStore = getUserDataSyncStore(productService, configurationService); + } + + read(key: ResourceKey, oldValue: IUserData | null, source?: SyncSource): Promise { + throw new Error('Not Supported'); + } + + write(key: ResourceKey, content: string, ref: string | null, source?: SyncSource): Promise { + throw new Error('Not Supported'); + } + + manifest(): Promise { + throw new Error('Not Supported'); + } + + clear(): Promise { + throw new Error('Not Supported'); + } + + getAllRefs(key: ResourceKey): Promise { + return this.channel.call('getAllRefs', [key]); + } + + resolveContent(key: ResourceKey, ref: string): Promise { + return this.channel.call('resolveContent', [key, ref]); + } + + delete(key: ResourceKey): Promise { + return this.channel.call('delete', [key]); + } + +} + +registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts index 81cae6975a..c1ead31d54 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -8,7 +8,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { Disposable, IDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; -import { TernarySearchTree, values } from 'vs/base/common/map'; +import { values, ResourceMap } from 'vs/base/common/map'; import { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; import { ITextSnapshot } from 'vs/editor/common/model'; import { Schemas } from 'vs/base/common/network'; // {{SQL CARBON EDIT}} @chlafreniere need to block working copies of notebook editors from being tracked @@ -43,10 +43,20 @@ export interface IWorkingCopyBackup { export interface IWorkingCopy { + /** + * The unique resource of the working copy. There can only be one + * working copy in the system with the same URI. + */ readonly resource: URI; + /** + * Human readable name of the working copy. + */ readonly name: string; + /** + * The capabilities of the working copy. + */ readonly capabilities: WorkingCopyCapabilities; @@ -84,15 +94,22 @@ export interface IWorkingCopy { * * Providers of working copies should use `IBackupFileService.resolve(workingCopy.resource)` * to retrieve the backup metadata associated when loading the working copy. - * - * Not providing this method from the working copy will disable any - * backups and hot-exit functionality for those working copies. */ - backup?(): Promise; + backup(): Promise; + /** + * Asks the working copy to save. If the working copy was dirty, it is + * expected to be non-dirty after this operation has finished. + * + * @returns `true` if the operation was successful and `false` otherwise. + */ save(options?: ISaveOptions): Promise; - revert(options?: IRevertOptions): Promise; + /** + * Asks the working copy to revert. If the working copy was dirty, it is + * expected to be non-dirty after this operation has finished. + */ + revert(options?: IRevertOptions): Promise; //#endregion } @@ -134,8 +151,12 @@ export interface IWorkingCopyService { readonly workingCopies: IWorkingCopy[]; - getWorkingCopies(resource: URI): IWorkingCopy[]; - + /** + * Register a new working copy with the service. This method will + * throw if you try to register a working copy with a resource + * that was already registered before. There can only be 1 working + * copy per resource registered to the service. + */ registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable; //#endregion @@ -164,18 +185,16 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic //#region Registry - private readonly mapResourceToWorkingCopy = TernarySearchTree.forPaths>(); - get workingCopies(): IWorkingCopy[] { return values(this._workingCopies); } private _workingCopies = new Set(); - getWorkingCopies(resource: URI): IWorkingCopy[] { - const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString()); - - return workingCopies ? values(workingCopies) : []; - } + private readonly mapResourceToWorkingCopy = new ResourceMap(); registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable { + if (this.mapResourceToWorkingCopy.has(workingCopy.resource)) { + throw new Error(`Cannot register more than one working copy with the same resource ${workingCopy.resource.toString()}.`); + } + const disposables = new DisposableStore(); // {{SQL CARBON EDIT}} @chlafreniere need to block working copies of notebook editors from being tracked @@ -184,15 +203,8 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic } // Registry - let workingCopiesForResource = this.mapResourceToWorkingCopy.get(workingCopy.resource.toString()); - if (!workingCopiesForResource) { - workingCopiesForResource = new Set(); - this.mapResourceToWorkingCopy.set(workingCopy.resource.toString(), workingCopiesForResource); - } - - workingCopiesForResource.add(workingCopy); - this._workingCopies.add(workingCopy); + this.mapResourceToWorkingCopy.set(workingCopy.resource, workingCopy); // Wire in Events disposables.add(workingCopy.onDidChangeContent(() => this._onDidChangeContent.fire(workingCopy))); @@ -216,12 +228,8 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic private unregisterWorkingCopy(workingCopy: IWorkingCopy): void { // Remove from registry - const workingCopiesForResource = this.mapResourceToWorkingCopy.get(workingCopy.resource.toString()); - if (workingCopiesForResource && workingCopiesForResource.delete(workingCopy) && workingCopiesForResource.size === 0) { - this.mapResourceToWorkingCopy.delete(workingCopy.resource.toString()); - } - this._workingCopies.delete(workingCopy); + this.mapResourceToWorkingCopy.delete(workingCopy.resource); // If copy is dirty, ensure to fire an event to signal the dirty change // (a disposed working copy cannot account for being dirty in our model) @@ -262,13 +270,9 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic } isDirty(resource: URI): boolean { - const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString()); - if (workingCopies) { - for (const workingCopy of workingCopies) { - if (workingCopy.isDirty()) { - return true; - } - } + const workingCopy = this.mapResourceToWorkingCopy.get(resource); + if (workingCopy) { + return workingCopy.isDirty(); } return false; diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts index b628bddbce..eaf08f7d38 100644 --- a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -56,10 +56,8 @@ suite('WorkingCopyService', () => { return true; } - async revert(options?: IRevertOptions): Promise { + async revert(options?: IRevertOptions): Promise { this.setDirty(false); - - return true; } async backup(): Promise { @@ -112,8 +110,8 @@ suite('WorkingCopyService', () => { assert.equal(service.dirtyCount, 1); assert.equal(service.dirtyWorkingCopies.length, 1); assert.equal(service.dirtyWorkingCopies[0], copy1); - assert.equal(service.getWorkingCopies(copy1.resource).length, 1); - assert.equal(service.getWorkingCopies(copy1.resource)[0], copy1); + assert.equal(service.workingCopies.length, 1); + assert.equal(service.workingCopies[0], copy1); assert.equal(service.isDirty(resource1), true); assert.equal(service.hasDirty, true); assert.equal(onDidChangeDirty.length, 1); @@ -167,7 +165,7 @@ suite('WorkingCopyService', () => { assert.equal(onDidChangeDirty[3], copy2); }); - test('registry - multiple copies on same resource', () => { + test('registry - multiple copies on same resource throws', () => { const service = new TestWorkingCopyService(); const onDidChangeDirty: IWorkingCopy[] = []; @@ -176,37 +174,10 @@ suite('WorkingCopyService', () => { const resource = URI.parse('custom://some/folder/custom.txt'); const copy1 = new TestWorkingCopy(resource); - const unregister1 = service.registerWorkingCopy(copy1); + service.registerWorkingCopy(copy1); const copy2 = new TestWorkingCopy(resource); - const unregister2 = service.registerWorkingCopy(copy2); - assert.equal(service.getWorkingCopies(copy1.resource).length, 2); - assert.equal(service.getWorkingCopies(copy1.resource)[0], copy1); - assert.equal(service.getWorkingCopies(copy1.resource)[1], copy2); - - copy1.setDirty(true); - - assert.equal(service.dirtyCount, 1); - assert.equal(onDidChangeDirty.length, 1); - assert.equal(service.isDirty(resource), true); - - copy2.setDirty(true); - - assert.equal(service.dirtyCount, 2); - assert.equal(onDidChangeDirty.length, 2); - assert.equal(service.isDirty(resource), true); - - unregister1.dispose(); - - assert.equal(service.dirtyCount, 1); - assert.equal(onDidChangeDirty.length, 3); - assert.equal(service.isDirty(resource), true); - - unregister2.dispose(); - - assert.equal(service.dirtyCount, 0); - assert.equal(onDidChangeDirty.length, 4); - assert.equal(service.isDirty(resource), false); + assert.throws(() => service.registerWorkingCopy(copy2)); }); }); diff --git a/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts index a43e1aa633..a6754728f6 100644 --- a/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts @@ -31,6 +31,7 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; import { isMacintosh } from 'vs/base/common/platform'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingService { @@ -49,7 +50,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi @IFileService fileService: IFileService, @ITextFileService textFileService: ITextFileService, @IWorkspacesService workspacesService: IWorkspacesService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService protected environmentService: INativeWorkbenchEnvironmentService, @IFileDialogService fileDialogService: IFileDialogService, @IDialogService protected dialogService: IDialogService, @ILifecycleService private readonly lifecycleService: ILifecycleService, diff --git a/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts b/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts index 9aec284b32..2a1b8c7684 100644 --- a/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts +++ b/src/vs/workbench/services/workspaces/electron-browser/workspacesService.ts @@ -6,8 +6,9 @@ import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; import { createChannelSender } from 'vs/base/parts/ipc/node/ipc'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class NativeWorkspacesService { @@ -15,9 +16,9 @@ export class NativeWorkspacesService { constructor( @IMainProcessService mainProcessService: IMainProcessService, - @IElectronEnvironmentService electronEnvironmentService: IElectronEnvironmentService + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService ) { - return createChannelSender(mainProcessService.getChannel('workspaces'), { context: electronEnvironmentService.windowId }); + return createChannelSender(mainProcessService.getChannel('workspaces'), { context: environmentService.configuration.windowId }); } } diff --git a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts index 0116ad65ec..db5f81a627 100644 --- a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts @@ -895,7 +895,7 @@ suite('ExtHostLanguageFeatureCommands', function () { disposables.push(extHost.registerCallHierarchyProvider(nullExtensionDescription, defaultSelector, new class implements vscode.CallHierarchyProvider { - prepareCallHierarchy(document: vscode.TextDocument, position: vscode.Position, ): vscode.ProviderResult { + prepareCallHierarchy(document: vscode.TextDocument, position: vscode.Position,): vscode.ProviderResult { return new types.CallHierarchyItem(types.SymbolKind.Constant, 'ROOT', 'ROOT', document.uri, new types.Range(0, 0, 0, 0), new types.Range(0, 0, 0, 0)); } @@ -931,4 +931,53 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.equal(outgoing.length, 1); assert.equal(outgoing[0].to.name, 'OUTGOING'); }); + + test('selectionRangeProvider on inner array always returns outer array #91852', async function () { + + disposables.push(extHost.registerSelectionRangeProvider(nullExtensionDescription, defaultSelector, { + provideSelectionRanges(_doc, positions) { + const [first] = positions; + return [ + new types.SelectionRange(new types.Range(first.line, first.character, first.line, first.character)), + ]; + } + })); + + await rpcProtocol.sync(); + let value = await commands.executeCommand('vscode.executeSelectionRangeProvider', model.uri, [new types.Position(0, 10)]); + assert.equal(value.length, 1); + assert.equal(value[0].range.start.line, 0); + assert.equal(value[0].range.start.character, 10); + assert.equal(value[0].range.end.line, 0); + assert.equal(value[0].range.end.character, 10); + }); + + test('selectionRangeProvider on inner array always returns outer array #91852', async function () { + + disposables.push(extHost.registerSelectionRangeProvider(nullExtensionDescription, defaultSelector, { + provideSelectionRanges(_doc, positions) { + const [first, second] = positions; + return [ + new types.SelectionRange(new types.Range(first.line, first.character, first.line, first.character)), + new types.SelectionRange(new types.Range(second.line, second.character, second.line, second.character)), + ]; + } + })); + + await rpcProtocol.sync(); + let value = await commands.executeCommand( + 'vscode.executeSelectionRangeProvider', + model.uri, + [new types.Position(0, 0), new types.Position(0, 10)] + ); + assert.equal(value.length, 2); + assert.equal(value[0].range.start.line, 0); + assert.equal(value[0].range.start.character, 0); + assert.equal(value[0].range.end.line, 0); + assert.equal(value[0].range.end.character, 0); + assert.equal(value[1].range.start.line, 0); + assert.equal(value[1].range.start.character, 10); + assert.equal(value[1].range.end.line, 0); + assert.equal(value[1].range.end.character, 10); + }); }); diff --git a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts index e31238e32e..16967ee12e 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts @@ -13,7 +13,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol'; import { createTestCodeEditor, TestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { mock } from 'vs/workbench/test/browser/api/mock'; -import { TestEditorService, TestEditorGroupsService, TestTextResourcePropertiesService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEditorService, TestEditorGroupsService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Event } from 'vs/base/common/event'; import { ITextModel } from 'vs/editor/common/model'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -25,6 +25,7 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices'; suite('MainThreadDocumentsAndEditors', () => { diff --git a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts index 6e6aeac0d9..adec2a43b3 100644 --- a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts @@ -19,7 +19,7 @@ import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { IModelService } from 'vs/editor/common/services/modelService'; import { EditOperation } from 'vs/editor/common/core/editOperation'; -import { TestFileService, TestEditorService, TestEditorGroupsService, TestEnvironmentService, TestContextService, TestTextResourcePropertiesService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestFileService, TestEditorService, TestEditorGroupsService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; import { BulkEditService } from 'vs/workbench/services/bulkEdit/browser/bulkEditService'; import { NullLogService, ILogService } from 'vs/platform/log/common/log'; import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; @@ -47,6 +47,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { TestTextResourcePropertiesService, TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; suite('MainThreadEditors', () => { diff --git a/src/vs/workbench/test/browser/part.test.ts b/src/vs/workbench/test/browser/part.test.ts index 1a69cb1dd8..9da5050529 100644 --- a/src/vs/workbench/test/browser/part.test.ts +++ b/src/vs/workbench/test/browser/part.test.ts @@ -8,8 +8,9 @@ import { Part } from 'vs/workbench/browser/part'; import * as Types from 'vs/base/common/types'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { append, $, hide } from 'vs/base/browser/dom'; -import { TestStorageService, TestLayoutService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestLayoutService } from 'vs/workbench/test/browser/workbenchTestServices'; import { StorageScope } from 'vs/platform/storage/common/storage'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; class SimplePart extends Part { diff --git a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index 952864a70e..71a37a88be 100644 --- a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts @@ -11,7 +11,7 @@ import * as Platform from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { workbenchInstantiationService, TestEditorGroupView, TestEditorGroupsService, TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestEditorGroupView, TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { URI } from 'vs/base/common/uri'; @@ -19,6 +19,7 @@ import { IEditorRegistry, Extensions, EditorDescriptor } from 'vs/workbench/brow import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { dispose } from 'vs/base/common/lifecycle'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; const NullThemeService = new TestThemeService(); @@ -92,7 +93,7 @@ class MyOtherInput extends EditorInput { return null; } } -class MyResourceInput extends ResourceEditorInput { } +class MyResourceEditorInput extends ResourceEditorInput { } suite('Workbench base editor', () => { @@ -103,11 +104,9 @@ suite('Workbench base editor', () => { assert(!e.isVisible()); assert(!e.input); - assert(!e.options); await e.setInput(input, options, CancellationToken.None); assert.strictEqual(input, e.input); - assert.strictEqual(options, e.options); const group = new TestEditorGroupView(1); e.setVisible(true, group); assert(e.isVisible()); @@ -120,7 +119,6 @@ suite('Workbench base editor', () => { e.setVisible(false, group); assert(!e.isVisible()); assert(!e.input); - assert(!e.options); assert(!e.getControl()); }); @@ -156,11 +154,11 @@ suite('Workbench base editor', () => { test('Editor Lookup favors specific class over superclass (match on specific class)', function () { let d1 = EditorDescriptor.create(MyEditor, 'id1', 'name'); - const disposable = EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyResourceInput)]); + const disposable = EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyResourceEditorInput)]); let inst = workbenchInstantiationService(); - const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); + const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceEditorInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); assert.strictEqual(editor.getId(), 'myEditor'); const otherEditor = EditorRegistry.getEditor(inst.createInstance(ResourceEditorInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); @@ -172,7 +170,7 @@ suite('Workbench base editor', () => { test('Editor Lookup favors specific class over superclass (match on super class)', function () { let inst = workbenchInstantiationService(); - const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); + const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceEditorInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); assert.strictEqual('workbench.editors.textResourceEditor', editor.getId()); }); diff --git a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts index 42e781995f..593ada2c44 100644 --- a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts @@ -7,9 +7,9 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; -import { TestContextService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { FileKind } from 'vs/platform/files/common/files'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; suite('Breadcrumb Model', function () { diff --git a/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts b/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts index 4470a31f9e..b211a4d279 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorGroups.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { EditorGroup, ISerializedEditorGroup, EditorCloseEvent } from 'vs/workbench/common/editor/editorGroup'; import { Extensions as EditorExtensions, IEditorInputFactoryRegistry, EditorInput, IFileEditorInput, IEditorInputFactory, CloseDirection, EditorsOrder } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { TestLifecycleService, TestContextService, TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -21,6 +21,7 @@ import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtil import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { TestContextService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; function inst(): IInstantiationService { let inst = new TestInstantiationService(); diff --git a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts index b3793de4a3..2af7160550 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts @@ -17,13 +17,13 @@ import { ITextBufferFactory } from 'vs/editor/common/model'; import { URI } from 'vs/base/common/uri'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; -import { TestTextResourcePropertiesService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices'; class MyEditorModel extends EditorModel { } class MyTextEditorModel extends BaseTextEditorModel { diff --git a/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts b/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts index ba34af1f56..342fa83d1c 100644 --- a/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts @@ -42,7 +42,7 @@ suite('Editor - Range decorations', () => { codeEditor = createTestCodeEditor({ model: model }); instantiationService.stub(IEditorService, 'activeEditor', { get resource() { return codeEditor.getModel()!.uri; } }); - instantiationService.stub(IEditorService, 'activeTextEditorWidget', codeEditor); + instantiationService.stub(IEditorService, 'activeTextEditorControl', codeEditor); testObject = instantiationService.createInstance(RangeHighlightDecorations); }); diff --git a/src/vs/workbench/test/browser/quickAccess.test.ts b/src/vs/workbench/test/browser/quickAccess.test.ts new file mode 100644 index 0000000000..d717e04f96 --- /dev/null +++ b/src/vs/workbench/test/browser/quickAccess.test.ts @@ -0,0 +1,186 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions, IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IQuickPick, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { DisposableStore, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; +import { timeout } from 'vs/base/common/async'; + +suite('QuickAccess', () => { + + let instantiationService: IInstantiationService; + let accessor: TestServiceAccessor; + + let provider1Called = false; + let provider1Canceled = false; + let provider1Disposed = false; + + let provider2Called = false; + let provider2Canceled = false; + let provider2Disposed = false; + + let provider3Called = false; + let provider3Canceled = false; + let provider3Disposed = false; + + let provider4Called = false; + let provider4Canceled = false; + let provider4Disposed = false; + + class TestProvider1 implements IQuickAccessProvider { + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + assert.ok(picker); + provider1Called = true; + token.onCancellationRequested(() => provider1Canceled = true); + + return toDisposable(() => provider1Disposed = true); + } + } + + class TestProvider2 implements IQuickAccessProvider { + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + assert.ok(picker); + provider2Called = true; + token.onCancellationRequested(() => provider2Canceled = true); + + return toDisposable(() => provider2Disposed = true); + } + } + + class TestProvider3 implements IQuickAccessProvider { + + constructor(@IQuickInputService private readonly quickInputService: IQuickInputService, disposables: DisposableStore) { } + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + assert.ok(picker); + provider3Called = true; + token.onCancellationRequested(() => provider3Canceled = true); + + // bring up provider #4 + setTimeout(() => this.quickInputService.quickAccess.show(providerDescriptor4.prefix)); + + return toDisposable(() => provider3Disposed = true); + } + } + + class TestProvider4 implements IQuickAccessProvider { + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + assert.ok(picker); + provider4Called = true; + token.onCancellationRequested(() => provider4Canceled = true); + + // hide without picking + setTimeout(() => picker.hide()); + + return toDisposable(() => provider4Disposed = true); + } + } + + const defaultProviderDescriptor = { ctor: TestProvider1, prefix: '', helpEntries: [] }; + const providerDescriptor1 = { ctor: TestProvider1, prefix: 'test', helpEntries: [] }; + const providerDescriptor2 = { ctor: TestProvider2, prefix: 'test something', helpEntries: [] }; + const providerDescriptor3 = { ctor: TestProvider3, prefix: 'default', helpEntries: [] }; + const providerDescriptor4 = { ctor: TestProvider4, prefix: 'changed', helpEntries: [] }; + + setup(() => { + instantiationService = workbenchInstantiationService(); + accessor = instantiationService.createInstance(TestServiceAccessor); + }); + + test('registry', () => { + const registry = (Registry.as(Extensions.Quickaccess)); + registry.defaultProvider = defaultProviderDescriptor; + + const initialSize = registry.getQuickAccessProviders().length; + + const disposable = registry.registerQuickAccessProvider(providerDescriptor1); + + assert(registry.getQuickAccessProvider('test') === providerDescriptor1); + + const providers = registry.getQuickAccessProviders(); + assert(providers.some(provider => provider.prefix === 'test')); + + disposable.dispose(); + assert.ok(!registry.getQuickAccessProvider('test')); + assert.equal(registry.getQuickAccessProviders().length - initialSize, 0); + }); + + test('provider', async () => { + const registry = (Registry.as(Extensions.Quickaccess)); + const defaultProvider = registry.defaultProvider; + + const disposables = new DisposableStore(); + + disposables.add(registry.registerQuickAccessProvider(providerDescriptor1)); + disposables.add(registry.registerQuickAccessProvider(providerDescriptor2)); + disposables.add(registry.registerQuickAccessProvider(providerDescriptor4)); + registry.defaultProvider = providerDescriptor3; + + accessor.quickInputService.quickAccess.show('test'); + assert.equal(provider1Called, true); + assert.equal(provider2Called, false); + assert.equal(provider3Called, false); + assert.equal(provider4Called, false); + assert.equal(provider1Canceled, false); + assert.equal(provider2Canceled, false); + assert.equal(provider3Canceled, false); + assert.equal(provider4Canceled, false); + assert.equal(provider1Disposed, false); + assert.equal(provider2Disposed, false); + assert.equal(provider3Disposed, false); + assert.equal(provider4Disposed, false); + provider1Called = false; + + accessor.quickInputService.quickAccess.show('test something'); + assert.equal(provider1Called, false); + assert.equal(provider2Called, true); + assert.equal(provider3Called, false); + assert.equal(provider4Called, false); + assert.equal(provider1Canceled, true); + assert.equal(provider2Canceled, false); + assert.equal(provider3Canceled, false); + assert.equal(provider4Canceled, false); + assert.equal(provider1Disposed, true); + assert.equal(provider2Disposed, false); + assert.equal(provider3Disposed, false); + assert.equal(provider4Disposed, false); + provider2Called = false; + provider1Canceled = false; + provider1Disposed = false; + + accessor.quickInputService.quickAccess.show('usedefault'); + assert.equal(provider1Called, false); + assert.equal(provider2Called, false); + assert.equal(provider3Called, true); + assert.equal(provider4Called, false); + assert.equal(provider1Canceled, false); + assert.equal(provider2Canceled, true); + assert.equal(provider3Canceled, false); + assert.equal(provider4Canceled, false); + assert.equal(provider1Disposed, false); + assert.equal(provider2Disposed, true); + assert.equal(provider3Disposed, false); + assert.equal(provider4Disposed, false); + + await timeout(1); + + assert.equal(provider3Canceled, true); + assert.equal(provider3Disposed, true); + assert.equal(provider4Called, true); + + await timeout(1); + + assert.equal(provider4Canceled, true); + assert.equal(provider4Disposed, true); + + disposables.dispose(); + registry.defaultProvider = defaultProvider; + }); +}); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 12facadaa3..3ce372364c 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -10,7 +10,7 @@ import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorInput, EditorOptions, EditorsOrder, IFileEditorInput, IEditorInputFactoryRegistry, IEditorInputFactory, Extensions as EditorExtensions, ISaveOptions, IMoveResult } from 'vs/workbench/common/editor'; +import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorInput, IEditorPane, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorInput, EditorOptions, EditorsOrder, IFileEditorInput, IEditorInputFactoryRegistry, IEditorInputFactory, Extensions as EditorExtensions, ISaveOptions, IMoveResult, ITextEditorPane, ITextDiffEditorPane, IVisibleEditorPane } from 'vs/workbench/common/editor'; import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView, IEditorGroupsAccessor } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; @@ -18,7 +18,7 @@ import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configur import { IWorkbenchLayoutService, Parts, Position as PartPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IEditorOptions, IResourceInput, IEditorModel } from 'vs/platform/editor/common/editor'; +import { IEditorOptions, IResourceEditorInput, IEditorModel, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ILifecycleService, BeforeShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; @@ -42,7 +42,7 @@ import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/pos import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; -import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, IModelDecorationOptions, ITextModel, ITextSnapshot } from 'vs/editor/common/model'; +import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, ITextSnapshot } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; import { IDialogService, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -51,24 +51,21 @@ import { IExtensionService, NullExtensionService } from 'vs/workbench/services/e import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from 'vs/workbench/services/decorations/browser/decorations'; import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService, IOpenEditorOverrideHandler, IVisibleEditor, ISaveEditorsOptions, IRevertAllEditorsOptions, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService, IOpenEditorOverrideHandler, ISaveEditorsOptions, IRevertAllEditorsOptions, IResourceEditorInputType, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; -import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; import { EditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { Dimension } from 'vs/base/browser/dom'; +import { Dimension, IDimension } from 'vs/base/browser/dom'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { ILabelService } from 'vs/platform/label/common/label'; import { timeout } from 'vs/base/common/async'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ViewletDescriptor, Viewlet } from 'vs/workbench/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { isLinux } from 'vs/base/common/platform'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { isLinux, isWindows } from 'vs/base/common/platform'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; -import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IPanel } from 'vs/workbench/common/panel'; @@ -84,7 +81,6 @@ import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbe import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { BrowserTextFileService } from 'vs/workbench/services/textfile/browser/browserTextFileService'; -import * as CommonWorkbenchTestServices from 'vs/workbench/test/common/workbenchTestServices'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; @@ -99,13 +95,18 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { CancellationToken } from 'vs/base/common/cancellation'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { CodeEditorService } from 'vs/workbench/services/editor/browser/codeEditorService'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IDiffEditor } from 'vs/editor/common/editorCommon'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { QuickInputService } from 'vs/workbench/services/quickinput/browser/quickInputService'; +import { IListService } from 'vs/platform/list/browser/listService'; +import { win32, posix } from 'vs/base/common/path'; +import { TestWorkingCopyService, TestContextService, TestStorageService, TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IViewsService, IView } from 'vs/workbench/common/views'; -export import TestTextResourcePropertiesService = CommonWorkbenchTestServices.TestTextResourcePropertiesService; -export import TestContextService = CommonWorkbenchTestServices.TestContextService; -export import TestStorageService = CommonWorkbenchTestServices.TestStorageService; -export import TestWorkingCopyService = CommonWorkbenchTestServices.TestWorkingCopyService; - -export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { +export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); } @@ -129,9 +130,12 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i instantiationService.stub(ITextResourceConfigurationService, new TestTextResourceConfigurationService(configService)); instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); instantiationService.stub(IStorageService, new TestStorageService()); - instantiationService.stub(IWorkbenchLayoutService, new TestLayoutService()); + instantiationService.stub(IRemotePathService, new TestRemotePathService(TestEnvironmentService)); + const layoutService = new TestLayoutService(); + instantiationService.stub(IWorkbenchLayoutService, layoutService); instantiationService.stub(IDialogService, new TestDialogService()); - instantiationService.stub(IAccessibilityService, new TestAccessibilityService()); + const accessibilityService = new TestAccessibilityService(); + instantiationService.stub(IAccessibilityService, accessibilityService); instantiationService.stub(IFileDialogService, new TestFileDialogService()); instantiationService.stub(IModeService, instantiationService.createInstance(ModeServiceImpl)); instantiationService.stub(IHistoryService, new TestHistoryService()); @@ -144,22 +148,26 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i instantiationService.stub(INotificationService, new TestNotificationService()); instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); instantiationService.stub(IMenuService, new TestMenuService()); - instantiationService.stub(IKeybindingService, new MockKeybindingService()); + const keybindingService = new MockKeybindingService(); + instantiationService.stub(IKeybindingService, keybindingService); instantiationService.stub(IDecorationsService, new TestDecorationsService()); instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IWorkingCopyFileService, instantiationService.createInstance(WorkingCopyFileService)); instantiationService.stub(ITextFileService, overrides?.textFileService ? overrides.textFileService(instantiationService) : instantiationService.createInstance(TestTextFileService)); instantiationService.stub(IHostService, instantiationService.createInstance(TestHostService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); - instantiationService.stub(IThemeService, new TestThemeService()); + const themeService = new TestThemeService(); + instantiationService.stub(IThemeService, themeService); instantiationService.stub(ILogService, new NullLogService()); const editorGroupService = new TestEditorGroupsService([new TestEditorGroupView(0)]); instantiationService.stub(IEditorGroupsService, editorGroupService); instantiationService.stub(ILabelService, instantiationService.createInstance(LabelService)); const editorService = new TestEditorService(editorGroupService); instantiationService.stub(IEditorService, editorService); - instantiationService.stub(ICodeEditorService, new TestCodeEditorService()); + instantiationService.stub(ICodeEditorService, new CodeEditorService(editorService, themeService)); instantiationService.stub(IViewletService, new TestViewletService()); + instantiationService.stub(IListService, new TestListService()); + instantiationService.stub(IQuickInputService, new QuickInputService(TestEnvironmentService, configService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService)); return instantiationService; } @@ -181,7 +189,9 @@ export class TestServiceAccessor { @ITextModelService public textModelResolverService: ITextModelService, @IUntitledTextEditorService public untitledTextEditorService: UntitledTextEditorService, @IConfigurationService public testConfigurationService: TestConfigurationService, - @IBackupFileService public backupFileService: TestBackupFileService + @IBackupFileService public backupFileService: TestBackupFileService, + @IHostService public hostService: TestHostService, + @IQuickInputService public quickInputService: IQuickInputService ) { } } @@ -311,10 +321,10 @@ export class TestHistoryService implements IHistoryService { forward(): void { } back(): void { } last(): void { } - remove(_input: IEditorInput | IResourceInput): void { } + remove(_input: IEditorInput | IResourceEditorInput): void { } clear(): void { } clearRecentlyOpened(): void { } - getHistory(): ReadonlyArray { return []; } + getHistory(): ReadonlyArray { return []; } openNextRecentlyUsedEditor(group?: GroupIdentifier): void { } openPreviouslyUsedEditor(group?: GroupIdentifier): void { } getLastActiveWorkspaceRoot(_schemeFilter: string): URI | undefined { return this.root; } @@ -376,7 +386,6 @@ export class TestLayoutService implements IWorkbenchLayoutService { getDimension(_part: Parts): Dimension { return new Dimension(0, 0); } getContainer(_part: Parts): HTMLElement { return null!; } isTitleBarHidden(): boolean { return false; } - getTitleBarOffset(): number { return 0; } isStatusBarHidden(): boolean { return false; } isActivityBarHidden(): boolean { return false; } setActivityBarHidden(_hidden: boolean): void { } @@ -395,7 +404,6 @@ export class TestLayoutService implements IWorkbenchLayoutService { removeClass(_clazz: string): void { } getMaximumEditorDimensions(): Dimension { throw new Error('not implemented'); } getWorkbenchContainer(): HTMLElement { throw new Error('not implemented'); } - getWorkbenchElement(): HTMLElement { throw new Error('not implemented'); } toggleZenMode(): void { } isEditorLayoutCentered(): boolean { return false; } centerEditorLayout(_active: boolean): void { } @@ -404,6 +412,7 @@ export class TestLayoutService implements IWorkbenchLayoutService { isWindowMaximized() { return false; } updateWindowMaximizedState(maximized: boolean): void { } getVisibleNeighborPart(part: Parts, direction: Direction): Parts | undefined { return undefined; } + focus() { } } let activeViewlet: Viewlet = {} as any; @@ -443,7 +452,7 @@ export class TestPanelService implements IPanelService { getPanel(id: string): any { return activeViewlet; } getPanels() { return []; } getPinnedPanels() { return []; } - getActivePanel(): IViewlet { return activeViewlet; } + getActivePanel(): IPanel { return activeViewlet; } setPanelEnablement(id: string, enabled: boolean): void { } dispose() { } showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable { throw new Error('Method not implemented.'); } @@ -452,6 +461,19 @@ export class TestPanelService implements IPanelService { getLastActivePanelId(): string { return undefined!; } } +export class TestViewsService implements IViewsService { + _serviceBrand: undefined; + + onDidChangeViewVisibilityEmitter = new Emitter<{ id: string; visible: boolean; }>(); + + onDidChangeViewVisibility = this.onDidChangeViewVisibilityEmitter.event; + isViewVisible(id: string): boolean { return true; } + getActiveViewWithId(id: string): T | null { return null; } + openView(id: string, focus?: boolean | undefined): Promise { return Promise.resolve(null); } + closeView(id: string): void { } + getProgressIndicator(id: string) { return null!; } +} + export class TestEditorGroupsService implements IEditorGroupsService { _serviceBrand: undefined; @@ -467,7 +489,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { onDidLayout: Event = Event.None; onDidEditorPartOptionsChange = Event.None; - orientation: any; + orientation = GroupOrientation.HORIZONTAL; whenRestored: Promise = Promise.resolve(undefined); willRestoreEditors = false; @@ -486,7 +508,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { setSize(_group: number | IEditorGroup, _size: { width: number, height: number }): void { } arrangeGroups(_arrangement: GroupsArrangement): void { } applyLayout(_layout: EditorGroupLayout): void { } - setGroupOrientation(_orientation: any): void { } + setGroupOrientation(_orientation: GroupOrientation): void { } addGroup(_location: number | IEditorGroup, _direction: GroupDirection, _options?: IAddGroupOptions): IEditorGroup { throw new Error('not implemented'); } removeGroup(_group: number | IEditorGroup): void { } moveGroup(_group: number | IEditorGroup, _location: number | IEditorGroup, _direction: GroupDirection): IEditorGroup { throw new Error('not implemented'); } @@ -504,7 +526,7 @@ export class TestEditorGroupView implements IEditorGroupView { constructor(public id: number) { } get group(): EditorGroup { throw new Error('not implemented'); } - activeControl!: IVisibleEditor; + activeEditorPane!: IVisibleEditorPane; activeEditor!: IEditorInput; previewEditor!: IEditorInput; count!: number; @@ -535,9 +557,9 @@ export class TestEditorGroupView implements IEditorGroupView { getEditors(_order?: EditorsOrder): ReadonlyArray { return []; } getEditorByIndex(_index: number): IEditorInput { throw new Error('not implemented'); } getIndexOfEditor(_editor: IEditorInput): number { return -1; } - openEditor(_editor: IEditorInput, _options?: IEditorOptions): Promise { throw new Error('not implemented'); } - openEditors(_editors: IEditorInputWithOptions[]): Promise { throw new Error('not implemented'); } - isOpened(_editor: IEditorInput | IResourceInput): boolean { return false; } + openEditor(_editor: IEditorInput, _options?: IEditorOptions): Promise { throw new Error('not implemented'); } + openEditors(_editors: IEditorInputWithOptions[]): Promise { throw new Error('not implemented'); } + isOpened(_editor: IEditorInput | IResourceEditorInput): boolean { return false; } isPinned(_editor: IEditorInput): boolean { return false; } isActive(_editor: IEditorInput): boolean { return false; } moveEditor(_editor: IEditorInput, _target: IEditorGroup, _options?: IMoveEditorOptions): void { } @@ -589,14 +611,14 @@ export class TestEditorService implements EditorServiceImpl { onDidOpenEditorFail: Event = Event.None; onDidMostRecentlyActiveEditorsChange: Event = Event.None; - activeControl!: IVisibleEditor; - activeTextEditorWidget: any; - activeTextEditorMode: any; - activeEditor!: IEditorInput; + activeEditorPane: IVisibleEditorPane | undefined; + activeTextEditorControl: ICodeEditor | IDiffEditor | undefined; + activeTextEditorMode: string | undefined; + activeEditor: IEditorInput | undefined; editors: ReadonlyArray = []; mostRecentlyActiveEditors: ReadonlyArray = []; - visibleControls: ReadonlyArray = []; - visibleTextEditorWidgets = []; + visibleEditorPanes: ReadonlyArray = []; + visibleTextEditorControls = []; visibleEditors: ReadonlyArray = []; count = this.editors.length; @@ -604,24 +626,28 @@ export class TestEditorService implements EditorServiceImpl { getEditors() { return []; } overrideOpenEditor(_handler: IOpenEditorOverrideHandler): IDisposable { return toDisposable(() => undefined); } - openEditor(_editor: any, _options?: any, _group?: any): Promise { throw new Error('not implemented'); } - doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditor): [IEditorGroup, EditorInput, EditorOptions | undefined] | undefined { + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceDiffEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + async openEditor(editor: IEditorInput | IResourceEditorInputType, optionsOrGroup?: IEditorOptions | ITextEditorOptions | IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise { + throw new Error('not implemented'); + } + doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditorInputType): [IEditorGroup, EditorInput, EditorOptions | undefined] | undefined { if (!this.editorGroupService) { return undefined; } return [this.editorGroupService.activeGroup, editor as EditorInput, undefined]; } - openEditors(_editors: any, _group?: any): Promise { throw new Error('not implemented'); } - isOpen(_editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): boolean { return false; } - getOpened(_editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): IEditorInput { throw new Error('not implemented'); } + openEditors(_editors: any, _group?: any): Promise { throw new Error('not implemented'); } + isOpen(_editor: IEditorInput | IResourceEditorInput): boolean { return false; } replaceEditors(_editors: any, _group: any) { return Promise.resolve(undefined); } invokeWithinEditorContext(fn: (accessor: ServicesAccessor) => T): T { throw new Error('not implemented'); } - createInput(_input: IResourceInput | IUntitledTextResourceInput | IResourceDiffInput | IResourceSideBySideInput): EditorInput { throw new Error('not implemented'); } + createEditorInput(_input: IResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput): EditorInput { throw new Error('not implemented'); } save(editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { throw new Error('Method not implemented.'); } saveAll(options?: ISaveEditorsOptions): Promise { throw new Error('Method not implemented.'); } - revert(editors: IEditorIdentifier[], options?: IRevertOptions): Promise { throw new Error('Method not implemented.'); } - revertAll(options?: IRevertAllEditorsOptions): Promise { throw new Error('Method not implemented.'); } + revert(editors: IEditorIdentifier[], options?: IRevertOptions): Promise { throw new Error('Method not implemented.'); } + revertAll(options?: IRevertAllEditorsOptions): Promise { throw new Error('Method not implemented.'); } } export class TestFileService implements IFileService { @@ -791,32 +817,6 @@ export class TestBackupFileService implements IBackupFileService { } } -export class TestCodeEditorService implements ICodeEditorService { - _serviceBrand: undefined; - - onCodeEditorAdd: Event = Event.None; - onCodeEditorRemove: Event = Event.None; - onDiffEditorAdd: Event = Event.None; - onDiffEditorRemove: Event = Event.None; - onDidChangeTransientModelProperty: Event = Event.None; - - addCodeEditor(_editor: ICodeEditor): void { } - removeCodeEditor(_editor: ICodeEditor): void { } - listCodeEditors(): ICodeEditor[] { return []; } - addDiffEditor(_editor: IDiffEditor): void { } - removeDiffEditor(_editor: IDiffEditor): void { } - listDiffEditors(): IDiffEditor[] { return []; } - getFocusedCodeEditor(): ICodeEditor | null { return null; } - registerDecorationType(_key: string, _options: IDecorationRenderOptions, _parentTypeKey?: string): void { } - removeDecorationType(_key: string): void { } - resolveDecorationOptions(_typeKey: string, _writable: boolean): IModelDecorationOptions { return Object.create(null); } - setTransientModelProperty(_model: ITextModel, _key: string, _value: any): void { } - getTransientModelProperty(_model: ITextModel, _key: string) { } - getTransientModelProperties(_model: ITextModel) { return undefined; } - getActiveCodeEditor(): ICodeEditor | null { return null; } - openCodeEditor(_input: IResourceInput, _source: ICodeEditor, _sideBySide?: boolean): Promise { return Promise.resolve(null); } -} - export class TestLifecycleService implements ILifecycleService { _serviceBrand: undefined; @@ -906,9 +906,17 @@ export class TestHostService implements IHostService { _serviceBrand: undefined; - readonly hasFocus: boolean = true; - async hadLastFocus(): Promise { return true; } - readonly onDidChangeFocus: Event = Event.None; + private _hasFocus = true; + get hasFocus() { return this._hasFocus; } + async hadLastFocus(): Promise { return this._hasFocus; } + + private _onDidChangeFocus = new Emitter(); + readonly onDidChangeFocus = this._onDidChangeFocus.event; + + setFocus(focus: boolean) { + this._hasFocus = focus; + this._onDidChangeFocus.fire(this._hasFocus); + } async restart(): Promise { } async reload(): Promise { } @@ -962,7 +970,7 @@ export function registerTestEditor(id: string, inputs: SyncDescriptor { + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { this.gotReverted = true; this.gotSaved = false; this.gotSavedAs = false; - return true; } setDirty(): void { this.dirty = true; } isDirty(): boolean { @@ -1059,3 +1066,47 @@ export class TestFileEditorInput extends EditorInput implements IFileEditorInput movedEditor: IMoveResult | undefined = undefined; move(): IMoveResult | undefined { return this.movedEditor; } } + +export class TestEditorPart extends EditorPart { + + saveState(): void { + return super.saveState(); + } + + clearState(): void { + const workspaceMemento = this.getMemento(StorageScope.WORKSPACE); + for (const key of Object.keys(workspaceMemento)) { + delete workspaceMemento[key]; + } + + const globalMemento = this.getMemento(StorageScope.GLOBAL); + for (const key of Object.keys(globalMemento)) { + delete globalMemento[key]; + } + } +} + +export class TestListService implements IListService { + _serviceBrand: undefined; + + lastFocusedList: any | undefined = undefined; + + register(): IDisposable { + return Disposable.None; + } +} + +export class TestRemotePathService implements IRemotePathService { + + _serviceBrand: undefined; + + constructor(@IWorkbenchEnvironmentService private readonly environmentService: IEnvironmentService) { } + + get path() { return Promise.resolve(isWindows ? win32 : posix); } + + get userHome() { return Promise.resolve(URI.file(this.environmentService.userHome)); } + + async fileURI(path: string): Promise { + return URI.file(path); + } +} diff --git a/src/vs/workbench/test/common/memento.test.ts b/src/vs/workbench/test/common/memento.test.ts index a72b4a6e73..d95c9eec6d 100644 --- a/src/vs/workbench/test/common/memento.test.ts +++ b/src/vs/workbench/test/common/memento.test.ts @@ -21,7 +21,7 @@ suite('Memento', () => { let myMemento = new Memento('memento.test', storage); // Global - let memento: any = myMemento.getMemento(StorageScope.GLOBAL); + let memento = myMemento.getMemento(StorageScope.GLOBAL); memento.foo = [1, 2, 3]; let globalMemento = myMemento.getMemento(StorageScope.GLOBAL); assert.deepEqual(globalMemento, memento); @@ -76,7 +76,7 @@ suite('Memento', () => { let myMemento = new Memento('memento.test', storage); // Global - let memento: any = myMemento.getMemento(context!); + let memento = myMemento.getMemento(context!); memento.foo = [1, 2, 3]; // Workspace @@ -141,7 +141,7 @@ suite('Memento', () => { let myMemento2 = new Memento('memento.test', storage); // Global - let memento: any = myMemento.getMemento(context!); + let memento = myMemento.getMemento(context!); memento.foo = [1, 2, 3]; memento = myMemento2.getMemento(context!); diff --git a/src/vs/workbench/test/common/notifications.test.ts b/src/vs/workbench/test/common/notifications.test.ts index cdfcd28fda..2413ce7dc1 100644 --- a/src/vs/workbench/test/common/notifications.test.ts +++ b/src/vs/workbench/test/common/notifications.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { NotificationsModel, NotificationViewItem, INotificationChangeEvent, NotificationChangeType, NotificationViewItemLabelKind, IStatusMessageChangeEvent, StatusMessageChangeType } from 'vs/workbench/common/notifications'; +import { NotificationsModel, NotificationViewItem, INotificationChangeEvent, NotificationChangeType, NotificationViewItemContentChangeKind, IStatusMessageChangeEvent, StatusMessageChangeType } from 'vs/workbench/common/notifications'; import { Action } from 'vs/base/common/actions'; import { INotification, Severity, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; @@ -58,8 +58,8 @@ suite('Notifications', () => { assert.equal(called, 2); called = 0; - item1.onDidChangeLabel(e => { - if (e.kind === NotificationViewItemLabelKind.PROGRESS) { + item1.onDidChangeContent(e => { + if (e.kind === NotificationViewItemContentChangeKind.PROGRESS) { called++; } }); @@ -70,8 +70,8 @@ suite('Notifications', () => { assert.equal(called, 2); called = 0; - item1.onDidChangeLabel(e => { - if (e.kind === NotificationViewItemLabelKind.MESSAGE) { + item1.onDidChangeContent(e => { + if (e.kind === NotificationViewItemContentChangeKind.MESSAGE) { called++; } }); @@ -79,8 +79,8 @@ suite('Notifications', () => { item1.updateMessage('message update'); called = 0; - item1.onDidChangeLabel(e => { - if (e.kind === NotificationViewItemLabelKind.SEVERITY) { + item1.onDidChangeContent(e => { + if (e.kind === NotificationViewItemContentChangeKind.SEVERITY) { called++; } }); @@ -88,8 +88,8 @@ suite('Notifications', () => { item1.updateSeverity(Severity.Error); called = 0; - item1.onDidChangeLabel(e => { - if (e.kind === NotificationViewItemLabelKind.ACTIONS) { + item1.onDidChangeContent(e => { + if (e.kind === NotificationViewItemContentChangeKind.ACTIONS) { called++; } }); @@ -159,6 +159,22 @@ suite('Notifications', () => { assert.equal(lastNotificationEvent.index, 0); assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD); + item1Handle.updateMessage('Error Message'); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE); + assert.equal(lastNotificationEvent.detail, NotificationViewItemContentChangeKind.MESSAGE); + + item1Handle.updateSeverity(Severity.Error); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE); + assert.equal(lastNotificationEvent.detail, NotificationViewItemContentChangeKind.SEVERITY); + + item1Handle.updateActions({ primary: [], secondary: [] }); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE); + assert.equal(lastNotificationEvent.detail, NotificationViewItemContentChangeKind.ACTIONS); + + item1Handle.progress.infinite(); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE); + assert.equal(lastNotificationEvent.detail, NotificationViewItemContentChangeKind.PROGRESS); + let item2Handle = model.addNotification(item2); assert.equal(lastNotificationEvent.item.severity, item2.severity); assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item2.message); @@ -204,7 +220,7 @@ suite('Notifications', () => { assert.equal(lastNotificationEvent.item.severity, item3.severity); assert.equal(lastNotificationEvent.item.message.linkedText.toString(), item3.message); assert.equal(lastNotificationEvent.index, 0); - assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.EXPAND_COLLAPSE); const disposable = model.showStatusMessage('Hello World'); assert.equal(model.statusMessage!.message, 'Hello World'); diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index 81861e2553..de66afcef0 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -35,16 +35,22 @@ export class TestTextResourcePropertiesService implements ITextResourcePropertie } export class TestContextService implements IWorkspaceContextService { + _serviceBrand: undefined; private workspace: Workspace; - private options: any; + private options: object; private readonly _onDidChangeWorkspaceName: Emitter; - private readonly _onDidChangeWorkspaceFolders: Emitter; - private readonly _onDidChangeWorkbenchState: Emitter; + get onDidChangeWorkspaceName(): Event { return this._onDidChangeWorkspaceName.event; } - constructor(workspace: any = TestWorkspace, options: any = null) { + private readonly _onDidChangeWorkspaceFolders: Emitter; + get onDidChangeWorkspaceFolders(): Event { return this._onDidChangeWorkspaceFolders.event; } + + private readonly _onDidChangeWorkbenchState: Emitter; + get onDidChangeWorkbenchState(): Event { return this._onDidChangeWorkbenchState.event; } + + constructor(workspace = TestWorkspace, options = null) { this.workspace = workspace; this.options = options || Object.create(null); this._onDidChangeWorkspaceName = new Emitter(); @@ -52,18 +58,6 @@ export class TestContextService implements IWorkspaceContextService { this._onDidChangeWorkbenchState = new Emitter(); } - get onDidChangeWorkspaceName(): Event { - return this._onDidChangeWorkspaceName.event; - } - - get onDidChangeWorkspaceFolders(): Event { - return this._onDidChangeWorkspaceFolders.event; - } - - get onDidChangeWorkbenchState(): Event { - return this._onDidChangeWorkbenchState.event; - } - getFolders(): IWorkspaceFolder[] { return this.workspace ? this.workspace.folders : []; } @@ -100,9 +94,7 @@ export class TestContextService implements IWorkspaceContextService { return this.options; } - updateOptions() { - - } + updateOptions() { } isInsideWorkspace(resource: URI): boolean { if (resource && this.workspace) { diff --git a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts index 8d8e16c727..ad05abdf3e 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts @@ -28,7 +28,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { LocalSearchService } from 'vs/workbench/services/search/node/searchService'; import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; -import { TestContextService, TestEditorGroupsService, TestEditorService, TestTextResourcePropertiesService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEditorGroupsService, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestEnvironmentService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; @@ -39,6 +39,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { TestContextService, TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices'; namespace Timer { export interface ITimerEvent { diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index 495db72a6a..e5bc17281d 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -17,7 +17,7 @@ import * as minimist from 'vscode-minimist'; import * as path from 'vs/base/common/path'; import { LocalSearchService } from 'vs/workbench/services/search/node/searchService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { TestContextService, TestEditorService, TestEditorGroupsService, TestTextResourcePropertiesService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEditorService, TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestEnvironmentService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; @@ -42,6 +42,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { TestTextResourcePropertiesService, TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; // declare var __dirname: string; diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index 008199045c..9ed7b443e7 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -3,10 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestLifecycleService, TestFilesConfigurationService, TestContextService, TestFileService, TestFileDialogService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestLifecycleService, TestFilesConfigurationService, TestFileService, TestFileDialogService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Event } from 'vs/base/common/event'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { NativeWorkbenchEnvironmentService, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { NativeTextFileService, EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; @@ -25,7 +25,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { URI } from 'vs/base/common/uri'; import { IReadTextFileOptions, ITextFileStreamContent, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; -import { IOpenedWindow, IOpenEmptyWindowOptions, IWindowOpenable, IOpenWindowOptions, IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { IOpenedWindow, IOpenEmptyWindowOptions, IWindowOpenable, IOpenWindowOptions } from 'vs/platform/windows/common/windows'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { LogLevel } from 'vs/platform/log/common/log'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; @@ -37,12 +37,16 @@ import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { NodeTestBackupFileService } from 'vs/workbench/services/backup/test/electron-browser/backupFileService.test'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; -export const TestWindowConfiguration: IWindowConfiguration = { +export const TestWindowConfiguration: INativeWindowConfiguration = { windowId: 0, + machineId: 'testMachineId', sessionId: 'testSessionId', logLevel: LogLevel.Error, mainPid: 0, + partsSplashPath: '', appRoot: '', userEnv: {}, execPath: process.execPath, @@ -50,7 +54,7 @@ export const TestWindowConfiguration: IWindowConfiguration = { ...parseArgs(process.argv, OPTIONS) }; -export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(TestWindowConfiguration, process.execPath, 0); +export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(TestWindowConfiguration, process.execPath); export class TestTextFileService extends NativeTextFileService { private resolveTextContentError!: FileOperationError | null; @@ -61,7 +65,7 @@ export class TestTextFileService extends NativeTextFileService { @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService instantiationService: IInstantiationService, @IModelService modelService: IModelService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index d1592b48a2..c672b89633 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -27,7 +27,6 @@ import 'vs/workbench/browser/actions/workspaceActions'; import 'vs/workbench/browser/actions/workspaceCommands'; import 'vs/workbench/browser/parts/quickopen/quickOpenActions'; -import 'vs/workbench/browser/parts/quickinput/quickInputActions'; //#endregion @@ -43,9 +42,7 @@ import 'vs/workbench/api/browser/viewsExtensionPoint'; //#region --- workbench parts -import 'vs/workbench/browser/parts/quickinput/quickInput'; import 'vs/workbench/browser/parts/quickopen/quickOpenController'; -import 'vs/workbench/browser/parts/titlebar/titlebarPart'; import 'vs/workbench/browser/parts/editor/editorPart'; import 'vs/workbench/browser/parts/activitybar/activitybarPart'; import 'vs/workbench/browser/parts/panel/panelPart'; @@ -88,6 +85,7 @@ import 'vs/workbench/services/workingCopy/common/workingCopyService'; import 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import 'vs/workbench/services/views/browser/viewDescriptorService'; +import 'vs/workbench/services/quickinput/browser/quickInputService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; @@ -253,6 +251,9 @@ import 'vs/workbench/contrib/logs/common/logs.contribution'; // Quick Open Handlers import 'vs/workbench/contrib/quickopen/browser/quickopen.contribution'; +// Quick Access Providers +import 'vs/workbench/contrib/quickaccess/browser/quickAccess.contribution'; + // Explorer import 'vs/workbench/contrib/files/browser/explorerViewlet'; import 'vs/workbench/contrib/files/browser/fileActions.contribution'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 0f3a39bb02..fd045a5f82 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -43,7 +43,7 @@ import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import 'vs/workbench/services/telemetry/electron-browser/telemetryService'; import 'vs/workbench/services/configurationResolver/electron-browser/configurationResolverService'; import 'vs/workbench/services/extensionManagement/node/extensionManagementService'; -import 'vs/workbench/services/accessibility/node/accessibilityService'; +import 'vs/workbench/services/accessibility/electron-browser/accessibilityService'; import 'vs/workbench/services/remote/node/tunnelService'; import 'vs/workbench/services/backup/node/backupFileService'; import 'vs/workbench/services/url/electron-browser/urlService'; @@ -52,6 +52,8 @@ import 'vs/workbench/services/workspaces/electron-browser/workspaceEditingServic import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService'; import 'vs/workbench/services/userDataSync/electron-browser/settingsSyncService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService'; +import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreService'; +import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService'; import 'vs/workbench/services/authentication/electron-browser/authenticationTokenService'; import 'vs/workbench/services/authentication/browser/authenticationService'; import 'vs/workbench/services/host/electron-browser/desktopHostService'; @@ -69,8 +71,11 @@ import 'vs/workbench/services/extensionResourceLoader/electron-browser/extension import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; +import { TitlebarPart } from 'vs/workbench/electron-browser/parts/titlebar/titlebarPart'; +import { ITitleService } from 'vs/workbench/services/title/common/titleService'; registerSingleton(ICredentialsService, KeytarCredentialsService, true); +registerSingleton(ITitleService, TitlebarPart); //#endregion diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 93bde5a9d2..7c7ac022fd 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -230,15 +230,24 @@ interface IWorkbenchConstructionOptions { * @param domElement the container to create the workbench in * @param options for setting up the workbench */ +let created = false; async function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise { + // Assert that the workbench is not created more than once. We currently + // do not support this and require a full context switch to clean-up. + if (created) { + throw new Error('Unable to create the VSCode workbench more than once.'); + } else { + created = true; + } + // Startup workbench await main(domElement, options); // Register commands if any if (Array.isArray(options.commands)) { for (const command of options.commands) { - CommandsRegistry.registerCommand(command.id, (accessor, ...args: any[]) => { + CommandsRegistry.registerCommand(command.id, (accessor, ...args) => { // we currently only pass on the arguments but not the accessor // to the command to reduce our exposure of internal API. command.handler(...args); diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index ca0ba513a8..cc7be33a55 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -62,15 +62,18 @@ import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { TunnelService } from 'vs/workbench/services/remote/common/tunnelService'; import { ILoggerService } from 'vs/platform/log/common/log'; import { FileLoggerService } from 'vs/platform/log/common/fileLogService'; -import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService, ISettingsSyncService, IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService, ISettingsSyncService, IUserDataAutoSyncService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { AuthenticationService, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; +import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { IAuthenticationTokenService, AuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import { UserDataAutoSyncService } from 'vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService'; import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; +import { ITitleService } from 'vs/workbench/services/title/common/titleService'; +import { TitlebarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; registerSingleton(IExtensionManagementService, ExtensionManagementService); registerSingleton(IBackupFileService, BackupFileService); @@ -81,10 +84,12 @@ registerSingleton(ILoggerService, FileLoggerService); registerSingleton(IAuthenticationService, AuthenticationService); registerSingleton(IUserDataSyncLogService, UserDataSyncLogService); registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService); +registerSingleton(IUserDataSyncBackupStoreService, UserDataSyncBackupStoreService); registerSingleton(IAuthenticationTokenService, AuthenticationTokenService); registerSingleton(IUserDataAutoSyncService, UserDataAutoSyncService); registerSingleton(ISettingsSyncService, SettingsSynchroniser); registerSingleton(IUserDataSyncService, UserDataSyncService); +registerSingleton(ITitleService, TitlebarPart); //#endregion diff --git a/test/smoke/src/areas/languages/languages.test.ts b/test/smoke/src/areas/languages/languages.test.ts new file mode 100644 index 0000000000..ef75ef61c5 --- /dev/null +++ b/test/smoke/src/areas/languages/languages.test.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Application, ProblemSeverity, Problems } from '../../../../automation/out'; + +export function setup() { + describe('Language Features', () => { + it('verifies quick outline', async function () { + const app = this.app as Application; + await app.workbench.quickopen.openFile('style.css'); + + await app.workbench.quickopen.openQuickOutline(); + await app.workbench.quickopen.waitForQuickOpenElements(names => names.length === 2); + }); + + it('verifies problems view', async function () { + const app = this.app as Application; + await app.workbench.quickopen.openFile('style.css'); + await app.workbench.editor.waitForTypeInEditor('style.css', '.foo{}'); + + await app.code.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.WARNING)); + + await app.workbench.problems.showProblemsView(); + await app.code.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.WARNING)); + await app.workbench.problems.hideProblemsView(); + }); + + it('verifies settings', async function () { + const app = this.app as Application; + await app.workbench.settingsEditor.addUserSetting('css.lint.emptyRules', '"error"'); + await app.workbench.quickopen.openFile('style.css'); + + await app.code.waitForElement(Problems.getSelectorInEditor(ProblemSeverity.ERROR)); + + const problems = new Problems(app.code); + await problems.showProblemsView(); + await app.code.waitForElement(Problems.getSelectorInProblemsView(ProblemSeverity.ERROR)); + await problems.hideProblemsView(); + }); + }); +} diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 4ead782a9a..76931643bb 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -31,6 +31,7 @@ import { setup as setupDataMigrationTests } from './areas/workbench/data-migrati import { setup as setupDataLossTests } from './areas/workbench/data-loss.test'; import { setup as setupDataPreferencesTests } from './areas/preferences/preferences.test'; import { setup as setupDataSearchTests } from './areas/search/search.test'; +import { setup as setupDataLanguagesTests } from './areas/languages/languages.test'; import { setup as setupDataEditorTests } from './areas/editor/editor.test'; import { setup as setupDataStatusbarTests } from './areas/statusbar/statusbar.test'; import { setup as setupDataExtensionTests } from './areas/extensions/extensions.test'; @@ -321,6 +322,7 @@ describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { if (!opts.web) { setupDataLossTests(); } if (!opts.web) { setupDataPreferencesTests(); } setupDataSearchTests(); + setupDataLanguagesTests(); setupDataEditorTests(); setupDataStatusbarTests(!!opts.web); if (!opts.web) { setupDataExtensionTests(); } diff --git a/yarn.lock b/yarn.lock index 8b062cabd6..bd9a20bbf5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9476,10 +9476,10 @@ typescript-formatter@7.1.0: commandpost "^1.0.0" editorconfig "^0.15.0" -typescript@3.8.2: - version "3.8.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.2.tgz#91d6868aaead7da74f493c553aeff76c0c0b1d5a" - integrity sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ== +typescript@3.9.0-dev.20200304: + version "3.9.0-dev.20200304" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200304.tgz#3cc35357eff29dc5604b4fa56d6597e13daf86ed" + integrity sha512-eUip/GgJmjp4qtHiJDxVhE5SDDiPzBUg7KBAFUgb7HgL/tv10JAHej7fnS1i+7xrq1eDtbkJyPaYOVnhL9db7Q== typescript@^2.6.2: version "2.6.2"