diff --git a/extensions/git/package.json b/extensions/git/package.json index c1cd8b5135..9d6d4f43f3 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -447,6 +447,22 @@ "command": "git.stashDrop", "title": "%command.stashDrop%", "category": "Git" + }, + { + "command": "git.timeline.openDiff", + "title": "%command.timelineOpenDiff%", + "icon": "$(compare-changes)", + "category": "Git" + }, + { + "command": "git.timeline.copyCommitId", + "title": "%command.timelineCopyCommitId%", + "category": "Git" + }, + { + "command": "git.timeline.copyCommitMessage", + "title": "%command.timelineCopyCommitMessage%", + "category": "Git" } ], "menus": { @@ -718,6 +734,18 @@ { "command": "git.stashDrop", "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, + { + "command": "git.timeline.openDiff", + "when": "false" + }, + { + "command": "git.timeline.copyCommitId", + "when": "false" + }, + { + "command": "git.timeline.copyCommitMessage", + "when": "false" } ], "scm/title": [ @@ -1248,6 +1276,28 @@ "command": "git.revertChange", "when": "originalResourceScheme == git" } + ], + "timeline/item/context": [ + { + "command": "git.timeline.openDiff", + "group": "inline", + "when": "timelineItem =~ /git:file\\b/" + }, + { + "command": "git.timeline.openDiff", + "group": "1_timeline", + "when": "timelineItem =~ /git:file\\b/" + }, + { + "command": "git.timeline.copyCommitId", + "group": "2_timeline@1", + "when": "timelineItem =~ /git:file:commit\\b/" + }, + { + "command": "git.timeline.copyCommitMessage", + "group": "2_timeline@2", + "when": "timelineItem =~ /git:file:commit\\b/" + } ] }, "configuration": { @@ -1779,10 +1829,11 @@ "@types/file-type": "^5.2.1", "@types/mocha": "2.2.43", "@types/node": "^12.11.7", + "@types/vscode": "^1.42", "@types/which": "^1.0.28", "mocha": "^3.2.0", "mocha-junit-reporter": "^1.23.3", "mocha-multi-reporters": "^1.1.7", - "vscode": "^1.1.36" + "vscode-test": "^1.3.0" } } diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index f3d7551965..534ec69429 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -70,6 +70,9 @@ "command.stashApply": "Apply Stash...", "command.stashApplyLatest": "Apply Latest Stash", "command.stashDrop": "Drop Stash...", + "command.timelineOpenDiff": "Open Changes", + "command.timelineCopyCommitId": "Copy Commit ID", + "command.timelineCopyCommitMessage": "Copy Commit Message", "config.enabled": "Whether git is enabled.", "config.path": "Path and filename of the git executable, e.g. `C:\\Program Files\\Git\\bin\\git.exe` (Windows).", "config.autoRepositoryDetection": "Configures when repositories should be automatically detected.", @@ -127,7 +130,7 @@ "config.showProgress": "Controls whether git actions should show progress.", "config.rebaseWhenSync": "Force git to use rebase when running the sync command.", "config.confirmEmptyCommits": "Always confirm the creation of empty commits for the 'Git: Commit Empty' command.", - "config.fetchOnPull": "Fetch all branches when pulling or just the current one.", + "config.fetchOnPull": "When enabled, fetch all branches when pulling. Otherwise, fetch just the current one.", "config.pullTags": "Fetch all tags when pulling.", "config.autoStash": "Stash any changes before pulling and restore them after successful pull.", "config.allowForcePush": "Controls whether force push (with or without lease) is enabled.", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 6264b22232..281190c1c6 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -6,7 +6,7 @@ import { lstat, Stats } from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder } from 'vscode'; +import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env } from 'vscode'; import TelemetryReporter from 'vscode-extension-telemetry'; import * as nls from 'vscode-nls'; import { Branch, GitErrorCodes, Ref, RefType, Status, CommitOptions } from './api/git'; @@ -17,6 +17,7 @@ import { applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineC import { fromGitUri, toGitUri, isGitUri } from './uri'; import { grep, isDescendant, pathEquals } from './util'; import { Log, LogLevel } from './log'; +import { GitTimelineItem } from './timelineProvider'; const localize = nls.loadMessageBundle(); @@ -2331,23 +2332,47 @@ export class CommandCenter { return result && result.stash; } - @command('git.openDiff', { repository: false }) - async openDiff(uri: Uri, lhs: string, rhs: string) { + @command('git.timeline.openDiff', { repository: false }) + async timelineOpenDiff(item: TimelineItem, uri: Uri | undefined, _source: string) { + // eslint-disable-next-line eqeqeq + if (uri == null || !GitTimelineItem.is(item)) { + return undefined; + } + const basename = path.basename(uri.fsPath); let title; - if ((lhs === 'HEAD' || lhs === '~') && rhs === '') { + if ((item.previousRef === 'HEAD' || item.previousRef === '~') && item.ref === '') { title = `${basename} (Working Tree)`; } - else if (lhs === 'HEAD' && rhs === '~') { + else if (item.previousRef === 'HEAD' && item.ref === '~') { title = `${basename} (Index)`; } else { - title = `${basename} (${lhs.endsWith('^') ? `${lhs.substr(0, 8)}^` : lhs.substr(0, 8)}) \u27f7 ${basename} (${rhs.endsWith('^') ? `${rhs.substr(0, 8)}^` : rhs.substr(0, 8)})`; + title = `${basename} (${item.shortPreviousRef}) \u27f7 ${basename} (${item.shortRef})`; } - return commands.executeCommand('vscode.diff', toGitUri(uri, lhs), rhs === '' ? uri : toGitUri(uri, rhs), title); + return commands.executeCommand('vscode.diff', toGitUri(uri, item.previousRef), item.ref === '' ? uri : toGitUri(uri, item.ref), title); } + @command('git.timeline.copyCommitId', { repository: false }) + async timelineCopyCommitId(item: TimelineItem, _uri: Uri | undefined, _source: string) { + if (!GitTimelineItem.is(item)) { + return; + } + + env.clipboard.writeText(item.ref); + } + + @command('git.timeline.copyCommitMessage', { repository: false }) + async timelineCopyCommitMessage(item: TimelineItem, _uri: Uri | undefined, _source: string) { + if (!GitTimelineItem.is(item)) { + return; + } + + env.clipboard.writeText(item.message); + } + + private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any { const result = (...args: any[]) => { let result: Promise; diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index 9edb4d683c..aabdbc5215 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -5,19 +5,62 @@ import * as dayjs from 'dayjs'; import * as advancedFormat from 'dayjs/plugin/advancedFormat'; -import * as relativeTime from 'dayjs/plugin/relativeTime'; -import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, TimelineItem, TimelineProvider, Uri, workspace, TimelineChangeEvent } from 'vscode'; +import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, Timeline, TimelineChangeEvent, TimelineCursor, TimelineItem, TimelineProvider, Uri, workspace } from 'vscode'; import { Model } from './model'; import { Repository } from './repository'; import { debounce } from './decorators'; import { Status } from './api/git'; dayjs.extend(advancedFormat); -dayjs.extend(relativeTime); // TODO[ECA]: Localize all the strings // TODO[ECA]: Localize or use a setting for date format +export class GitTimelineItem extends TimelineItem { + static is(item: TimelineItem): item is GitTimelineItem { + return item instanceof GitTimelineItem; + } + + readonly ref: string; + readonly previousRef: string; + readonly message: string; + + constructor( + ref: string, + previousRef: string, + message: string, + timestamp: number, + id: string, + contextValue: string + ) { + const index = message.indexOf('\n'); + const label = index !== -1 ? `${message.substring(0, index)} \u2026` : message; + + super(label, timestamp); + + this.ref = ref; + this.previousRef = previousRef; + this.message = message; + this.id = id; + this.contextValue = contextValue; + } + + get shortRef() { + return this.shortenRef(this.ref); + } + + get shortPreviousRef() { + return this.shortenRef(this.previousRef); + } + + private shortenRef(ref: string): string { + if (ref === '' || ref === '~' || ref === 'HEAD') { + return ref; + } + return ref.endsWith('^') ? `${ref.substr(0, 8)}^` : ref.substr(0, 8); + } +} + export class GitTimelineProvider implements TimelineProvider { private _onDidChange = new EventEmitter(); get onDidChange(): Event { @@ -44,7 +87,7 @@ export class GitTimelineProvider implements TimelineProvider { this._disposable.dispose(); } - async provideTimeline(uri: Uri, _token: CancellationToken): Promise { + async provideTimeline(uri: Uri, _cursor: TimelineCursor, _token: CancellationToken): Promise { // console.log(`GitTimelineProvider.provideTimeline: uri=${uri} state=${this._model.state}`); const repo = this._model.getRepository(uri); @@ -53,7 +96,7 @@ export class GitTimelineProvider implements TimelineProvider { this._repoStatusDate = undefined; this._repo = undefined; - return []; + return { items: [] }; } if (this._repo?.root !== repo.root) { @@ -72,25 +115,17 @@ export class GitTimelineProvider implements TimelineProvider { const commits = await repo.logFile(uri); let dateFormatter: dayjs.Dayjs; - const items = commits.map(c => { - let message = c.message; - - const index = message.indexOf('\n'); - if (index !== -1) { - message = `${message.substring(0, index)} \u2026`; - } - + const items = commits.map(c => { dateFormatter = dayjs(c.authorDate); - const item = new TimelineItem(message, c.authorDate?.getTime() ?? 0); - item.id = c.hash; + const item = new GitTimelineItem(c.hash, `${c.hash}^`, c.message, c.authorDate?.getTime() ?? 0, c.hash, 'git:file:commit'); item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = `${dateFormatter.fromNow()} \u2022 ${c.authorName}`; - item.detail = `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n\n${c.message}`; + 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.command = { - title: 'Open Diff', - command: 'git.openDiff', - arguments: [uri, `${c.hash}^`, c.hash] + title: 'Open Comparison', + command: 'git.timeline.openDiff', + arguments: [uri, this.id, item] }; return item; @@ -123,16 +158,15 @@ export class GitTimelineProvider implements TimelineProvider { break; } - const item = new TimelineItem('Staged Changes', date.getTime()); - item.id = 'index'; + const item = new GitTimelineItem('~', 'HEAD', 'Staged Changes', date.getTime(), 'index', 'git:file:index'); // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = `${dateFormatter.fromNow()} \u2022 You`; - item.detail = `You \u2014 Index\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n${status}`; + item.description = 'You'; + item.detail = `You \u2014 Index\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n${status}`; item.command = { title: 'Open Comparison', - command: 'git.openDiff', - arguments: [uri, 'HEAD', '~'] + command: 'git.timeline.openDiff', + arguments: [uri, this.id, item] }; items.push(item); @@ -166,22 +200,21 @@ export class GitTimelineProvider implements TimelineProvider { break; } - const item = new TimelineItem('Uncommited Changes', date.getTime()); - item.id = 'working'; + const item = new GitTimelineItem('', index ? '~' : 'HEAD', 'Uncommited Changes', date.getTime(), 'working', 'git:file:working'); // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? item.iconPath = new (ThemeIcon as any)('git-commit'); - item.description = `${dateFormatter.fromNow()} \u2022 You`; - item.detail = `You \u2014 Working Tree\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n${status}`; + item.description = 'You'; + item.detail = `You \u2014 Working Tree\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n${status}`; item.command = { title: 'Open Comparison', - command: 'git.openDiff', - arguments: [uri, index ? '~' : 'HEAD', ''] + command: 'git.timeline.openDiff', + arguments: [uri, this.id, item] }; items.push(item); } - return items; + return { items: items }; } private onRepositoriesChanged(_repo: Repository) { @@ -208,6 +241,6 @@ export class GitTimelineProvider implements TimelineProvider { @debounce(500) private fireChanged() { - this._onDidChange.fire(); + this._onDidChange.fire({}); } } diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index af5b10127d..0ae8237610 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -31,6 +31,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== +"@types/vscode@^1.42": + version "1.42.0" + resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.42.0.tgz#0ad891a9487e91e34be7c56985058a179031eb76" + integrity sha512-ds6TceMsh77Fs0Mq0Vap6Y72JbGWB8Bay4DrnJlf5d9ui2RSe1wis13oQm+XhguOeH1HUfLGzaDAoupTUtgabw== + "@types/which@^1.0.28": version "1.0.28" resolved "https://registry.yarnpkg.com/@types/which/-/which-1.0.28.tgz#016e387629b8817bed653fe32eab5d11279c8df6" @@ -43,16 +48,6 @@ agent-base@4, agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" -ajv@^6.5.5: - version "6.11.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9" - integrity sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - ansi-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" @@ -67,45 +62,11 @@ applicationinsights@1.0.8: diagnostic-channel-publishers "0.2.1" zone.js "0.7.6" -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" - integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== - balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" @@ -119,43 +80,16 @@ browser-stdout@1.3.0: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" integrity sha1-81HTKWnTL6XXpVZxVCY9korjvR8= -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - charenc@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= -combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@2.15.1: - version "2.15.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" - integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== - commander@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" @@ -168,23 +102,11 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -core-util-is@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - 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" @@ -218,11 +140,6 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - diagnostic-channel-publishers@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" @@ -240,19 +157,6 @@ diff@3.2.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" integrity sha1-yc45Okt8vQsFinJck98pkCeGj/k= -diff@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - es6-promise@^4.0.3: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" @@ -270,62 +174,16 @@ escape-string-regexp@1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-deep-equal@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" - integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - file-type@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/file-type/-/file-type-7.2.0.tgz#113cfed52e1d6959ab80248906e2f25a8cdccb74" integrity sha1-ETz+1S4daVmrgCSJBuLyWozcy3Q= -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - glob@7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" @@ -338,19 +196,7 @@ glob@7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.1.2: +glob@^7.1.3: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -367,39 +213,16 @@ glob@^7.1.2: resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" integrity sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8= -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.0: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== - dependencies: - ajv "^6.5.5" - har-schema "^2.0.0" - has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" @@ -413,16 +236,7 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -https-proxy-agent@^2.2.1: +https-proxy-agent@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== @@ -455,61 +269,21 @@ is-buffer@~1.1.1: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - jschardet@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.1.1.tgz#af6f8fd0b3b0f5d46a8fd9614a4fce490575c184" integrity sha512-pA5qG9Zwm8CBpGlK/lo2GE9jPxwqRgMV7Lzc/1iaPccw6v4Rhj8Zg2BTyrdmHmxlJojnbLupLeRnaPLsq03x6Q== -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - json3@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE= -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - lodash._baseassign@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" @@ -580,19 +354,7 @@ md5@^2.1.0: crypt "~0.0.1" is-buffer "~1.1.1" -mime-db@1.43.0: - version "1.43.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" - integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== - -mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.26" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" - integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== - dependencies: - mime-db "1.43.0" - -minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4: +minimatch@^3.0.2, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -648,23 +410,6 @@ mocha@^3.2.0: mkdirp "0.5.1" supports-color "3.1.2" -mocha@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" - integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== - dependencies: - browser-stdout "1.3.1" - commander "2.15.1" - debug "3.1.0" - diff "3.5.0" - escape-string-regexp "1.0.5" - glob "7.1.2" - growl "1.10.5" - he "1.1.1" - minimatch "3.0.4" - mkdirp "0.5.1" - supports-color "5.4.0" - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -675,11 +420,6 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -692,73 +432,14 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - -psl@^1.1.24: - version "1.7.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" - integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== - -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== - -querystringify@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" - integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== - -request@^2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== +rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" + glob "^7.1.3" -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= - -safe-buffer@^5.0.1, safe-buffer@^5.1.2: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== - -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -768,39 +449,6 @@ semver@^5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA== -semver@^5.4.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -source-map-support@^0.5.0: - version "0.5.16" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" - integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -815,62 +463,6 @@ supports-color@3.1.2: dependencies: has-flag "^1.0.0" -supports-color@5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" - integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w== - dependencies: - has-flag "^3.0.0" - -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== - dependencies: - psl "^1.1.24" - punycode "^1.4.1" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== - dependencies: - punycode "^2.1.0" - -url-parse@^1.4.4: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - vscode-extension-telemetry@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.1.tgz#91387e06b33400c57abd48979b0e790415ae110b" @@ -883,32 +475,20 @@ vscode-nls@^4.0.0: resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== -vscode-test@^0.4.1: - version "0.4.3" - resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-0.4.3.tgz#461ebf25fc4bc93d77d982aed556658a2e2b90b8" - integrity sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w== +vscode-test@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-1.3.0.tgz#3310ab385d9b887b4c82e8f52be1030e7cf9493d" + integrity sha512-LddukcBiSU2FVTDr3c1D8lwkiOvwlJdDL2hqVbn6gIz+rpTqUCkMZSKYm94Y1v0WXlHSDQBsXyY+tchWQgGVsw== dependencies: http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.1" + https-proxy-agent "^2.2.4" + rimraf "^2.6.3" vscode-uri@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.0.0.tgz#2df704222f72b8a71ff266ba0830ed6c51ac1542" integrity sha512-lWXWofDSYD8r/TIyu64MdwB4FaSirQ608PP/TzUyslyOeHGwQ0eTHUZeJrK1ILOmwUHaJtV693m2JoUYroUDpw== -vscode@^1.1.36: - version "1.1.36" - resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.36.tgz#5e1a0d1bf4977d0c7bc5159a9a13d5b104d4b1b6" - integrity sha512-cGFh9jmGLcTapCpPCKvn8aG/j9zVQ+0x5hzYJq5h5YyUXVGa1iamOaB2M2PZXoumQPES4qeAP1FwkI0b6tL4bQ== - dependencies: - glob "^7.1.2" - mocha "^5.2.0" - request "^2.88.0" - semver "^5.4.1" - source-map-support "^0.5.0" - url-parse "^1.4.4" - vscode-test "^0.4.1" - which@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" diff --git a/package.json b/package.json index 1156d0ec31..bf99892887 100644 --- a/package.json +++ b/package.json @@ -165,7 +165,7 @@ "opn": "^6.0.0", "optimist": "0.3.5", "p-all": "^1.0.0", - "playwright": "^0.10.0", + "playwright": "0.11.0", "pump": "^1.0.1", "queue": "3.0.6", "rcedit": "^1.1.0", diff --git a/src/main.js b/src/main.js index d7dafd836c..ddf0c4dae3 100644 --- a/src/main.js +++ b/src/main.js @@ -140,6 +140,9 @@ function configureCommandlineSwitchesSync(cliArgs) { // provided by Electron 'disable-color-correct-rendering' ]; + if (process.platform === 'linux') { + SUPPORTED_ELECTRON_SWITCHES.push('force-renderer-accessibility'); + } // Read argv config const argvConfig = readArgvConfigSync(); diff --git a/src/vs/base/common/date.ts b/src/vs/base/common/date.ts index 6cdff36446..e698ffd9a9 100644 --- a/src/vs/base/common/date.ts +++ b/src/vs/base/common/date.ts @@ -5,6 +5,53 @@ import { pad } from './strings'; +const minute = 60; +const hour = minute * 60; +const day = hour * 24; +const week = day * 7; +const month = day * 30; +const year = day * 365; + +// TODO[ECA]: Localize strings +export function fromNow(date: number | Date) { + if (typeof date !== 'number') { + date = date.getTime(); + } + + const seconds = Math.round((new Date().getTime() - date) / 1000); + if (seconds < 30) { + return 'now'; + } + + let value: number; + let unit: string; + if (seconds < minute) { + value = seconds; + unit = 'sec'; + } else if (seconds < hour) { + value = Math.floor(seconds / minute); + unit = 'min'; + } else if (seconds < day) { + value = Math.floor(seconds / hour); + unit = 'hr'; + } else if (seconds < week) { + value = Math.floor(seconds / day); + unit = 'day'; + } else if (seconds < month) { + value = Math.floor(seconds / week); + unit = 'wk'; + } else if (seconds < year) { + value = Math.floor(seconds / month); + unit = 'mo'; + } else { + value = Math.floor(seconds / year); + unit = 'yr'; + } + + return `${value} ${unit}${value === 1 ? '' : 's'}`; + +} + export function toLocalISOString(date: Date): string { return date.getFullYear() + '-' + pad(date.getMonth() + 1, 2) + diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 47773b730c..badcdd1e9a 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, Event, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IApplicationLinkProvider, IApplicationLink } from 'vs/workbench/workbench.web.api'; +import { IWorkbenchConstructionOptions, create, URI, Event, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IApplicationLink } 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'; @@ -279,39 +279,6 @@ class WorkspaceProvider implements IWorkspaceProvider { } } -class ApplicationLinkProvider { - - private links: IApplicationLink[] | undefined = undefined; - - constructor(workspace: IWorkspace) { - this.computeLink(workspace); - } - - private computeLink(workspace: IWorkspace): void { - if (!workspace) { - return; // not for empty workspaces - } - - const workspaceUri = isWorkspaceToOpen(workspace) ? workspace.workspaceUri : isFolderToOpen(workspace) ? workspace.folderUri : undefined; - if (workspaceUri) { - this.links = [{ - 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") - }]; - } - } - - get provider(): IApplicationLinkProvider { - return () => this.links; - } -} - (function () { // Find config by checking for DOM @@ -375,12 +342,30 @@ class ApplicationLinkProvider { } } + // 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(), - applicationLinkProvider: new ApplicationLinkProvider(workspace).provider + applicationLinks: applicationLinks }); })(); diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 282f0641f2..aa0a70e307 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -265,6 +265,34 @@ export interface HoverProvider { provideHover(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult; } +/** + * An evaluatable expression represents additional information for an expression in a document. Evaluatable expression are + * evaluated by a debugger or runtime and their result is rendered in a tooltip-like widget. + */ +export interface EvaluatableExpression { + /** + * The range to which this expression applies. + */ + range: IRange; + /* + * This expression overrides the expression extracted from the range. + */ + expression?: string; +} + +/** + * The hover provider interface defines the contract between extensions and + * the [hover](https://code.visualstudio.com/docs/editor/intellisense)-feature. + */ +export interface EvaluatableExpressionProvider { + /** + * Provide a hover for the given position and document. Multiple hovers at the same + * position will be merged by the editor. A hover can have a range which defaults + * to the word range at the position when omitted. + */ + provideEvaluatableExpression(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult; +} + export const enum CompletionItemKind { Method, Function, @@ -1595,6 +1623,11 @@ export const SignatureHelpProviderRegistry = new LanguageFeatureRegistry(); +/** + * @internal + */ +export const EvaluatableExpressionProviderRegistry = new LanguageFeatureRegistry(); + /** * @internal */ diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index abbad9ba71..f60cbf4137 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5242,6 +5242,31 @@ declare namespace monaco.languages { provideHover(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult; } + /** + * An evaluatable expression represents additional information for an expression in a document. Evaluatable expression are + * evaluated by a debugger or runtime and their result is rendered in a tooltip-like widget. + */ + export interface EvaluatableExpression { + /** + * The range to which this expression applies. + */ + range: IRange; + expression?: string; + } + + /** + * The hover provider interface defines the contract between extensions and + * the [hover](https://code.visualstudio.com/docs/editor/intellisense)-feature. + */ + export interface EvaluatableExpressionProvider { + /** + * Provide a hover for the given position and document. Multiple hovers at the same + * position will be merged by the editor. A hover can have a range which defaults + * to the word range at the position when omitted. + */ + provideEvaluatableExpression(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult; + } + export enum CompletionItemKind { Method = 0, Function = 1, diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 24b5832458..e340bd814e 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -119,7 +119,9 @@ export class MenuId { static readonly DataExplorerContext = new MenuId('DataExplorerContext'); // {{SQL CARBON EDIT}} static readonly DataExplorerAction = new MenuId('DataExplorerAction'); // {{SQL CARBON EDIT}} static readonly ExplorerWidgetContext = new MenuId('ExplorerWidgetContext'); // {{SQL CARBON EDIT}} - + static readonly TimelineItemContext = new MenuId('TimelineItemContext'); + static readonly TimelineTitle = new MenuId('TimelineTitle'); + static readonly TimelineTitleContext = new MenuId('TimelineTitleContext'); readonly id: number; readonly _debugName: string; diff --git a/src/vs/platform/instantiation/common/instantiation.ts b/src/vs/platform/instantiation/common/instantiation.ts index 047e5c4092..987eb81f69 100644 --- a/src/vs/platform/instantiation/common/instantiation.ts +++ b/src/vs/platform/instantiation/common/instantiation.ts @@ -29,7 +29,7 @@ export interface IConstructorSignature0 { } export interface IConstructorSignature1 { - new(first: A1, ...services: BrandedService[]): T; + new (first: A1, ...services: Services): T; } export interface IConstructorSignature2 { diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index 5a49473ce1..96a95614b1 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -98,7 +98,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto async triggerAutoSync(): Promise { if (this.enabled) { return this.syncDelayer.trigger(() => { - this.logService.info('Auto Sync: Triggerred.'); + this.logService.info('Auto Sync: Triggered.'); return this.sync(false, true); }, this.successiveFailures ? 1000 * 1 * Math.min(this.successiveFailures, 60) /* Delay by number of seconds as number of failures up to 1 minute */ diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index edee6914b4..4cfba2c344 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -876,7 +876,65 @@ declare module 'vscode' { //#endregion - //#region Debug: + //#region locate evaluatable expressions for debug hover: https://github.com/microsoft/vscode/issues/89084 + + /** + * An EvaluatableExpression represents an expression in a document that can be evaluated by an active debugger or runtime. + * The result of this evaluation is shown in a tooltip-like widget. + * If only a range is specified, the expression will be extracted from the underlying document. + * An optional expression can be used to override the extracted expression. + * In this case the range is still used to highlight the range in the document. + */ + export class EvaluatableExpression { + /* + * The range is used to extract the evaluatable expression from the underlying document and to highlight it. + */ + readonly range: Range; + /* + * If specified the expression overrides the extracted expression. + */ + readonly expression?: string; + + /** + * Creates a new evaluatable expression object. + * + * @param range The range in the underlying document from which the evaluatable expression is extracted. + * @param expression If specified overrides the extracted expression. + */ + constructor(range: Range, expression?: string); + } + + /** + * The evaluatable expression provider interface defines the contract between extensions and + * the debug hover. + */ + export interface EvaluatableExpressionProvider { + + /** + * Provide an evaluatable expression for the given document and position. + * The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression. + * + * @param document The document in which the command was invoked. + * @param position The position where the command was invoked. + * @param token A cancellation token. + * @return An EvaluatableExpression or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideEvaluatableExpression(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + } + + export namespace languages { + /** + * Register a provider that locates evaluatable expressions in text documents. + * + * If multiple providers are registered for a language an arbitrary provider will be used. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider An evaluatable expression provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable; + } // deprecated @@ -1553,6 +1611,40 @@ declare module 'vscode' { uri?: Uri; } + export interface TimelineCursor { + /** + * A provider-defined cursor specifing the range of timeline items to be returned. Must be serializable. + */ + cursor?: any; + + /** + * A flag to specify whether the timeline items requested are before or after (default) the provided cursor. + */ + before?: boolean; + + /** + * The maximum number of timeline items that should be returned. + */ + limit?: number; + } + + export interface Timeline { + /** + * A provider-defined cursor specifing the range of timeline items returned. Must be serializable. + */ + cursor?: any; + + /** + * A flag which indicates whether there are any more items that weren't returned. + */ + more?: boolean; + + /** + * An array of [timeline items](#TimelineItem). + */ + items: TimelineItem[]; + } + export interface TimelineProvider { /** * An optional event to signal that the timeline for a source has changed. @@ -1575,10 +1667,11 @@ declare module 'vscode' { * * @param uri The [uri](#Uri) of the file to provide the timeline for. * @param token A cancellation token. - * @return An array of timeline items or a thenable that resolves to such. The lack of a result + * @param cursor TBD + * @return The [timeline result](#TimelineResult) or a thenable that resolves to such. The lack of a result * can be signaled by returning `undefined`, `null`, or an empty array. */ - provideTimeline(uri: Uri, token: CancellationToken): ProviderResult; + provideTimeline(uri: Uri, cursor: TimelineCursor, token: CancellationToken): ProviderResult; } export namespace workspace { diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 31d5117fad..c7eb18ae12 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -213,6 +213,16 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } + // --- debug hover + + $registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void { + this._registrations.set(handle, modes.EvaluatableExpressionProviderRegistry.register(selector, { + provideEvaluatableExpression: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => { + return this._proxy.$provideEvaluatableExpression(handle, model.uri, position, token); + } + })); + } + // --- occurrences $registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void { diff --git a/src/vs/workbench/api/browser/mainThreadTimeline.ts b/src/vs/workbench/api/browser/mainThreadTimeline.ts index 4ecbe4cdbc..3bd7e23fb9 100644 --- a/src/vs/workbench/api/browser/mainThreadTimeline.ts +++ b/src/vs/workbench/api/browser/mainThreadTimeline.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { MainContext, MainThreadTimelineShape, IExtHostContext, ExtHostTimelineShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ITimelineService, TimelineItem, TimelineProviderDescriptor, TimelineChangeEvent } from 'vs/workbench/contrib/timeline/common/timeline'; +import { TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor, ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline'; @extHostNamedCustomer(MainContext.MainThreadTimeline) export class MainThreadTimeline implements MainThreadTimelineShape { @@ -24,10 +24,6 @@ export class MainThreadTimeline implements MainThreadTimelineShape { this._proxy = context.getProxy(ExtHostContext.ExtHostTimeline); } - $getTimeline(uri: URI, token: CancellationToken): Promise { - return this._timelineService.getTimeline(uri, token); - } - $registerTimelineProvider(provider: TimelineProviderDescriptor): void { this.logService.trace(`MainThreadTimeline#registerTimelineProvider: id=${provider.id}`); @@ -43,8 +39,8 @@ export class MainThreadTimeline implements MainThreadTimelineShape { this._timelineService.registerTimelineProvider({ ...provider, onDidChange: onDidChange.event, - provideTimeline(uri: URI, token: CancellationToken) { - return proxy.$getTimeline(provider.id, uri, token); + provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }) { + return proxy.$getTimeline(provider.id, uri, cursor, token, options); }, dispose() { emitters.delete(provider.id); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index f8e2c8dbfe..8fc656d764 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -134,7 +134,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHostLabelService, new ExtHostLabelService(rpcProtocol)); const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol)); const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol)); - const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol)); + const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands)); // Check that no named customers are missing // {{SQL CARBON EDIT}} filter out the services we don't expose @@ -348,6 +348,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerHoverProvider(selector: vscode.DocumentSelector, provider: vscode.HoverProvider): vscode.Disposable { return extHostLanguageFeatures.registerHoverProvider(extension, checkSelector(selector), provider, extension.identifier); }, + registerEvaluatableExpressionProvider(selector: vscode.DocumentSelector, provider: vscode.EvaluatableExpressionProvider): vscode.Disposable { + return extHostLanguageFeatures.registerEvaluatableExpressionProvider(extension, checkSelector(selector), provider, extension.identifier); + }, registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable { return extHostLanguageFeatures.registerDocumentHighlightProvider(extension, checkSelector(selector), provider); }, @@ -928,6 +931,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I DocumentLink: extHostTypes.DocumentLink, DocumentSymbol: extHostTypes.DocumentSymbol, EndOfLine: extHostTypes.EndOfLine, + EvaluatableExpression: extHostTypes.EvaluatableExpression, EventEmitter: Emitter, ExtensionKind: extHostTypes.ExtensionKind, CustomExecution: extHostTypes.CustomExecution, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 1c9c9e640d..2617ff482e 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -49,7 +49,7 @@ import { SaveReason } from 'vs/workbench/common/editor'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; -import { TimelineItem, TimelineProviderDescriptor, TimelineChangeEvent, TimelineItemWithSource } from 'vs/workbench/contrib/timeline/common/timeline'; +import { Timeline, TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; // {{SQL CARBON EDIT}} import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views'; @@ -357,6 +357,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerImplementationSupport(handle: number, selector: IDocumentFilterDto[]): void; $registerTypeDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void; $registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void; $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto): void; @@ -806,8 +807,6 @@ export interface MainThreadTimelineShape extends IDisposable { $registerTimelineProvider(provider: TimelineProviderDescriptor): void; $unregisterTimelineProvider(source: string): void; $emitTimelineChangeEvent(e: TimelineChangeEvent): void; - - $getTimeline(uri: UriComponents, token: CancellationToken): Promise; } // -- extension host @@ -1213,6 +1212,7 @@ export interface ExtHostLanguageFeaturesShape { $provideImplementation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise; $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise; @@ -1461,7 +1461,7 @@ export interface ExtHostTunnelServiceShape { } export interface ExtHostTimelineShape { - $getTimeline(source: string, uri: UriComponents, token: CancellationToken): Promise; + $getTimeline(source: string, uri: UriComponents, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }): Promise; } // --- proxy identifiers diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index dbfc10d3b4..fcfd5813e7 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -276,6 +276,27 @@ class HoverAdapter { } } +class EvaluatableExpressionAdapter { + + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.EvaluatableExpressionProvider, + ) { } + + public provideEvaluatableExpression(resource: URI, position: IPosition, token: CancellationToken): Promise { + + const doc = this._documents.getDocument(resource); + const pos = typeConvert.Position.to(position); + + return asPromise(() => this._provider.provideEvaluatableExpression(doc, pos, token)).then(value => { + if (value) { + return typeConvert.EvaluatableExpression.from(value); + } + return undefined; + }); + } +} + class DocumentHighlightAdapter { constructor( @@ -1329,7 +1350,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter - | SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter; + | SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter; class AdapterData { constructor( @@ -1549,6 +1570,18 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, HoverAdapter, adapter => adapter.provideHover(URI.revive(resource), position, token), undefined); } + // --- debug hover + + registerEvaluatableExpressionProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.EvaluatableExpressionProvider, extensionId?: ExtensionIdentifier): vscode.Disposable { + const handle = this._addNewAdapter(new EvaluatableExpressionAdapter(this._documents, provider), extension); + this._proxy.$registerEvaluatableExpressionProvider(handle, this._transformDocumentSelector(selector)); + return this._createDisposable(handle); + } + + $provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { + return this._withAdapter(handle, EvaluatableExpressionAdapter, adapter => adapter.provideEvaluatableExpression(URI.revive(resource), position, token), undefined); + } + // --- occurrences registerDocumentHighlightProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable { diff --git a/src/vs/workbench/api/common/extHostTimeline.ts b/src/vs/workbench/api/common/extHostTimeline.ts index daf5cef752..6975b6b759 100644 --- a/src/vs/workbench/api/common/extHostTimeline.ts +++ b/src/vs/workbench/api/common/extHostTimeline.ts @@ -7,61 +7,91 @@ import * as vscode from 'vscode'; import { UriComponents, URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ExtHostTimelineShape, MainThreadTimelineShape, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; -import { TimelineItemWithSource, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline'; +import { Timeline, TimelineCursor, TimelineItem, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline'; import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; +import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ThemeIcon } from 'vs/workbench/api/common/extHostTypes'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export interface IExtHostTimeline extends ExtHostTimelineShape { readonly _serviceBrand: undefined; - $getTimeline(id: string, uri: UriComponents, token: vscode.CancellationToken): Promise; + $getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken, options?: { cacheResults?: boolean }): Promise; } export const IExtHostTimeline = createDecorator('IExtHostTimeline'); export class ExtHostTimeline implements IExtHostTimeline { + private static handlePool = 0; + _serviceBrand: undefined; private _proxy: MainThreadTimelineShape; private _providers = new Map(); + private _itemsBySourceByUriMap = new Map>>(); + constructor( mainContext: IMainContext, + commands: ExtHostCommands, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadTimeline); + + commands.registerArgumentProcessor({ + processArgument: arg => { + if (arg && arg.$mid === 11) { + const uri = arg.uri === undefined ? undefined : URI.revive(arg.uri); + return this._itemsBySourceByUriMap.get(getUriKey(uri))?.get(arg.source)?.get(arg.handle); + } + + return arg; + } + }); } - async $getTimeline(id: string, uri: UriComponents, token: vscode.CancellationToken): Promise { + async $getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken, options?: { cacheResults?: boolean }): Promise { const provider = this._providers.get(id); - return provider?.provideTimeline(URI.revive(uri), token) ?? []; + return provider?.provideTimeline(URI.revive(uri), cursor, token, options); } - registerTimelineProvider(scheme: string | string[], provider: vscode.TimelineProvider, extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable { + registerTimelineProvider(scheme: string | string[], provider: vscode.TimelineProvider, _extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable { const timelineDisposables = new DisposableStore(); - const convertTimelineItem = this.convertTimelineItem(provider.id, commandConverter, timelineDisposables); + const convertTimelineItem = this.convertTimelineItem(provider.id, commandConverter, timelineDisposables).bind(this); let disposable: IDisposable | undefined; if (provider.onDidChange) { disposable = provider.onDidChange(this.emitTimelineChangeEvent(provider.id), this); } + const itemsBySourceByUriMap = this._itemsBySourceByUriMap; return this.registerTimelineProviderCore({ ...provider, scheme: scheme, onDidChange: undefined, - async provideTimeline(uri: URI, token: CancellationToken) { + async provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }) { timelineDisposables.clear(); - const results = await provider.provideTimeline(uri, token); + // For now, only allow the caching of a single Uri + if (options?.cacheResults && !itemsBySourceByUriMap.has(getUriKey(uri))) { + itemsBySourceByUriMap.clear(); + } + + const result = await provider.provideTimeline(uri, cursor, token); // Intentional == we don't know how a provider will respond // eslint-disable-next-line eqeqeq - return results != null - ? results.map(item => convertTimelineItem(item)) - : []; + if (result == null) { + return undefined; + } + + // TODO: Determine if we should cache dependent on who calls us (internal vs external) + const convertItem = convertTimelineItem(uri, options?.cacheResults ?? false); + return { + ...result, + source: provider.id, + items: result.items.map(convertItem) + }; }, dispose() { disposable?.dispose(); @@ -70,39 +100,72 @@ export class ExtHostTimeline implements IExtHostTimeline { }); } - private convertTimelineItem(source: string, commandConverter: CommandsConverter, disposables: DisposableStore): (item: vscode.TimelineItem) => TimelineItemWithSource { - return (item: vscode.TimelineItem) => { - const { iconPath, ...props } = item; + private convertTimelineItem(source: string, commandConverter: CommandsConverter, disposables: DisposableStore) { + return (uri: URI, cacheResults: boolean) => { + let itemsMap: Map | undefined; + if (cacheResults) { + const uriKey = getUriKey(uri); - let icon; - let iconDark; - let themeIcon; - if (item.iconPath) { - if (iconPath instanceof ThemeIcon) { - themeIcon = { id: iconPath.id }; + let sourceMap = this._itemsBySourceByUriMap.get(uriKey); + if (sourceMap === undefined) { + sourceMap = new Map(); + this._itemsBySourceByUriMap.set(uriKey, sourceMap); } - else if (URI.isUri(iconPath)) { - icon = iconPath; - iconDark = iconPath; - } - else { - ({ light: icon, dark: iconDark } = iconPath as { light: URI; dark: URI }); + + itemsMap = sourceMap.get(source); + if (itemsMap === undefined) { + itemsMap = new Map(); + sourceMap.set(source, itemsMap); } } - return { - ...props, - source: source, - command: item.command ? commandConverter.toInternal(item.command, disposables) : undefined, - icon: icon, - iconDark: iconDark, - themeIcon: themeIcon + return (item: vscode.TimelineItem): TimelineItem => { + const { iconPath, ...props } = item; + + const handle = `${source}|${item.id ?? `${item.timestamp}-${ExtHostTimeline.handlePool++}`}`; + itemsMap?.set(handle, item); + + let icon; + let iconDark; + let themeIcon; + if (item.iconPath) { + if (iconPath instanceof ThemeIcon) { + themeIcon = { id: iconPath.id }; + } + else if (URI.isUri(iconPath)) { + icon = iconPath; + iconDark = iconPath; + } + else { + ({ light: icon, dark: iconDark } = iconPath as { light: URI; dark: URI }); + } + } + + return { + ...props, + handle: handle, + source: source, + command: item.command ? commandConverter.toInternal(item.command, disposables) : undefined, + icon: icon, + iconDark: iconDark, + themeIcon: themeIcon + }; }; }; } private emitTimelineChangeEvent(id: string) { return (e: vscode.TimelineChangeEvent) => { + // Clear caches + if (e?.uri === undefined) { + for (const sourceMap of this._itemsBySourceByUriMap.values()) { + sourceMap.get(id)?.clear(); + } + } + else { + this._itemsBySourceByUriMap.get(getUriKey(e.uri))?.clear(); + } + this._proxy.$emitTimelineChangeEvent({ ...e, id: id }); }; } @@ -123,9 +186,18 @@ export class ExtHostTimeline implements IExtHostTimeline { this._providers.set(provider.id, provider); return toDisposable(() => { + for (const sourceMap of this._itemsBySourceByUriMap.values()) { + sourceMap.get(provider.id)?.clear(); + } + this._providers.delete(provider.id); this._proxy.$unregisterTimelineProvider(provider.id); provider.dispose(); }); } } + +function getUriKey(uri: URI | undefined): string | undefined { + return uri?.toString(); +} + diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index c9b873009d..42e905f489 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -257,7 +257,7 @@ export namespace MarkdownString { } else if (htmlContent.isMarkdownString(markup)) { res = markup; } else if (typeof markup === 'string') { - res = { value: markup }; + res = { value: markup }; } else { res = { value: '' }; } @@ -737,6 +737,20 @@ export namespace Hover { return new types.Hover(info.contents.map(MarkdownString.to), Range.to(info.range)); } } + +export namespace EvaluatableExpression { + export function from(expression: vscode.EvaluatableExpression): modes.EvaluatableExpression { + return { + range: Range.from(expression.range), + expression: expression.expression + }; + } + + export function to(info: modes.EvaluatableExpression): types.EvaluatableExpression { + return new types.EvaluatableExpression(Range.to(info.range), info.expression); + } +} + export namespace DocumentHighlight { export function from(documentHighlight: vscode.DocumentHighlight): modes.DocumentHighlight { return { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 75f0740571..76ad91efc3 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2283,6 +2283,17 @@ export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInli } } +@es5ClassCompat +export class EvaluatableExpression implements vscode.EvaluatableExpression { + readonly range: vscode.Range; + readonly expression?: string; + + constructor(range: vscode.Range, expression?: string) { + this.range = range; + this.expression = expression; + } +} + export enum LogLevel { Trace = 1, Debug = 2, diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 0ba5da0818..a66729d3fc 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -57,6 +57,8 @@ namespace schema { case 'comments/comment/title': return MenuId.CommentTitle; case 'comments/comment/context': return MenuId.CommentActions; case 'extension/context': return MenuId.ExtensionContext; + case 'timeline/title': return MenuId.TimelineTitle; + case 'timeline/item/context': return MenuId.TimelineItemContext; } return undefined; @@ -220,6 +222,16 @@ namespace schema { type: 'array', items: menuItem }, + 'timeline/title': { + description: localize('view.timelineTitle', "The Timeline view title menu"), + type: 'array', + items: menuItem + }, + 'timeline/item/context': { + description: localize('view.timelineContext', "The Timeline view item context menu"), + type: 'array', + items: menuItem + }, } }; diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 60c77e6d9f..90442e646e 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -56,7 +56,7 @@ export class PanelPart extends CompositePart implements IPanelService { //#region IView - readonly minimumWidth: number = 420; + readonly minimumWidth: number = 300; readonly maximumWidth: number = Number.POSITIVE_INFINITY; readonly minimumHeight: number = 77; readonly maximumHeight: number = Number.POSITIVE_INFINITY; diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 6e016988d8..e30c349f63 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -57,12 +57,12 @@ export interface IViewContainerDescriptor { export interface IViewContainersRegistry { /** - * An event that is triggerred when a view container is registered. + * An event that is triggered when a view container is registered. */ readonly onDidRegister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>; /** - * An event that is triggerred when a view container is deregistered. + * An event that is triggered when a view container is deregistered. */ readonly onDidDeregister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 965b68e978..f31353062f 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -6,7 +6,7 @@ import 'vs/css!./bulkEdit'; import { WorkbenchAsyncDataTree, TreeResourceNavigator, IOpenEvent } from 'vs/platform/list/browser/listService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; -import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, BulkEditAriaProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; +import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, BulkEditAriaProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement, BulkEditSorter } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; @@ -135,6 +135,7 @@ export class BulkEditPane extends ViewPane { expandOnlyOnTwistieClick: true, multipleSelectionSupport: false, keyboardNavigationLabelProvider: new BulkEditNaviLabelProvider(), + sorter: new BulkEditSorter() } ); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index 9f183f7483..9c4751e682 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -99,6 +99,15 @@ export class BulkFileOperation { this.newUri = edit.newUri; } } + + needsConfirmation(): boolean { + for (let [, edit] of this.originalEdits) { + if (!this.parent.checked.isChecked(edit)) { + return true; + } + } + return false; + } } export class BulkCategory { @@ -230,7 +239,7 @@ export class BulkFileOperations { } operationByResource.forEach(value => this.fileOperations.push(value)); - operationByCategory.forEach(value => value.metadata.needsConfirmation ? this.categories.unshift(value) : this.categories.push(value)); + operationByCategory.forEach(value => this.categories.push(value)); // "correct" invalid parent-check child states that is // unchecked file edits (rename, create, delete) uncheck diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index d1c50eefd1..e574f4ff66 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; @@ -24,6 +24,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { basename } from 'vs/base/common/resources'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { WorkspaceFileEdit } from 'vs/editor/common/modes'; +import { compare } from 'vs/base/common/strings'; // --- VIEW MODEL @@ -248,6 +249,39 @@ export class BulkEditDataSource implements IAsyncDataSource { + + compare(a: BulkEditElement, b: BulkEditElement): number { + if (a instanceof CategoryElement && b instanceof CategoryElement) { + // + const aConfirm = BulkEditSorter._needsConfirmation(a.category); + const bConfirm = BulkEditSorter._needsConfirmation(b.category); + if (aConfirm === bConfirm) { + return a.category.metadata.label.localeCompare(b.category.metadata.label); + } else if (aConfirm) { + return -1; + } else { + return 1; + } + } + + if (a instanceof FileElement && b instanceof FileElement) { + return compare(a.edit.uri.toString(), b.edit.uri.toString()); + } + + if (a instanceof TextEditElement && b instanceof TextEditElement) { + return Range.compareRangesUsingStarts(a.edit.textEdit.edit.range, b.edit.textEdit.edit.range); + } + + return 0; + } + + private static _needsConfirmation(a: BulkCategory): boolean { + return a.fileOperations.some(ops => ops.needsConfirmation()); + } +} + // --- ACCESSI export class BulkEditAccessibilityProvider implements IAccessibilityProvider { diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index 62c565bb03..33a41d323f 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -40,7 +40,7 @@ CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAcces // #region Reopen With const REOPEN_WITH_COMMAND_ID = 'reOpenWith'; -const REOPEN_WITH_TITLE = { value: nls.localize('reopenWith.title', 'Reopen With'), original: 'Reopen With' }; +const REOPEN_WITH_TITLE = { value: nls.localize('reopenWith.title', 'Reopen With...'), original: 'Reopen With' }; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: REOPEN_WITH_COMMAND_ID, @@ -83,6 +83,17 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { when: CONTEXT_HAS_CUSTOM_EDITORS, }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { + command: { + id: REOPEN_WITH_COMMAND_ID, + title: REOPEN_WITH_TITLE, + category: viewCategory, + }, + group: '3_open', + order: 20, + when: CONTEXT_HAS_CUSTOM_EDITORS, +}); + // #endregion diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 3c75786311..2365a85370 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -19,7 +19,6 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; import { once } from 'vs/base/common/functional'; -import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; export const twistiePixels = 20; @@ -233,11 +232,3 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer; private needsRefresh = false; @@ -82,6 +81,7 @@ export class BreakpointsView extends BaseDebugViewPane { public renderBody(container: HTMLElement): void { super.renderBody(container); + dom.addClass(this.element, 'debug-pane'); dom.addClass(container, 'debug-breakpoints'); const delegate = new BreakpointsDelegate(this.debugService); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 292283414b..6e95e3adef 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -13,12 +13,12 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { renderViewTree, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IAction, Action } from 'vs/base/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { ILabelService } from 'vs/platform/label/common/label'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -74,7 +74,7 @@ export function getContextForContributedActions(element: CallStackItem | null): return ''; } -export class CallStackView extends BaseDebugViewPane { +export class CallStackView extends ViewPane { private pauseMessage!: HTMLSpanElement; private pauseMessageLabel!: HTMLSpanElement; private onCallStackChangeScheduler: RunOnceScheduler; @@ -158,7 +158,7 @@ export class CallStackView extends BaseDebugViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); - + dom.addClass(this.element, 'debug-pane'); dom.addClass(container, 'debug-call-stack'); const treeContainer = renderViewTree(container); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 0a36fb337b..2a44f8cff1 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -136,17 +136,19 @@ function getWordToLineNumbersMap(model: ITextModel | null): Map { - const pos = range.getStartPosition(); const session = this.debugService.getViewModel().focusedSession; - if (!this.editor.hasModel()) { + + if (!session || !this.editor.hasModel()) { return Promise.resolve(this.hide()); } - const lineContent = this.editor.getModel().getLineContent(pos.lineNumber); - const { start, end } = getExactExpressionStartAndEnd(lineContent, range.startColumn, range.endColumn); - // use regex to extract the sub-expression #9821 - const matchingExpression = lineContent.substring(start - 1, end); - if (!matchingExpression || !session) { + const model = this.editor.getModel(); + const pos = range.getStartPosition(); + + let rng: IRange | undefined = undefined; + let matchingExpression: string | undefined; + + if (EvaluatableExpressionProviderRegistry.has(model)) { + const supports = EvaluatableExpressionProviderRegistry.ordered(model); + + const promises = supports.map(support => { + return Promise.resolve(support.provideEvaluatableExpression(model, pos, CancellationToken.None)).then(expression => { + return expression; + }, err => { + //onUnexpectedExternalError(err); + return undefined; + }); + }); + + const results = await Promise.all(promises).then(coalesce); + if (results.length > 0) { + matchingExpression = results[0].expression; + rng = results[0].range; + + if (!matchingExpression) { + const lineContent = model.getLineContent(pos.lineNumber); + matchingExpression = lineContent.substring(rng.startColumn - 1, rng.endColumn); + } + } + + } else { // old one-size-fits-all strategy + const lineContent = model.getLineContent(pos.lineNumber); + const { start, end } = getExactExpressionStartAndEnd(lineContent, range.startColumn, range.endColumn); + + // use regex to extract the sub-expression #9821 + matchingExpression = lineContent.substring(start - 1, end); + rng = new Range(pos.lineNumber, start, pos.lineNumber, start + matchingExpression.length); + } + + if (!matchingExpression) { return Promise.resolve(this.hide()); } @@ -202,13 +239,15 @@ export class DebugHoverWidget implements IContentWidget { if (!expression || (expression instanceof Expression && !expression.available)) { this.hide(); - return undefined; + return; } - this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{ - range: new Range(pos.lineNumber, start, pos.lineNumber, start + matchingExpression.length), - options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS - }]); + if (rng) { + this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{ + range: rng, + options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS + }]); + } return this.doShow(pos, expression, focus); } diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 8462813bf0..48497841be 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -7,12 +7,12 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { normalize, isAbsolute, posix } from 'vs/base/common/path'; -import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { renderViewTree, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IDebugSession, IDebugService, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from 'vs/workbench/contrib/debug/common/debug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -402,7 +402,7 @@ function asTreeElement(item: BaseTreeItem, viewState?: IViewState): ITreeElement }; } -export class LoadedScriptsView extends BaseDebugViewPane { +export class LoadedScriptsView extends ViewPane { private treeContainer!: HTMLElement; private loadedScriptsItemType: IContextKey; @@ -435,6 +435,7 @@ export class LoadedScriptsView extends BaseDebugViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); + dom.addClass(this.element, 'debug-pane'); dom.addClass(container, 'debug-loaded-scripts'); dom.addClass(container, 'show-file-icons'); diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 362adbf89f..ae69b0d958 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -75,7 +75,7 @@ /* Expressions */ -.monaco-workbench .debug-viewlet .monaco-list-row .expression, +.monaco-workbench .debug-pane .monaco-list-row .expression, .monaco-workbench .debug-hover-widget .monaco-list-row .expression { font-size: 13px; overflow: hidden; @@ -84,7 +84,7 @@ white-space: pre; } -.monaco-workbench.mac .debug-viewlet .monaco-list-row .expression, +.monaco-workbench.mac .debug-pane .monaco-list-row .expression, .monaco-workbench.mac .debug-hover-widget .monaco-list-row .expression { font-size: 11px; } diff --git a/src/vs/workbench/contrib/debug/browser/media/debugHover.css b/src/vs/workbench/contrib/debug/browser/media/debugHover.css index 8664275597..98265ffb18 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugHover.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugHover.css @@ -12,7 +12,6 @@ user-select: text; -webkit-user-select: text; word-break: break-all; - padding: 4px 5px; } .monaco-editor .debug-hover-widget .complex-value { @@ -62,6 +61,7 @@ overflow: auto; font-family: var(--monaco-monospace-font); max-height: 500px; + padding: 4px 5px; } .monaco-editor .debug-hover-widget .error { diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index ad66a71c68..265e05ae19 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -601,7 +601,7 @@ export class RawDebugSession implements IDisposable { private send(command: string, args: any, token?: CancellationToken, timeout?: number): Promise { return new Promise((completeDispatch, errorDispatch) => { if (!this.debugAdapter) { - errorDispatch(new Error('no debug adapter found')); + errorDispatch(new Error(nls.localize('noDebugAdapter', "No debug adapter found. Can not send '{0}'.", command))); return; } let cancelationListener: IDisposable; diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts index c66c6e3e04..f78e8158fc 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -20,7 +20,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { equals } from 'vs/base/common/arrays'; -import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -170,6 +170,7 @@ export class StartView extends ViewPane { })); attachButtonStyler(this.debugButton, this.themeService); + dom.addClass(this.element, 'debug-pane'); dom.addClass(container, 'debug-start-view'); this.secondMessageContainer = $('.section'); diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 9ad97aaccd..da068eed1c 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -12,12 +12,12 @@ import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IViewMod import { Variable, Scope } from 'vs/workbench/contrib/debug/common/debugModel'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IAction, Action } from 'vs/base/common/actions'; import { CopyValueAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeMouseEvent, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; @@ -39,7 +39,7 @@ let forgetScopes = true; export const variableSetEmitter = new Emitter(); -export class VariablesView extends BaseDebugViewPane { +export class VariablesView extends ViewPane { private onFocusStackFrameScheduler: RunOnceScheduler; private needsRefresh = false; @@ -90,6 +90,7 @@ export class VariablesView extends BaseDebugViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); + dom.addClass(this.element, 'debug-pane'); dom.addClass(container, 'debug-variables'); const treeContainer = renderViewTree(container); diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 10faaaed9f..6e1a5150a6 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -16,9 +16,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; @@ -38,7 +38,7 @@ const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; let ignoreVariableSetEmitter = false; let useCachedEvaluation = false; -export class WatchExpressionsView extends BaseDebugViewPane { +export class WatchExpressionsView extends ViewPane { private onWatchExpressionsUpdatedScheduler: RunOnceScheduler; private needsRefresh = false; @@ -67,6 +67,7 @@ export class WatchExpressionsView extends BaseDebugViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); + dom.addClass(this.element, 'debug-pane'); dom.addClass(container, 'debug-watch'); const treeContainer = renderViewTree(container); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 709d14f47d..15fcd92353 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -676,7 +676,11 @@ export class ExplorerView extends ViewPane { if (item.isDisposed) { return this.onSelectResource(resource, reveal, retry + 1); } - this.tree.reveal(item, 0.5); + + // Don't scroll to the item if it's already visible + if (this.tree.getRelativeTop(item) === null) { + this.tree.reveal(item, 0.5); + } } this.tree.setFocus([item]); diff --git a/src/vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution.ts b/src/vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution.ts index 2a38da8ba5..b5b915b595 100644 --- a/src/vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution.ts +++ b/src/vs/workbench/contrib/openInDesktop/browser/openInDesktop.web.contribution.ts @@ -29,13 +29,13 @@ export class OpenInDesktopIndicator extends Disposable implements IWorkbenchCont ) { super(); - const links = environmentService.options?.applicationLinkProvider?.(); + const links = environmentService.options?.applicationLinks; if (Array.isArray(links) && links?.length > 0) { this.installOpenInDesktopIndicator(links); } } - private installOpenInDesktopIndicator(links: IApplicationLink[]): void { + private installOpenInDesktopIndicator(links: readonly IApplicationLink[]): void { // Register action to trigger "Open In Desktop" const registry = Registry.as(ActionExtensions.WorkbenchActions); @@ -71,7 +71,7 @@ export class OpenInDesktopAction extends Action { } async run(): Promise { - const links = this.environmentService.options?.applicationLinkProvider?.(); + const links = this.environmentService.options?.applicationLinks; if (Array.isArray(links)) { if (links.length === 1) { return this.openApplicationLink(links[0]); @@ -83,7 +83,7 @@ export class OpenInDesktopAction extends Action { return true; } - private async runWithPicker(links: IApplicationLink[]): Promise { + private async runWithPicker(links: readonly IApplicationLink[]): Promise { // Show a picker with choices const quickPick = this.quickInputService.createQuickPick(); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index adb987c7e1..66b14da1ef 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -27,7 +27,7 @@ import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IResourceInput, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; +import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { WorkbenchDataTree } from 'vs/platform/list/browser/listService'; @@ -37,7 +37,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { CollapseAction } from 'vs/workbench/browser/viewlet'; -import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { OutlineConfigKeys, OutlineViewFocused, OutlineViewFiltered } from 'vs/editor/contrib/documentSymbols/outline'; import { FuzzyScore } from 'vs/base/common/filters'; import { OutlineDataSource, OutlineItemComparator, OutlineSortOrder, OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItem, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; @@ -49,6 +49,7 @@ import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDeco import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; class RequestState { @@ -261,7 +262,7 @@ export class OutlinePane extends ViewPane { @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IThemeService private readonly _themeService: IThemeService, @IStorageService private readonly _storageService: IStorageService, - @IEditorService private readonly _editorService: IEditorService, + @ICodeEditorService private readonly _editorService: ICodeEditorService, @IMarkerDecorationsService private readonly _markerDecorationService: IMarkerDecorationsService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IKeybindingService keybindingService: IKeybindingService, @@ -347,7 +348,7 @@ export class OutlinePane extends ViewPane { this._disposables.push(this._tree); this._disposables.push(this._outlineViewState.onDidChange(this._onDidChangeUserState, this)); - this._disposables.push(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { + this._disposables.push(this.viewDescriptorService.onDidChangeLocation(({ views }) => { if (views.some(v => v.id === this.id)) { this._tree.updateOptions({ overrideStyles: { listBackground: this.getBackgroundColor() } }); } @@ -629,15 +630,18 @@ export class OutlinePane extends ViewPane { } private async _revealTreeSelection(model: OutlineModel, element: OutlineElement, focus: boolean, aside: boolean): Promise { - - await this._editorService.openEditor({ - resource: model.textModel.uri, - options: { - preserveFocus: !focus, - selection: Range.collapseToStart(element.symbol.selectionRange), - selectionRevealType: TextEditorSelectionRevealType.NearTop, - } - } as IResourceInput, aside ? SIDE_GROUP : ACTIVE_GROUP); + await this._editorService.openCodeEditor( + { + resource: model.textModel.uri, + options: { + preserveFocus: !focus, + selection: Range.collapseToStart(element.symbol.selectionRange), + selectionRevealType: TextEditorSelectionRevealType.NearTop, + } + }, + this._editorService.getActiveCodeEditor(), + aside + ); } private _revealEditorSelection(model: OutlineModel, selection: Selection): void { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index c5afa5fee4..d687d8af69 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -29,7 +29,7 @@ import { SelectionHighlighter } from 'vs/editor/contrib/multicursor/multicursor' import * as nls from 'vs/nls'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -984,7 +984,7 @@ export class DefaultPreferencesEditor extends BaseTextEditor { private static _getContributions(): IEditorContributionDescription[] { const skipContributions = [FoldingController.ID, SelectionHighlighter.ID, FindController.ID]; const contributions = EditorExtensionsRegistry.getEditorContributions().filter(c => skipContributions.indexOf(c.id) === -1); - contributions.push({ id: DefaultSettingsEditorContribution.ID, ctor: DefaultSettingsEditorContribution }); + contributions.push({ id: DefaultSettingsEditorContribution.ID, ctor: DefaultSettingsEditorContribution as IConstructorSignature1 }); return contributions; } diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index 1c24bae37c..22e364f8a1 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -15,7 +15,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { RunOnceScheduler } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; import { isEqual } from 'vs/base/common/resources'; -import { isMacintosh, isNative } from 'vs/base/common/platform'; +import { isMacintosh, isNative, isLinux } from 'vs/base/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -26,6 +26,7 @@ interface IConfiguration extends IWindowsConfiguration { telemetry: { enableCrashReporter: boolean }; workbench: { list: { horizontalScrolling: boolean } }; debug: { console: { wordWrap: boolean } }; + editor: { accessibilitySupport: 'on' | 'off' | 'auto' }; } export class SettingsChangeRelauncher extends Disposable implements IWorkbenchContribution { @@ -38,6 +39,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private enableCrashReporter: boolean | undefined; private treeHorizontalScrolling: boolean | undefined; private debugConsoleWordWrap: boolean | undefined; + private accessibilitySupport: 'on' | 'off' | 'auto' | undefined; constructor( @IHostService private readonly hostService: IHostService, @@ -103,6 +105,14 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo this.enableCrashReporter = config.telemetry.enableCrashReporter; changed = true; } + + // On linux turning on accessibility support will also pass this flag to the chrome renderer, thus a restart is required + if (isLinux && typeof config.editor?.accessibilitySupport === 'string' && config.editor.accessibilitySupport !== this.accessibilitySupport) { + this.accessibilitySupport = config.editor.accessibilitySupport; + if (this.accessibilitySupport === 'on') { + changed = true; + } + } } // Notify only when changed and we are the focused window (avoids notification spam across windows) diff --git a/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css b/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css index 88d015ee45..591d2f6895 100644 --- a/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css +++ b/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css @@ -13,3 +13,20 @@ position: absolute; pointer-events: none; } + +.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .monaco-icon-label { + flex: 1; + text-overflow: ellipsis; + overflow: hidden; +} + +.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .timeline-timestamp-container { + margin-left: 2px; + margin-right: 4px; + text-overflow: ellipsis; + overflow: hidden; +} + +.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item .timeline-timestamp-container .timeline-timestamp { + opacity: 0.5; +} diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 40356e87e1..e1efc70983 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -8,11 +8,11 @@ import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IListVirtualDelegate, IIdentityProvider, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list'; -import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { ITreeNode, ITreeRenderer, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { TreeResourceNavigator, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -20,7 +20,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TimelineItem, ITimelineService, TimelineChangeEvent, TimelineProvidersChangeEvent, TimelineRequest, TimelineItemWithSource } from 'vs/workbench/contrib/timeline/common/timeline'; +import { ITimelineService, TimelineChangeEvent, TimelineProvidersChangeEvent, TimelineRequest, TimelineItem } from 'vs/workbench/contrib/timeline/common/timeline'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { SideBySideEditor, toResource } from 'vs/workbench/common/editor'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -31,10 +31,20 @@ import { IProgressService } from 'vs/platform/progress/common/progress'; import { VIEWLET_ID } from 'vs/workbench/contrib/files/common/files'; import { debounce } from 'vs/base/common/decorators'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IActionViewItemProvider, ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction, ActionRunner } from 'vs/base/common/actions'; +import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { MenuItemAction, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { fromNow } from 'vs/base/common/date'; + +// TODO[ECA]: Localize all the strings type TreeElement = TimelineItem; -// TODO[ECA]: Localize all the strings +interface TimelineActionContext { + uri: URI | undefined; + item: TreeElement; +} export class TimelinePane extends ViewPane { static readonly ID = 'timeline'; @@ -44,10 +54,12 @@ export class TimelinePane extends ViewPane { private _messageElement!: HTMLDivElement; private _treeElement!: HTMLDivElement; private _tree!: WorkbenchObjectTree; + private _treeRenderer: TimelineTreeRenderer | undefined; + private _menus: TimelineMenus; private _visibilityDisposables: DisposableStore | undefined; // private _excludedSources: Set | undefined; - private _items: TimelineItemWithSource[] = []; + private _items: TimelineItem[] = []; private _loadingMessageTimer: any | undefined; private _pendingRequests = new Map(); private _uri: URI | undefined; @@ -67,7 +79,9 @@ export class TimelinePane extends ViewPane { @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...options, titleMenuId: MenuId.TimelineTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + + this._menus = this._register(this.instantiationService.createInstance(TimelineMenus, this.id)); const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); scopedContextKeyService.createKey('view', TimelinePane.ID); @@ -88,6 +102,7 @@ export class TimelinePane extends ViewPane { } this._uri = uri; + this._treeRenderer?.setUri(uri); this.loadTimeline(); } @@ -187,7 +202,7 @@ export class TimelinePane extends ViewPane { let request = this._pendingRequests.get(source); request?.tokenSource.dispose(true); - request = this.timelineService.getTimelineRequest(source, this._uri, new CancellationTokenSource())!; + request = this.timelineService.getTimeline(source, this._uri, {}, new CancellationTokenSource(), { cacheResults: true })!; this._pendingRequests.set(source, request); request.tokenSource.token.onCancellationRequested(() => this._pendingRequests.delete(source)); @@ -199,7 +214,7 @@ export class TimelinePane extends ViewPane { private async handleRequest(request: TimelineRequest) { let items; try { - items = await this.progressService.withProgress({ location: VIEWLET_ID }, () => request.items); + items = await this.progressService.withProgress({ location: VIEWLET_ID }, () => request.result.then(r => r?.items ?? [])); } catch { } @@ -211,7 +226,7 @@ export class TimelinePane extends ViewPane { this.replaceItems(request.source, items); } - private replaceItems(source: string, items?: TimelineItemWithSource[]) { + private replaceItems(source: string, items?: TimelineItem[]) { const hasItems = this._items.length !== 0; if (items?.length) { @@ -291,17 +306,20 @@ export class TimelinePane extends ViewPane { // DOM.addClass(this._treeElement, 'show-file-icons'); container.appendChild(this._treeElement); - const renderer = this.instantiationService.createInstance(TimelineTreeRenderer); - this._tree = >this.instantiationService.createInstance(WorkbenchObjectTree, 'TimelinePane', this._treeElement, new TimelineListVirtualDelegate(), [renderer], { + this._treeRenderer = this.instantiationService.createInstance(TimelineTreeRenderer, this._menus); + this._tree = >this.instantiationService.createInstance(WorkbenchObjectTree, 'TimelinePane', + this._treeElement, new TimelineListVirtualDelegate(), [this._treeRenderer], { identityProvider: new TimelineIdentityProvider(), keyboardNavigationLabelProvider: new TimelineKeyboardNavigationLabelProvider(), overrideStyles: { - listBackground: this.getBackgroundColor() + listBackground: this.getBackgroundColor(), + } }); const customTreeNavigator = new TreeResourceNavigator(this._tree, { openOnFocus: false, openOnSelection: false }); this._register(customTreeNavigator); + this._register(this._tree.onContextMenu(e => this.onContextMenu(this._menus, e))); this._register( customTreeNavigator.onDidOpenResource(e => { if (!e.browserEvent) { @@ -316,36 +334,112 @@ export class TimelinePane extends ViewPane { }) ); } -} -export class TimelineElementTemplate { - static readonly id = 'TimelineElementTemplate'; + private onContextMenu(menus: TimelineMenus, treeEvent: ITreeContextMenuEvent): void { + const item = treeEvent.element; + if (item === null) { + return; + } + const event: UIEvent = treeEvent.browserEvent; - constructor( - readonly container: HTMLElement, - readonly iconLabel: IconLabel, - readonly icon: HTMLElement - ) { } -} + event.preventDefault(); + event.stopPropagation(); -export class TimelineIdentityProvider implements IIdentityProvider { - getId(item: TimelineItem): { toString(): string } { - return `${item.id}|${item.timestamp}`; + this._tree.setFocus([item]); + const actions = menus.getResourceContextActions(item); + if (!actions.length) { + return; + } + + this.contextMenuService.showContextMenu({ + getAnchor: () => treeEvent.anchor, + getActions: () => actions, + getActionViewItem: (action) => { + const keybinding = this.keybindingService.lookupKeybinding(action.id); + if (keybinding) { + return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() }); + } + return undefined; + }, + onHide: (wasCancelled?: boolean) => { + if (wasCancelled) { + this._tree.domFocus(); + } + }, + getActionsContext: (): TimelineActionContext => ({ uri: this._uri, item: item }), + actionRunner: new TimelineActionRunner() + }); } } -export class TimelineKeyboardNavigationLabelProvider implements IKeyboardNavigationLabelProvider { - getKeyboardNavigationLabel(element: TimelineItem): { toString(): string } { +export class TimelineElementTemplate implements IDisposable { + static readonly id = 'TimelineElementTemplate'; + + readonly actionBar: ActionBar; + readonly icon: HTMLElement; + readonly iconLabel: IconLabel; + readonly timestamp: HTMLSpanElement; + + constructor( + readonly container: HTMLElement, + actionViewItemProvider: IActionViewItemProvider + ) { + DOM.addClass(container, 'custom-view-tree-node-item'); + this.icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon')); + + this.iconLabel = new IconLabel(container, { supportHighlights: true, supportCodicons: true }); + + const timestampContainer = DOM.append(this.iconLabel.element, DOM.$('.timeline-timestamp-container')); + this.timestamp = DOM.append(timestampContainer, DOM.$('span.timeline-timestamp')); + + const actionsContainer = DOM.append(this.iconLabel.element, DOM.$('.actions')); + this.actionBar = new ActionBar(actionsContainer, { actionViewItemProvider: actionViewItemProvider }); + } + + dispose() { + this.iconLabel.dispose(); + this.actionBar.dispose(); + } + + reset() { + this.actionBar.clear(); + } +} + +export class TimelineIdentityProvider implements IIdentityProvider { + getId(item: TreeElement): { toString(): string } { + return item.handle; + } +} + +class TimelineActionRunner extends ActionRunner { + + runAction(action: IAction, { uri, item }: TimelineActionContext): Promise { + return action.run(...[ + { + $mid: 11, + handle: item.handle, + source: item.source, + uri: uri + }, + uri, + item.source, + ]); + } +} + +export class TimelineKeyboardNavigationLabelProvider implements IKeyboardNavigationLabelProvider { + getKeyboardNavigationLabel(element: TreeElement): { toString(): string } { return element.label; } } -export class TimelineListVirtualDelegate implements IListVirtualDelegate { - getHeight(_element: TimelineItem): number { +export class TimelineListVirtualDelegate implements IListVirtualDelegate { + getHeight(_element: TreeElement): number { return 22; } - getTemplateId(element: TimelineItem): string { + getTemplateId(element: TreeElement): string { return TimelineElementTemplate.id; } } @@ -353,14 +447,25 @@ export class TimelineListVirtualDelegate implements IListVirtualDelegate { readonly templateId: string = TimelineElementTemplate.id; - constructor(@IThemeService private _themeService: IThemeService) { } + private _actionViewItemProvider: IActionViewItemProvider; + + constructor( + private readonly _menus: TimelineMenus, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IThemeService private _themeService: IThemeService + ) { + this._actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction + ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) + : undefined; + } + + private _uri: URI | undefined; + setUri(uri: URI | undefined) { + this._uri = uri; + } renderTemplate(container: HTMLElement): TimelineElementTemplate { - DOM.addClass(container, 'custom-view-tree-node-item'); - const icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon')); - - const iconLabel = new IconLabel(container, { supportHighlights: true, supportCodicons: true }); - return new TimelineElementTemplate(container, iconLabel, icon); + return new TimelineElementTemplate(container, this._actionViewItemProvider); } renderElement( @@ -369,30 +474,74 @@ class TimelineTreeRenderer implements ITreeRenderer /^inline/.test(g)); + + menu.dispose(); + contextKeyService.dispose(); + + return result; + } +} diff --git a/src/vs/workbench/contrib/timeline/common/timeline.ts b/src/vs/workbench/contrib/timeline/common/timeline.ts index 27cbd96a2e..7bf57d03a1 100644 --- a/src/vs/workbench/contrib/timeline/common/timeline.ts +++ b/src/vs/workbench/contrib/timeline/common/timeline.ts @@ -16,9 +16,11 @@ export function toKey(extension: ExtensionIdentifier | string, source: string) { } export interface TimelineItem { + handle: string; + source: string; + timestamp: number; label: string; - id?: string; icon?: URI, iconDark?: URI, themeIcon?: { id: string }, @@ -28,19 +30,29 @@ export interface TimelineItem { contextValue?: string; } -export interface TimelineItemWithSource extends TimelineItem { - source: string; -} - export interface TimelineChangeEvent { id: string; uri?: URI; } +export interface TimelineCursor { + cursor?: any; + before?: boolean; + limit?: number; +} + +export interface Timeline { + source: string; + items: TimelineItem[]; + + cursor?: any; + more?: boolean; +} + export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable { onDidChange?: Event; - provideTimeline(uri: URI, token: CancellationToken): Promise; + provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }): Promise; } export interface TimelineProviderDescriptor { @@ -55,7 +67,7 @@ export interface TimelineProvidersChangeEvent { } export interface TimelineRequest { - readonly items: Promise; + readonly result: Promise; readonly source: string; readonly tokenSource: CancellationTokenSource; readonly uri: URI; @@ -72,9 +84,7 @@ export interface ITimelineService { getSources(): string[]; - getTimeline(uri: URI, token: CancellationToken): Promise; - - getTimelineRequest(id: string, uri: URI, tokenSource: CancellationTokenSource): TimelineRequest | undefined; + getTimeline(id: string, uri: URI, cursor: TimelineCursor, tokenSource: CancellationTokenSource, options?: { cacheResults?: boolean }): TimelineRequest | undefined; } const TIMELINE_SERVICE_ID = 'timeline'; diff --git a/src/vs/workbench/contrib/timeline/common/timelineService.ts b/src/vs/workbench/contrib/timeline/common/timelineService.ts index c75aee9d2d..0b3fd16f96 100644 --- a/src/vs/workbench/contrib/timeline/common/timelineService.ts +++ b/src/vs/workbench/contrib/timeline/common/timelineService.ts @@ -3,13 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; // import { basename } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; -import { ITimelineService, TimelineProvider, TimelineItem, TimelineChangeEvent, TimelineProvidersChangeEvent } from './timeline'; +import { ITimelineService, TimelineChangeEvent, TimelineCursor, TimelineProvidersChangeEvent, TimelineProvider } from './timeline'; export class TimelineService implements ITimelineService { _serviceBrand: undefined; @@ -81,42 +81,7 @@ export class TimelineService implements ITimelineService { return [...this._providers.keys()]; } - async getTimeline(uri: URI, token: CancellationToken, predicate?: (provider: TimelineProvider) => boolean) { - this.logService.trace(`TimelineService#getTimeline(${uri.toString(true)})`); - - const requests: Promise<[string, TimelineItem[]]>[] = []; - - for (const provider of this._providers.values()) { - if (typeof provider.scheme === 'string') { - if (provider.scheme !== '*' && provider.scheme !== uri.scheme) { - continue; - } - } else if (!provider.scheme.includes(uri.scheme)) { - continue; - } - if (!(predicate?.(provider) ?? true)) { - continue; - } - - requests.push(provider.provideTimeline(uri, token).then(p => [provider.id, p])); - } - - const timelines = await Promise.all(requests); - - const timeline = []; - for (const [source, items] of timelines) { - if (items.length === 0) { - continue; - } - - timeline.push(...items.map(item => ({ ...item, source: source }))); - } - - timeline.sort((a, b) => b.timestamp - a.timestamp); - return timeline; - } - - getTimelineRequest(id: string, uri: URI, tokenSource: CancellationTokenSource) { + getTimeline(id: string, uri: URI, cursor: TimelineCursor, tokenSource: CancellationTokenSource, options?: { cacheResults?: boolean }) { this.logService.trace(`TimelineService#getTimeline(${id}): uri=${uri.toString(true)}`); const provider = this._providers.get(id); @@ -133,12 +98,16 @@ export class TimelineService implements ITimelineService { } return { - items: provider.provideTimeline(uri, tokenSource.token) - .then(items => { - items = items.map(item => ({ ...item, source: provider.id })); - items.sort((a, b) => (b.timestamp - a.timestamp) || b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' })); + result: provider.provideTimeline(uri, cursor, tokenSource.token, options) + .then(result => { + if (result === undefined) { + return undefined; + } - return items; + result.items = result.items.map(item => ({ ...item, source: provider.id })); + result.items.sort((a, b) => (b.timestamp - a.timestamp) || b.source.localeCompare(a.source, undefined, { numeric: true, sensitivity: 'base' })); + + return result; }), source: provider.id, tokenSource: tokenSource, diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index d091afc90c..3d64a28395 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -127,41 +127,41 @@ export class ProductContribution implements IWorkbenchContribution { @IHostService hostService: IHostService, @IProductService productService: IProductService ) { - if (!hostService.hasFocus) { - return; - } + hostService.hadLastFocus().then(hadLastFocus => { + if (!hadLastFocus) { + return; + } - const lastVersion = storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, ''); - const shouldShowReleaseNotes = configurationService.getValue('update.showReleaseNotes'); + const lastVersion = storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, ''); + const shouldShowReleaseNotes = configurationService.getValue('update.showReleaseNotes'); - // was there an update? if so, open release notes - const releaseNotesUrl = productService.releaseNotesUrl; - if (shouldShowReleaseNotes && !environmentService.args['skip-release-notes'] && releaseNotesUrl && lastVersion && productService.version !== lastVersion && productService.quality === 'stable') { // {{SQL CARBON EDIT}} Only show release notes for stable build - /* // {{SQL CARBON EDIT}} Prompt user to open release notes in browser until we can get ADS release notes from the web - showReleaseNotes(instantiationService, productService.version) - .then(undefined, () => { - */ - notificationService.prompt( - severity.Info, - nls.localize('read the release notes', "Welcome to {0} v{1}! Would you like to read the Release Notes?", productService.nameLong, productService.version), - [{ - label: nls.localize('releaseNotes', "Release Notes"), - run: () => { - const uri = URI.parse(releaseNotesUrl); - openerService.open(uri); - } - }], - { sticky: true } - ); - // }); // {{SQL CARBON EDIT}} - } + // was there an update? if so, open release notes + const releaseNotesUrl = productService.releaseNotesUrl; + if (shouldShowReleaseNotes && !environmentService.args['skip-release-notes'] && releaseNotesUrl && lastVersion && productService.version !== lastVersion && productService.quality === 'stable') { // {{SQL CARBON EDIT}} Only show release notes for stable build) { + /*showReleaseNotes(instantiationService, productService.version) {{SQL CARBON EDIT}} Prompt user to open release notes in browser until we can get ADS release notes from the web + .then(undefined, () => {*/ + notificationService.prompt( + severity.Info, + nls.localize('read the release notes', "Welcome to {0} v{1}! Would you like to read the Release Notes?", productService.nameLong, productService.version), + [{ + label: nls.localize('releaseNotes', "Release Notes"), + run: () => { + const uri = URI.parse(releaseNotesUrl); + openerService.open(uri); + } + }], + { sticky: true } + ); + }/*); + }*/ - // should we show the new license? - if (productService.licenseUrl && lastVersion && semver.satisfies(lastVersion, '<1.0.0') && semver.satisfies(productService.version, '>=1.0.0')) { - notificationService.info(nls.localize('licenseChanged', "Our license terms have changed, please click [here]({0}) to go through them.", productService.licenseUrl)); - } + // should we show the new license? + if (productService.licenseUrl && lastVersion && semver.satisfies(lastVersion, '<1.0.0') && semver.satisfies(productService.version, '>=1.0.0')) { + notificationService.info(nls.localize('licenseChanged', "Our license terms have changed, please click [here]({0}) to go through them.", productService.licenseUrl)); + } - storageService.store(ProductContribution.KEY, productService.version, StorageScope.GLOBAL); + storageService.store(ProductContribution.KEY, productService.version, StorageScope.GLOBAL); + }); } } diff --git a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts index 82d33c6011..ffda312e30 100644 --- a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts @@ -286,4 +286,10 @@ export abstract class BaseWebview extends Disposable { this.element.style.pointerEvents = ''; } } + + public selectAll() { + if (this.element) { + this._send('execCommand', 'selectAll'); + } + } } diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index a4edd1ac7a..deda2452e7 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -187,6 +187,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd showFind(): void { this.withWebview(webview => webview.showFind()); } hideFind(): void { this.withWebview(webview => webview.hideFind()); } runFindAction(previous: boolean): void { this.withWebview(webview => webview.runFindAction(previous)); } + selectAll(): void { this.withWebview(webview => webview.selectAll()); } public getInnerWebview() { return this._webview.value; diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 52b0c3f3c6..e398e631d3 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -232,23 +232,23 @@ * @param {MouseEvent} event */ const handleAuxClick = - (event) => { - // Prevent middle clicks opening a broken link in the browser - if (!event.view || !event.view.document) { - return; - } - - if (event.button === 1) { - let node = /** @type {any} */ (event.target); - while (node) { - if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { - event.preventDefault(); - break; - } - node = node.parentNode; + (event) => { + // Prevent middle clicks opening a broken link in the browser + if (!event.view || !event.view.document) { + return; } - } - }; + + if (event.button === 1) { + let node = /** @type {any} */ (event.target); + while (node) { + if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { + event.preventDefault(); + break; + } + node = node.parentNode; + } + } + }; /** * @param {KeyboardEvent} e @@ -449,6 +449,10 @@ }, 0); }); + /** + * @param {Document} contentDocument + * @param {Window} contentWindow + */ const onLoad = (contentDocument, contentWindow) => { if (contentDocument && contentDocument.body) { // Workaround for https://github.com/Microsoft/vscode/issues/12865 @@ -492,10 +496,12 @@ }, 200); newFrame.contentWindow.addEventListener('load', function (e) { + const contentDocument = /** @type {Document} */ (e.target); + if (loadTimeout) { clearTimeout(loadTimeout); loadTimeout = undefined; - onLoad(e.target, this); + onLoad(contentDocument, this); } }); @@ -539,6 +545,13 @@ initData.initialScrollProgress = progress; }); + host.onMessage('execCommand', (_event, data) => { + const target = getActiveFrame(); + if (!target) { + return; + } + target.contentDocument.execCommand(data); + }); trackFocus({ onFocus: () => host.postMessage('did-focus'), diff --git a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts index f1c65ce83a..300b81385c 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts @@ -14,7 +14,7 @@ import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/wor import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { webviewDeveloperCategory } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/browser/webviewEditorInputFactory'; -import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetAction, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand } from '../browser/webviewCommands'; +import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetAction, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand, SelectAllWebviewEditorCommand } from '../browser/webviewCommands'; import { WebviewEditor } from './webviewEditor'; import { WebviewInput } from './webviewEditorInput'; import { IWebviewWorkbenchService, WebviewEditorService } from './webviewWorkbenchService'; @@ -50,6 +50,11 @@ registerAction2(class extends WebViewEditorFindPreviousCommand { constructor() { super(webviewActiveContextKeyExpr); } }); +registerAction2(class extends SelectAllWebviewEditorCommand { + constructor() { super(webviewActiveContextKeyExpr); } +}); + + const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); actionRegistry.registerWorkbenchAction( SyncActionDescriptor.create(ReloadWebviewAction, ReloadWebviewAction.ID, ReloadWebviewAction.LABEL), diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index a7ff805ac8..7aadbe6c95 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -93,6 +93,8 @@ export interface Webview extends IDisposable { hideFind(): void; runFindAction(previous: boolean): void; + selectAll(): void; + windowDidDragStart(): void; windowDidDragEnd(): void; } diff --git a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts index 1da029bd8a..61c6b710e6 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewCommands.ts @@ -13,6 +13,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; export class ShowWebViewEditorFindWidgetAction extends Action2 { public static readonly ID = 'editor.action.webvieweditor.showFind'; @@ -97,6 +98,29 @@ export class WebViewEditorFindPreviousCommand extends Action2 { getActiveWebviewEditor(accessor)?.find(true); } } + +export class SelectAllWebviewEditorCommand extends Action2 { + public static readonly ID = 'editor.action.webvieweditor.selectAll'; + public static readonly LABEL = nls.localize('editor.action.webvieweditor.selectAll', 'Select all'); + + constructor(contextKeyExpr: ContextKeyExpr) { + const precondition = ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)); + super({ + id: SelectAllWebviewEditorCommand.ID, + title: SelectAllWebviewEditorCommand.LABEL, + keybinding: { + when: precondition, + primary: KeyMod.CtrlCmd | KeyCode.KEY_A, + weight: KeybindingWeight.EditorContrib + } + }); + } + + public run(accessor: ServicesAccessor, args: any): void { + getActiveWebviewEditor(accessor)?.selectAll(); + } +} + export class ReloadWebviewAction extends Action { static readonly ID = 'workbench.action.webview.reloadWebviewAction'; static readonly LABEL = nls.localize('refreshWebviewLabel', "Reload Webviews"); diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index 2a027e02a9..7d1e0f0802 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -15,7 +15,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; -import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -71,31 +71,33 @@ export class WebviewEditor extends BaseEditor { } public showFind() { - this.withWebview(webview => { - webview.showFind(); + if (this.webview) { + this.webview.showFind(); this._findWidgetVisible.set(true); - }); + } } public hideFind() { this._findWidgetVisible.reset(); - this.withWebview(webview => webview.hideFind()); + this.webview?.hideFind(); } public find(previous: boolean) { - this.withWebview(webview => { - webview.runFindAction(previous); - }); + this.webview?.runFindAction(previous); + } + + public selectAll() { + this.webview?.selectAll(); } public reload() { - this.withWebview(webview => webview.reload()); + this.webview?.reload(); } public layout(dimension: DOM.Dimension): void { this._dimension = dimension; - if (this.input && this.input instanceof WebviewInput) { - this.synchronizeWebviewContainerDimensions(this.input.webview, dimension); + if (this.webview) { + this.synchronizeWebviewContainerDimensions(this.webview, dimension); } } @@ -109,22 +111,19 @@ export class WebviewEditor extends BaseEditor { } }); } - this.withWebview(webview => webview.focus()); + this.webview?.focus(); } - public withWebview(f: (element: Webview) => void): void { - if (this.input && this.input instanceof WebviewInput) { - f(this.input.webview); - } + public get webview(): WebviewEditorOverlay | undefined { + return this.input instanceof WebviewInput ? this.input.webview : undefined; } protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - if (this.input instanceof WebviewInput) { - const webview = this.input.webview; + if (this.input instanceof WebviewInput && this.webview) { if (visible) { - webview.claim(this); + this.webview.claim(this); } else { - webview.release(this); + this.webview.release(this); } this.claimWebview(this.input); } @@ -132,8 +131,8 @@ export class WebviewEditor extends BaseEditor { } public clearInput() { - if (this.input && this.input instanceof WebviewInput) { - this.input.webview.release(this); + if (this.webview) { + this.webview.release(this); this._webviewVisibleDisposables.clear(); } @@ -145,8 +144,8 @@ export class WebviewEditor extends BaseEditor { return; } - if (this.input && this.input instanceof WebviewInput) { - this.input.webview.release(this); + if (this.webview) { + this.webview.release(this); } await super.setInput(input, options, token); @@ -189,15 +188,11 @@ export class WebviewEditor extends BaseEditor { } this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.DRAG_START, () => { - if (this.input instanceof WebviewInput) { - this.input.webview.windowDidDragStart(); - } + this.webview?.windowDidDragStart(); })); const onDragEnd = () => { - if (this.input instanceof WebviewInput) { - this.input.webview.windowDidDragEnd(); - } + this.webview?.windowDidDragEnd(); }; this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.DRAG_END, onDragEnd)); this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.MOUSE_MOVE, currentEvent => { diff --git a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts index 00fd2535ed..6533f1868f 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts @@ -26,8 +26,6 @@ actionRegistry.registerWorkbenchAction( function registerWebViewCommands(editorId: string): void { const contextKeyExpr = ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', editorId), ContextKeyExpr.not('editorFocus') /* https://github.com/Microsoft/vscode/issues/58668 */)!; - registerAction2(class extends webviewCommands.SelectAllWebviewEditorCommand { constructor() { super(contextKeyExpr); } }); - // These commands are only needed on MacOS where we have to disable the menu bar commands if (isMacintosh) { registerAction2(class extends webviewCommands.CopyWebviewEditorCommand { constructor() { super(contextKeyExpr); } }); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts index f128642f08..d4aa1c6c74 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts @@ -38,28 +38,6 @@ export class OpenWebviewDeveloperToolsAction extends Action { } } -export class SelectAllWebviewEditorCommand extends Action2 { - public static readonly ID = 'editor.action.webvieweditor.selectAll'; - public static readonly LABEL = nls.localize('editor.action.webvieweditor.selectAll', 'Select all'); - - constructor(contextKeyExpr: ContextKeyExpr) { - const precondition = ContextKeyExpr.and(contextKeyExpr, ContextKeyExpr.not(InputFocusedContextKey)); - super({ - id: SelectAllWebviewEditorCommand.ID, - title: SelectAllWebviewEditorCommand.LABEL, - keybinding: { - when: precondition, - primary: KeyMod.CtrlCmd | KeyCode.KEY_A, - weight: KeybindingWeight.EditorContrib - } - }); - } - - public run(accessor: ServicesAccessor, args: any): void { - withActiveWebviewBasedWebview(accessor, webview => webview.selectAll()); - } -} - export class CopyWebviewEditorCommand extends Action2 { public static readonly ID = 'editor.action.webvieweditor.copy'; public static readonly LABEL = nls.localize('editor.action.webvieweditor.copy', "Copy2"); @@ -77,7 +55,7 @@ export class CopyWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - withActiveWebviewBasedWebview(accessor, webview => webview.copy()); + getActiveWebviewBasedWebview(accessor)?.copy(); } } @@ -98,7 +76,7 @@ export class PasteWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - withActiveWebviewBasedWebview(accessor, webview => webview.paste()); + getActiveWebviewBasedWebview(accessor)?.paste(); } } @@ -119,7 +97,7 @@ export class CutWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor): void { - withActiveWebviewBasedWebview(accessor, webview => webview.cut()); + getActiveWebviewBasedWebview(accessor)?.cut(); } } @@ -140,7 +118,7 @@ export class UndoWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor, args: any): void { - withActiveWebviewBasedWebview(accessor, webview => webview.undo()); + getActiveWebviewBasedWebview(accessor)?.undo(); } } @@ -163,22 +141,24 @@ export class RedoWebviewEditorCommand extends Action2 { } public run(accessor: ServicesAccessor, args: any): void { - withActiveWebviewBasedWebview(accessor, webview => webview.redo()); + getActiveWebviewBasedWebview(accessor)?.redo(); } } -function withActiveWebviewBasedWebview(accessor: ServicesAccessor, f: (webview: ElectronWebviewBasedWebview) => void): void { - const webViewEditor = getActiveWebviewEditor(accessor); - if (webViewEditor) { - webViewEditor.withWebview(webview => { - if (webview instanceof ElectronWebviewBasedWebview) { - f(webview); - } else if ((webview as WebviewEditorOverlay).getInnerWebview) { - const innerWebview = (webview as WebviewEditorOverlay).getInnerWebview(); - if (innerWebview instanceof ElectronWebviewBasedWebview) { - f(innerWebview); - } - } - }); +function getActiveWebviewBasedWebview(accessor: ServicesAccessor): ElectronWebviewBasedWebview | undefined { + const webview = getActiveWebviewEditor(accessor)?.webview; + if (!webview) { + return undefined; } + + if (webview instanceof ElectronWebviewBasedWebview) { + return webview; + } else if ((webview as WebviewEditorOverlay).getInnerWebview) { + const innerWebview = (webview as WebviewEditorOverlay).getInnerWebview(); + if (innerWebview instanceof ElectronWebviewBasedWebview) { + return innerWebview; + } + } + + return undefined; } diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index 7a008e381b..2c9163e741 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -22,6 +22,7 @@ import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench import { IElectronService } from 'vs/platform/electron/node/electron'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import product from 'vs/platform/product/common/product'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; // {{SQL CARBON EDIT}} add import @@ -333,8 +334,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten (function registerJSONSchemas(): void { const argvDefinitionFileSchemaId = 'vscode://schemas/argv'; const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); - - jsonRegistry.registerSchema(argvDefinitionFileSchemaId, { + const schema: IJSONSchema = { id: argvDefinitionFileSchemaId, allowComments: true, allowTrailingCommas: true, @@ -355,5 +355,13 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten description: nls.localize('argv.disableColorCorrectRendering', 'Resolves issues around color profile selection. ONLY change this option if you encounter graphic issues.') } } - }); + }; + if (isLinux) { + schema.properties!['force-renderer-accessibility'] = { + type: 'boolean', + description: nls.localize('argv.force-renderer-accessibility', 'Forces the renderer to be accessible. ONLY change this if you are using a screen reader on Linux. On other platforms the renderer will automatically be accessible. This flag is automatically set if you have editor.accessibilitySupport: on.'), + }; + } + + jsonRegistry.registerSchema(argvDefinitionFileSchemaId, schema); })(); diff --git a/src/vs/workbench/services/accessibility/node/accessibilityService.ts b/src/vs/workbench/services/accessibility/node/accessibilityService.ts index 83845518a8..ae308e1d8d 100644 --- a/src/vs/workbench/services/accessibility/node/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/node/accessibilityService.ts @@ -4,13 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { isWindows } from 'vs/base/common/platform'; +import { isWindows, isLinux } from 'vs/base/common/platform'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Registry } from 'vs/platform/registry/common/platform'; import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; interface AccessibilityMetrics { enabled: boolean; @@ -65,3 +70,22 @@ export class NodeAccessibilityService extends AccessibilityService implements IA } registerSingleton(IAccessibilityService, NodeAccessibilityService, true); + +// On linux we do not automatically detect that a screen reader is detected, thus we have to implicitly notify the renderer to enable accessibility when user configures it in settings +class LinuxAccessibilityContribution implements IWorkbenchContribution { + constructor( + @IJSONEditingService jsonEditingService: IJSONEditingService, + @IAccessibilityService accessibilityService: AccessibilityService, + @IEnvironmentService environmentService: IEnvironmentService + ) { + accessibilityService.onDidChangeScreenReaderOptimized(async () => { + if (accessibilityService.isScreenReaderOptimized()) { + await jsonEditingService.write(environmentService.argvResource, [{ key: 'force-renderer-accessibility', value: true }], true); + } + }); + } +} + +if (isLinux) { + Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LinuxAccessibilityContribution, LifecyclePhase.Ready); +} diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index e96aea117b..da94e91e2e 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -95,6 +95,10 @@ export class BrowserHostService extends Disposable implements IHostService { return document.hasFocus(); } + async hadLastFocus(): Promise { + return true; + } + async focus(): Promise { window.focus(); } diff --git a/src/vs/workbench/services/host/browser/host.ts b/src/vs/workbench/services/host/browser/host.ts index 74678cd205..ca472fede8 100644 --- a/src/vs/workbench/services/host/browser/host.ts +++ b/src/vs/workbench/services/host/browser/host.ts @@ -25,6 +25,11 @@ export interface IHostService { */ readonly hasFocus: boolean; + /** + * Find out if the window had the last focus. + */ + hadLastFocus(): Promise; + /** * Attempt to bring the window to the foreground and focus it. */ diff --git a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts index dcd3396c4f..0316a9b248 100644 --- a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts +++ b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts @@ -36,6 +36,16 @@ export class DesktopHostService extends Disposable implements IHostService { return document.hasFocus(); } + async hadLastFocus(): Promise { + const activeWindowId = await this.electronService.getActiveWindowId(); + + if (typeof activeWindowId === 'undefined') { + return false; + } + + return activeWindowId === this.electronEnvironmentService.windowId; + } + openWindow(options?: IOpenEmptyWindowOptions): Promise; openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise; openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise { diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 65ad8b6773..fdff42bff3 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -79,7 +79,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private lastResolvedFileStat: IFileStatWithMetadata | undefined; private readonly saveSequentializer = new TaskSequentializer(); - private lastSaveAttemptTime = 0; private dirty = false; private inConflictMode = false; @@ -553,16 +552,15 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return false; // if model is in save conflict or error, do not save unless save reason is explicit } + // Actually do save and log this.logService.trace('[text file model] save() - enter', this.resource.toString()); - await this.doSave(options); - this.logService.trace('[text file model] save() - exit', this.resource.toString()); return true; } - private doSave(options: ITextFileSaveOptions): Promise { + private async doSave(options: ITextFileSaveOptions): Promise { if (typeof options.reason !== 'number') { options.reason = SaveReason.EXPLICIT; } @@ -587,7 +585,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (!options.force && !this.dirty) { this.logService.trace(`[text file model] doSave(${versionId}) - exit - because not dirty and/or versionId is different (this.isDirty: ${this.dirty}, this.versionId: ${this.versionId})`, this.resource.toString()); - return Promise.resolve(); + return; } // Return if currently saving by storing this save request as the next save that should happen. @@ -618,26 +616,22 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.textEditorModel.pushStackElement(); } - // A save participant can still change the model now and since we are so close to saving - // we do not want to trigger another auto save or similar, so we block this - // In addition we update our version right after in case it changed because of a model change - // - // Save participants can also be skipped through API. const saveParticipantCancellation = new CancellationTokenSource(); - let saveParticipantPromise: Promise = Promise.resolve(versionId); - if (this.isResolved() && this.textFileService.saveParticipant && !options.skipSaveParticipants) { - const onCompleteOrError = () => { - this.ignoreDirtyOnModelContentChange = false; - return this.versionId; - }; + return (this.saveSequentializer as TaskSequentializer).setPending(versionId, (async () => { // {{SQL CARBON EDIT}} strict-null-checks - this.ignoreDirtyOnModelContentChange = true; - saveParticipantPromise = this.textFileService.saveParticipant.participate(this, { reason: options.reason }, saveParticipantCancellation.token).then(onCompleteOrError, onCompleteOrError); - } - - // mark the save participant as current pending save operation - return (this.saveSequentializer as TaskSequentializer).setPending(versionId, saveParticipantPromise.then(newVersionId => { // {{SQL CARBON EDIT}} strict-null-check + // A save participant can still change the model now and since we are so close to saving + // we do not want to trigger another auto save or similar, so we block this + // In addition we update our version right after in case it changed because of a model change + // + // Save participants can also be skipped through API. + if (this.isResolved() && this.textFileService.saveParticipant && !options.skipSaveParticipants) { + try { + await this.textFileService.saveParticipant.participate(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveParticipantCancellation.token); + } catch (error) { + // Ignore + } + } // We have to protect against being disposed at this point. It could be that the save() operation // was triggerd followed by a dispose() operation right after without waiting. Typically we cannot @@ -661,32 +655,39 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // - the model is not dirty (otherwise we know there are changed which needs to go to the file) // - the model is not in orphan mode (because in that case we know the file does not exist on disk) // - the model version did not change due to save participants running - if (options.force && !this.dirty && !this.inOrphanMode && options.reason === SaveReason.EXPLICIT && versionId === newVersionId) { - return this.doTouch(newVersionId, options.reason); + if (options.force && !this.dirty && !this.inOrphanMode && options.reason === SaveReason.EXPLICIT && versionId === this.versionId) { + return this.doTouch(this.versionId, options.reason); } // update versionId with its new value (if pre-save changes happened) - versionId = newVersionId; + versionId = this.versionId; // Clear error flag since we are trying to save again this.inErrorMode = false; - // Remember when this model was saved last - this.lastSaveAttemptTime = Date.now(); - - // Save to Disk - // mark the save operation as currently pending with the versionId (it might have changed from a save participant triggering) + // Save to Disk. We mark the save operation as currently pending with + // the latest versionId because it might have changed from a save + // participant triggering this.logService.trace(`[text file model] doSave(${versionId}) - before write()`, this.resource.toString()); const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat); - return this.saveSequentializer.setPending(newVersionId, this.textFileService.write(lastResolvedFileStat.resource, this.createSnapshot(), { - overwriteReadonly: options.overwriteReadonly, - overwriteEncoding: options.overwriteEncoding, - mtime: lastResolvedFileStat.mtime, - encoding: this.getEncoding(), - etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource, this.getMode())) ? ETAG_DISABLED : lastResolvedFileStat.etag, - writeElevated: options.writeElevated - }).then(stat => this.handleSaveSuccess(stat, versionId, options), error => this.handleSaveError(error, versionId, options))); - }), () => saveParticipantCancellation.cancel()); + const textFileEdiorModel = this; + return this.saveSequentializer.setPending(versionId, (async () => { + try { + const stat = await this.textFileService.write(lastResolvedFileStat.resource, textFileEdiorModel.createSnapshot(), { + overwriteReadonly: options.overwriteReadonly, + overwriteEncoding: options.overwriteEncoding, + mtime: lastResolvedFileStat.mtime, + encoding: this.getEncoding(), + etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource, textFileEdiorModel.getMode())) ? ETAG_DISABLED : lastResolvedFileStat.etag, + writeElevated: options.writeElevated + }); + + this.handleSaveSuccess(stat, versionId, options); + } catch (error) { + this.handleSaveError(error, versionId, options); + } + })()); + })(), () => saveParticipantCancellation.cancel()); } private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: ITextFileSaveOptions): void { @@ -733,19 +734,24 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private doTouch(this: TextFileEditorModel & IResolvedTextFileEditorModel, versionId: number, reason: SaveReason): Promise { const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat); - return this.saveSequentializer.setPending(versionId, this.textFileService.write(lastResolvedFileStat.resource, this.createSnapshot(), { - mtime: lastResolvedFileStat.mtime, - encoding: this.getEncoding(), - etag: lastResolvedFileStat.etag - }).then(stat => { - // Updated resolved stat with updated stat since touching it might have changed mtime - this.updateLastResolvedFileStat(stat); + return this.saveSequentializer.setPending(versionId, (async () => { + try { + const stat = await this.textFileService.write(lastResolvedFileStat.resource, this.createSnapshot(), { + mtime: lastResolvedFileStat.mtime, + encoding: this.getEncoding(), + etag: lastResolvedFileStat.etag + }); - // Emit File Saved Event - this._onDidSave.fire(reason); + // Updated resolved stat with updated stat since touching it might have changed mtime + this.updateLastResolvedFileStat(stat); - }, error => onUnexpectedError(error) /* just log any error but do not notify the user since the file was not dirty */)); + // Emit File Saved Event + this._onDidSave.fire(reason); + } catch (error) { + onUnexpectedError(error); // just log any error but do not notify the user since the file was not dirty + } + })()); } private updateSavedVersionId(): void { @@ -776,10 +782,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil //#endregion - getLastSaveAttemptTime(): number { - return this.lastSaveAttemptTime; - } - hasState(state: ModelState): boolean { switch (state) { case ModelState.CONFLICT: diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index ff4b88d2af..12b47bc0c8 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -84,7 +84,13 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // to have a size of 2 (1 running load and 1 queued load). const queue = this.modelLoadQueue.queueFor(model.resource); if (queue.size <= 1) { - queue.queue(() => model.load().then(undefined, onUnexpectedError)); + queue.queue(async () => { + try { + await model.load(); + } catch (error) { + onUnexpectedError(error); + } + }); } } diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts index 6f9df55635..db989be8f0 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -120,7 +120,6 @@ suite('Files - TextFileEditorModel', () => { await pendingSave; - assert.ok(model.getLastSaveAttemptTime() <= Date.now()); assert.ok(model.hasState(ModelState.SAVED)); assert.ok(!model.isDirty()); assert.ok(savedEvent); @@ -488,8 +487,6 @@ suite('Files - TextFileEditorModel', () => { assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async2.txt'))); assert.ok(assertIsDefined(model1.getStat()).mtime > m1Mtime); assert.ok(assertIsDefined(model2.getStat()).mtime > m2Mtime); - assert.ok(model1.getLastSaveAttemptTime() > m1Mtime); - assert.ok(model2.getLastSaveAttemptTime() > m2Mtime); model1.dispose(); model2.dispose(); @@ -506,12 +503,11 @@ suite('Files - TextFileEditorModel', () => { }); accessor.textFileService.saveParticipant = { - participate: model => { + participate: async model => { assert.ok(model.isDirty()); model.textEditorModel!.setValue('bar'); assert.ok(model.isDirty()); eventCounter++; - return Promise.resolve(); } }; @@ -545,8 +541,8 @@ suite('Files - TextFileEditorModel', () => { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); accessor.textFileService.saveParticipant = { - participate: (model) => { - return Promise.reject(new Error('boom')); + participate: async model => { + new Error('boom'); } }; @@ -563,10 +559,9 @@ suite('Files - TextFileEditorModel', () => { let participations: boolean[] = []; accessor.textFileService.saveParticipant = { - participate: (model) => { - return timeout(10).then(() => { - participations.push(true); - }); + participate: async model => { + await timeout(10); + participations.push(true); } }; @@ -586,4 +581,49 @@ suite('Files - TextFileEditorModel', () => { assert.equal(participations.length, 1); model.dispose(); }); + + test('Save Participant, calling save from within is unsupported but does not explode (sync save)', async function () { + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + + await testSaveFromSaveParticipant(model, false); + + model.dispose(); + }); + + test('Save Participant, calling save from within is unsupported but does not explode (async save)', async function () { + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + + await testSaveFromSaveParticipant(model, true); + + model.dispose(); + }); + + async function testSaveFromSaveParticipant(model: TextFileEditorModel, async: boolean): Promise { + let savePromise: Promise; + let breakLoop = false; + + accessor.textFileService.saveParticipant = { + participate: async model => { + if (breakLoop) { + return; + } + + breakLoop = true; + + if (async) { + await timeout(10); + } + const newSavePromise = model.save(); + + // assert that this is the same promise as the outer one + assert.equal(savePromise, newSavePromise); + } + }; + + await model.load(); + model.textEditorModel!.setValue('foo'); + + savePromise = model.save(); + await savePromise; + } }); diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index b7feb5b4e9..7d67cbe38d 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -537,6 +537,22 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } + // If a value is not present in the cache, it must be reset to default + this.viewContainersRegistry.all.forEach(viewContainer => { + const viewDescriptorCollection = this.getViewDescriptors(viewContainer); + viewDescriptorCollection.allViewDescriptors.forEach(viewDescriptor => { + if (!newCachedPositions.has(viewDescriptor.id)) { + const currentContainer = this.getViewContainer(viewDescriptor.id); + const defaultContainer = this.getDefaultContainer(viewDescriptor.id); + if (currentContainer && defaultContainer && currentContainer !== defaultContainer) { + this.moveViews([viewDescriptor], currentContainer, defaultContainer); + } + + this.cachedViewInfo.delete(viewDescriptor.id); + } + }); + }); + this.cachedViewInfo = this.getCachedViewPositions(); } } @@ -571,6 +587,16 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor }); }); + // Do no save default positions to the cache + // so that default changes can be recognized + // https://github.com/microsoft/vscode/issues/90414 + for (const [viewId, containerInfo] of this.cachedViewInfo) { + const defaultContainer = this.getDefaultContainer(viewId); + if (defaultContainer?.id === containerInfo.containerId) { + this.cachedViewInfo.delete(viewId); + } + } + this.cachedViewPositionsValue = JSON.stringify([...this.cachedViewInfo]); } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 1c040077a8..29e40f1583 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -865,6 +865,7 @@ export class TestHostService implements IHostService { _serviceBrand: undefined; readonly hasFocus: boolean = true; + async hadLastFocus(): Promise { return true; } readonly onDidChangeFocus: Event = Event.None; async restart(): Promise { } diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index c70682d411..93bde5a9d2 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -80,17 +80,29 @@ interface IApplicationLink { uri: URI; /** - * A label for the link to display. + * A label for the application link to display. */ label: string; } -interface IApplicationLinkProvider { - (): IApplicationLink[] | undefined +interface ICommand { + + /** + * An identifier for the command. Commands can be executed from extensions + * using the `vscode.commands.executeCommand` API using that command ID. + */ + id: string, + + /** + * A function that is being executed with any arguments passed over. + */ + handler: (...args: any[]) => void; } interface IWorkbenchConstructionOptions { + //#region Connection related configuration + /** * The remote authority is the IP:PORT from where the workbench is served * from. It is for example being used for the websocket connections as address. @@ -108,6 +120,36 @@ interface IWorkbenchConstructionOptions { */ readonly webviewEndpoint?: string; + /** + * A factory for web sockets. + */ + readonly webSocketFactory?: IWebSocketFactory; + + /** + * A provider for resource URIs. + */ + readonly resourceUriProvider?: IResourceUriProvider; + + /** + * Resolves an external uri before it is opened. + */ + readonly resolveExternalUri?: IExternalUriResolver; + + /** + * Support for creating tunnels. + */ + readonly tunnelFactory?: ITunnelFactory; + + /** + * Support for filtering candidate ports + */ + readonly showCandidate?: IShowCandidate; + + //#endregion + + + //#region Workbench configuration + /** * A handler for opening workspaces and providing the initial workspace. */ @@ -119,16 +161,6 @@ interface IWorkbenchConstructionOptions { */ userDataProvider?: IFileSystemProvider; - /** - * A factory for web sockets. - */ - readonly webSocketFactory?: IWebSocketFactory; - - /** - * A provider for resource URIs. - */ - readonly resourceUriProvider?: IResourceUriProvider; - /** * The credentials provider to store and retrieve secrets. */ @@ -154,21 +186,6 @@ interface IWorkbenchConstructionOptions { */ readonly resolveCommonTelemetryProperties?: ICommontTelemetryPropertiesResolver; - /** - * Resolves an external uri before it is opened. - */ - readonly resolveExternalUri?: IExternalUriResolver; - - /** - * Support for creating tunnels. - */ - readonly tunnelFactory?: ITunnelFactory; - - /** - * Support for filtering candidate ports - */ - readonly showCandidate?: IShowCandidate; - /** * Provide entries for the "Open in Desktop" feature. * @@ -179,7 +196,20 @@ interface IWorkbenchConstructionOptions { * - N elements: there will be a "Open in Desktop" affordance that opens * a picker on click to select which application to open. */ - readonly applicationLinkProvider?: IApplicationLinkProvider; + readonly applicationLinks?: readonly IApplicationLink[]; + + /** + * A set of optional commands that should be registered with the commands + * registry. + * + * Note: commands can be called from extensions if the identifier is known! + */ + readonly commands?: readonly ICommand[]; + + //#endregion + + + //#region Diagnostics /** * Current logging level. Default is `LogLevel.Info`. @@ -190,20 +220,8 @@ interface IWorkbenchConstructionOptions { * Whether to enable the smoke test driver. */ readonly driver?: boolean; -} -interface ICommandHandler { - (...args: any[]): void; -} - -interface IWorkbench { - - /** - * Register a command with the provided identifier and handler with - * the workbench. The command can be called from extensions using the - * `vscode.commands.executeCommand` API. - */ - registerCommand(id: string, command: ICommandHandler): IDisposable; + //#endregion } /** @@ -211,24 +229,22 @@ interface IWorkbench { * * @param domElement the container to create the workbench in * @param options for setting up the workbench - * - * @returns the workbench facade with additional methods to call on. */ -async function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise { +async function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise { // Startup workbench await main(domElement, options); - // Return facade - return { - registerCommand: (id: string, command: ICommandHandler): IDisposable => { - return CommandsRegistry.registerCommand(id, (accessor, ...args: any[]) => { + // Register commands if any + if (Array.isArray(options.commands)) { + for (const command of options.commands) { + CommandsRegistry.registerCommand(command.id, (accessor, ...args: any[]) => { // we currently only pass on the arguments but not the accessor // to the command to reduce our exposure of internal API. - command(...args); + command.handler(...args); }); } - }; + } } export { @@ -237,9 +253,6 @@ export { create, IWorkbenchConstructionOptions, - // Workbench Facade - IWorkbench, - ICommandHandler, // Basic Types URI, @@ -291,5 +304,7 @@ export { // Protocol Links IApplicationLink, - IApplicationLinkProvider + + // Commands + ICommand }; diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index 8cd69fbcb9..a41bcb12a0 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -141,10 +141,12 @@ export function connect(engine: 'chromium' | 'webkit' | 'firefox' = 'chromium'): return new Promise(async (c) => { const browser = await playwright[engine].launch({ // Run in Edge dev on macOS - // executablePath: '/Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev' + // executablePath: '/Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev', + headless: false }); - const page = (await browser.defaultContext().pages())[0]; - await page.setViewport({ width, height }); + const context = await browser.newContext(); + const page = await context.newPage(); + await page.setViewportSize({ width, height }); await page.goto(`${endpoint}&folder=vscode-remote://localhost:9888${URI.file(workspacePath!).path}`); const result = { client: { dispose: () => browser.close() && teardown() }, diff --git a/test/integration/browser/package.json b/test/integration/browser/package.json index d43ee230c4..beb4142c55 100644 --- a/test/integration/browser/package.json +++ b/test/integration/browser/package.json @@ -9,6 +9,7 @@ "devDependencies": { "@types/mkdirp": "0.5.1", "@types/node": "^12.11.7", + "@types/optimist": "0.0.29", "@types/rimraf": "2.0.2", "@types/tmp": "^0.1.0", "rimraf": "^2.6.1", diff --git a/test/integration/browser/src/index.ts b/test/integration/browser/src/index.ts index 9cdbfad551..0b05cc9b29 100644 --- a/test/integration/browser/src/index.ts +++ b/test/integration/browser/src/index.ts @@ -11,8 +11,9 @@ import * as tmp from 'tmp'; import * as rimraf from 'rimraf'; import { URI } from 'vscode-uri'; import * as kill from 'tree-kill'; +import * as optimistLib from 'optimist'; -const optimist = require('optimist') +const optimist = optimistLib .describe('workspacePath', 'path to the workspace to open in the test').string('workspacePath') .describe('extensionDevelopmentPath', 'path to the extension to test').string('extensionDevelopmentPath') .describe('extensionTestsPath', 'path to the extension tests').string('extensionTestsPath') @@ -28,11 +29,12 @@ if (optimist.argv.help) { const width = 1200; const height = 800; -async function runTestsInBrowser(browserType: string, endpoint: url.UrlWithStringQuery, server: cp.ChildProcess): Promise { +async function runTestsInBrowser(browserType: 'chromium' | 'firefox' | 'webkit', endpoint: url.UrlWithStringQuery, server: cp.ChildProcess): Promise { const args = process.platform === 'linux' && browserType === 'chromium' ? ['--no-sandbox'] : undefined; // disable sandbox to run chrome on certain Linux distros const browser = await playwright[browserType].launch({ headless: !Boolean(optimist.argv.debug), dumpio: true, args }); - const page = (await browser.defaultContext().pages())[0]; - await page.setViewport({ width, height }); + const context = await browser.newContext(); + const page = await context.newPage(); + await page.setViewportSize({ width, height }); const host = endpoint.host; const protocol = 'vscode-remote'; diff --git a/test/integration/browser/yarn.lock b/test/integration/browser/yarn.lock index 35884dcfc5..126da8138c 100644 --- a/test/integration/browser/yarn.lock +++ b/test/integration/browser/yarn.lock @@ -38,6 +38,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.26.tgz#213e153babac0ed169d44a6d919501e68f59dea9" integrity sha512-UmUm94/QZvU5xLcUlNR8hA7Ac+fGpO1EG/a8bcWVz0P0LqtxFmun9Y2bbtuckwGboWJIT70DoWq1r3hb56n3DA== +"@types/optimist@0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/optimist/-/optimist-0.0.29.tgz#a8873580b3a84b69ac1e687323b15fbbeb90479a" + integrity sha1-qIc1gLOoS2msHmhzI7Ffu+uQR5o= + "@types/rimraf@2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.2.tgz#7f0fc3cf0ff0ad2a99bb723ae1764f30acaf8b6e" diff --git a/test/unit/browser/index.js b/test/unit/browser/index.js index 41c92f4261..98acb5c60f 100644 --- a/test/unit/browser/index.js +++ b/test/unit/browser/index.js @@ -120,7 +120,8 @@ const testModules = (async function () { async function runTestsInBrowser(testModules, browserType) { const args = process.platform === 'linux' && browserType === 'chromium' ? ['--no-sandbox'] : undefined; // disable sandbox to run chrome on certain Linux distros const browser = await playwright[browserType].launch({ headless: !Boolean(argv.debug), dumpio: true, args }); - const page = (await browser.defaultContext().pages())[0] + const context = await browser.newContext(); + const page = await context.newPage(); const target = url.pathToFileURL(path.join(__dirname, 'renderer.html')); if (argv.build) { target.search = `?build=true`; diff --git a/yarn.lock b/yarn.lock index 3dac5caf6e..ee2053a77a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6046,11 +6046,6 @@ mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.0.3: - version "2.4.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" - integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== - mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" @@ -7107,29 +7102,28 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -playwright-core@=0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-0.10.0.tgz#86699c9cc3e613d733e6635a54aceea1993013d5" - integrity sha512-yernA6yrrBhmb8M5eO6GZsJOrBKWOZszlu65Luz8LP7ryaDExN1sE9XjQBNbiwJ5Gfs8cehtAO7GfTDJt+Z2cQ== +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== dependencies: debug "^4.1.0" extract-zip "^1.6.6" https-proxy-agent "^3.0.0" jpeg-js "^0.3.6" - mime "^2.0.3" pngjs "^3.4.0" progress "^2.0.3" proxy-from-env "^1.0.0" - rimraf "^2.6.1" + rimraf "^3.0.2" uuid "^3.4.0" ws "^6.1.0" -playwright@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-0.10.0.tgz#d37f7e42e0e868dcc4ec35cb0a8dbc6248457642" - integrity sha512-f3VRME/PIO5NbcWnlCDfXwPC0DAZJ7ETkcAdE+sensLCOkfDtLh97E71ZuxNCaPYsUA6FIPi5syD8pHJW/4hQQ== +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== dependencies: - playwright-core "=0.10.0" + playwright-core "=0.11.0" plist@^3.0.1: version "3.0.1" @@ -8160,6 +8154,13 @@ rimraf@^2.4.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: dependencies: glob "^7.0.5" +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + rimraf@~2.2.6: version "2.2.8" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582"