Browse Source

Adds rich hovers to the status bar

main
Eric Amodio 3 years ago
parent
commit
20e6bfea4a
8 changed files with 179 additions and 50 deletions
  1. +3
    -0
      CHANGELOG.md
  2. +1
    -0
      README.md
  3. +6
    -0
      package.json
  4. +7
    -0
      src/annotations/blameAnnotationProvider.ts
  5. +1
    -0
      src/config.ts
  6. +37
    -15
      src/hovers/hovers.ts
  7. +7
    -0
      src/hovers/lineHoverController.ts
  8. +117
    -35
      src/statusbar/statusBarController.ts

+ 3
- 0
CHANGELOG.md View File

@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
### Added ### Added
- Adds a new rich commit details hover to the blame information in the status bar
- Adds a `gitlens.statusBar.tooltipFormat` setting to specify the format (in markdown) of hover shown over the blame information in the status bar
- Adds a new rich hover to the GitLens mode in the status bar
- Adds a new _Cherry Pick without Committing_ confirmation option to the _Git Command Palette_'s _cherry-pick_ command — closes [#1693](https://github.com/eamodio/vscode-gitlens/issues/1693) - Adds a new _Cherry Pick without Committing_ confirmation option to the _Git Command Palette_'s _cherry-pick_ command — closes [#1693](https://github.com/eamodio/vscode-gitlens/issues/1693)
- Adds new _Open File_ command (with _Open Revision_ as an `alt-click`) to files in comparisons — closes [#1710](https://github.com/eamodio/vscode-gitlens/issues/1710) - Adds new _Open File_ command (with _Open Revision_ as an `alt-click`) to files in comparisons — closes [#1710](https://github.com/eamodio/vscode-gitlens/issues/1710)

+ 1
- 0
README.md View File

@ -719,6 +719,7 @@ GitLens is highly customizable and provides many configuration settings to allow
| `gitlens.statusBar.format` | Specifies the format of the blame information in the status bar. See [_Commit Tokens_](https://github.com/eamodio/vscode-gitlens/wiki/Custom-Formatting#commit-tokens) in the GitLens docs. Date formatting is controlled by the `gitlens.statusBar.dateFormat` setting | | `gitlens.statusBar.format` | Specifies the format of the blame information in the status bar. See [_Commit Tokens_](https://github.com/eamodio/vscode-gitlens/wiki/Custom-Formatting#commit-tokens) in the GitLens docs. Date formatting is controlled by the `gitlens.statusBar.dateFormat` setting |
| `gitlens.statusBar.pullRequests.enabled` | Specifies whether to provide information about the Pull Request (if any) that introduced the commit in the status bar. Requires a connection to a supported remote service (e.g. GitHub) | | `gitlens.statusBar.pullRequests.enabled` | Specifies whether to provide information about the Pull Request (if any) that introduced the commit in the status bar. Requires a connection to a supported remote service (e.g. GitHub) |
| `gitlens.statusBar.reduceFlicker` | Specifies whether to avoid clearing the previous blame information when changing lines to reduce status bar "flashing" | | `gitlens.statusBar.reduceFlicker` | Specifies whether to avoid clearing the previous blame information when changing lines to reduce status bar "flashing" |
| `gitlens.statusBar.tooltipFormat` | Specifies the format (in markdown) of hover shown over the blame information in the status bar. See [_Commit Tokens_](https://github.com/eamodio/vscode-gitlens/wiki/Custom-Formatting#commit-tokens) in the GitLens docs |
## Hover Settings [#](#hover-settings- 'Hover Settings') ## Hover Settings [#](#hover-settings- 'Hover Settings')

+ 6
- 0
package.json View File

@ -1723,6 +1723,12 @@
"markdownDescription": "Specifies whether to avoid clearing the previous blame information when changing lines to reduce status bar \"flashing\"", "markdownDescription": "Specifies whether to avoid clearing the previous blame information when changing lines to reduce status bar \"flashing\"",
"scope": "window" "scope": "window"
}, },
"gitlens.statusBar.tooltipFormat": {
"type": "string",
"default": "${avatar}  __${author}__, ${ago}${' via 'pullRequest}   _(${date})_ \n\n${message}\n\n${commands}${\n\n---\n\nfootnotes}",
"markdownDescription": "Specifies the format (in markdown) of hover shown over the blame information in the status bar. See [_Commit Tokens_](https://github.com/eamodio/vscode-gitlens/wiki/Custom-Formatting#commit-tokens) in the GitLens docs",
"scope": "window"
},
"gitlens.strings.codeLens.unsavedChanges.recentChangeAndAuthors": { "gitlens.strings.codeLens.unsavedChanges.recentChangeAndAuthors": {
"type": "string", "type": "string",
"default": "$(ellipsis)", "default": "$(ellipsis)",

+ 7
- 0
src/annotations/blameAnnotationProvider.ts View File

@ -194,7 +194,14 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
logCommit ?? commit, logCommit ?? commit,
await GitUri.fromUri(document.uri), await GitUri.fromUri(document.uri),
editorLine, editorLine,
Container.config.hovers.detailsMarkdownFormat,
Container.config.defaultDateFormat, Container.config.defaultDateFormat,
{
autolinks: Container.config.hovers.autolinks.enabled,
pullRequests: {
enabled: Container.config.hovers.pullRequests.enabled,
},
},
); );
} }
} }

