From 9ae0fd36c91dd523cbc3483e4fbf5c897e4ae0c3 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Mon, 10 Feb 2020 10:24:33 -0500 Subject: [PATCH] Enhances timeline - commands, timestamp, etc Adds contributable commands to timeline items Adds right-aligned timestamp to timeline items Adds Open Changes to Git timeline items Adds Copy Commit ID to Git timeline items Adds Copy Commit Message to Git timeline items --- extensions/git/package.json | 54 +- extensions/git/package.nls.json | 3 + extensions/git/src/commands.ts | 39 +- extensions/git/src/timelineProvider.ts | 93 ++-- extensions/git/yarn.lock | 504 +----------------- src/vs/base/common/date.ts | 47 ++ src/vs/platform/actions/common/actions.ts | 4 +- .../api/browser/mainThreadTimeline.ts | 4 +- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 2 +- .../workbench/api/common/extHostTimeline.ts | 128 +++-- .../api/common/menusExtensionPoint.ts | 12 + .../timeline/browser/media/timelinePane.css | 17 + .../contrib/timeline/browser/timelinePane.ts | 221 ++++++-- .../contrib/timeline/common/timeline.ts | 14 +- .../timeline/common/timelineService.ts | 4 +- 16 files changed, 530 insertions(+), 618 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index c1cd8b5135c..b1ae36f57a7 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,10 @@ "@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" + "mocha-multi-reporters": "^1.1.7" } } diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index f51154cebf6..534ec69429a 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.", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index dba21595ea8..9d8fe2a4ef4 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, 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; } - return commands.executeCommand('vscode.diff', toGitUri(uri, lhs), rhs === '' ? uri : toGitUri(uri, rhs), title); + 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 a83436dc29e..9db3495aec7 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -5,7 +5,6 @@ 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, Timeline, TimelineChangeEvent, TimelineCursor, TimelineItem, TimelineProvider, Uri, workspace } from 'vscode'; import { Model } from './model'; import { Repository } from './repository'; @@ -13,11 +12,55 @@ 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 { @@ -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,16 +200,15 @@ 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); @@ -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 af5b10127dc..ff2f8994487 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -31,28 +31,16 @@ 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" integrity sha1-AW44dim4gXvtZT/jLqtdESecjfY= -agent-base@4, agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - 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 +55,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 +73,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 +95,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" @@ -197,13 +112,6 @@ debug@2.6.8: dependencies: ms "2.0.0" -debug@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - debug@^2.2.0: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -218,11 +126,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,92 +143,21 @@ 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" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - escape-string-regexp@1.0.5: version "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,98 +170,26 @@ 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: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - 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" - "graceful-readlink@>= 1.0.0": version "1.0.1" 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" integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= -http-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== - dependencies: - 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: - 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== - dependencies: - agent-base "^4.3.0" - debug "^3.1.0" - iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -455,61 +215,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 +300,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: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -648,23 +356,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 +366,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 +378,7 @@ 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== - 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" - -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 +388,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 +402,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 +414,11 @@ 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== - dependencies: - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.1" - 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/src/vs/base/common/date.ts b/src/vs/base/common/date.ts index a6da9d5ae33..acfc19d6edb 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/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 25a4db0972c..c30c37e1394 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -114,7 +114,9 @@ export class MenuId { static readonly CommentActions = new MenuId('CommentActions'); static readonly BulkEditTitle = new MenuId('BulkEditTitle'); static readonly BulkEditContext = new MenuId('BulkEditContext'); - + 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/workbench/api/browser/mainThreadTimeline.ts b/src/vs/workbench/api/browser/mainThreadTimeline.ts index f65a89b432c..428bf0ed2d9 100644 --- a/src/vs/workbench/api/browser/mainThreadTimeline.ts +++ b/src/vs/workbench/api/browser/mainThreadTimeline.ts @@ -39,8 +39,8 @@ export class MainThreadTimeline implements MainThreadTimelineShape { this._timelineService.registerTimelineProvider({ ...provider, onDidChange: onDidChange.event, - provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken) { - return proxy.$getTimeline(provider.id, uri, cursor, 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 61353fa2683..0d25c1176e7 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -133,7 +133,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 const expected: ProxyIdentifier[] = values(ExtHostContext); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 21d4884818e..b780bccbd4d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1457,7 +1457,7 @@ export interface ExtHostTunnelServiceShape { } export interface ExtHostTimelineShape { - $getTimeline(source: string, uri: UriComponents, cursor: TimelineCursor, 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/extHostTimeline.ts b/src/vs/workbench/api/common/extHostTimeline.ts index c4e3832649a..f5f63a2ce4d 100644 --- a/src/vs/workbench/api/common/extHostTimeline.ts +++ b/src/vs/workbench/api/common/extHostTimeline.ts @@ -7,55 +7,77 @@ import * as vscode from 'vscode'; import { UriComponents, URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ExtHostTimelineShape, MainThreadTimelineShape, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; -import { Timeline, TimelineCursor, 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, cursor: vscode.TimelineCursor, 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, cursor: vscode.TimelineCursor, 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), cursor, 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, cursor: TimelineCursor, token: CancellationToken) { + async provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }) { timelineDisposables.clear(); + // 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 @@ -63,10 +85,12 @@ export class ExtHostTimeline implements IExtHostTimeline { return undefined; } + // TODO: Determine if we should cache dependent on who calls us (internal vs external) + const convertItem = convertTimelineItem(uri, options?.cacheResults ?? false); return { ...result, source: provider.id, - items: result.items.map(convertTimelineItem) + items: result.items.map(convertItem) }; }, dispose() { @@ -76,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 }); }; } @@ -129,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/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index c79a3c16ab0..ab474c9825f 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -52,6 +52,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; @@ -215,6 +217,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/contrib/timeline/browser/media/timelinePane.css b/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css index ae431e80709..ad6200c6561 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 dfdc574d6a0..1afbedc6563 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.getTimeline(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)); @@ -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 { }) ); } + + private onContextMenu(menus: TimelineMenus, treeEvent: ITreeContextMenuEvent): void { + const item = treeEvent.element; + if (item === null) { + return; + } + const event: UIEvent = treeEvent.browserEvent; + + event.preventDefault(); + event.stopPropagation(); + + 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 TimelineElementTemplate { +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, - readonly iconLabel: IconLabel, - readonly icon: 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; + } } -export class TimelineIdentityProvider implements IIdentityProvider { - getId(item: TimelineItem): { toString(): string } { - return `${item.id}|${item.timestamp}`; +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: TimelineItem): { toString(): string } { +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; - renderTemplate(container: HTMLElement): TimelineElementTemplate { - DOM.addClass(container, 'custom-view-tree-node-item'); - const icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon')); + 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; + } - const iconLabel = new IconLabel(container, { supportHighlights: true, supportCodicons: true }); - return new TimelineElementTemplate(container, iconLabel, icon); + renderTemplate(container: HTMLElement): TimelineElementTemplate { + 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 4d3ece3caa7..7ffca608646 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,10 +30,6 @@ export interface TimelineItem { contextValue?: string; } -export interface TimelineItemWithSource extends TimelineItem { - source: string; -} - export interface TimelineChangeEvent { id: string; uri?: URI; @@ -45,7 +43,7 @@ export interface TimelineCursor { export interface Timeline { source: string; - items: TimelineItemWithSource[]; + items: TimelineItem[]; cursor?: any; more?: boolean; @@ -54,7 +52,7 @@ export interface Timeline { export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable { onDidChange?: Event; - provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken): Promise; + provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }): Promise; } export interface TimelineProviderDescriptor { @@ -86,7 +84,7 @@ export interface ITimelineService { getSources(): string[]; - getTimeline(id: string, uri: URI, pagination: TimelineCursor, 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 3fc416e7464..27038106272 100644 --- a/src/vs/workbench/contrib/timeline/common/timelineService.ts +++ b/src/vs/workbench/contrib/timeline/common/timelineService.ts @@ -81,7 +81,7 @@ export class TimelineService implements ITimelineService { return [...this._providers.keys()]; } - getTimeline(id: string, uri: URI, cursor: TimelineCursor, 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); @@ -98,7 +98,7 @@ export class TimelineService implements ITimelineService { } return { - result: provider.provideTimeline(uri, cursor, tokenSource.token) + result: provider.provideTimeline(uri, cursor, tokenSource.token, options) .then(result => { if (result === undefined) { return undefined; -- GitLab