diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 8e560b96cb..835cee2d26 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -110,10 +110,6 @@ "name": "vs/workbench/contrib/output", "project": "vscode-workbench" }, - { - "name": "vs/workbench/contrib/openInDesktop", - "project": "vscode-workbench" - }, { "name": "vs/workbench/contrib/performance", "project": "vscode-workbench" diff --git a/extensions/git/package.json b/extensions/git/package.json index e299d60c11..75306b8a78 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1808,7 +1808,6 @@ }, "dependencies": { "byline": "^5.0.0", - "dayjs": "1.8.19", "file-type": "^7.2.0", "iconv-lite": "^0.4.24", "jschardet": "2.1.1", @@ -1821,7 +1820,7 @@ "@types/byline": "4.2.31", "@types/file-type": "^5.2.1", "@types/mocha": "2.2.43", - "@types/node": "^12.11.7", + "@types/node": "^12.12.31", "@types/which": "^1.0.28", "mocha": "^3.2.0", "mocha-junit-reporter": "^1.23.3", diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index fb3d0150cd..53266f95df 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { promises as fs, exists } from 'fs'; +import { promises as fs, exists, realpath } from 'fs'; import * as path from 'path'; import * as os from 'os'; import * as cp from 'child_process'; @@ -21,6 +21,7 @@ import { StringDecoder } from 'string_decoder'; // https://github.com/microsoft/vscode/issues/65693 const MAX_CLI_LENGTH = 30000; +const isWindows = process.platform === 'win32'; export interface IGit { path: string; @@ -419,8 +420,40 @@ export class Git { async getRepositoryRoot(repositoryPath: string): Promise { const result = await this.exec(repositoryPath, ['rev-parse', '--show-toplevel']); + // Keep trailing spaces which are part of the directory name - return path.normalize(result.stdout.trimLeft().replace(/(\r\n|\r|\n)+$/, '')); + const repoPath = path.normalize(result.stdout.trimLeft().replace(/(\r\n|\r|\n)+$/, '')); + + if (isWindows) { + // On Git 2.25+ if you call `rev-parse --show-toplevel` on a mapped drive, instead of getting the mapped drive path back, you get the UNC path for the mapped drive. + // So we will try to normalize it back to the mapped drive path, if possible + const repoUri = Uri.file(repoPath); + const pathUri = Uri.file(repositoryPath); + if (repoUri.authority.length !== 0 && pathUri.authority.length === 0) { + let match = /(?<=^\/?)([a-zA-Z])(?=:\/)/.exec(pathUri.path); + if (match !== null) { + const [, letter] = match; + + try { + const networkPath = await new Promise(resolve => + realpath.native(`${letter}:`, { encoding: 'utf8' }, (err, resolvedPath) => + // eslint-disable-next-line eqeqeq + resolve(err != null ? undefined : resolvedPath), + ), + ); + if (networkPath !== undefined) { + return path.normalize( + repoUri.fsPath.replace(networkPath, `${letter.toLowerCase()}:`), + ); + } + } catch { } + } + + return path.normalize(pathUri.fsPath); + } + } + + return repoPath; } async getRepositoryDotGit(repositoryPath: string): Promise { diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index 559e889d67..53989138a5 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -4,19 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vscode-nls'; -import * as dayjs from 'dayjs'; -import * as advancedFormat from 'dayjs/plugin/advancedFormat'; -import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace } from 'vscode'; +import { CancellationToken, Disposable, env, Event, EventEmitter, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace } from 'vscode'; import { Model } from './model'; import { Repository, Resource } from './repository'; import { debounce } from './decorators'; -dayjs.extend(advancedFormat); - const localize = nls.loadMessageBundle(); -// TODO@eamodio: Localize or use a setting for date format - export class GitTimelineItem extends TimelineItem { static is(item: TimelineItem): item is GitTimelineItem { return item instanceof GitTimelineItem; @@ -145,16 +139,15 @@ export class GitTimelineProvider implements TimelineProvider { commits.splice(commits.length - 1, 1); } - let dateFormatter: dayjs.Dayjs; + const dateFormatter = new Intl.DateTimeFormat(env.language, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }); + const items = commits.map((c, i) => { const date = c.commitDate; // c.authorDate - dateFormatter = dayjs(date); - const item = new GitTimelineItem(c.hash, commits[i + 1]?.hash ?? `${c.hash}^`, c.message, date?.getTime() ?? 0, c.hash, 'git:file:commit'); item.iconPath = new (ThemeIcon as any)('git-commit'); item.description = c.authorName; - item.detail = `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n\n${c.message}`; + item.detail = `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.format(date)}\n\n${c.message}`; item.command = { title: 'Open Comparison', command: 'git.timeline.openDiff', @@ -170,13 +163,12 @@ export class GitTimelineProvider implements TimelineProvider { const index = repo.indexGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); if (index) { const date = this.repoStatusDate ?? new Date(); - dateFormatter = dayjs(date); const item = new GitTimelineItem('~', 'HEAD', localize('git.timeline.stagedChanges', 'Staged Changes'), date.getTime(), 'index', 'git:file:index'); // TODO@eamodio: Replace with a better icon -- reflecting its status maybe? item.iconPath = new (ThemeIcon as any)('git-commit'); 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.detail = localize('git.timeline.detail', '{0} \u2014 {1}\n{2}\n\n{3}', you, localize('git.index', 'Index'), dateFormatter.format(date), Resource.getStatusText(index.type)); item.command = { title: 'Open Comparison', command: 'git.timeline.openDiff', @@ -189,13 +181,12 @@ export class GitTimelineProvider implements TimelineProvider { const working = repo.workingTreeGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); if (working) { const date = new Date(); - dateFormatter = dayjs(date); const item = new GitTimelineItem('', index ? '~' : 'HEAD', localize('git.timeline.uncommitedChanges', 'Uncommited Changes'), date.getTime(), 'working', 'git:file:working'); // TODO@eamodio: Replace with a better icon -- reflecting its status maybe? item.iconPath = new (ThemeIcon as any)('git-commit'); 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.detail = localize('git.timeline.detail', '{0} \u2014 {1}\n{2}\n\n{3}', you, localize('git.workingTree', 'Working Tree'), dateFormatter.format(date), Resource.getStatusText(working.type)); item.command = { title: 'Open Comparison', command: 'git.timeline.openDiff', diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index ac135e43a8..f72600de7b 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -26,10 +26,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" integrity sha512-El3+WJk2D/ppWNd2X05aiP5l2k4EwF7KwheknQZls+I26eSICoWRhRIJ56jGgw2dqNGQ5LtNajmBU2ajS28EvQ== -"@types/node@^12.11.7": - version "12.11.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" - integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== +"@types/node@^12.12.31": + version "12.12.31" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.31.tgz#d6b4f9645fee17f11319b508fb1001797425da51" + integrity sha512-T+wnJno8uh27G9c+1T+a1/WYCHzLeDqtsGJkoEdSp2X8RTh3oOCZQcUnjAx90CS8cmmADX51O0FI/tu9s0yssg== "@types/which@^1.0.28": version "1.0.28" @@ -185,11 +185,6 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -dayjs@1.8.19: - version "1.8.19" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.19.tgz#5117dc390d8f8e586d53891dbff3fa308f51abfe" - integrity sha512-7kqOoj3oQSmqbvtvGFLU5iYqies+SqUiEGNT0UtUPPxcPYgY1BrkXR0Cq2R9HYSimBXN+xHkEN4Hi399W+Ovlg== - debug@2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index 254516ee52..bf2d01cbe3 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -22,7 +22,8 @@ "postinstall": "node build/postinstall.js" }, "dependencies": { - "uuid": "^3.3.3" + "uuid": "^3.3.3", + "vscode-nls": "^4.1.2" }, "devDependencies": { "@types/keytar": "^4.4.2", diff --git a/extensions/github-authentication/src/common/keychain.ts b/extensions/github-authentication/src/common/keychain.ts index 27c30b19b3..3f33793c81 100644 --- a/extensions/github-authentication/src/common/keychain.ts +++ b/extensions/github-authentication/src/common/keychain.ts @@ -6,8 +6,11 @@ // keytar depends on a native module shipped in vscode, so this is // how we load it import * as keytarType from 'keytar'; -import { env } from 'vscode'; +import * as vscode from 'vscode'; import Logger from './logger'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); function getKeytar(): Keytar | undefined { try { @@ -25,7 +28,7 @@ export type Keytar = { deletePassword: typeof keytarType['deletePassword']; }; -const SERVICE_ID = `${env.uriScheme}-github.login`; +const SERVICE_ID = `${vscode.env.uriScheme}-github.login`; const ACCOUNT_ID = 'account'; export class Keychain { @@ -46,6 +49,11 @@ export class Keychain { } catch (e) { // Ignore Logger.error(`Setting token failed: ${e}`); + const troubleshooting = localize('troubleshooting', "Troubleshooting Guide"); + const result = await vscode.window.showErrorMessage(localize('keychainWriteError', "Writing login information to the keychain failed with error '{0}'.", e.message), troubleshooting); + if (result === troubleshooting) { + vscode.env.openExternal(vscode.Uri.parse('https://code.visualstudio.com/docs/editor/settings-sync#_troubleshooting-keychain-issues')); + } } } diff --git a/extensions/github-authentication/yarn.lock b/extensions/github-authentication/yarn.lock index dcab74a1fe..c1f0b96f5b 100644 --- a/extensions/github-authentication/yarn.lock +++ b/extensions/github-authentication/yarn.lock @@ -436,6 +436,11 @@ uuid@^3.3.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +vscode-nls@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" + integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== + which-pm-runs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" diff --git a/extensions/vscode-account/src/AADHelper.ts b/extensions/vscode-account/src/AADHelper.ts index c2dbe0f13e..ec287d9bca 100644 --- a/extensions/vscode-account/src/AADHelper.ts +++ b/extensions/vscode-account/src/AADHelper.ts @@ -205,7 +205,7 @@ export class AzureActiveDirectoryService { Logger.info('Token expired or unavailable, trying refresh'); const refreshedToken = await this.refreshToken(token.refreshToken, token.scope); if (refreshedToken.accessToken) { - Promise.resolve(token.accessToken); + return refreshedToken.accessToken; } else { throw new Error(); } diff --git a/extensions/vscode-account/src/keychain.ts b/extensions/vscode-account/src/keychain.ts index d05285eb84..b5314e5de3 100644 --- a/extensions/vscode-account/src/keychain.ts +++ b/extensions/vscode-account/src/keychain.ts @@ -6,8 +6,11 @@ // keytar depends on a native module shipped in vscode, so this is // how we load it import * as keytarType from 'keytar'; -import { env } from 'vscode'; +import * as vscode from 'vscode'; import Logger from './logger'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); function getKeytar(): Keytar | undefined { try { @@ -25,7 +28,7 @@ export type Keytar = { deletePassword: typeof keytarType['deletePassword']; }; -const SERVICE_ID = `${env.uriScheme}-vscode.login`; +const SERVICE_ID = `${vscode.env.uriScheme}-vscode.login`; const ACCOUNT_ID = 'account'; export class Keychain { @@ -46,6 +49,11 @@ export class Keychain { } catch (e) { // Ignore Logger.error(`Setting token failed: ${e}`); + const troubleshooting = localize('troubleshooting', "Troubleshooting Guide"); + const result = await vscode.window.showErrorMessage(localize('keychainWriteError', "Writing login information to the keychain failed with error '{0}'.", e.message), troubleshooting); + if (result === troubleshooting) { + vscode.env.openExternal(vscode.Uri.parse('https://code.visualstudio.com/docs/editor/settings-sync#_troubleshooting-keychain-issues')); + } } } diff --git a/package.json b/package.json index 2399755db0..a07c115a3e 100644 --- a/package.json +++ b/package.json @@ -167,7 +167,7 @@ "opn": "^6.0.0", "optimist": "0.3.5", "p-all": "^1.0.0", - "playwright": "0.11.0", + "playwright": "0.12.1", "pump": "^1.0.1", "queue": "3.0.6", "rcedit": "^1.1.0", diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index 24f6cba29a..ced2bd342f 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?3d9ee7d873425ff0bc441f48a1de0c54") format("truetype"); + src: url("./codicon.ttf?70edf2c63384951357ed8517386759dd") format("truetype"); } .codicon[class*='codicon-'] { diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf index a3c15e7e4b..7f910daafb 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/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 5c25b68f7b..4740afe544 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -21,6 +21,7 @@ import { asArray } from 'vs/base/common/arrays'; import { ScanCodeUtils, ScanCode } from 'vs/base/common/scanCode'; import { isMacintosh } from 'vs/base/common/platform'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; const $ = DOM.$; @@ -90,7 +91,7 @@ export class MenuBar extends Disposable { private menuStyle: IMenuStyles | undefined; private overflowLayoutScheduled: IDisposable | undefined = undefined; - constructor(private container: HTMLElement, private options: IMenuBarOptions = {}) { + constructor(private container: HTMLElement, private options: IMenuBarOptions = {}, private compactMenuActions?: IAction[]) { super(); this.container.setAttribute('role', 'menubar'); @@ -490,6 +491,11 @@ export class MenuBar extends Disposable { this.container.insertBefore(this.overflowMenu.buttonElement, this.menuCache[this.numMenusShown].buttonElement); this.overflowMenu.buttonElement.style.visibility = 'visible'; } + + if (this.compactMenuActions && this.compactMenuActions.length) { + this.overflowMenu.actions.push(new Separator()); + this.overflowMenu.actions.push(...this.compactMenuActions); + } } else { DOM.removeNode(this.overflowMenu.buttonElement); this.container.appendChild(this.overflowMenu.buttonElement); diff --git a/src/vs/base/parts/storage/common/storage.ts b/src/vs/base/parts/storage/common/storage.ts index 93dd13c275..689672993f 100644 --- a/src/vs/base/parts/storage/common/storage.ts +++ b/src/vs/base/parts/storage/common/storage.ts @@ -74,26 +74,24 @@ export class Storage extends Disposable implements IStorage { private static readonly DEFAULT_FLUSH_DELAY = 100; - private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); - readonly onDidChangeStorage: Event = this._onDidChangeStorage.event; + private readonly _onDidChangeStorage = this._register(new Emitter()); + readonly onDidChangeStorage = this._onDidChangeStorage.event; private state = StorageState.None; - private cache: Map = new Map(); + private cache = new Map(); - private flushDelayer: ThrottledDelayer; + private readonly flushDelayer = this._register(new ThrottledDelayer(Storage.DEFAULT_FLUSH_DELAY)); - private pendingDeletes: Set = new Set(); - private pendingInserts: Map = new Map(); + private pendingDeletes = new Set(); + private pendingInserts = new Map(); constructor( - protected database: IStorageDatabase, - private options: IStorageOptions = Object.create(null) + protected readonly database: IStorageDatabase, + private readonly options: IStorageOptions = Object.create(null) ) { super(); - this.flushDelayer = this._register(new ThrottledDelayer(Storage.DEFAULT_FLUSH_DELAY)); - this.registerListeners(); } @@ -146,7 +144,7 @@ export class Storage extends Disposable implements IStorage { async init(): Promise { if (this.state !== StorageState.None) { - return Promise.resolve(); // either closed or already initialized + return; // either closed or already initialized } this.state = StorageState.Initialized; @@ -155,7 +153,7 @@ export class Storage extends Disposable implements IStorage { // return early if we know the storage file does not exist. this is a performance // optimization to not load all items of the underlying storage if we know that // there can be no items because the storage does not exist. - return Promise.resolve(); + return; } this.cache = await this.database.getItems(); @@ -296,13 +294,13 @@ export class InMemoryStorageDatabase implements IStorageDatabase { readonly onDidChangeItemsExternal = Event.None; - private items = new Map(); + private readonly items = new Map(); - getItems(): Promise> { - return Promise.resolve(this.items); + async getItems(): Promise> { + return this.items; } - updateItems(request: IUpdateRequest): Promise { + async updateItems(request: IUpdateRequest): Promise { if (request.insert) { request.insert.forEach((value, key) => this.items.set(key, value)); } @@ -310,11 +308,7 @@ export class InMemoryStorageDatabase implements IStorageDatabase { if (request.delete) { request.delete.forEach(key => this.items.delete(key)); } - - return Promise.resolve(); } - close(): Promise { - return Promise.resolve(); - } + async close(): Promise { } } diff --git a/src/vs/base/parts/storage/node/storage.ts b/src/vs/base/parts/storage/node/storage.ts index 024fb6137d..4b72c64392 100644 --- a/src/vs/base/parts/storage/node/storage.ts +++ b/src/vs/base/parts/storage/node/storage.ts @@ -13,9 +13,9 @@ import { fill } from 'vs/base/common/arrays'; import { IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest } from 'vs/base/parts/storage/common/storage'; interface IDatabaseConnection { - db: Database; + readonly db: Database; - isInMemory: boolean; + readonly isInMemory: boolean; isErroneous?: boolean; lastError?: string; @@ -39,21 +39,13 @@ export class SQLiteStorageDatabase implements IStorageDatabase { private static readonly BUSY_OPEN_TIMEOUT = 2000; // timeout in ms to retry when opening DB fails with SQLITE_BUSY private static readonly MAX_HOST_PARAMETERS = 256; // maximum number of parameters within a statement - private path: string; - private name: string; + private readonly name = basename(this.path); - private logger: SQLiteStorageDatabaseLogger; + private readonly logger = new SQLiteStorageDatabaseLogger(this.options.logging); - private whenConnected: Promise; + private readonly whenConnected = this.connect(this.path); - constructor(path: string, options: ISQLiteStorageDatabaseOptions = Object.create(null)) { - this.path = path; - this.name = basename(path); - - this.logger = new SQLiteStorageDatabaseLogger(options.logging); - - this.whenConnected = this.connect(path); - } + constructor(private path: string, private options: ISQLiteStorageDatabaseOptions = Object.create(null)) { } async getItems(): Promise> { const connection = await this.whenConnected; @@ -166,7 +158,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { return new Promise((resolve, reject) => { connection.db.close(closeError => { if (closeError) { - this.handleSQLiteError(connection, closeError, `[storage ${this.name}] close(): ${closeError}`); + this.handleSQLiteError(connection, `[storage ${this.name}] close(): ${closeError}`); } // Return early if this storage was created only in-memory @@ -296,7 +288,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { } } - private handleSQLiteError(connection: IDatabaseConnection, error: Error & { code?: string }, msg: string): void { + private handleSQLiteError(connection: IDatabaseConnection, msg: string): void { connection.isErroneous = true; connection.lastError = msg; @@ -328,7 +320,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { }; // Errors - connection.db.on('error', error => this.handleSQLiteError(connection, error, `[storage ${this.name}] Error (event): ${error}`)); + connection.db.on('error', error => this.handleSQLiteError(connection, `[storage ${this.name}] Error (event): ${error}`)); // Tracing if (this.logger.isTracing) { @@ -342,7 +334,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { return new Promise((resolve, reject) => { connection.db.exec(sql, error => { if (error) { - this.handleSQLiteError(connection, error, `[storage ${this.name}] exec(): ${error}`); + this.handleSQLiteError(connection, `[storage ${this.name}] exec(): ${error}`); return reject(error); } @@ -356,7 +348,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { return new Promise((resolve, reject) => { connection.db.get(sql, (error, row) => { if (error) { - this.handleSQLiteError(connection, error, `[storage ${this.name}] get(): ${error}`); + this.handleSQLiteError(connection, `[storage ${this.name}] get(): ${error}`); return reject(error); } @@ -370,7 +362,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { return new Promise((resolve, reject) => { connection.db.all(sql, (error, rows) => { if (error) { - this.handleSQLiteError(connection, error, `[storage ${this.name}] all(): ${error}`); + this.handleSQLiteError(connection, `[storage ${this.name}] all(): ${error}`); return reject(error); } @@ -389,7 +381,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { connection.db.run('END TRANSACTION', error => { if (error) { - this.handleSQLiteError(connection, error, `[storage ${this.name}] transaction(): ${error}`); + this.handleSQLiteError(connection, `[storage ${this.name}] transaction(): ${error}`); return reject(error); } @@ -404,7 +396,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { const stmt = connection.db.prepare(sql); const statementErrorListener = (error: Error) => { - this.handleSQLiteError(connection, error, `[storage ${this.name}] prepare(): ${error} (${sql}). Details: ${errorDetails()}`); + this.handleSQLiteError(connection, `[storage ${this.name}] prepare(): ${error} (${sql}). Details: ${errorDetails()}`); }; stmt.on('error', statementErrorListener); diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts index 94bb23b1a6..1e220c2095 100644 --- a/src/vs/base/test/common/uri.test.ts +++ b/src/vs/base/test/common/uri.test.ts @@ -538,7 +538,7 @@ suite('URI', () => { assert.throws(() => assertJoined(('foo:'), 'bazz', '')); assert.throws(() => new URL('bazz', 'foo:')); assert.throws(() => assertJoined(('foo://bar'), 'bazz', '')); - assert.throws(() => new URL('bazz', 'foo://bar')); + // assert.throws(() => new URL('bazz', 'foo://bar')); Edge,Chrome => throw, Safari => foo://bar/bazz, Firefox ?? }); test('URI#joinPath (posix)', function () { diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 9213773c22..1f4523f977 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchConstructionOptions, create, URI, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IApplicationLink } from 'vs/workbench/workbench.web.api'; +import { IWorkbenchConstructionOptions, create, URI, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace } from 'vs/workbench/workbench.web.api'; import { generateUuid } from 'vs/base/common/uuid'; import { CancellationToken } from 'vs/base/common/cancellation'; import { streamToBuffer } from 'vs/base/common/buffer'; @@ -12,10 +12,6 @@ import { request } from 'vs/base/parts/request/browser/request'; import { isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/windows'; import { isEqual } from 'vs/base/common/resources'; import { isStandalone } from 'vs/base/browser/browser'; -import product from 'vs/platform/product/common/product'; -import { Schemas } from 'vs/base/common/network'; -import { posix } from 'vs/base/common/path'; -import { localize } from 'vs/nls'; interface ICredential { service: string; @@ -342,30 +338,11 @@ class WorkspaceProvider implements IWorkspaceProvider { } } - // Application links ("Open in Desktop") - let applicationLinks: IApplicationLink[] | undefined = undefined; - if (workspace) { - const workspaceUri = isWorkspaceToOpen(workspace) ? workspace.workspaceUri : isFolderToOpen(workspace) ? workspace.folderUri : undefined; - if (workspaceUri) { - applicationLinks = [{ - uri: URI.from({ - scheme: product.quality === 'stable' ? 'vscode' : 'vscode-insiders', - authority: Schemas.vscodeRemote, - path: posix.join(posix.sep, workspaceUri.authority, workspaceUri.path), - query: workspaceUri.query, - fragment: workspaceUri.fragment, - }), - label: localize('openInDesktop', "Open in Desktop") - }]; - } - } - // Finally create workbench create(document.body, { ...config, workspaceProvider: new WorkspaceProvider(workspace, payload), urlCallbackProvider: new PollingURLCallbackProvider(), - credentialsProvider: new LocalStorageCredentialsProvider(), - applicationLinks: applicationLinks + credentialsProvider: new LocalStorageCredentialsProvider() }); })(); diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 3b3825c0b4..fb0f3d98a6 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -417,9 +417,9 @@ export interface CompletionItemLabel { name: string; /** - * The signature without the return type. Render after `name`. + * The parameters without the return type. Render after `name`. */ - signature?: string; + parameters?: string; /** * The fully qualified name, like package name or file path. Rendered after `signature`. diff --git a/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts b/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts index f041d35753..be1db124ca 100644 --- a/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts @@ -10,6 +10,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { stripCodicons } from 'vs/base/common/codicons'; export abstract class AbstractEditorCommandsQuickAccessProvider extends AbstractCommandsQuickAccessProvider { @@ -40,7 +41,7 @@ export abstract class AbstractEditorCommandsQuickAccessProvider extends Abstract editorCommandPicks.push({ commandId: editorAction.id, commandAlias: editorAction.alias, - label: editorAction.label || editorAction.id, + label: stripCodicons(editorAction.label) || editorAction.id, }); } diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index b39abb179b..fd6d1c7076 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -63,7 +63,7 @@ interface ISuggestionTemplateData { colorspan: HTMLElement; iconLabel: IconLabel; iconContainer: HTMLElement; - signatureLabel: HTMLElement; + parametersLabel: HTMLElement; qualifierLabel: HTMLElement; /** * Showing either `CompletionItem#details` or `CompletionItemLabel#type` @@ -151,7 +151,7 @@ class ItemRenderer implements IListRenderer> = { 'waitMarkerFilePath': { type: 'string' }, 'locale': { type: 'string', cat: 'o', args: 'locale', description: localize('locale', "The locale to use (e.g. en-US or zh-TW).") }, 'user-data-dir': { type: 'string', cat: 'o', args: 'dir', description: localize('userDataDir', "Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.") }, - 'telemetry': { type: 'boolean', cat: 'o', description: localize('telemetry', "Shows all telemetry events which VS code collects.") }, 'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") }, 'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") }, @@ -76,6 +75,7 @@ export const OPTIONS: OptionDescriptions> = { 'inspect-brk-extensions': { type: 'string', deprecates: 'debugBrkPluginHost', args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") }, 'disable-gpu': { type: 'boolean', cat: 't', description: localize('disableGPU', "Disable GPU hardware acceleration.") }, 'max-memory': { type: 'string', cat: 't', description: localize('maxMemory', "Max memory size for a window (in Mbytes).") }, + 'telemetry': { type: 'boolean', cat: 't', description: localize('telemetry', "Shows all telemetry events which VS code collects.") }, 'remote': { type: 'string' }, 'locate-extension': { type: 'string[]' }, diff --git a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts index 684487331f..1e79da292c 100644 --- a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -21,6 +21,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export interface ICommandQuickPick extends IPickerQuickAccessItem { commandId: string; @@ -202,10 +203,15 @@ export class CommandsHistory extends Disposable { constructor( @IStorageService private readonly storageService: IStorageService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); + // opt-in to syncing + storageKeysSyncRegistryService.registerStorageKey({ key: CommandsHistory.PREF_KEY_CACHE, version: 1 }); + storageKeysSyncRegistryService.registerStorageKey({ key: CommandsHistory.PREF_KEY_COUNTER, version: 1 }); + this.updateConfiguration(); this.load(); diff --git a/src/vs/platform/quickinput/browser/pickerQuickAccess.ts b/src/vs/platform/quickinput/browser/pickerQuickAccess.ts index 5b0500ce10..097093e800 100644 --- a/src/vs/platform/quickinput/browser/pickerQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/pickerQuickAccess.ts @@ -63,12 +63,22 @@ export interface IPickerQuickAccessProviderOptions { canAcceptInBackground?: boolean; } -export type FastAndSlowPicksType = { picks: Array, additionalPicks: Promise> }; +export type Pick = T | IQuickPickSeparator; +export type PicksWithActive = { items: ReadonlyArray>, active?: T }; +export type Picks = ReadonlyArray> | PicksWithActive; +export type FastAndSlowPicks = { picks: Picks, additionalPicks: Promise> }; +export type FastAndSlowPicksWithActive = { picks: PicksWithActive, additionalPicks: PicksWithActive> }; -function isFastAndSlowPicksType(obj: unknown): obj is FastAndSlowPicksType { - const candidate = obj as FastAndSlowPicksType; +function isPicksWithActive(obj: unknown): obj is PicksWithActive { + const candidate = obj as PicksWithActive; - return Array.isArray(candidate.picks) && candidate.additionalPicks instanceof Promise; + return Array.isArray(candidate.items); +} + +function isFastAndSlowPicks(obj: unknown): obj is FastAndSlowPicks { + const candidate = obj as FastAndSlowPicks; + + return !!candidate.picks && candidate.additionalPicks instanceof Promise; } export abstract class PickerQuickAccessProvider extends Disposable implements IQuickAccessProvider { @@ -103,15 +113,26 @@ export abstract class PickerQuickAccessProvider): void { + if (isPicksWithActive(picks)) { + picker.items = picks.items; + if (picks.active) { + picker.activeItems = [picks.active]; + } + } else { + picker.items = picks; + } + } // No Picks - if (res === null) { + if (providedPicks === null) { // Ignore } // Fast and Slow Picks - else if (isFastAndSlowPicksType(res)) { + else if (isFastAndSlowPicks(providedPicks)) { let fastPicksHandlerDone = false; let slowPicksHandlerDone = false; @@ -129,7 +150,7 @@ export abstract class PickerQuickAccessProvider { picker.busy = true; try { - const additionalPicks = await res.additionalPicks; + const awaitedAdditionalPicks = await providedPicks.additionalPicks; if (picksToken.isCancellationRequested) { return; } + let picks: ReadonlyArray>; + let activePick: Pick | undefined = undefined; + if (isPicksWithActive(providedPicks.picks)) { + picks = providedPicks.picks.items; + activePick = providedPicks.picks.active; + } else { + picks = providedPicks.picks; + } + + let additionalPicks: ReadonlyArray>; + let additionalActivePick: Pick | undefined = undefined; + if (isPicksWithActive(awaitedAdditionalPicks)) { + additionalPicks = awaitedAdditionalPicks.items; + additionalActivePick = awaitedAdditionalPicks.active; + } else { + additionalPicks = awaitedAdditionalPicks; + } + if (additionalPicks.length > 0 || !fastPicksHandlerDone) { - picker.items = [...res.picks, ...additionalPicks]; + applyPicks({ + items: [...picks, ...additionalPicks], + active: activePick as T || additionalActivePick as T // {{SQL CARBON EDIT}} strict-null-checks + }); } } finally { if (!picksToken.isCancellationRequested) { @@ -162,20 +204,20 @@ export abstract class PickerQuickAccessProvider | Promise> | FastAndSlowPicksType | null; + protected abstract getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Picks | Promise> | FastAndSlowPicks | null; } diff --git a/src/vs/platform/storage/node/storageIpc.ts b/src/vs/platform/storage/node/storageIpc.ts index c5809930d5..2e1bee6594 100644 --- a/src/vs/platform/storage/node/storageIpc.ts +++ b/src/vs/platform/storage/node/storageIpc.ts @@ -9,7 +9,6 @@ import { IStorageChangeEvent, IStorageMainService } from 'vs/platform/storage/no import { IUpdateRequest, IStorageDatabase, IStorageItemsChangeEvent } from 'vs/base/parts/storage/common/storage'; import { mapToSerializable, serializableToMap, values } from 'vs/base/common/map'; 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, crashReporterIdStorageKey } from 'vs/platform/telemetry/common/telemetry'; @@ -50,8 +49,7 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC try { await this.storageMainService.initialize(); } catch (error) { - onUnexpectedError(error); - this.logService.error(error); + this.logService.error(`[storage] init(): Unable to init global storage due to ${error}`); } // This is unique to the application instance and thereby diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index c892193d53..a293db5da9 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -14,7 +14,6 @@ import { join } from 'vs/base/common/path'; import { copy, exists, mkdirp, writeFile } from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceInitializationPayload, isWorkspaceIdentifier, isSingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; -import { onUnexpectedError } from 'vs/base/common/errors'; import { assertIsDefined } from 'vs/base/common/types'; import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; @@ -31,7 +30,7 @@ export class NativeStorageService extends Disposable implements IStorageService private readonly _onWillSaveState = this._register(new Emitter()); readonly onWillSaveState = this._onWillSaveState.event; - private globalStorage: IStorage; + private readonly globalStorage = new Storage(this.globalStorageDatabase); private workspaceStoragePath: string | undefined; private workspaceStorage: IStorage | undefined; @@ -43,14 +42,13 @@ export class NativeStorageService extends Disposable implements IStorageService private runWhenIdleDisposable: IDisposable | undefined = undefined; constructor( - globalStorageDatabase: IStorageDatabase, + private globalStorageDatabase: IStorageDatabase, @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService ) { super(); - // Global Storage - this.globalStorage = new Storage(globalStorageDatabase); + // Global Storage change events this._register(this.globalStorage.onDidChangeStorage(key => this.handleDidChangeStorage(key, StorageScope.GLOBAL))); } @@ -95,15 +93,16 @@ export class NativeStorageService extends Disposable implements IStorageService // Create workspace storage and initialize mark('willInitWorkspaceStorage'); try { - await this.createWorkspaceStorage(useInMemoryStorage ? SQLiteStorageDatabase.IN_MEMORY_PATH : join(result.path, NativeStorageService.WORKSPACE_STORAGE_NAME), result.wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : undefined).init(); + const workspaceStorage = this.createWorkspaceStorage( + useInMemoryStorage ? SQLiteStorageDatabase.IN_MEMORY_PATH : join(result.path, NativeStorageService.WORKSPACE_STORAGE_NAME), + result.wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : undefined + ); + await workspaceStorage.init(); } finally { mark('didInitWorkspaceStorage'); } } catch (error) { - onUnexpectedError(error); - - // Upon error, fallback to in-memory storage - return this.createWorkspaceStorage(SQLiteStorageDatabase.IN_MEMORY_PATH).init(); + this.logService.error(`[storage] initializeWorkspaceStorage(): Unable to init workspace storage due to ${error}`); } } @@ -156,6 +155,7 @@ export class NativeStorageService extends Disposable implements IStorageService } if (meta) { + const logService = this.logService; const workspaceStorageMetaPath = join(this.getWorkspaceStorageFolderPath(payload), NativeStorageService.WORKSPACE_META_NAME); (async function () { try { @@ -164,7 +164,7 @@ export class NativeStorageService extends Disposable implements IStorageService await writeFile(workspaceStorageMetaPath, JSON.stringify(meta, undefined, 2)); } } catch (error) { - onUnexpectedError(error); + logService.error(error); } })(); } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index a717489d08..4703b27f75 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -60,6 +60,11 @@ declare module 'vscode' { readonly changed: string[]; } + /** + * **WARNING** When writing an AuthenticationProvider, `id` should be treated as part of your extension's + * API, changing it is a breaking change for all extensions relying on the provider. The id is + * treated case-sensitively. + */ export interface AuthenticationProvider { /** * Used as an identifier for extensions trying to work with a particular @@ -1676,6 +1681,8 @@ declare module 'vscode' { * This metadata is ignored for markdown cell. */ runnable: boolean; + + executionOrder?: number; } export interface NotebookCell { @@ -1828,9 +1835,9 @@ declare module 'vscode' { name: string; /** - * The signature without the return type. Render after `name`. + * The parameters without the return type. Render after `name`. */ - signature?: string; + parameters?: string; /** * The fully qualified name, like package name or file path. Rendered after `signature`. diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index d23f1d323d..8cfc69e7d5 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -34,7 +34,8 @@ const BUILT_IN_AUTH_DEPENDENTS: AuthDependent[] = [ export class MainThreadAuthenticationProvider extends Disposable { private _sessionMenuItems = new Map(); - private _sessionIds: string[] = []; + private _accounts = new Map(); // Map account name to session ids + private _sessions = new Map(); // Map account id to name constructor( private readonly _proxy: ExtHostAuthenticationShape, @@ -44,10 +45,6 @@ export class MainThreadAuthenticationProvider extends Disposable { ) { super(); - if (!dependents.length) { - return; - } - this.registerCommandsAndContextMenuItems(); } @@ -86,21 +83,23 @@ export class MainThreadAuthenticationProvider extends Disposable { } private registerCommandsAndContextMenuItems(): void { - this._register(CommandsRegistry.registerCommand({ - id: `signIn${this.id}`, - handler: (accessor, args) => { - this.setPermissionsForAccount(accessor.get(IQuickInputService), true); - }, - })); - - this._register(MenuRegistry.appendMenuItem(MenuId.AccountsContext, { - group: '2_providers', - command: { + if (this.dependents.length) { + this._register(CommandsRegistry.registerCommand({ id: `signIn${this.id}`, - title: nls.localize('addAccount', "Sign in to {0}", this.displayName) - }, - order: 3 - })); + handler: (accessor, args) => { + this.setPermissionsForAccount(accessor.get(IQuickInputService), true); + }, + })); + + this._register(MenuRegistry.appendMenuItem(MenuId.AccountsContext, { + group: '2_providers', + command: { + id: `signIn${this.id}`, + title: nls.localize('addAccount', "Sign in to {0}", this.displayName) + }, + order: 3 + })); + } this._proxy.$getSessions(this.id).then(sessions => { sessions.forEach(session => this.registerSession(session)); @@ -108,7 +107,16 @@ export class MainThreadAuthenticationProvider extends Disposable { } private registerSession(session: modes.AuthenticationSession) { - this._sessionIds.push(session.id); + this._sessions.set(session.id, session.accountName); + + const existingSessionsForAccount = this._accounts.get(session.accountName); + if (existingSessionsForAccount) { + this._accounts.set(session.accountName, existingSessionsForAccount.concat(session.id)); + return; + } else { + this._accounts.set(session.accountName, [session.id]); + } + const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { group: '1_accounts', command: { @@ -131,7 +139,8 @@ export class MainThreadAuthenticationProvider extends Disposable { quickPick.onDidAccept(e => { const selected = quickPick.selectedItems[0]; if (selected.label === 'Sign Out') { - this.logout(session.id); + const sessionsForAccount = this._accounts.get(session.accountName); + sessionsForAccount?.forEach(sessionId => this.logout(sessionId)); } quickPick.dispose(); @@ -145,7 +154,7 @@ export class MainThreadAuthenticationProvider extends Disposable { }, }); - this._sessionMenuItems.set(session.id, [menuItem, manageCommand]); + this._sessionMenuItems.set(session.accountName, [menuItem, manageCommand]); } async getSessions(): Promise> { @@ -158,22 +167,29 @@ export class MainThreadAuthenticationProvider extends Disposable { }); } - async updateSessionItems(): Promise { - const currentSessions = await this._proxy.$getSessions(this.id); - const removedSessionIds = this._sessionIds.filter(id => !currentSessions.some(session => session.id === id)); - const addedSessions = currentSessions.filter(session => !this._sessionIds.some(id => id === session.id)); + async updateSessionItems(event: modes.AuthenticationSessionsChangeEvent): Promise { + const { added, removed } = event; + const session = await this._proxy.$getSessions(this.id); + const addedSessions = session.filter(session => added.some(id => id === session.id)); - removedSessionIds.forEach(id => { - const disposeables = this._sessionMenuItems.get(id); - if (disposeables) { - disposeables.forEach(disposeable => disposeable.dispose()); - this._sessionMenuItems.delete(id); + removed.forEach(sessionId => { + const accountName = this._sessions.get(sessionId); + if (accountName) { + let sessionsForAccount = this._accounts.get(accountName) || []; + const sessionIndex = sessionsForAccount.indexOf(sessionId); + sessionsForAccount.splice(sessionIndex); + + if (!sessionsForAccount.length) { + const disposeables = this._sessionMenuItems.get(accountName); + if (disposeables) { + disposeables.forEach(disposeable => disposeable.dispose()); + this._sessionMenuItems.delete(accountName); + } + } } }); addedSessions.forEach(session => this.registerSession(session)); - - this._sessionIds = currentSessions.map(session => session.id); } login(scopes: string[]): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 3c30ff5889..4a391e8746 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -752,18 +752,23 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod }); } - public async save(_options?: ISaveOptions): Promise { + public async save(options?: ISaveOptions): Promise { + return !!await this.saveCustomEditor(options); + } + + public async saveCustomEditor(_options?: ISaveOptions): Promise { if (!this._editable) { - return false; + return undefined; } + // TODO: handle save untitled case await createCancelablePromise(token => this._proxy.$onSave(this._editorResource, this.viewType, token)); this.change(() => { this._savePoint = this._currentEditIndex; }); - return true; + return this._editorResource; } - public async saveAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise { + public async saveCustomEditorAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise { if (this._editable) { await this._proxy.$onSaveAs(this._editorResource, this.viewType, targetResource); this.change(() => { diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index c486a93b5b..e2786976f5 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -3,18 +3,39 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; -import { ExtHostNotebookShape, IMainContext, MainThreadNotebookShape, MainContext, ICellDto, NotebookCellsSplice, NotebookCellOutputsSplice, CellKind, CellOutputKind } from 'vs/workbench/api/common/extHost.protocol'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { Disposable as VSCodeDisposable } from './extHostTypes'; -import { URI, UriComponents } from 'vs/base/common/uri'; -import { DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { readonly } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { INotebookDisplayOrder, ITransformedDisplayOutputDto, IOrderedMimeType, IStreamOutput, IErrorOutput, mimeTypeSupportedByCore, IOutput, sortMimeTypes, diff, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ISplice } from 'vs/base/common/sequence'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { CellKind, CellOutputKind, ExtHostNotebookShape, ICellDto, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, NotebookCellsSplice } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { CellUri, diff, IErrorOutput, INotebookDisplayOrder, IOrderedMimeType, IOutput, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import * as vscode from 'vscode'; +import { Disposable as VSCodeDisposable } from './extHostTypes'; + +interface IObservable { + proxy: T; + onDidChange: Event; +} + +function getObservable(obj: T): IObservable { + const onDidChange = new Emitter(); + const proxy = new Proxy(obj, { + set(target: T, p: PropertyKey, value: any, _receiver: any): boolean { + target[p as keyof T] = value; + onDidChange.fire(); + return true; + } + }); + + return { + proxy, + onDidChange: onDidChange.event + }; +} const notebookDocumentMetadataDefaults: vscode.NotebookDocumentMetadata = { editable: true, @@ -22,8 +43,7 @@ const notebookDocumentMetadataDefaults: vscode.NotebookDocumentMetadata = { cellRunnable: true }; -export class ExtHostCell implements vscode.NotebookCell { - +export class ExtHostCell extends Disposable implements vscode.NotebookCell { public source: string[]; private _outputs: any[]; private _onDidChangeOutputs = new Emitter[]>(); @@ -31,6 +51,9 @@ export class ExtHostCell implements vscode.NotebookCell { private _textDocument: vscode.TextDocument | undefined; private _initalVersion: number = -1; private _outputMapping = new Set(); + private _metadata: vscode.NotebookCellMetadata; + + private _metadataChangeListener: IDisposable; constructor( private viewType: string, @@ -41,11 +64,19 @@ export class ExtHostCell implements vscode.NotebookCell { public cellKind: CellKind, public language: string, outputs: any[], - private _metadata: vscode.NotebookCellMetadata | undefined, + _metadata: vscode.NotebookCellMetadata | undefined, private _proxy: MainThreadNotebookShape ) { + super(); + this.source = this._content.split(/\r|\n|\r\n/g); this._outputs = outputs; + + const observableMetadata = getObservable(_metadata || {} as any); + this._metadata = observableMetadata.proxy; + this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { + this.updateMetadata(); + })); } get outputs() { @@ -76,13 +107,18 @@ export class ExtHostCell implements vscode.NotebookCell { } set metadata(newMetadata: vscode.NotebookCellMetadata | undefined) { - const newMetadataWithDefaults: vscode.NotebookCellMetadata | undefined = newMetadata ? { - editable: newMetadata.editable, - runnable: newMetadata.runnable - } : undefined; + this._metadataChangeListener.dispose(); + const observableMetadata = getObservable(newMetadata || {} as any); // TODO defaults + this._metadata = observableMetadata.proxy; + this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { + this.updateMetadata(); + })); - this._metadata = newMetadataWithDefaults; - this._proxy.$updateNotebookCellMetadata(this.viewType, this.documentUri, this.handle, newMetadataWithDefaults); + this.updateMetadata(); + } + + private updateMetadata(): Promise { + return this._proxy.$updateNotebookCellMetadata(this.viewType, this.documentUri, this.handle, this._metadata); } getContent(): string { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 561a66258f..5796f7478f 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1362,7 +1362,7 @@ export enum CompletionItemTag { export interface CompletionItemLabel { name: string; - signature?: string; + parameters?: string; qualifier?: string; type?: string; } diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 2cab0f4025..fe2542a71f 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -510,6 +510,16 @@ export class CustomMenubarControl extends MenubarControl { } if (firstTime) { + const webActions = []; + const webMenu = this.menuService.createMenu(MenuId.WebMenuActions, this.contextKeyService); + for (const groups of webMenu.getActions()) { + const [, actions] = groups; + for (const action of actions) { + action.label = mnemonicMenuLabel(this.calculateActionLabel(action)); + webActions.push(action); + } + } + this.menubar = this._register(new MenuBar( this.container, { enableMnemonics: this.currentEnableMenuBarMnemonics, @@ -517,7 +527,7 @@ export class CustomMenubarControl extends MenubarControl { visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), compactMode: this.currentCompactMenuMode - })); + }, webActions.length > 0 ? webActions : undefined)); this.accessibilityService.alwaysUnderlineAccessKeys().then(val => { this.alwaysOnMnemonics = val; diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 8900958f2d..692205be66 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -1022,7 +1022,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { if (this.dimension) { const totalWeight = this.viewsModel.visibleViewDescriptors.reduce((totalWeight, { weight }) => totalWeight + (weight || 20), 0); for (const viewDescriptor of this.viewsModel.visibleViewDescriptors) { - sizes.set(viewDescriptor.id, this.dimension.height * (viewDescriptor.weight || 20) / totalWeight); + if (this.orientation === Orientation.VERTICAL) { + sizes.set(viewDescriptor.id, this.dimension.height * (viewDescriptor.weight || 20) / totalWeight); + } else { + sizes.set(viewDescriptor.id, this.dimension.width * (viewDescriptor.weight || 20) / totalWeight); + } } } return sizes; diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 5327a3cb5a..3478a67369 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/views'; import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IViewDescriptorService, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, ViewContainerLocation, IViewsService, IViewPaneContainer, getVisbileViewContextKey } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; @@ -34,6 +34,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { URI } from 'vs/base/common/uri'; import { IProgressIndicator } from 'vs/platform/progress/common/progress'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export interface IViewState { visibleGlobal: boolean | undefined; @@ -101,30 +102,46 @@ export class ContributableViewsModel extends Disposable { } setVisible(id: string, visible: boolean, size?: number): void { - const { visibleIndex, viewDescriptor, state } = this.find(id); + this.doSetVisible([{ id, visible, size }]); + } - if (!viewDescriptor.canToggleVisibility) { - throw new Error(`Can't toggle this view's visibility`); + protected doSetVisible(viewDescriptors: { id: string, visible: boolean, size?: number }[]): void { + const added: IAddedViewDescriptorRef[] = []; + const removed: IViewDescriptorRef[] = []; + + for (const { id, visible, size } of viewDescriptors) { + const { visibleIndex, viewDescriptor, state } = this.find(id); + + if (!viewDescriptor.canToggleVisibility) { + throw new Error(`Can't toggle this view's visibility`); + } + + if (this.isViewDescriptorVisible(viewDescriptor) === visible) { + return; + } + + if (viewDescriptor.workspace) { + state.visibleWorkspace = visible; + } else { + state.visibleGlobal = visible; + } + + if (typeof size === 'number') { + state.size = size; + } + + if (visible) { + added.push({ index: visibleIndex, viewDescriptor, size: state.size, collapsed: !!state.collapsed }); + } else { + removed.push({ index: visibleIndex, viewDescriptor }); + } } - if (this.isViewDescriptorVisible(viewDescriptor) === visible) { - return; + if (added.length) { + this._onDidAdd.fire(added); } - - if (viewDescriptor.workspace) { - state.visibleWorkspace = visible; - } else { - state.visibleGlobal = visible; - } - - if (typeof size === 'number') { - state.size = size; - } - - if (visible) { - this._onDidAdd.fire([{ index: visibleIndex, viewDescriptor, size: state.size, collapsed: !!state.collapsed }]); - } else { - this._onDidRemove.fire([{ index: visibleIndex, viewDescriptor }]); + if (removed.length) { + this._onDidRemove.fire(removed); } } @@ -318,6 +335,7 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { viewletStateStorageId: string, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IStorageService storageService: IStorageService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { const globalViewsStateStorageId = `${viewletStateStorageId}.hidden`; const viewStates = PersistentContributableViewsModel.loadViewsStates(viewletStateStorageId, globalViewsStateStorageId, storageService); @@ -334,6 +352,30 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { Event.map(this.onDidMove, ({ from, to }) => [from, to]), Event.map(this.onDidChangeViewState, viewDescriptorRef => [viewDescriptorRef])) (viewDescriptorRefs => this.saveViewsStates())); + + storageKeysSyncRegistryService.registerStorageKey({ key: this.globalViewsStateStorageId, version: 1 }); + this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); + this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); + } + + private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { + if (e.key === this.globalViewsStateStorageId && e.scope === StorageScope.GLOBAL + && this.globalViewsStatesValue !== this.getStoredGlobalViewsStatesValue() /* This checks if current window changed the value or not */) { + this._globalViewsStatesValue = undefined; + const storedViewsVisibilityStates = PersistentContributableViewsModel.loadGlobalViewsState(this.globalViewsStateStorageId, this.storageService, StorageScope.GLOBAL); + const changedViews: { id: string, visible: boolean }[] = []; + for (const [id, state] of storedViewsVisibilityStates) { + const viewState = this.viewStates.get(id); + if (viewState) { + if (viewState.visibleGlobal !== !state.isHidden) { + changedViews.push({ id, visible: !state.isHidden }); + } + } + } + if (changedViews.length) { + this.doSetVisible(changedViews); + } + } } private saveViewsStates(): void { @@ -372,9 +414,32 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { order: !viewDescriptor.workspace && viewState ? viewState.order : undefined }); } - this.storageService.store(this.globalViewsStateStorageId, JSON.stringify(values(storedViewsVisibilityStates)), StorageScope.GLOBAL); + this.globalViewsStatesValue = JSON.stringify(values(storedViewsVisibilityStates)); } + private _globalViewsStatesValue: string | undefined; + private get globalViewsStatesValue(): string { + if (!this._globalViewsStatesValue) { + this._globalViewsStatesValue = this.getStoredGlobalViewsStatesValue(); + } + + return this._globalViewsStatesValue; + } + + private set globalViewsStatesValue(globalViewsStatesValue: string) { + if (this.globalViewsStatesValue !== globalViewsStatesValue) { + this._globalViewsStatesValue = globalViewsStatesValue; + this.setStoredGlobalViewsStatesValue(globalViewsStatesValue); + } + } + + private getStoredGlobalViewsStatesValue(): string { + return this.storageService.get(this.globalViewsStateStorageId, StorageScope.GLOBAL, '[]'); + } + + private setStoredGlobalViewsStatesValue(value: string): void { + this.storageService.store(this.globalViewsStateStorageId, value, StorageScope.GLOBAL); + } private static loadViewsStates(workspaceViewsStateStorageId: string, globalViewsStateStorageId: string, storageService: IStorageService): Map { const viewStates = new Map(); diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index bd095780ea..5987c63524 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -133,9 +133,6 @@ export class Workbench extends Layout { // Configure emitter leak warning threshold setGlobalLeakWarningThreshold(175); - // ARIA - setARIAContainer(document.body); - // Services const instantiationService = this.initServices(this.serviceCollection); @@ -324,6 +321,7 @@ export class Workbench extends Layout { private renderWorkbench(instantiationService: IInstantiationService, notificationService: NotificationService, storageService: IStorageService, configurationService: IConfigurationService): void { // ARIA + setARIAContainer(this.container); this.container.setAttribute('role', 'application'); // State specific classes diff --git a/src/vs/workbench/common/actions.ts b/src/vs/workbench/common/actions.ts index db3aba8ee6..b19176fde5 100644 --- a/src/vs/workbench/common/actions.ts +++ b/src/vs/workbench/common/actions.ts @@ -22,6 +22,7 @@ export interface IWorkbenchActionRegistry { /** * Registers a workbench action to the platform. Workbench actions are not * visible by default and can only be invoked through a keybinding if provided. + * @deprecated Register directly with KeybindingsRegistry and MenuRegistry or use registerAction2 instead. */ registerWorkbenchAction(descriptor: SyncActionDescriptor, alias: string, category?: string, when?: ContextKeyExpr): IDisposable; } diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts index 5833314aa1..2b979cd9f1 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts @@ -121,21 +121,33 @@ suite.skip('BackupRestorer', () => { // {{SQL CARBON EDIT}} TODO @anthonydresser const resource = editor.resource; if (isEqual(resource, untitledFile1)) { const model = await accessor.textFileService.untitled.resolve({ untitledResource: resource }); - assert.equal(model.textEditorModel.getValue(), 'untitled-1'); + if (model.textEditorModel.getValue() !== 'untitled-1') { + const backupContents = await backupFileService.getBackupContents(untitledFile1); + assert.fail(`Unable to restore backup for resource ${untitledFile1.toString()}. Backup contents: ${backupContents}`); + } model.dispose(); counter++; } else if (isEqual(resource, untitledFile2)) { const model = await accessor.textFileService.untitled.resolve({ untitledResource: resource }); - assert.equal(model.textEditorModel.getValue(), 'untitled-2'); + if (model.textEditorModel.getValue() !== 'untitled-2') { + const backupContents = await backupFileService.getBackupContents(untitledFile2); + assert.fail(`Unable to restore backup for resource ${untitledFile2.toString()}. Backup contents: ${backupContents}`); + } model.dispose(); counter++; } else if (isEqual(resource, fooFile)) { - const model = await accessor.textFileService.files.get(resource!)?.load(); - assert.equal(model?.textEditorModel?.getValue(), 'fooFile'); + const model = await accessor.textFileService.files.get(fooFile!)?.load(); + if (model?.textEditorModel?.getValue() !== 'fooFile') { + const backupContents = await backupFileService.getBackupContents(fooFile); + assert.fail(`Unable to restore backup for resource ${fooFile.toString()}. Backup contents: ${backupContents}`); + } counter++; } else { - const model = await accessor.textFileService.files.get(resource!)?.load(); - assert.equal(model?.textEditorModel?.getValue(), 'barFile'); + const model = await accessor.textFileService.files.get(barFile!)?.load(); + if (model?.textEditorModel?.getValue() !== 'barFile') { + const backupContents = await backupFileService.getBackupContents(barFile); + assert.fail(`Unable to restore backup for resource ${barFile.toString()}. Backup contents: ${backupContents}`); + } counter++; } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index c3f4974649..b650135ef4 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -70,6 +70,12 @@ class InspectEditorTokensController extends Disposable implements IEditorContrib this._register(this._editor.onDidChangeModel((e) => this.stop())); this._register(this._editor.onDidChangeModelLanguage((e) => this.stop())); this._register(this._editor.onKeyUp((e) => e.keyCode === KeyCode.Escape && this.stop())); + this._register(this._themeService.onDidColorThemeChange(_ => { + if (this._widget) { + this.stop(); + this.launch(); + } + })); } public dispose(): void { diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 16973e3746..cab96e7e70 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -6,6 +6,7 @@ import { memoize } from 'vs/base/common/decorators'; import { Lazy } from 'vs/base/common/lazy'; import { IReference } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; import { basename } from 'vs/base/common/path'; import { isEqual } from 'vs/base/common/resources'; import { assertIsDefined } from 'vs/base/common/types'; @@ -99,6 +100,10 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return this._modelRef ? this._modelRef.object.isReadonly() : false; } + public isUntitled(): boolean { + return this.resource.scheme === Schemas.untitled; + } + public isDirty(): boolean { if (!this._modelRef) { return false; @@ -120,8 +125,16 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { public async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { const modelRef = assertIsDefined(this._modelRef); - const result = await modelRef.object.save(options); - return result ? this : undefined; + const target = await modelRef.object.saveCustomEditor(options); + if (!target) { + return undefined; // save cancelled + } + + if (!isEqual(target, this.resource)) { + return this.customEditorService.createInput(target, this.viewType, groupId); + } + + return this; } public async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { @@ -133,7 +146,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return undefined; // save cancelled } - if (!await modelRef.object.saveAs(this._editorResource, target, options)) { + if (!await modelRef.object.saveCustomEditorAs(this._editorResource, target, options)) { return undefined; } @@ -160,29 +173,29 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined { + // See if we can keep using the same custom editor provider const editorInfo = this.customEditorService.getCustomEditor(this.viewType); if (editorInfo?.matches(newResource)) { - // We can keep using the same custom editor provider - - if (!this._moveHandler) { - return { - editor: this.customEditorService.createInput(newResource, this.viewType, group), - }; - } - - this._moveHandler(newResource); - const newEditor = this.instantiationService.createInstance(CustomEditorInput, - newResource, - this.viewType, - this.id, - new Lazy(() => undefined!)); // this webview is replaced in the transfer call - this.transfer(newEditor); - newEditor.updateGroup(group); - return { editor: newEditor }; - } else { - // const possible = this.customEditorService.getContributedCustomEditors(newResource); - return { editor: this.editorService.createEditorInput({ resource: newResource, forceFile: true }) }; + return { editor: this.doMove(group, newResource) }; } + + return { editor: this.editorService.createEditorInput({ resource: newResource, forceFile: true }) }; + } + + private doMove(group: GroupIdentifier, newResource: URI): IEditorInput { + if (!this._moveHandler) { + return this.customEditorService.createInput(newResource, this.viewType, group); + } + + this._moveHandler(newResource); + const newEditor = this.instantiationService.createInstance(CustomEditorInput, + newResource, + this.viewType, + this.id, + new Lazy(() => undefined!)); // this webview is replaced in the transfer call + this.transfer(newEditor); + newEditor.updateGroup(group); + return newEditor; } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 7e8cb26b6d..31d4da74fc 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -327,8 +327,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ private async handleMovedFileInOpenedFileEditors(_oldResource: URI, newResource: URI): Promise { // See if the new resource can be opened in a custom editor - const possibleEditors = this.getAllCustomEditors(newResource).allEditors; - if (!possibleEditors.length) { + const possibleEditors = this.getAllCustomEditors(newResource); + if (!possibleEditors.allEditors.length) { return; } @@ -354,19 +354,25 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return; } - // If there is, show a single prompt for all editors to see if the user wants to re-open them - // - // TODO: instead of prompting eagerly, it'd likly be better to replace all the editors with - // ones that would prompt when they first become visible - await new Promise(resolve => setTimeout(resolve, 50)); - const pickedViewType = await this.showOpenWithPrompt(newResource); - if (!pickedViewType) { + let viewType: string | undefined; + if (possibleEditors.defaultEditor) { + viewType = possibleEditors.defaultEditor.id; + } else { + // If there is, show a single prompt for all editors to see if the user wants to re-open them + // + // TODO: instead of prompting eagerly, it'd likly be better to replace all the editors with + // ones that would prompt when they first become visible + await new Promise(resolve => setTimeout(resolve, 50)); + viewType = await this.showOpenWithPrompt(newResource); + } + + if (!viewType) { return; } for (const [group, entries] of editorsToReplace) { this.editorService.replaceEditors(entries.map(editor => { - const replacement = this.createInput(newResource, pickedViewType, group); + const replacement = this.createInput(newResource, viewType!, group); return { editor, replacement, diff --git a/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts b/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts index ee468c4a64..17cafa4be0 100644 --- a/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts +++ b/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts @@ -26,7 +26,15 @@ interface IWebviewEditorsExtensionPoint { const webviewEditorsContribution: IJSONSchema = { description: nls.localize('contributes.customEditors', 'Contributed custom editors.'), type: 'array', - defaultSnippets: [{ body: [{ viewType: '', displayName: '' }] }], + defaultSnippets: [{ + body: [{ + [WebviewEditorContribution.viewType]: '$1', + [WebviewEditorContribution.displayName]: '$2', + [WebviewEditorContribution.selector]: [{ + filenamePattern: '$3' + }], + }] + }], items: { type: 'object', required: [ @@ -48,6 +56,11 @@ const webviewEditorsContribution: IJSONSchema = { description: nls.localize('contributes.selector', 'Set of globs that the custom editor is enabled for.'), items: { type: 'object', + defaultSnippets: [{ + body: { + filenamePattern: '$1', + } + }], properties: { filenamePattern: { type: 'string', @@ -64,7 +77,7 @@ const webviewEditorsContribution: IJSONSchema = { CustomEditorPriority.option, CustomEditorPriority.builtin, ], - enumDescriptions: [ + markdownEnumDescriptions: [ nls.localize('contributes.priority.default', 'Editor is automatically used for a resource if no other default custom editors are registered for it.'), nls.localize('contributes.priority.option', 'Editor is not automatically used but can be selected by a user.'), nls.localize('contributes.priority.builtin', 'Editor automatically used if no other `default` or `builtin` editors are registered for the resource.'), diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index c3388d43db..90468a4e5c 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -57,8 +57,8 @@ export interface ICustomEditorModel extends IDisposable { revert(options?: IRevertOptions): Promise; - save(options?: ISaveOptions): Promise; - saveAs(resource: URI, targetResource: URI, currentOptions?: ISaveOptions): Promise; + saveCustomEditor(options?: ISaveOptions): Promise; + saveCustomEditorAs(resource: URI, targetResource: URI, currentOptions?: ISaveOptions): Promise; } export const enum CustomEditorPriority { diff --git a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts index c29e35c82e..5ef1db343c 100644 --- a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts +++ b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts @@ -68,11 +68,11 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo return this.textFileService.revert(this.resource, options); } - public async save(options?: ISaveOptions): Promise { - return !!await this.textFileService.save(this.resource, options); + public saveCustomEditor(options?: ISaveOptions): Promise { + return this.textFileService.save(this.resource, options); } - public async saveAs(resource: URI, targetResource: URI, options?: ISaveOptions): Promise { + public async saveCustomEditorAs(resource: URI, targetResource: URI, options?: ISaveOptions): Promise { return !!await this.textFileService.saveAs(resource, targetResource, options); } } diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index b0d12ae1cb..e60a8a5be5 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -103,6 +103,7 @@ const VIEW_CONTAINER: ViewContainer = Registry.as(ViewE Registry.as(ViewExtensions.ViewsRegistry).registerViews([{ id: REPL_VIEW_ID, name: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'), + containerIcon: 'codicon-debug-console', canToggleVisibility: false, canMoveView: true, ctorDescriptor: new SyncDescriptor(Repl), diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 7a0e69967c..f38a14baaa 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -36,7 +36,7 @@ import { IDecorationOptions } from 'vs/editor/common/editorCommon'; import { transparent, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; -import { CompletionContext, CompletionList, CompletionProviderRegistry, CompletionItem, completionKindFromString, CompletionItemKind } from 'vs/editor/common/modes'; +import { CompletionContext, CompletionList, CompletionProviderRegistry, CompletionItem, completionKindFromString, CompletionItemKind, CompletionItemInsertTextRule } from 'vs/editor/common/modes'; import { first } from 'vs/base/common/arrays'; import { ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -147,13 +147,24 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { if (response && response.body && response.body.targets) { response.body.targets.forEach(item => { if (item && item.label) { + let insertTextRules: CompletionItemInsertTextRule | undefined = undefined; + let insertText = item.text || item.label; + if (typeof item.selectionStart === 'number') { + // If a debug completion item sets a selection we need to use snippets to make sure the selection is selected #90974 + insertTextRules = CompletionItemInsertTextRule.InsertAsSnippet; + const selectionLength = typeof item.selectionLength === 'number' ? item.selectionLength : 0; + const placeholder = selectionLength > 0 ? '${1:' + insertText.substr(item.selectionStart, selectionLength) + '}$0' : '$0'; + insertText = insertText.substr(0, item.selectionStart) + placeholder + insertText.substr(item.selectionStart + selectionLength); + } + suggestions.push({ label: item.label, - insertText: item.text || item.label, + insertText, kind: completionKindFromString(item.type || 'property'), filterText: (item.start && item.length) ? text.substr(item.start, item.length).concat(item.label) : undefined, range: computeRange(item.length || overwriteBefore), - sortText: item.sortText + sortText: item.sortText, + insertTextRules }); } }); diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index 6933c7ad5e..1043dd0ca7 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -247,6 +247,9 @@ function convertPaths(msg: DebugProtocol.ProtocolMessage, fixSourcePath: (toDA: export function getVisibleAndSorted(array: T[]): T[] { return array.filter(config => !config.presentation?.hidden).sort((first, second) => { if (!first.presentation) { + if (!second.presentation) { + return 0; + } return 1; } if (!second.presentation) { @@ -271,6 +274,10 @@ export function getVisibleAndSorted(); this.conflictResolutionContext = new RawContextKey(CONFLICT_RESOLUTION_CONTEXT, false).bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts index 0d68a7bc41..fb8f86430f 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts @@ -26,11 +26,14 @@ import { minimumTranslatedStrings } from 'vs/workbench/contrib/localizations/bro import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; // Register action to configure locale and related settings const registry = Registry.as(Extensions.WorkbenchActions); registry.registerWorkbenchAction(SyncActionDescriptor.create(ConfigureLocaleAction, ConfigureLocaleAction.ID, ConfigureLocaleAction.LABEL), 'Configure Display Language'); +const LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY = 'extensionsAssistant/languagePackSuggestionIgnore'; + export class LocalizationWorkbenchContribution extends Disposable implements IWorkbenchContribution { constructor( @INotificationService private readonly notificationService: INotificationService, @@ -41,9 +44,12 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IViewletService private readonly viewletService: IViewletService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); + + storageKeysSyncRegistryService.registerStorageKey({ key: LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY, version: 1 }); this.checkAndInstall(); this._register(this.extensionManagementService.onDidInstallExtension(e => this.onDidInstallExtension(e))); } @@ -77,7 +83,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo private checkAndInstall(): void { const language = platform.language; const locale = platform.locale; - const languagePackSuggestionIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/languagePackSuggestionIgnore', StorageScope.GLOBAL, '[]')); + const languagePackSuggestionIgnoreList = JSON.parse(this.storageService.get(LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY, StorageScope.GLOBAL, '[]')); if (!this.galleryService.isEnabled()) { return; @@ -168,7 +174,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo run: () => { languagePackSuggestionIgnoreList.push(language); this.storageService.store( - 'extensionsAssistant/languagePackSuggestionIgnore', + LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY, JSON.stringify(languagePackSuggestionIgnoreList), StorageScope.GLOBAL ); diff --git a/src/vs/workbench/contrib/notebook/browser/constants.ts b/src/vs/workbench/contrib/notebook/browser/constants.ts index 2e86f5233e..8817cec72d 100644 --- a/src/vs/workbench/contrib/notebook/browser/constants.ts +++ b/src/vs/workbench/contrib/notebook/browser/constants.ts @@ -20,11 +20,11 @@ export const COPY_CELL_DOWN_COMMAND_ID = 'workbench.notebook.cell.copyDown'; export const EXECUTE_CELL_COMMAND_ID = 'workbench.notebook.cell.execute'; // Cell sizing related -export const CELL_MARGIN = 32; +export const CELL_MARGIN = 20; +export const CELL_RUN_GUTTER = 32; // TODO should be dynamic based on execution order width, and runnable enablement export const EDITOR_TOP_PADDING = 8; export const EDITOR_BOTTOM_PADDING = 8; export const EDITOR_TOOLBAR_HEIGHT = 22; -export const RUN_BUTTON_WIDTH = 20; // Cell context keys export const NOTEBOOK_CELL_TYPE_CONTEXT_KEY = 'notebookCellType'; // code, markdown diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts index f940966bef..41e83b04f0 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts @@ -16,6 +16,7 @@ import { CellRenderTemplate, CellEditState, ICellViewModel, INotebookEditor, KEY import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; const enum CellToolbarOrder { MoveCellUp, @@ -709,9 +710,9 @@ registerAction2(class extends Action2 { id: 'workbench.action.notebook.cursorDown', title: 'Notebook Cursor Move Down', keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')), + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), EditorContextKeys.editorTextFocus, NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')), primary: KeyCode.DownArrow, - weight: KeybindingWeight.WorkbenchContrib + weight: KeybindingWeight.EditorContrib // smaller than Suggest Widget, etc } }); } @@ -748,9 +749,9 @@ registerAction2(class extends Action2 { id: 'workbench.action.notebook.cursorUp', title: 'Notebook Cursor Move Up', keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')), + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), EditorContextKeys.editorTextFocus, NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')), primary: KeyCode.UpArrow, - weight: KeybindingWeight.WorkbenchContrib + weight: KeybindingWeight.EditorContrib // smaller than Suggest Widget, etc }, }); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.css b/src/vs/workbench/contrib/notebook/browser/notebook.css index 6a5401784d..d6c448af59 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/notebook.css @@ -30,7 +30,7 @@ position: relative; } -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .cell { +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell { display: flex; } @@ -87,16 +87,16 @@ } -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row { +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { overflow: visible !important; } -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row:focus-within { +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:focus-within { z-index: 10; } -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .menu { +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .menu { position: absolute; left: 0; top: 28px; @@ -107,40 +107,65 @@ } -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .menu.mouseover, -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row:hover .menu { +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .menu.mouseover, +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .menu { visibility: visible; } -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row:hover { +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover { outline: none !important; } -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row.selected, -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row.focused { +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected, +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { outline: none !important; } -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .menu.mouseover, -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .menu:hover { +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .menu.mouseover, +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .menu:hover { cursor: pointer; } -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > .monaco-toolbar { +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .monaco-toolbar { visibility: hidden; margin-right: 24px; } -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .cell .run-button-container .monaco-toolbar { - margin-top: 8px; +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell .run-button-container { + position: relative; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell .run-button-container .monaco-toolbar { + margin-top: 5px; visibility: hidden; } -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row:hover .cell .run-button-container .monaco-toolbar, -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.focused .cell .run-button-container .monaco-toolbar { +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell .run-button-container .monaco-toolbar .codicon-play { + margin-right: 8px; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .cell.runnable .run-button-container .monaco-toolbar, +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused .cell.runnable .run-button-container .monaco-toolbar { visibility: visible; } +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell .run-button-container .execution-count-label { + position: absolute; + top: 2px; + font-size: 12px; + visibility: visible; + white-space: pre; + width: 100%; + text-align: center; + padding-right: 2px; + box-sizing: border-box; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .cell .run-button-container .execution-count-label, +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused .cell .run-button-container .execution-count-label { + visibility: hidden; +} + .monaco-workbench .part.editor > .content .notebook-editor .cell .cell-editor-container { position: relative; } @@ -149,13 +174,13 @@ top: 0px; } -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.focused .monaco-toolbar, -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row:hover .monaco-toolbar { +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused .monaco-toolbar, +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .monaco-toolbar { visibility: visible; } -.monaco-workbench .part.editor > .content .notebook-editor .monaco-tree.focused.no-focused-item:focus:before, -.monaco-workbench .part.editor > .content .notebook-editor .monaco-list:not(.element-focused):focus:before { + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list:not(.element-focused):focus:before { outline: none !important; } @@ -167,7 +192,7 @@ width: 6px; border-left-width: 2px; border-left-style: solid; - left: 28px; + left: 20px; top: 22px; bottom: 8px; visibility: hidden; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 6c6f44f3c2..cf29920f86 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -15,7 +15,7 @@ import { FindMatch } from 'vs/editor/common/model'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; import { IModelDecorationsChangeAccessor, NotebookViewModel, CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellKind, IOutput, IRenderOutput, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, IOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NOTEBOOK_EDITABLE_CONTEXT_KEY } from 'vs/workbench/contrib/notebook/browser/constants'; export const KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED = new RawContextKey('notebookFindWidgetFocused', false); @@ -72,6 +72,7 @@ export interface ICellViewModel { focusMode: CellFocusMode; getText(): string; metadata: NotebookCellMetadata | undefined; + getEvaluatedMetadata(documentMetadata: NotebookDocumentMetadata | undefined): NotebookCellMetadata; } export interface INotebookEditor { @@ -249,6 +250,7 @@ export interface CellRenderTemplate { focusIndicator?: HTMLElement; runToolbar?: ToolBar; runButtonContainer?: HTMLElement; + executionOrderLabel?: HTMLElement; editingContainer?: HTMLElement; outputContainer?: HTMLElement; editor?: CodeEditorWidget; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index fb4578a154..b15fd505da 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -14,7 +14,6 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -29,7 +28,6 @@ import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/outpu import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; import { CodeCellRenderer, MarkdownCellRenderer, NotebookCellListDelegate } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; import { IOutput, CellKind, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -42,7 +40,7 @@ import { NotebookViewModel, INotebookEditorViewState, IModelDecorationsChangeAcc import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { Range } from 'vs/editor/common/core/range'; -import { CELL_MARGIN, RUN_BUTTON_WIDTH } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CELL_MARGIN, CELL_RUN_GUTTER } from 'vs/workbench/contrib/notebook/browser/constants'; import { Color, RGBA } from 'vs/base/common/color'; import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; @@ -113,11 +111,9 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { @IThemeService themeService: IThemeService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, - @IWebviewService private webviewService: IWebviewService, @INotebookService private notebookService: INotebookService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IEnvironmentService private readonly environmentSerice: IEnvironmentService, @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(NotebookEditor.ID, telemetryService, themeService, storageService); @@ -223,7 +219,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { ); this.control = new NotebookCodeEditors(this.list, this.renderedEditors); - this.webview = new BackLayerWebView(this.webviewService, this.notebookService, this, this.environmentSerice); + this.webview = this.instantiationService.createInstance(BackLayerWebView, this); this._register(this.webview.onMessage(message => { if (this.viewModel) { this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.viewModel.uri, message); @@ -334,7 +330,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { private async attachModel(input: NotebookEditorInput, model: NotebookEditorModel) { if (!this.webview) { - this.webview = new BackLayerWebView(this.webviewService, this.notebookService, this, this.environmentSerice); + this.webview = this.instantiationService.createInstance(BackLayerWebView, this); this.list?.rowsContainer.insertAdjacentElement('afterbegin', this.webview!.element); } @@ -798,8 +794,10 @@ registerThemingParticipant((theme, collector) => { } // Cell Margin - collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > div.cell { padding: 8px ${CELL_MARGIN}px 8px ${CELL_MARGIN}px; }`); - collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { margin: 8px ${CELL_MARGIN}px 8px ${CELL_MARGIN + RUN_BUTTON_WIDTH}px }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > div.cell { margin: 8px ${CELL_MARGIN}px 8px ${CELL_MARGIN}px; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { margin: 8px ${CELL_MARGIN}px 8px ${CELL_MARGIN + CELL_RUN_GUTTER}px }`); - collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .cell-editor-container { width: calc(100% - ${RUN_BUTTON_WIDTH}px); }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .cell-editor-container { width: calc(100% - ${CELL_RUN_GUTTER}px); }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > div.cell.markdown { padding-left: ${CELL_RUN_GUTTER}px; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .run-button-container { width: ${CELL_RUN_GUTTER}px; }`); }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index fee5bd313d..ae1f6e2021 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -15,8 +15,9 @@ import { IOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IWebviewService, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewResourceScheme } from 'vs/workbench/contrib/webview/common/resourceLoader'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; -import { CELL_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CELL_MARGIN, CELL_RUN_GUTTER } from 'vs/workbench/contrib/notebook/browser/constants'; import { Emitter, Event } from 'vs/base/common/event'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; export interface IDimentionMessage { __vscode_notebook_message: boolean; @@ -43,11 +44,13 @@ export interface ICreationRequestMessage { id: string; outputId: string; top: number; + left: number; } export interface IContentWidgetTopRequest { id: string; top: number; + left: number; } export interface IViewScrollTopRequestMessage { @@ -85,7 +88,13 @@ export class BackLayerWebView extends Disposable { public readonly onMessage: Event = this._onMessage.event; - constructor(public webviewService: IWebviewService, public notebookService: INotebookService, public notebookEditor: INotebookEditor, public environmentSerice: IEnvironmentService) { + constructor( + public notebookEditor: INotebookEditor, + @IWebviewService webviewService: IWebviewService, + @IOpenerService openerService: IOpenerService, + @IEnvironmentService private readonly environmentSerice: IEnvironmentService, + @INotebookService private readonly notebookService: INotebookService, + ) { super(); this.element = document.createElement('div'); @@ -194,6 +203,7 @@ export class BackLayerWebView extends Disposable { let outputNode = document.createElement('div'); outputNode.style.position = 'absolute'; outputNode.style.top = event.data.top + 'px'; + outputNode.style.left = event.data.left + 'px'; outputNode.id = outputId; let content = event.data.content; @@ -258,6 +268,10 @@ export class BackLayerWebView extends Disposable { this.webview = this._createInset(webviewService, content); this.webview.mountTo(this.element); + this._register(this.webview.onDidClickLink(link => { + openerService.open(link, { fromUserGesture: true }); + })); + this._register(this.webview.onDidWheel(e => { this.notebookEditor.triggerScroll(e); })); @@ -331,7 +345,8 @@ export class BackLayerWebView extends Disposable { return { id: id, - top: outputOffset + top: outputOffset, + left: CELL_RUN_GUTTER }; }); @@ -355,7 +370,8 @@ export class BackLayerWebView extends Disposable { content: shadowContent, id: cell.id, outputId: outputId, - top: initialTop + top: initialTop, + left: CELL_RUN_GUTTER }; this.webview.sendMessage(message); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 9af683defb..caeee33cfd 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -6,6 +6,7 @@ import { getZoomLevel } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IAction } from 'vs/base/common/actions'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -16,15 +17,16 @@ import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { MenuItemAction, IMenu } from 'vs/platform/actions/common/actions'; +import { IMenu, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_PADDING, NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, NOTEBOOK_CELL_TYPE_CONTEXT_KEY, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellRenderTemplate, CellRunState, ICellViewModel, INotebookEditor, CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_PADDING, NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY, NOTEBOOK_CELL_TYPE_CONTEXT_KEY } from 'vs/workbench/contrib/notebook/browser/constants'; +import { ExecuteCellAction, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/notebookActions'; +import { CellEditState, CellRenderTemplate, CellRunState, ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellMenus } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellMenus'; import { CodeCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/codeCell'; import { StatefullMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/markdownCell'; @@ -32,8 +34,6 @@ import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookCellActionContext, ExecuteCellAction } from 'vs/workbench/contrib/notebook/browser/contrib/notebookActions'; -import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; const $ = DOM.$; @@ -221,8 +221,8 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR const contextKeyService = this.contextKeyService.createScoped(templateData.container); contextKeyService.createKey(NOTEBOOK_CELL_TYPE_CONTEXT_KEY, 'markdown'); const cellEditableKey = contextKeyService.createKey(NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, !!(element.metadata?.editable)); - elementDisposable.add(element.onDidChangeMetadata((e) => { - cellEditableKey.set(!!e?.editable); + elementDisposable.add(element.onDidChangeMetadata(() => { + cellEditableKey.set(!!element.metadata?.editable); })); const editModeKey = contextKeyService.createKey(NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY, element.editState === CellEditState.Editing); @@ -286,6 +286,8 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende ])(); disposables.add(runToolbar); + const executionOrderLabel = DOM.append(runButtonContainer, $('div.execution-count-label')); + const editorContainer = DOM.append(cellContainer, $('.cell-editor-container')); const editor = this.instantiationService.createInstance(CodeEditorWidget, editorContainer, { ...this.editorOptions, @@ -314,6 +316,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende toolbar, runToolbar, runButtonContainer, + executionOrderLabel, outputContainer, editor, disposables, @@ -352,21 +355,36 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende } })); + function renderExecutionOrder() { + const executionOrdeerLabel = typeof element.metadata?.executionOrder === 'number' ? `[ ${element.metadata.executionOrder} ]` : + '[ ]'; + templateData.executionOrderLabel!.innerText = executionOrdeerLabel; + } + + const contextKeyService = this.contextKeyService.createScoped(templateData.container); + contextKeyService.createKey(NOTEBOOK_CELL_TYPE_CONTEXT_KEY, 'code'); + const cellEditableKey = contextKeyService.createKey(NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, !!(element.metadata?.editable)); + + const updateForMetadata = () => { + const metadata = element.getEvaluatedMetadata(this.notebookEditor.viewModel!.notebookDocument.metadata); + DOM.toggleClass(templateData.cellContainer, 'runnable', !!metadata.runnable); + renderExecutionOrder(); + cellEditableKey.set(!!metadata.editable); + }; + + updateForMetadata(); + elementDisposable.add(element.onDidChangeMetadata(() => { + updateForMetadata(); + })); + + this.setupCellToolbarActions(contextKeyService, templateData, elementDisposable); + const toolbarContext = { cell: element, cellTemplate: templateData, notebookEditor: this.notebookEditor, $mid: 12 }; - - const contextKeyService = this.contextKeyService.createScoped(templateData.container); - contextKeyService.createKey(NOTEBOOK_CELL_TYPE_CONTEXT_KEY, 'code'); - const cellEditableKey = contextKeyService.createKey(NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, !!(element.metadata?.editable)); - elementDisposable.add(element.onDidChangeMetadata((e) => { - cellEditableKey.set(!!e?.editable); - })); - - this.setupCellToolbarActions(contextKeyService, templateData, elementDisposable); templateData.toolbar.context = toolbarContext; templateData.runToolbar!.context = toolbarContext; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts index d18e402070..68c1d9eb8d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -83,7 +83,7 @@ export class CodeCell extends Disposable { })); templateData.editor?.updateOptions({ readOnly: !(viewCell.getEvaluatedMetadata(notebookEditor.viewModel?.metadata).editable) }); - this._register(viewCell.onDidChangeMetadata((e) => { + this._register(viewCell.onDidChangeMetadata(() => { templateData.editor?.updateOptions({ readOnly: !(viewCell.getEvaluatedMetadata(notebookEditor.viewModel?.metadata).editable) }); })); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index 8c314114d2..0fbb4fe4c5 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -32,8 +32,8 @@ export abstract class BaseCellViewModel extends Disposable { readonly onDidChangeEditorAttachState = this._onDidChangeEditorAttachState.event; protected readonly _onDidChangeCursorSelection: Emitter = this._register(new Emitter()); public readonly onDidChangeCursorSelection: Event = this._onDidChangeCursorSelection.event; - protected readonly _onDidChangeMetadata: Emitter = this._register(new Emitter()); - public readonly onDidChangeMetadata: Event = this._onDidChangeMetadata.event; + protected readonly _onDidChangeMetadata: Emitter = this._register(new Emitter()); + public readonly onDidChangeMetadata: Event = this._onDidChangeMetadata.event; protected readonly _onDidChangeLanguage: Emitter = this._register(new Emitter()); public readonly onDidChangeLanguage: Event = this._onDidChangeLanguage.event; get handle() { @@ -106,8 +106,8 @@ export abstract class BaseCellViewModel extends Disposable { this._onDidChangeLanguage.fire(e); })); - this._register(cell.onDidChangeMetadata((e) => { - this._onDidChangeMetadata.fire(e); + this._register(cell.onDidChangeMetadata(() => { + this._onDidChangeMetadata.fire(); })); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index a7e158ea40..82e7cf6a46 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -8,7 +8,7 @@ import * as UUID from 'vs/base/common/uuid'; import * as model from 'vs/editor/common/model'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; -import { EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_PADDING, CELL_MARGIN, RUN_BUTTON_WIDTH } from 'vs/workbench/contrib/notebook/browser/constants'; +import { EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_PADDING, CELL_MARGIN, CELL_RUN_GUTTER } from 'vs/workbench/contrib/notebook/browser/constants'; import { CellEditState, ICellViewModel, CellFindMatch, CodeCellLayoutChangeEvent, CodeCellLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, ICell, NotebookCellOutputsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { BaseCellViewModel } from './baseCellViewModel'; @@ -72,6 +72,10 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod })); } + this._register(this.cell.onDidChangeMetadata(() => { + this._onDidChangeMetadata.fire(); + })); + this._outputCollection = new Array(this.cell.outputs.length); this._buffer = null; @@ -105,7 +109,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod ? EDITOR_TOOLBAR_HEIGHT + this.editorHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING + 16 + outputTotalHeight : EDITOR_TOOLBAR_HEIGHT + this.editorHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING + outputTotalHeight; const indicatorHeight = totalHeight - EDITOR_TOOLBAR_HEIGHT - 16; - const editorWidth = state.outerWidth !== undefined ? state.outerWidth - CELL_MARGIN * 2 - RUN_BUTTON_WIDTH : 0; + const editorWidth = state.outerWidth !== undefined ? state.outerWidth - CELL_MARGIN * 2 - CELL_RUN_GUTTER : 0; this._layoutInfo = { fontInfo: state.font || null, editorHeight: this._editorHeight, diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index 9eca161cc9..82d073468c 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -15,8 +15,8 @@ export class NotebookCellTextModel implements ICell { private _onDidChangeContent = new Emitter(); onDidChangeContent: Event = this._onDidChangeContent.event; - private _onDidChangeMetadata = new Emitter(); - onDidChangeMetadata: Event = this._onDidChangeMetadata.event; + private _onDidChangeMetadata = new Emitter(); + onDidChangeMetadata: Event = this._onDidChangeMetadata.event; private _onDidChangeLanguage = new Emitter(); onDidChangeLanguage: Event = this._onDidChangeLanguage.event; @@ -44,7 +44,7 @@ export class NotebookCellTextModel implements ICell { set metadata(newMetadata: NotebookCellMetadata | undefined) { this._metadata = newMetadata; - this._onDidChangeMetadata.fire(this._metadata); + this._onDidChangeMetadata.fire(); } get language() { diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 28e449287f..869a9c09fd 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -45,6 +45,7 @@ export interface NotebookDocumentMetadata { export interface NotebookCellMetadata { editable?: boolean; runnable?: boolean; + executionOrder?: number; } export interface INotebookDisplayOrder { @@ -135,8 +136,8 @@ export interface ICell { outputs: IOutput[]; metadata?: NotebookCellMetadata; onDidChangeOutputs?: Event; - onDidChangeMetadata: Event; onDidChangeLanguage: Event; + onDidChangeMetadata: Event; resolveTextBufferFactory(): PieceTreeTextBufferFactory; // TODO@rebornix it should be later on replaced by moving textmodel resolution into CellTextModel contentChange(): void; diff --git a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts index 1dcc634dfa..6e39249843 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts @@ -83,4 +83,81 @@ suite('NotebookViewModel', () => { } ); }); + + test('metadata', function () { + withTestNotebook( + instantiationService, + blukEditService, + undoRedoService, + [ + [['var a = 1;'], 'javascript', CellKind.Code, [], {}], + [['var b = 2;'], 'javascript', CellKind.Code, [], { editable: true, runnable: true }], + [['var c = 3;'], 'javascript', CellKind.Code, [], { editable: true, runnable: false }], + [['var d = 4;'], 'javascript', CellKind.Code, [], { editable: false, runnable: true }], + [['var e = 5;'], 'javascript', CellKind.Code, [], { editable: false, runnable: false }], + ], + (editor, viewModel) => { + viewModel.notebookDocument.metadata = { editable: true, cellRunnable: true, cellEditable: true }; + + assert.deepEqual(viewModel.viewCells[0].getEvaluatedMetadata(viewModel.metadata), { + editable: true, + runnable: true + }); + + assert.deepEqual(viewModel.viewCells[1].getEvaluatedMetadata(viewModel.metadata), { + editable: true, + runnable: true + }); + + assert.deepEqual(viewModel.viewCells[2].getEvaluatedMetadata(viewModel.metadata), { + editable: true, + runnable: false + }); + + assert.deepEqual(viewModel.viewCells[3].getEvaluatedMetadata(viewModel.metadata), { + editable: false, + runnable: true + }); + + assert.deepEqual(viewModel.viewCells[4].getEvaluatedMetadata(viewModel.metadata), { + editable: false, + runnable: false + }); + + viewModel.notebookDocument.metadata = { editable: true, cellRunnable: false, cellEditable: true }; + + assert.deepEqual(viewModel.viewCells[0].getEvaluatedMetadata(viewModel.metadata), { + editable: true, + runnable: false + }); + + assert.deepEqual(viewModel.viewCells[1].getEvaluatedMetadata(viewModel.metadata), { + editable: true, + runnable: true + }); + + assert.deepEqual(viewModel.viewCells[2].getEvaluatedMetadata(viewModel.metadata), { + editable: true, + runnable: false + }); + + assert.deepEqual(viewModel.viewCells[3].getEvaluatedMetadata(viewModel.metadata), { + editable: false, + runnable: true + }); + + assert.deepEqual(viewModel.viewCells[4].getEvaluatedMetadata(viewModel.metadata), { + editable: false, + runnable: false + }); + + viewModel.notebookDocument.metadata = { editable: true, cellRunnable: false, cellEditable: false }; + + assert.deepEqual(viewModel.viewCells[0].getEvaluatedMetadata(viewModel.metadata), { + editable: false, + runnable: false + }); + } + ); + }); }); diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index c74e43a2d7..6c9b97bb6e 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -25,11 +25,10 @@ export class TestCell implements ICell { uri: URI; private _onDidChangeOutputs = new Emitter(); onDidChangeOutputs: Event = this._onDidChangeOutputs.event; - private _onDidChangeMetadata = new Emitter(); - onDidChangeMetadata: Event = this._onDidChangeMetadata.event; private _onDidChangeLanguage = new Emitter(); onDidChangeLanguage: Event = this._onDidChangeLanguage.event; - + private _onDidChangeMetadata = new Emitter(); + onDidChangeMetadata: Event = this._onDidChangeMetadata.event; private _isDirty: boolean = false; private _outputs: IOutput[]; diff --git a/src/vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution.ts b/src/vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution.ts deleted file mode 100644 index b5b915b595..0000000000 --- a/src/vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution.ts +++ /dev/null @@ -1,111 +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 { localize } from 'vs/nls'; -import { STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND } from 'vs/workbench/common/theme'; -import { themeColorFromId } from 'vs/platform/theme/common/themeService'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions'; -import { StatusbarAlignment, IStatusbarService, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { Action } from 'vs/base/common/actions'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IApplicationLink } from 'vs/workbench/workbench.web.api'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; - -export class OpenInDesktopIndicator extends Disposable implements IWorkbenchContribution { - - constructor( - @IStatusbarService private readonly statusbarService: IStatusbarService, - @IWorkspaceContextService workspaceService: IWorkspaceContextService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService - ) { - super(); - - const links = environmentService.options?.applicationLinks; - if (Array.isArray(links) && links?.length > 0) { - this.installOpenInDesktopIndicator(links); - } - } - - private installOpenInDesktopIndicator(links: readonly IApplicationLink[]): void { - - // Register action to trigger "Open In Desktop" - const registry = Registry.as(ActionExtensions.WorkbenchActions); - registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenInDesktopAction, OpenInDesktopAction.ID, OpenInDesktopAction.LABEL), 'Open Workspace in Desktop'); - - // Show in status bar - const properties: IStatusbarEntry = { - backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND), - color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND), - text: links.length === 1 ? links[0].label : localize('openInDesktop', "Open in Desktop..."), - command: 'workbench.web.openWorkspaceInDesktop' - }; - - this.statusbarService.addEntry(properties, 'status.openInDesktop', properties.text, StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */); - } -} - -const workbenchContributionsRegistry = Registry.as(WorkbenchContributionsExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(OpenInDesktopIndicator, LifecyclePhase.Starting); - -export class OpenInDesktopAction extends Action { - static readonly ID = 'workbench.web.openWorkspaceInDesktop'; - static readonly LABEL = localize('openWorkspaceInDesktop', "Open Workspace in Desktop"); - - constructor( - id: string, - label: string, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IOpenerService private readonly openerService: IOpenerService - ) { - super(id, label); - } - - async run(): Promise { - const links = this.environmentService.options?.applicationLinks; - if (Array.isArray(links)) { - if (links.length === 1) { - return this.openApplicationLink(links[0]); - } - - return this.runWithPicker(links); - } - - return true; - } - - private async runWithPicker(links: readonly IApplicationLink[]): Promise { - - // Show a picker with choices - const quickPick = this.quickInputService.createQuickPick(); - quickPick.items = links; - quickPick.placeholder = OpenInDesktopAction.LABEL; - quickPick.canSelectMany = false; - quickPick.onDidAccept(() => { - const selectedItems = quickPick.selectedItems; - if (selectedItems.length === 1) { - this.openApplicationLink(selectedItems[0]); - } - quickPick.hide(); - }); - - quickPick.show(); - - return true; - } - - private async openApplicationLink(link: IApplicationLink): Promise { - this.openerService.open(link.uri, { openExternal: true }); - - return true; - } -} diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 952a1b518f..ec1faacc30 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -69,6 +69,8 @@ const VIEW_CONTAINER: ViewContainer = Registry.as(ViewC Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: OUTPUT_VIEW_ID, name: nls.localize('output', "Output"), + containerIcon: 'codicon-output', + canMoveView: true, canToggleVisibility: false, ctorDescriptor: new SyncDescriptor(OutputViewPane), }], VIEW_CONTAINER); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 343330538c..7fe786a2b9 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -50,6 +50,7 @@ import { IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEdit import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IEditorModel } from 'vs/platform/editor/common/editor'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; function createGroupIterator(group: SettingsTreeGroupElement): Iterator> { const groupsIt = Iterator.fromArray(group.children); @@ -70,6 +71,7 @@ interface IFocusEventFromScroll extends KeyboardEvent { fromScroll: true; } +const SETTINGS_AUTOSAVE_NOTIFIED_KEY = 'hasNotifiedOfSettingsAutosave'; const SETTINGS_EDITOR_STATE_KEY = 'settingsEditorState'; export class SettingsEditor2 extends BaseEditor { @@ -160,7 +162,8 @@ export class SettingsEditor2 extends BaseEditor { @IStorageService private readonly storageService: IStorageService, @INotificationService private readonly notificationService: INotificationService, @IEditorGroupsService protected editorGroupService: IEditorGroupsService, - @IKeybindingService private readonly keybindingService: IKeybindingService + @IKeybindingService private readonly keybindingService: IKeybindingService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, ) { super(SettingsEditor2.ID, telemetryService, themeService, storageService); this.delayedFilterLogging = new Delayer(1000); @@ -186,6 +189,8 @@ export class SettingsEditor2 extends BaseEditor { this.onConfigUpdate(e.affectedKeys); } })); + + storageKeysSyncRegistryService.registerStorageKey({ key: SETTINGS_AUTOSAVE_NOTIFIED_KEY, version: 1 }); } get minimumWidth(): number { return 375; } @@ -712,8 +717,8 @@ export class SettingsEditor2 extends BaseEditor { } private notifyNoSaveNeeded() { - if (!this.storageService.getBoolean('hasNotifiedOfSettingsAutosave', StorageScope.GLOBAL, false)) { - this.storageService.store('hasNotifiedOfSettingsAutosave', true, StorageScope.GLOBAL); + if (!this.storageService.getBoolean(SETTINGS_AUTOSAVE_NOTIFIED_KEY, StorageScope.GLOBAL, false)) { + this.storageService.store(SETTINGS_AUTOSAVE_NOTIFIED_KEY, true, StorageScope.GLOBAL); this.notificationService.info(localize('settingsNoSaveNeeded', "Your changes are automatically saved as you edit.")); } } diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index 15d3057b74..f51c3a7b2a 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -23,6 +23,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { DefaultQuickAccessFilterValue } from 'vs/platform/quickinput/common/quickAccess'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchQuickOpenConfiguration } from 'vs/workbench/browser/quickopen'; +import { stripCodicons } from 'vs/base/common/codicons'; export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { @@ -114,7 +115,7 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce globalCommandPicks.push({ commandId: action.item.id, commandAlias, - label + label: stripCodicons(label) }); } diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index 71e4d14536..e644dc6a09 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/anythingQuickAccess'; -import { IQuickPickSeparator, IQuickInputButton, IKeyMods, quickPickItemScorerAccessor, QuickPickItemScorerAccessor, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; -import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction, FastAndSlowPicksType } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { IQuickInputButton, IKeyMods, quickPickItemScorerAccessor, QuickPickItemScorerAccessor, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction, FastAndSlowPicks, Picks, PicksWithActive } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { prepareQuery, IPreparedQuery, compareItemsByScore, scoreItem, ScorerCache } from 'vs/base/common/fuzzyScorer'; import { IFileQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -88,7 +88,8 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider | undefined = undefined; isQuickNavigating: boolean | undefined = undefined; @@ -116,7 +117,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider this.clearDecorations(activeEditorControl)); } - protected getPicks(originalFilter: string, disposables: DisposableStore, token: CancellationToken): Promise> | FastAndSlowPicksType | null { + protected getPicks(originalFilter: string, disposables: DisposableStore, token: CancellationToken): Picks | Promise> | FastAndSlowPicks | null { // Find a suitable range from the pattern looking for ":", "#" or "," // unless we have the `@` editor symbol character inside the filter @@ -269,16 +270,25 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider> | FastAndSlowPicksType | null { + private doGetPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Picks | Promise> | FastAndSlowPicks | null { const query = prepareQuery(filter); // Return early if we have editor symbol picks. We support this by: @@ -289,6 +299,14 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider> => { + additionalPicks: (async (): Promise> => { // Exclude any result that is already present in editor history const additionalPicksExcludes = new ResourceMap(); @@ -636,13 +654,13 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider> | null { + private getEditorSymbolPicks(query: IPreparedQuery, disposables: DisposableStore, token: CancellationToken): Promise> | null { const filter = query.original.split(GotoSymbolQuickAccessProvider.PREFIX)[1]?.trim(); if (typeof filter !== 'string') { return null; // we need to be searched for editor symbols via `@` } - const activeGlobalPick = this.pickState.lastActiveGlobalPick; + const activeGlobalPick = this.pickState.lastGlobalPicks?.active; if (!activeGlobalPick) { return null; // we need an active global pick to find symbols for } @@ -655,7 +673,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider> { + private async doGetEditorSymbolPicks(activeGlobalPick: IAnythingQuickPickItem, activeGlobalResource: URI, filter: string, disposables: DisposableStore, token: CancellationToken): Promise> { // Bring the editor to front to review symbols to go to try { diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index bc8f9193cf..1b1651ee91 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -390,6 +390,7 @@ Registry.as(panel.Extensions.Panels).setDefaultPanelId(TERM Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: TERMINAL_VIEW_ID, name: nls.localize('terminal', "Terminal"), + containerIcon: 'codicon-terminal', canToggleVisibility: false, canMoveView: true, ctorDescriptor: new SyncDescriptor(TerminalViewPane) @@ -459,10 +460,7 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ManageWorkspa actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(RenameTerminalAction, RenameTerminalAction.ID, RenameTerminalAction.LABEL), 'Terminal: Rename', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_F -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Find Widget', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.KEY_F -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Focus Find Widget', category); +}, ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FOCUS)), 'Terminal: Focus Find Widget', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(HideTerminalFindWidgetAction, HideTerminalFindWidgetAction.ID, HideTerminalFindWidgetAction.LABEL, { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape] @@ -550,43 +548,25 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationMod }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Exit Navigation Mode', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusPreviousTerminalAction, NavigationModeFocusPreviousTerminalAction.ID, NavigationModeFocusPreviousTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.UpArrow -}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Previous Line (Navigation Mode)', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusPreviousTerminalAction, NavigationModeFocusPreviousTerminalAction.ID, NavigationModeFocusPreviousTerminalAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.UpArrow -}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Previous Line (Navigation Mode)', category); +}, ContextKeyExpr.or(ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED), ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED))), 'Terminal: Focus Previous Line (Navigation Mode)', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusNextTerminalAction, NavigationModeFocusNextTerminalAction.ID, NavigationModeFocusNextTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.DownArrow -}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Next Line (Navigation Mode)', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NavigationModeFocusNextTerminalAction, NavigationModeFocusNextTerminalAction.ID, NavigationModeFocusNextTerminalAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.DownArrow -}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED)), 'Terminal: Focus Next Line (Navigation Mode)', category); +}, ContextKeyExpr.or(ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED), ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED))), 'Terminal: Focus Next Line (Navigation Mode)', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectToPreviousLineAction, SelectToPreviousLineAction.ID, SelectToPreviousLineAction.LABEL), 'Terminal: Select To Previous Line', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SelectToNextLineAction, SelectToNextLineAction.ID, SelectToNextLineAction.LABEL), 'Terminal: Select To Next Line', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleEscapeSequenceLoggingAction, ToggleEscapeSequenceLoggingAction.ID, ToggleEscapeSequenceLoggingAction.LABEL), 'Terminal: Toggle Escape Sequence Logging', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleRegexCommand, ToggleRegexCommand.ID, ToggleRegexCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_R, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R } -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using regex', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleRegexCommand, ToggleRegexCommand.ID, ToggleRegexCommand.LABEL, { - primary: KeyMod.Alt | KeyCode.KEY_R, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find using regex', category); +}, ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED)), 'Terminal: Toggle find using regex', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleWholeWordCommand, ToggleWholeWordCommand.ID, ToggleWholeWordCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_W, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_W } -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using whole word', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleWholeWordCommand, ToggleWholeWordCommand.ID, ToggleWholeWordCommand.LABEL, { - primary: KeyMod.Alt | KeyCode.KEY_W, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_W } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find using whole word', category); +}, ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED)), 'Terminal: Toggle find using whole word', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleCaseSensitiveCommand, ToggleCaseSensitiveCommand.ID, ToggleCaseSensitiveCommand.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_C, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C } -}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Toggle find using case sensitive', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleCaseSensitiveCommand, ToggleCaseSensitiveCommand.ID, ToggleCaseSensitiveCommand.LABEL, { - primary: KeyMod.Alt | KeyCode.KEY_C, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Toggle find using case sensitive', category); +}, ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED)), 'Terminal: Toggle find using case sensitive', category); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FindNext, FindNext.ID, FindNext.LABEL, { primary: KeyCode.F3, mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_G, secondary: [KeyCode.F3] } diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 54f649cc61..a6ad3c5dd8 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -776,12 +776,14 @@ export class TimelinePane extends ViewPane { // Refresh the view on focus to update the relative timestamps this.onDidFocus(() => this.refreshDebounced(), this, this.visibilityDisposables); + super.setVisible(visible); + this.onActiveEditorChanged(); } else { this.visibilityDisposables?.dispose(); - } - super.setVisible(visible); + super.setVisible(visible); + } } protected layoutBody(height: number, width: number): void { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 79b9529b78..8fddef1f52 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -53,6 +53,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { timeout } from 'vs/base/common/async'; +import { distinct } from 'vs/base/common/arrays'; const enum AuthStatus { Initializing = 'Initializing', @@ -98,19 +99,19 @@ const getActivityTitle = (label: string, userDataSyncService: IUserDataSyncServi const getIdentityTitle = (label: string, authenticationProviderId: string, account: AuthenticationSession | undefined, authenticationService: IAuthenticationService): string => { return account ? `${label} (${authenticationService.getDisplayName(authenticationProviderId)}:${account.accountName})` : label; }; -const turnOnSyncCommand = { id: 'workbench.userData.actions.syncStart', title: localize('turn on sync with category', "Sync: Turn on Sync") }; -const signInCommand = { id: 'workbench.userData.actions.signin', title: localize('sign in', "Sync: Sign in to sync") }; -const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title(authenticationProviderId: string, account: AuthenticationSession | undefined, authenticationService: IAuthenticationService) { return getIdentityTitle(localize('stop sync', "Sync: Turn off Sync"), authenticationProviderId, account, authenticationService); } }; -const resolveSettingsConflictsCommand = { id: 'workbench.userData.actions.resolveSettingsConflicts', title: localize('showConflicts', "Sync: Show Settings Conflicts") }; -const resolveKeybindingsConflictsCommand = { id: 'workbench.userData.actions.resolveKeybindingsConflicts', title: localize('showKeybindingsConflicts', "Sync: Show Keybindings Conflicts") }; -const resolveSnippetsConflictsCommand = { id: 'workbench.userData.actions.resolveSnippetsConflicts', title: localize('showSnippetsConflicts', "Sync: Show User Snippets Conflicts") }; -const configureSyncCommand = { id: 'workbench.userData.actions.configureSync', title: localize('configure sync', "Sync: Configure") }; +const turnOnSyncCommand = { id: 'workbench.userData.actions.syncStart', title: localize('turn on sync with category', "Preferences Sync: Turn on...") }; +const signInCommand = { id: 'workbench.userData.actions.signin', title: localize('sign in', "Preferences Sync: Sign in to sync") }; +const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title(authenticationProviderId: string, account: AuthenticationSession | undefined, authenticationService: IAuthenticationService) { return getIdentityTitle(localize('stop sync', "Preferences Sync: Turn off"), authenticationProviderId, account, authenticationService); } }; +const resolveSettingsConflictsCommand = { id: 'workbench.userData.actions.resolveSettingsConflicts', title: localize('showConflicts', "Preferences Sync: Show Settings Conflicts") }; +const resolveKeybindingsConflictsCommand = { id: 'workbench.userData.actions.resolveKeybindingsConflicts', title: localize('showKeybindingsConflicts', "Preferences Sync: Show Keybindings Conflicts") }; +const resolveSnippetsConflictsCommand = { id: 'workbench.userData.actions.resolveSnippetsConflicts', title: localize('showSnippetsConflicts', "Preferences Sync: Show User Snippets Conflicts") }; +const configureSyncCommand = { id: 'workbench.userData.actions.configureSync', title: localize('configure sync', "Preferences Sync: Configure...") }; const showSyncActivityCommand = { id: 'workbench.userData.actions.showSyncActivity', title(userDataSyncService: IUserDataSyncService): string { - return getActivityTitle(localize('show sync log', "Sync: Show Log"), userDataSyncService); + return getActivityTitle(localize('show sync log', "Preferences Sync: Show Log"), userDataSyncService); } }; -const showSyncSettingsCommand = { id: 'workbench.userData.actions.syncSettings', title: localize('sync settings', "Sync: Settings"), }; +const showSyncSettingsCommand = { id: 'workbench.userData.actions.syncSettings', title: localize('sync settings', "Preferences Sync: Show Settings"), }; export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution { @@ -249,9 +250,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private async showSwitchAccountPicker(sessions: readonly AuthenticationSession[]): Promise { return new Promise((resolve, _) => { const quickPick = this.quickInputService.createQuickPick<{ label: string, session: AuthenticationSession }>(); - quickPick.title = localize('chooseAccountTitle', "Sync: Choose Account"); + quickPick.title = localize('chooseAccountTitle', "Preferences Sync: Choose Account"); quickPick.placeholder = localize('chooseAccount', "Choose an account you would like to use for settings sync"); - quickPick.items = sessions.map(session => { + const dedupedSessions = distinct(sessions, (session) => session.accountName); + quickPick.items = dedupedSessions.map(session => { return { label: session.accountName, session: session @@ -577,7 +579,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.userDataSyncEnablementService.isEnabled() && this.authenticationState.get() === AuthStatus.SignedOut) { badge = new NumberBadge(1, () => localize('sign in to sync', "Sign in to Sync")); } else if (this.userDataSyncService.conflicts.length) { - badge = new NumberBadge(this.userDataSyncService.conflicts.reduce((result, syncResourceConflict) => { return result + syncResourceConflict.conflicts.length; }, 0), () => localize('has conflicts', "Sync: Conflicts Detected")); + badge = new NumberBadge(this.userDataSyncService.conflicts.reduce((result, syncResourceConflict) => { return result + syncResourceConflict.conflicts.length; }, 0), () => localize('has conflicts', "Preferences Sync: Conflicts Detected")); } if (badge) { @@ -608,7 +610,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const disposables: DisposableStore = new DisposableStore(); const quickPick = this.quickInputService.createQuickPick(); disposables.add(quickPick); - quickPick.title = localize('turn on title', "Sync: Turn On"); + quickPick.title = localize('turn on title', "Preferences Sync: Turn On"); quickPick.ok = false; quickPick.customButton = true; if (this.authenticationState.get() === AuthStatus.SignedIn) { @@ -713,7 +715,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const disposables: DisposableStore = new DisposableStore(); const quickPick = this.quickInputService.createQuickPick(); disposables.add(quickPick); - quickPick.title = localize('turn on sync', "Turn on Sync"); + quickPick.title = localize('configure sync', "Preferences Sync: Configure..."); quickPick.placeholder = localize('configure sync placeholder', "Choose what to sync"); quickPick.canSelectMany = true; quickPick.ignoreFocusOut = true; @@ -888,7 +890,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo group: '5_sync', command: { id: turnOnSyncCommand.id, - title: localize('global activity turn on sync', "Turn on Sync...") + title: localize('global activity turn on sync', "Preferences Sync: Turn on...") }, when: turnOnSyncWhenContext, order: 1 @@ -901,7 +903,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo group: '5_sync', command: { id: turnOnSyncCommand.id, - title: localize('global activity turn on sync', "Turn on Sync...") + title: localize('global activity turn on sync', "Preferences Sync: Turn on...") }, when: turnOnSyncWhenContext, }); @@ -913,7 +915,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo constructor() { super({ id: signInCommand.id, - title: localize('sign in 2', "Sync: Sign in to sync (1)"), + title: localize('sign in 2', "Preferences Sync: Sign in to sync (1)"), menu: { group: '5_sync', id: MenuId.GlobalActivity, @@ -939,7 +941,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo group: '5_sync', command: { id: resolveSettingsConflictsCommand.id, - title: localize('resolveConflicts_global', "Sync: Show Settings Conflicts (1)"), + title: localize('resolveConflicts_global', "Preferences Sync: Show Settings Conflicts (1)"), }, when: resolveSettingsConflictsWhenContext, order: 2 @@ -948,7 +950,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo group: '5_sync', command: { id: resolveSettingsConflictsCommand.id, - title: localize('resolveConflicts_global', "Sync: Show Settings Conflicts (1)"), + title: localize('resolveConflicts_global', "Preferences Sync: Show Settings Conflicts (1)"), }, when: resolveSettingsConflictsWhenContext, order: 2 @@ -966,7 +968,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo group: '5_sync', command: { id: resolveKeybindingsConflictsCommand.id, - title: localize('resolveKeybindingsConflicts_global', "Sync: Show Keybindings Conflicts (1)"), + title: localize('resolveKeybindingsConflicts_global', "Preferences Sync: Show Keybindings Conflicts (1)"), }, when: resolveKeybindingsConflictsWhenContext, order: 2 @@ -975,7 +977,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo group: '5_sync', command: { id: resolveKeybindingsConflictsCommand.id, - title: localize('resolveKeybindingsConflicts_global', "Sync: Show Keybindings Conflicts (1)"), + title: localize('resolveKeybindingsConflicts_global', "Preferences Sync: Show Keybindings Conflicts (1)"), }, when: resolveKeybindingsConflictsWhenContext, order: 2 @@ -996,7 +998,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo group: '5_sync', command: { id: resolveSnippetsConflictsCommand.id, - title: localize('resolveSnippetsConflicts_global', "Sync: Show User Snippets Conflicts ({0})", conflicts?.length || 1), + title: localize('resolveSnippetsConflicts_global', "Preferences Sync: Show User Snippets Conflicts ({0})", conflicts?.length || 1), }, when: resolveSnippetsConflictsWhenContext, order: 2 @@ -1005,7 +1007,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo group: '5_sync', command: { id: resolveSnippetsConflictsCommand.id, - title: localize('resolveSnippetsConflicts_global', "Sync: Show User Snippets Conflicts ({0})", conflicts?.length || 1), + title: localize('resolveSnippetsConflicts_global', "Preferences Sync: Show User Snippets Conflicts ({0})", conflicts?.length || 1), }, when: resolveSnippetsConflictsWhenContext, order: 2 @@ -1023,7 +1025,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo constructor() { super({ id: 'workbench.userData.actions.syncStatus', - title: localize('sync is on', "Sync is on"), + title: localize('sync is on', "Preferences Sync: On"), menu: [ { id: MenuId.GlobalActivity, @@ -1262,8 +1264,8 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio const result = await this.dialogService.confirm({ type: 'info', title: isRemote - ? localize('Sync accept remote', "Sync: {0}", acceptRemoteLabel) - : localize('Sync accept local', "Sync: {0}", acceptLocalLabel), + ? localize('Sync accept remote', "Preferences Sync: {0}", acceptRemoteLabel) + : localize('Sync accept local', "Preferences 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()), diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts index 6886850deb..db815c5fe6 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts @@ -36,7 +36,7 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution { return Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( { id: 'workbench.view.sync', - name: localize('sync', "Sync"), + name: localize('sync preferences', "Preferences Sync"), ctorDescriptor: new SyncDescriptor( ViewPaneContainer, ['workbench.view.sync', `workbench.view.sync.state`, { mergeViewWithContainerWhenSingleView: true }] @@ -80,7 +80,7 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution { 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` }, + category: { value: localize('sync preferences', "Preferences Sync"), original: `Preferences Sync` }, menu: { id: MenuId.CommandPalette, when: CONTEXT_SYNC_ENABLEMENT diff --git a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts index 4f9fa64c1d..b849e44f77 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts @@ -33,7 +33,8 @@ registerAction2(class OpenSyncBackupsFolder extends Action2 { constructor() { super({ id: 'workbench.userData.actions.openSyncBackupsFolder', - title: localize('Open Backup folder', "Sync: Open Local Backups Folder"), + title: { value: localize('Open Backup folder', "Open Local Backups Folder"), original: 'Open Local Backups Folder' }, + category: { value: localize('sync preferences', "Preferences Sync"), original: `Preferences Sync` }, menu: { id: MenuId.CommandPalette, when: CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index bf588d5eb3..5401055d2e 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -233,11 +233,10 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme const webviewAndContents = this._register(new WebviewTagHandle(this.element!)); const session = this._register(new WebviewSession(webviewAndContents)); - this._protocolProvider = new WebviewProtocolProvider - (webviewAndContents, - () => this.extension ? this.extension.location : undefined, - () => (this.content.options.localResourceRoots || []), - fileService); + this._protocolProvider = new WebviewProtocolProvider(webviewAndContents, + () => this.extension?.location, + () => (this.content.options.localResourceRoots || []), + fileService); this._register(this._protocolProvider); this._register(new WebviewPortMappingProvider( diff --git a/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts b/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts index d1967481f2..c6158dbb6f 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; export default () => `
-
+

${escape(localize('welcomePage.vscode', "Visual Studio Code"))}

${escape(localize({ key: 'welcomePage.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved"))}

diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 3775e5d9a0..c83f07f128 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -88,7 +88,7 @@ export class AuthenticationService extends Disposable implements IAuthentication this._onDidChangeSessions.fire({ providerId: id, event: event }); const provider = this._authenticationProviders.get(id); if (provider) { - provider.updateSessionItems(); + provider.updateSessionItems(event); } } diff --git a/src/vs/workbench/services/backup/common/backupFileService.ts b/src/vs/workbench/services/backup/common/backupFileService.ts index b5e7805234..20ea0daa15 100644 --- a/src/vs/workbench/services/backup/common/backupFileService.ts +++ b/src/vs/workbench/services/backup/common/backupFileService.ts @@ -20,6 +20,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { VSBuffer } from 'vs/base/common/buffer'; import { TextSnapshotReadable, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles'; import { Disposable } from 'vs/base/common/lifecycle'; +import { ILogService } from 'vs/platform/log/common/log'; export interface IBackupFilesModel { resolve(backupRoot: URI): Promise; @@ -114,7 +115,8 @@ export class BackupFileService implements IBackupFileService { constructor( @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, - @IFileService protected fileService: IFileService + @IFileService protected fileService: IFileService, + @ILogService private readonly logService: ILogService ) { this.impl = this.initialize(); } @@ -128,7 +130,7 @@ export class BackupFileService implements IBackupFileService { private initialize(): BackupFileServiceImpl | InMemoryBackupFileService { const backupWorkspaceResource = this.environmentService.configuration.backupWorkspaceResource; if (backupWorkspaceResource) { - return new BackupFileServiceImpl(backupWorkspaceResource, this.hashPath, this.fileService); + return new BackupFileServiceImpl(backupWorkspaceResource, this.hashPath, this.fileService, this.logService); } return new InMemoryBackupFileService(this.hashPath); @@ -194,7 +196,8 @@ class BackupFileServiceImpl extends Disposable implements IBackupFileService { constructor( backupWorkspaceResource: URI, private readonly hashPath: (resource: URI) => string, - @IFileService private readonly fileService: IFileService + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService ) { super(); @@ -372,7 +375,9 @@ class BackupFileServiceImpl extends Disposable implements IBackupFileService { // the meta-end marker ('\n') and as such the backup can only be invalid. We bail out // here if that is the case. if (!metaEndFound) { - throw new Error(`Backup: Could not find meta end marker in ${backupResource}. The file is probably corrupt.`); + this.logService.error(`Backup: Could not find meta end marker in ${backupResource}. The file is probably corrupt.`); + + return undefined; } return { value: factory, meta }; 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 5372ba47bc..3213af026b 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 @@ -62,12 +62,13 @@ export class NodeTestBackupFileService extends BackupFileService { constructor(workspaceBackupPath: string) { const environmentService = new TestBackupEnvironmentService(workspaceBackupPath); - const fileService = new FileService(new NullLogService()); - const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + const logService = new NullLogService(); + const fileService = new FileService(logService); + const diskFileSystemProvider = new DiskFileSystemProvider(logService); fileService.registerProvider(Schemas.file, diskFileSystemProvider); fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService)); - super(environmentService, fileService); + super(environmentService, fileService, logService); this.fileService = fileService; this.backupResourceJoiners = []; @@ -99,6 +100,14 @@ export class NodeTestBackupFileService extends BackupFileService { this.discardBackupJoiners.pop()!(); } } + + async getBackupContents(resource: URI): Promise { + const backupResource = this.toBackupResource(resource); + + const fileContents = await this.fileService.readFile(backupResource); + + return fileContents.value.toString(); + } } suite('BackupFileService', () => { @@ -473,7 +482,7 @@ suite('BackupFileService', () => { await testResolveBackup(fooBarFile, contents, meta, null); }); - test('should throw an error when restoring invalid backup', async () => { + test('should ignore invalid backups', async () => { const contents = 'test\nand more stuff'; await service.backup(fooBarFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false), 1); @@ -485,14 +494,14 @@ suite('BackupFileService', () => { await service.fileService.writeFile(service.toBackupResource(fooBarFile), VSBuffer.fromString('')); - let err: Error; + let err: Error | undefined = undefined; try { await service.resolve(fooBarFile); } catch (error) { err = error; } - assert.ok(err!); + assert.ok(!err); }); async function testResolveBackup(resource: URI, contents: string, meta?: IBackupTestMetaData, expectedMeta?: IBackupTestMetaData | null) { diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index 3b100d796b..d1b73158e3 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -11,6 +11,7 @@ import { Event } from 'vs/base/common/event'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IAction, Action } from 'vs/base/common/actions'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export class NotificationService extends Disposable implements INotificationService { @@ -19,7 +20,10 @@ export class NotificationService extends Disposable implements INotificationServ private _model: INotificationsModel = this._register(new NotificationsModel()); get model(): INotificationsModel { return this._model; } - constructor(@IStorageService private readonly storageService: IStorageService) { + constructor( + @IStorageService private readonly storageService: IStorageService, + @IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService + ) { super(); } @@ -64,10 +68,15 @@ export class NotificationService extends Disposable implements INotificationServ let handle: INotificationHandle; if (notification.neverShowAgain) { const scope = notification.neverShowAgain.scope === NeverShowAgainScope.WORKSPACE ? StorageScope.WORKSPACE : StorageScope.GLOBAL; + const id = notification.neverShowAgain.id; + + // opt-in to syncing if global + if (scope === StorageScope.GLOBAL) { + this.storageKeysSyncRegistryService.registerStorageKey({ key: id, version: 1 }); + } // If the user already picked to not show the notification // again, we return with a no-op notification here - const id = notification.neverShowAgain.id; if (this.storageService.getBoolean(id, scope)) { return new NoOpNotification(); } @@ -115,10 +124,15 @@ export class NotificationService extends Disposable implements INotificationServ // Handle neverShowAgain option accordingly if (options?.neverShowAgain) { const scope = options.neverShowAgain.scope === NeverShowAgainScope.WORKSPACE ? StorageScope.WORKSPACE : StorageScope.GLOBAL; + const id = options.neverShowAgain.id; + + // opt-in to syncing if global + if (scope === StorageScope.GLOBAL) { + this.storageKeysSyncRegistryService.registerStorageKey({ key: id, version: 1 }); + } // If the user already picked to not show the notification // again, we return with a no-op notification here - const id = options.neverShowAgain.id; if (this.storageService.getBoolean(id, scope)) { return new NoOpNotification(); } diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index e59e663687..5e7dbfbf72 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -68,7 +68,14 @@ export class ProgressService extends Disposable implements IProgressService { case ProgressLocation.Notification: return this.withNotificationProgress({ ...options, location }, task, onDidCancel); case ProgressLocation.Window: - return this.withWindowProgress({ ...options, location }, task); + if ((options as IProgressWindowOptions).command) { + // Window progress with command get's shown in the status bar + return this.withWindowProgress({ ...options, location }, task); + } + // Window progress without command can be shown as silent notification + // which will first appear in the status bar and can then be brought to + // the front when clicking. + return this.withNotificationProgress({ ...options, silent: true, location: ProgressLocation.Notification }, task, onDidCancel); case ProgressLocation.Explorer: return this.withViewletProgress('workbench.view.explorer', task, { ...options, location }); case ProgressLocation.Scm: diff --git a/src/vs/workbench/services/workspaces/browser/workspacesService.ts b/src/vs/workbench/services/workspaces/browser/workspacesService.ts index 7605056175..fe9dbe8ac7 100644 --- a/src/vs/workbench/services/workspaces/browser/workspacesService.ts +++ b/src/vs/workbench/services/workspaces/browser/workspacesService.ts @@ -6,7 +6,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkspacesService, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IEnterWorkspaceResult, IRecentlyOpened, restoreRecentlyOpened, IRecent, isRecentFile, isRecentFolder, toStoreData, IStoredWorkspaceFolder, getStoredWorkspaceFolder, WORKSPACE_EXTENSION, IStoredWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ILogService } from 'vs/platform/log/common/log'; @@ -16,6 +16,7 @@ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platfo import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { joinPath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; export class BrowserWorkspacesService extends Disposable implements IWorkspacesService { @@ -23,18 +24,22 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS _serviceBrand: undefined; - private readonly _onRecentlyOpenedChange: Emitter = this._register(new Emitter()); - readonly onRecentlyOpenedChange: Event = this._onRecentlyOpenedChange.event; + private readonly _onRecentlyOpenedChange = this._register(new Emitter()); + readonly onRecentlyOpenedChange = this._onRecentlyOpenedChange.event; constructor( @IStorageService private readonly storageService: IStorageService, @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, @ILogService private readonly logService: ILogService, @IFileService private readonly fileService: IFileService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); + // opt-in to syncing + storageKeysSyncRegistryService.registerStorageKey({ key: BrowserWorkspacesService.RECENTLY_OPENED_KEY, version: 1 }); + // Opening a workspace should push it as most // recently used to the workspaces history this.addWorkspaceToRecentlyOpened(); diff --git a/src/vs/workbench/test/browser/quickAccess.test.ts b/src/vs/workbench/test/browser/quickAccess.test.ts index ee398f136d..721038121e 100644 --- a/src/vs/workbench/test/browser/quickAccess.test.ts +++ b/src/vs/workbench/test/browser/quickAccess.test.ts @@ -12,7 +12,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti 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'; -import { PickerQuickAccessProvider, FastAndSlowPicksType } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { PickerQuickAccessProvider, FastAndSlowPicks } from 'vs/platform/quickinput/browser/pickerQuickAccess'; suite('QuickAccess', () => { @@ -239,7 +239,7 @@ suite('QuickAccess', () => { super('bothFastAndSlow'); } - protected getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): FastAndSlowPicksType { + protected getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): FastAndSlowPicks { fastAndSlowProviderCalled = true; return { diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 7c7ac022fd..b2ca9610d4 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -64,27 +64,6 @@ interface IShowCandidate { (host: string, port: number, detail: string): Thenable; } -interface IApplicationLink { - - /** - * A link that is opened in the OS. If you want to open VSCode it must - * follow our expected structure of links: - * - * ://// - * - * For example: - * - * vscode://vscode-remote/vsonline+2005711d/home/vsonline/workspace for - * a remote folder in VSO or vscode://file/home/workspace for a local folder. - */ - uri: URI; - - /** - * A label for the application link to display. - */ - label: string; -} - interface ICommand { /** @@ -186,18 +165,6 @@ interface IWorkbenchConstructionOptions { */ readonly resolveCommonTelemetryProperties?: ICommontTelemetryPropertiesResolver; - /** - * Provide entries for the "Open in Desktop" feature. - * - * Depending on the returned elements the behaviour is: - * - no elements: there will not be a "Open in Desktop" affordance - * - 1 element: there will be a "Open in Desktop" affordance that opens on click - * and it will use the label provided by the link - * - N elements: there will be a "Open in Desktop" affordance that opens - * a picker on click to select which application to open. - */ - readonly applicationLinks?: readonly IApplicationLink[]; - /** * A set of optional commands that should be registered with the commands * registry. @@ -311,9 +278,6 @@ export { // External Uris IExternalUriResolver, - // Protocol Links - IApplicationLink, - // Commands ICommand }; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 568a57d9b2..f4c32d4af6 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -132,7 +132,4 @@ import 'vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.con // Issues import 'vs/workbench/contrib/issue/browser/issue.contribution'; -// Open In Desktop -import 'vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution'; - //#endregion diff --git a/yarn.lock b/yarn.lock index 7585b1b15b..39d9ffbacd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7121,10 +7121,10 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -playwright-core@=0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-0.11.0.tgz#a2372833f6ec4e7886c4409e3da93df997aee61b" - integrity sha512-9UPP/Max65PMiZJz9DNWB3ZRWtTlYlceLFnm6JO8aU7m6Vw3gwCvuSGoC5W69H67q98jH0VPSPp546+EnkiR2g== +playwright-core@=0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-0.12.1.tgz#07581d1cbe84bb1e438ecdb188de3ed6d5e81ee0" + integrity sha512-NZ8Qe/kqsgAmFBxWZnUeE+MoZ04UzNI0DHOKA+I1p/5rbpaWhe1Vx5zVNa05A1iEvOtnKV1PdIEe4IPumG2y2w== dependencies: debug "^4.1.0" extract-zip "^1.6.6" @@ -7132,17 +7132,16 @@ playwright-core@=0.11.0: jpeg-js "^0.3.6" pngjs "^3.4.0" progress "^2.0.3" - proxy-from-env "^1.0.0" + proxy-from-env "^1.1.0" rimraf "^3.0.2" - uuid "^3.4.0" ws "^6.1.0" -playwright@0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-0.11.0.tgz#2abec99ea278b220bcd3902d7520ec22abc2d97e" - integrity sha512-cTJZ06OhwseMC9+D6KX1NmZXyEoaJl0o6GLkDhwmou3IFTrUFVOw7KYMBpcbJz0Rhb/de5ZPFlDTffLfEy/9lg== +playwright@0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-0.12.1.tgz#59445ede1aecec120091db7bc95b4e626451e0b0" + integrity sha512-icF4+I8y7A5HjhbTsa4Eqtl2fuGe3ECvW0Wrn6aRM5eL5/AqUIgIf2U/0e1S1bEsDfz1JVvClGl5Gqw4aI5H4w== dependencies: - playwright-core "=0.11.0" + playwright-core "=0.12.1" plist@^3.0.1: version "3.0.1" @@ -7575,10 +7574,10 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= -proxy-from-env@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" - integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== prr@~1.0.1: version "1.0.1" @@ -9691,11 +9690,6 @@ uuid@^3.0.1, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== -uuid@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - v8-compile-cache@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"