+ 1
- 0
src/config.ts View File

@ -134,6 +134,7 @@ export interface Config {
pullRequests: { pullRequests: {
enabled: boolean; enabled: boolean;
}; };
tooltipFormat: string;
}; };
strings: { strings: {
codeLens: { codeLens: {

+ 37
- 15
src/hovers/hovers.ts View File

@ -12,6 +12,7 @@ import {
GitLogCommit, GitLogCommit,
GitRemote, GitRemote,
GitRevision, GitRevision,
PullRequest,
} from '../git/git'; } from '../git/git';
import { GitUri } from '../git/gitUri'; import { GitUri } from '../git/gitUri';
import { Logger } from '../logger'; import { Logger } from '../logger';
@ -181,7 +182,19 @@ export namespace Hovers {
commit: GitCommit, commit: GitCommit,
uri: GitUri, uri: GitUri,
editorLine: number, editorLine: number,
format: string,
dateFormat: string | null, dateFormat: string | null,
options?: {
autolinks?: boolean;
pullRequests?: {
enabled: boolean;
pr?: PullRequest | Promises.CancellationError<Promise<PullRequest | undefined>>;
};
getBranchAndTagTips?: (
sha: string,
options?: { compact?: boolean | undefined; icons?: boolean | undefined },
) => string | undefined;
},
): Promise<MarkdownString> { ): Promise<MarkdownString> {
if (dateFormat === null) { if (dateFormat === null) {
dateFormat = 'MMMM Do, YYYY h:mma'; dateFormat = 'MMMM Do, YYYY h:mma';
@ -192,19 +205,32 @@ export namespace Hovers {
const [previousLineDiffUris, autolinkedIssuesOrPullRequests, pr, presence] = await Promise.all([ const [previousLineDiffUris, autolinkedIssuesOrPullRequests, pr, presence] = await Promise.all([
commit.isUncommitted ? commit.getPreviousLineDiffUris(uri, editorLine, uri.sha) : undefined, commit.isUncommitted ? commit.getPreviousLineDiffUris(uri, editorLine, uri.sha) : undefined,
getAutoLinkedIssuesOrPullRequests(commit.message, remotes), getAutoLinkedIssuesOrPullRequests(commit.message, remotes),
getPullRequestForCommit(commit.ref, remotes),
options?.pullRequests?.pr ??
getPullRequestForCommit(commit.ref, remotes, {
pullRequests:
options?.pullRequests?.enabled ||
CommitFormatter.has(
format,
'pullRequest',
'pullRequestAgo',
'pullRequestAgoOrDate',
'pullRequestDate',
'pullRequestState',
),
}),
Container.vsls.maybeGetPresence(commit.email), Container.vsls.maybeGetPresence(commit.email),
]); ]);
const details = await CommitFormatter.fromTemplateAsync(Container.config.hovers.detailsMarkdownFormat, commit, {
const details = await CommitFormatter.fromTemplateAsync(format, commit, {
autolinkedIssuesOrPullRequests: autolinkedIssuesOrPullRequests, autolinkedIssuesOrPullRequests: autolinkedIssuesOrPullRequests,
dateFormat: dateFormat, dateFormat: dateFormat,
editor: { editor: {
line: editorLine, line: editorLine,
uri: uri, uri: uri,
}, },
getBranchAndTagTips: options?.getBranchAndTagTips,
markdown: true, markdown: true,
messageAutolinks: Container.config.hovers.autolinks.enabled,
messageAutolinks: options?.autolinks,
pullRequestOrRemote: pr, pullRequestOrRemote: pr,
presence: presence, presence: presence,
previousLineDiffUris: previousLineDiffUris, previousLineDiffUris: previousLineDiffUris,
@ -303,23 +329,19 @@ export namespace Hovers {
} }
} }
async function getPullRequestForCommit(ref: string, remotes: GitRemote[]) {
async function getPullRequestForCommit(
ref: string,
remotes: GitRemote[],
options?: {
pullRequests?: boolean;
},
) {
const cc = Logger.getNewCorrelationContext('Hovers.getPullRequestForCommit'); const cc = Logger.getNewCorrelationContext('Hovers.getPullRequestForCommit');
Logger.debug(cc, `${GlyphChars.Dash} ref=${ref}`); Logger.debug(cc, `${GlyphChars.Dash} ref=${ref}`);
const start = process.hrtime(); const start = process.hrtime();
if (
!Container.config.hovers.pullRequests.enabled ||
!CommitFormatter.has(
Container.config.hovers.detailsMarkdownFormat,
'pullRequest',
'pullRequestAgo',
'pullRequestAgoOrDate',
'pullRequestDate',
'pullRequestState',
)
) {
if (!options?.pullRequests) {
Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`); Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
return undefined; return undefined;

+ 7
- 0
src/hovers/lineHoverController.ts View File

@ -138,7 +138,14 @@ export class LineHoverController implements Disposable {
logCommit ?? commit, logCommit ?? commit,
trackedDocument.uri, trackedDocument.uri,
editorLine, editorLine,
Container.config.hovers.detailsMarkdownFormat,
Container.config.defaultDateFormat, Container.config.defaultDateFormat,
{
autolinks: Container.config.hovers.autolinks.enabled,
pullRequests: {
enabled: Container.config.hovers.pullRequests.enabled,
},
},
); );
return new Hover(message, range); return new Hover(message, range);
} }

+ 117
- 35
src/statusbar/statusBarController.ts View File

@ -4,6 +4,7 @@ import {
CancellationTokenSource, CancellationTokenSource,
ConfigurationChangeEvent, ConfigurationChangeEvent,
Disposable, Disposable,
MarkdownString,
StatusBarAlignment, StatusBarAlignment,
StatusBarItem, StatusBarItem,
TextEditor, TextEditor,
@ -15,12 +16,16 @@ import { configuration, FileAnnotationType, StatusBarCommand } from '../configur
import { GlyphChars, isTextEditor } from '../constants'; import { GlyphChars, isTextEditor } from '../constants';
import { Container } from '../container'; import { Container } from '../container';
import { CommitFormatter, GitBlameCommit, PullRequest } from '../git/git'; import { CommitFormatter, GitBlameCommit, PullRequest } from '../git/git';
import { Hovers } from '../hovers/hovers';
import { LogCorrelationContext, Logger } from '../logger'; import { LogCorrelationContext, Logger } from '../logger';
import { debug, Promises } from '../system';
import { debug, Functions, Promises } from '../system';
import { LinesChangeEvent } from '../trackers/gitLineTracker'; import { LinesChangeEvent } from '../trackers/gitLineTracker';
export class StatusBarController implements Disposable { export class StatusBarController implements Disposable {
private _cancellation: CancellationTokenSource | undefined;
private _pullRequestCancellation: CancellationTokenSource | undefined;
private _tooltipCancellation: CancellationTokenSource | undefined;
private _tooltipDelayTimer: any | undefined;
private readonly _disposable: Disposable; private readonly _disposable: Disposable;
private _statusBarBlame: StatusBarItem | undefined; private _statusBarBlame: StatusBarItem | undefined;
private _statusBarMode: StatusBarItem | undefined; private _statusBarMode: StatusBarItem | undefined;
@ -69,7 +74,10 @@ export class StatusBarController implements Disposable {
this._statusBarMode.name = 'GitLens Modes'; this._statusBarMode.name = 'GitLens Modes';
this._statusBarMode.command = Commands.SwitchMode; this._statusBarMode.command = Commands.SwitchMode;
this._statusBarMode.text = mode.statusBarItemName; this._statusBarMode.text = mode.statusBarItemName;
this._statusBarMode.tooltip = 'Switch GitLens Mode';
this._statusBarMode.tooltip = new MarkdownString(
`**${mode.statusBarItemName}** ${GlyphChars.Dash} ${mode.description}\n\n---\n\nClick to Switch GitLens Mode`,
true,
);
this._statusBarMode.show(); this._statusBarMode.show();
} else { } else {
this._statusBarMode?.dispose(); this._statusBarMode?.dispose();
@ -148,7 +156,8 @@ export class StatusBarController implements Disposable {
} }
clearBlame() { clearBlame() {
this._cancellation?.cancel();
this._pullRequestCancellation?.cancel();
this._tooltipCancellation?.cancel();
this._statusBarBlame?.hide(); this._statusBarBlame?.hide();
} }
@ -159,30 +168,40 @@ export class StatusBarController implements Disposable {
const cc = Logger.getCorrelationContext(); const cc = Logger.getCorrelationContext();
// TODO: Make this configurable?
const timeout = 100;
const [getBranchAndTagTips, pr] = await Promise.all([
CommitFormatter.has(cfg.format, 'tips')
? Container.git.getBranchesAndTagsTipsFn(commit.repoPath)
: undefined,
const showPullRequests =
cfg.pullRequests.enabled && cfg.pullRequests.enabled &&
CommitFormatter.has(
(CommitFormatter.has(
cfg.format, cfg.format,
'pullRequest', 'pullRequest',
'pullRequestAgo', 'pullRequestAgo',
'pullRequestAgoOrDate', 'pullRequestAgoOrDate',
'pullRequestDate', 'pullRequestDate',
'pullRequestState', 'pullRequestState',
) &&
options?.pr === undefined
) ||
CommitFormatter.has(
cfg.tooltipFormat,
'pullRequest',
'pullRequestAgo',
'pullRequestAgoOrDate',
'pullRequestDate',
'pullRequestState',
));
// TODO: Make this configurable?
const timeout = 100;
const [getBranchAndTagTips, pr] = await Promise.all([
CommitFormatter.has(cfg.format, 'tips') || CommitFormatter.has(cfg.tooltipFormat, 'tips')
? Container.git.getBranchesAndTagsTipsFn(commit.repoPath)
: undefined,
showPullRequests && options?.pr === undefined
? this.getPullRequest(commit, { timeout: timeout }) ? this.getPullRequest(commit, { timeout: timeout })
: options?.pr ?? undefined, : options?.pr ?? undefined,
]); ]);
if (pr != null) { if (pr != null) {
this._cancellation?.cancel();
this._cancellation = new CancellationTokenSource();
void this.waitForPendingPullRequest(editor, commit, pr, this._cancellation.token, timeout, cc);
this._pullRequestCancellation?.cancel();
this._pullRequestCancellation = new CancellationTokenSource();
void this.waitForPendingPullRequest(editor, commit, pr, this._pullRequestCancellation.token, timeout, cc);
} }
this._statusBarBlame.text = `$(git-commit) ${CommitFormatter.fromTemplate(cfg.format, commit, { this._statusBarBlame.text = `$(git-commit) ${CommitFormatter.fromTemplate(cfg.format, commit, {
@ -193,51 +212,52 @@ export class StatusBarController implements Disposable {
pullRequestPendingMessage: 'PR $(loading~spin)', pullRequestPendingMessage: 'PR $(loading~spin)',
})}`; })}`;
let tooltip: string;
switch (cfg.command) { switch (cfg.command) {
case StatusBarCommand.CopyRemoteCommitUrl: case StatusBarCommand.CopyRemoteCommitUrl:
this._statusBarBlame.tooltip = 'Copy Remote Commit Url';
tooltip = 'Click to Copy Remote Commit Url';
break; break;
case StatusBarCommand.CopyRemoteFileUrl: case StatusBarCommand.CopyRemoteFileUrl:
this._statusBarBlame.command = Commands.CopyRemoteFileUrl; this._statusBarBlame.command = Commands.CopyRemoteFileUrl;
this._statusBarBlame.tooltip = 'Copy Remote File Revision Url';
tooltip = 'Click to Copy Remote File Revision Url';
break; break;
case StatusBarCommand.DiffWithPrevious: case StatusBarCommand.DiffWithPrevious:
this._statusBarBlame.command = Commands.DiffLineWithPrevious; this._statusBarBlame.command = Commands.DiffLineWithPrevious;
this._statusBarBlame.tooltip = 'Open Line Changes with Previous Revision';
tooltip = 'Click to Open Line Changes with Previous Revision';
break; break;
case StatusBarCommand.DiffWithWorking: case StatusBarCommand.DiffWithWorking:
this._statusBarBlame.command = Commands.DiffLineWithWorking; this._statusBarBlame.command = Commands.DiffLineWithWorking;
this._statusBarBlame.tooltip = 'Open Line Changes with Working File';
tooltip = 'Click to Open Line Changes with Working File';
break; break;
case StatusBarCommand.OpenCommitOnRemote: case StatusBarCommand.OpenCommitOnRemote:
this._statusBarBlame.tooltip = 'Open Commit on Remote';
tooltip = 'Click to Open Commit on Remote';
break; break;
case StatusBarCommand.OpenFileOnRemote: case StatusBarCommand.OpenFileOnRemote:
this._statusBarBlame.tooltip = 'Open Revision on Remote';
tooltip = 'Click to Open Revision on Remote';
break; break;
case StatusBarCommand.RevealCommitInView: case StatusBarCommand.RevealCommitInView:
this._statusBarBlame.tooltip = 'Reveal Commit in the Side Bar';
tooltip = 'Click to Reveal Commit in the Side Bar';
break; break;
case StatusBarCommand.ShowCommitsInView: case StatusBarCommand.ShowCommitsInView:
this._statusBarBlame.tooltip = 'Search for Commit';
tooltip = 'Click to Search for Commit';
break; break;
case StatusBarCommand.ShowQuickCommitDetails: case StatusBarCommand.ShowQuickCommitDetails:
this._statusBarBlame.tooltip = 'Show Commit';
tooltip = 'Click to Show Commit';
break; break;
case StatusBarCommand.ShowQuickCommitFileDetails: case StatusBarCommand.ShowQuickCommitFileDetails:
this._statusBarBlame.tooltip = 'Show Commit (file)';
tooltip = 'Click to Show Commit (file)';
break; break;
case StatusBarCommand.ShowQuickCurrentBranchHistory: case StatusBarCommand.ShowQuickCurrentBranchHistory:
this._statusBarBlame.tooltip = 'Show Branch History';
tooltip = 'Click to Show Branch History';
break; break;
case StatusBarCommand.ShowQuickFileHistory: case StatusBarCommand.ShowQuickFileHistory:
this._statusBarBlame.tooltip = 'Show File History';
tooltip = 'Click to Show File History';
break; break;
case StatusBarCommand.ToggleCodeLens: case StatusBarCommand.ToggleCodeLens:
this._statusBarBlame.tooltip = 'Toggle Git CodeLens';
tooltip = 'Click to Toggle Git CodeLens';
break; break;
case StatusBarCommand.ToggleFileBlame: case StatusBarCommand.ToggleFileBlame:
this._statusBarBlame.tooltip = 'Toggle File Blame';
tooltip = 'Click to Toggle File Blame';
break; break;
case StatusBarCommand.ToggleFileChanges: { case StatusBarCommand.ToggleFileChanges: {
this._statusBarBlame.command = command<[Uri, ToggleFileChangesAnnotationCommandArgs]>({ this._statusBarBlame.command = command<[Uri, ToggleFileChangesAnnotationCommandArgs]>({
@ -251,7 +271,7 @@ export class StatusBarController implements Disposable {
}, },
], ],
}); });
this._statusBarBlame.tooltip = 'Toggle File Changes';
tooltip = 'Click to Toggle File Changes';
break; break;
} }
case StatusBarCommand.ToggleFileChangesOnly: { case StatusBarCommand.ToggleFileChangesOnly: {
@ -266,18 +286,42 @@ export class StatusBarController implements Disposable {
}, },
], ],
}); });
this._statusBarBlame.tooltip = 'Toggle File Changes';
tooltip = 'Click to Toggle File Changes';
break; break;
} }
case StatusBarCommand.ToggleFileHeatmap: case StatusBarCommand.ToggleFileHeatmap:
this._statusBarBlame.tooltip = 'Toggle File Heatmap';
tooltip = 'Click to Toggle File Heatmap';
break; break;
} }
this._statusBarBlame.tooltip = tooltip;
clearTimeout(this._tooltipDelayTimer);
this._tooltipCancellation?.cancel();
this._tooltipDelayTimer = setTimeout(() => {
this._tooltipCancellation = new CancellationTokenSource();
void this.updateCommitTooltip(
this._statusBarBlame!,
commit,
tooltip,
getBranchAndTagTips,
{
enabled: showPullRequests || pr != null,
pr: pr,
},
this._tooltipCancellation.token,
);
}, 500);
this._statusBarBlame.show(); this._statusBarBlame.show();
} }
private async getPullRequest(commit: GitBlameCommit, { timeout }: { timeout?: number } = {}) {
private async getPullRequest(
commit: GitBlameCommit,
{ timeout }: { timeout?: number } = {},
): Promise<PullRequest | Promises.CancellationError<Promise<PullRequest | undefined>> | undefined> {
const remote = await Container.git.getRichRemoteProvider(commit.repoPath); const remote = await Container.git.getRichRemoteProvider(commit.repoPath);
if (remote?.provider == null) return undefined; if (remote?.provider == null) return undefined;
@ -285,10 +329,48 @@ export class StatusBarController implements Disposable {
try { try {
return await Container.git.getPullRequestForCommit(commit.ref, provider, { timeout: timeout }); return await Container.git.getPullRequestForCommit(commit.ref, provider, { timeout: timeout });
} catch (ex) { } catch (ex) {
return ex;
return ex instanceof Promises.CancellationError ? ex : undefined;
} }
} }
private async updateCommitTooltip(
statusBarItem: StatusBarItem,
commit: GitBlameCommit,
actionTooltip: string,
getBranchAndTagTips:
| ((
sha: string,
options?: { compact?: boolean | undefined; icons?: boolean | undefined } | undefined,
) => string | undefined)
| undefined,
pullRequests: {
enabled: boolean;
pr: PullRequest | Promises.CancellationError<Promise<PullRequest | undefined>> | undefined | undefined;
},
cancellationToken: CancellationToken,
) {
if (cancellationToken.isCancellationRequested) return;
void (await Functions.wait(10000));
const tooltip = await Hovers.detailsMessage(
commit,
commit.toGitUri(),
commit.lines[0].line,
Container.config.statusBar.tooltipFormat,
Container.config.defaultDateFormat,
{
autolinks: true,
getBranchAndTagTips: getBranchAndTagTips,
pullRequests: pullRequests,
},
);
if (cancellationToken.isCancellationRequested) return;
tooltip.appendMarkdown(`\n\n---\n\n${actionTooltip}`);
statusBarItem.tooltip = tooltip;
}
private async waitForPendingPullRequest( private async waitForPendingPullRequest(
editor: TextEditor, editor: TextEditor,
commit: GitBlameCommit, commit: GitBlameCommit,

Loading…
Cancel
Save