Browse Source

Adds PR support to status bar blame

main
Eric Amodio 4 years ago
parent
commit
6b30f8a308
8 changed files with 173 additions and 65 deletions
  1. +9
    -8
      README.md
  2. +7
    -1
      package.json
  3. +3
    -0
      src/config.ts
  4. +122
    -46
      src/statusbar/statusBarController.ts
  5. +4
    -2
      src/system/promise.ts
  6. +3
    -3
      src/webviews/apps/settings/partials/blame.html
  7. +2
    -2
      src/webviews/apps/settings/partials/current-line.html
  8. +23
    -3
      src/webviews/apps/settings/partials/status-bar.html

+ 9
- 8
README.md View File

@ -719,14 +719,15 @@ GitLens is highly customizable and provides many configuration settings to allow
## Status Bar Settings [#](#status-bar-settings- 'Status Bar Settings')
| Name | Description |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `gitlens.statusBar.alignment` | Specifies the blame alignment in the status bar<br /><br />`left` - aligns to the left<br />`right` - aligns to the right |
| `gitlens.statusBar.command` | Specifies the command to be executed when the blame status bar item is clicked<br /><br />`gitlens.toggleFileBlame` - toggles file blame annotations<br />`gitlens.diffWithPrevious` - opens line changes with the previous revision<br />`gitlens.diffWithWorking` - opens line changes with the working file<br />`gitlens.revealCommitInView` - reveals the commit in the Side Bar<br />`gitlens.showCommitsInView` - searches for the commit<br />`gitlens.toggleCodeLens` - toggles the Git code lens<br />`gitlens.showQuickCommitDetails` - shows details of the commit<br />`gitlens.showQuickCommitFileDetails` - show file details of the commit<br />`gitlens.showQuickFileHistory` - shows the current file history<br />`gitlens.showQuickRepoHistory` - shows the current branch history |
| `gitlens.statusBar.dateFormat` | Specifies how to format absolute dates (e.g. using the `${date}` token) in the blame information in the status bar. See the [Moment.js docs](https://momentjs.com/docs/#/displaying/format/) for valid formats |
| `gitlens.statusBar.enabled` | Specifies whether to provide blame information in the status bar |
| `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.reduceFlicker` | Specifies whether to avoid clearing the previous blame information when changing lines to reduce status bar "flashing" |
| Name | Description |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `gitlens.statusBar.alignment` | Specifies the blame alignment in the status bar<br /><br />`left` - aligns to the left<br />`right` - aligns to the right |
| `gitlens.statusBar.command` | Specifies the command to be executed when the blame status bar item is clicked<br /><br />`gitlens.toggleFileBlame` - toggles file blame annotations<br />`gitlens.diffWithPrevious` - opens line changes with the previous revision<br />`gitlens.diffWithWorking` - opens line changes with the working file<br />`gitlens.revealCommitInView` - reveals the commit in the Side Bar<br />`gitlens.showCommitsInView` - searches for the commit<br />`gitlens.toggleCodeLens` - toggles the Git code lens<br />`gitlens.showQuickCommitDetails` - shows details of the commit<br />`gitlens.showQuickCommitFileDetails` - show file details of the commit<br />`gitlens.showQuickFileHistory` - shows the current file history<br />`gitlens.showQuickRepoHistory` - shows the current branch history |
| `gitlens.statusBar.dateFormat` | Specifies how to format absolute dates (e.g. using the `${date}` token) in the blame information in the status bar. See the [Moment.js docs](https://momentjs.com/docs/#/displaying/format/) for valid formats |
| `gitlens.statusBar.enabled` | Specifies whether to provide blame information in the status bar |
| `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.reduceFlicker` | Specifies whether to avoid clearing the previous blame information when changing lines to reduce status bar "flashing" |
## Hover Settings [#](#hover-settings- 'Hover Settings')

+ 7
- 1
package.json View File

@ -1571,10 +1571,16 @@
},
"gitlens.statusBar.format": {
"type": "string",
"default": "${author}, ${agoOrDate}",
"default": "${author}, ${agoOrDate}${' via 'pullRequest}",
"markdownDescription": "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",
"scope": "window"
},
"gitlens.statusBar.pullRequests.enabled": {
"type": "boolean",
"default": true,
"markdownDescription": "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)",
"scope": "window"
},
"gitlens.statusBar.reduceFlicker": {
"type": "boolean",
"default": false,

+ 3
- 0
src/config.ts View File

@ -114,6 +114,9 @@ export interface Config {
enabled: boolean;
format: string;
reduceFlicker: boolean;
pullRequests: {
enabled: boolean;
};
};
strings: {
codeLens: {

+ 122
- 46
src/statusbar/statusBarController.ts View File

@ -1,17 +1,28 @@
'use strict';
import { ConfigurationChangeEvent, Disposable, StatusBarAlignment, StatusBarItem, TextEditor, window } from 'vscode';
import {
CancellationToken,
CancellationTokenSource,
ConfigurationChangeEvent,
Disposable,
StatusBarAlignment,
StatusBarItem,
TextEditor,
window,
} from 'vscode';
import { Commands } from '../commands';
import { configuration, StatusBarCommand } from '../configuration';
import { isTextEditor } from '../constants';
import { GlyphChars, isTextEditor } from '../constants';
import { Container } from '../container';
import { CommitFormatter, GitCommit } from '../git/git';
import { CommitFormatter, GitBlameCommit, PullRequest } from '../git/git';
import { LinesChangeEvent } from '../trackers/gitLineTracker';
import { debug } from '../system';
import { debug, Promises } from '../system';
import { LogCorrelationContext, Logger } from '../logger';
export class StatusBarController implements Disposable {
private _blameStatusBarItem: StatusBarItem | undefined;
private _cancellation: CancellationTokenSource | undefined;
private readonly _disposable: Disposable;
private _modeStatusBarItem: StatusBarItem | undefined;
private _statusBarBlame: StatusBarItem | undefined;
private _statusBarMode: StatusBarItem | undefined;
constructor() {
this._disposable = Disposable.from(configuration.onDidChange(this.onConfigurationChanged, this));
@ -21,8 +32,8 @@ export class StatusBarController implements Disposable {
dispose() {
this.clearBlame();
this._blameStatusBarItem?.dispose();
this._modeStatusBarItem?.dispose();
this._statusBarBlame?.dispose();
this._statusBarMode?.dispose();
Container.lineTracker.stop(this);
this._disposable.dispose();
@ -41,22 +52,22 @@ export class StatusBarController implements Disposable {
: StatusBarAlignment.Left;
if (configuration.changed(e, 'mode', 'statusBar', 'alignment')) {
if (this._modeStatusBarItem?.alignment !== alignment) {
this._modeStatusBarItem?.dispose();
this._modeStatusBarItem = undefined;
if (this._statusBarMode?.alignment !== alignment) {
this._statusBarMode?.dispose();
this._statusBarMode = undefined;
}
}
this._modeStatusBarItem =
this._modeStatusBarItem ??
this._statusBarMode =
this._statusBarMode ??
window.createStatusBarItem(alignment, alignment === StatusBarAlignment.Right ? 999 : 1);
this._modeStatusBarItem.command = Commands.SwitchMode;
this._modeStatusBarItem.text = mode.statusBarItemName;
this._modeStatusBarItem.tooltip = 'Switch GitLens Mode';
this._modeStatusBarItem.show();
this._statusBarMode.command = Commands.SwitchMode;
this._statusBarMode.text = mode.statusBarItemName;
this._statusBarMode.tooltip = 'Switch GitLens Mode';
this._statusBarMode.show();
} else {
this._modeStatusBarItem?.dispose();
this._modeStatusBarItem = undefined;
this._statusBarMode?.dispose();
this._statusBarMode = undefined;
}
}
@ -67,16 +78,16 @@ export class StatusBarController implements Disposable {
Container.config.statusBar.alignment !== 'left' ? StatusBarAlignment.Right : StatusBarAlignment.Left;
if (configuration.changed(e, 'statusBar', 'alignment')) {
if (this._blameStatusBarItem?.alignment !== alignment) {
this._blameStatusBarItem?.dispose();
this._blameStatusBarItem = undefined;
if (this._statusBarBlame?.alignment !== alignment) {
this._statusBarBlame?.dispose();
this._statusBarBlame = undefined;
}
}
this._blameStatusBarItem =
this._blameStatusBarItem ??
this._statusBarBlame =
this._statusBarBlame ??
window.createStatusBarItem(alignment, alignment === StatusBarAlignment.Right ? 1000 : 0);
this._blameStatusBarItem.command = Container.config.statusBar.command;
this._statusBarBlame.command = Container.config.statusBar.command;
if (configuration.changed(e, 'statusBar', 'enabled')) {
Container.lineTracker.start(
@ -87,8 +98,8 @@ export class StatusBarController implements Disposable {
} else if (configuration.changed(e, 'statusBar', 'enabled')) {
Container.lineTracker.stop(this);
this._blameStatusBarItem?.dispose();
this._blameStatusBarItem = undefined;
this._statusBarBlame?.dispose();
this._statusBarBlame = undefined;
}
}
@ -110,7 +121,7 @@ export class StatusBarController implements Disposable {
if (!e.pending && e.selections != null) {
const state = Container.lineTracker.getState(e.selections[0].active);
if (state?.commit != null) {
this.updateBlame(state.commit, e.editor!);
void this.updateBlame(e.editor!, state.commit);
return;
}
@ -124,53 +135,118 @@ export class StatusBarController implements Disposable {
}
clearBlame() {
this._blameStatusBarItem?.hide();
this._cancellation?.cancel();
this._statusBarBlame?.hide();
}
private updateBlame(commit: GitCommit, editor: TextEditor) {
@debug({ args: false })
private async updateBlame(editor: TextEditor, commit: GitBlameCommit, options?: { pr?: PullRequest | undefined }) {
const cfg = Container.config.statusBar;
if (!cfg.enabled || this._blameStatusBarItem == null || !isTextEditor(editor)) return;
if (!cfg.enabled || this._statusBarBlame == null || !isTextEditor(editor)) return;
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,
cfg.pullRequests.enabled &&
CommitFormatter.has(
cfg.format,
'pullRequest',
'pullRequestAgo',
'pullRequestAgoOrDate',
'pullRequestDate',
'pullRequestState',
)
? options?.pr ?? this.getPullRequest(commit, { timeout: timeout })
: undefined,
]);
if (pr != null) {
this._cancellation?.cancel();
this._cancellation = new CancellationTokenSource();
void this.waitForPendingPullRequest(editor, commit, pr, this._cancellation.token, timeout, cc);
}
this._blameStatusBarItem.text = `$(git-commit) ${CommitFormatter.fromTemplate(cfg.format, commit, {
messageTruncateAtNewLine: true,
this._statusBarBlame.text = `$(git-commit) ${CommitFormatter.fromTemplate(cfg.format, commit, {
dateFormat: cfg.dateFormat === null ? Container.config.defaultDateFormat : cfg.dateFormat,
getBranchAndTagTips: getBranchAndTagTips,
messageTruncateAtNewLine: true,
pullRequestOrRemote: pr,
})}`;
switch (cfg.command) {
case StatusBarCommand.ToggleFileBlame:
this._blameStatusBarItem.tooltip = 'Toggle File Blame Annotations';
this._statusBarBlame.tooltip = 'Toggle File Blame Annotations';
break;
case StatusBarCommand.DiffWithPrevious:
this._blameStatusBarItem.command = Commands.DiffLineWithPrevious;
this._blameStatusBarItem.tooltip = 'Open Line Changes with Previous Revision';
this._statusBarBlame.command = Commands.DiffLineWithPrevious;
this._statusBarBlame.tooltip = 'Open Line Changes with Previous Revision';
break;
case StatusBarCommand.DiffWithWorking:
this._blameStatusBarItem.command = Commands.DiffLineWithWorking;
this._blameStatusBarItem.tooltip = 'Open Line Changes with Working File';
this._statusBarBlame.command = Commands.DiffLineWithWorking;
this._statusBarBlame.tooltip = 'Open Line Changes with Working File';
break;
case StatusBarCommand.ToggleCodeLens:
this._blameStatusBarItem.tooltip = 'Toggle Git CodeLens';
this._statusBarBlame.tooltip = 'Toggle Git CodeLens';
break;
case StatusBarCommand.RevealCommitInView:
this._blameStatusBarItem.tooltip = 'Reveal Commit in the Side Bar';
this._statusBarBlame.tooltip = 'Reveal Commit in the Side Bar';
break;
case StatusBarCommand.ShowCommitsInView:
this._blameStatusBarItem.tooltip = 'Search for Commit';
this._statusBarBlame.tooltip = 'Search for Commit';
break;
case StatusBarCommand.ShowQuickCommitDetails:
this._blameStatusBarItem.tooltip = 'Show Commit';
this._statusBarBlame.tooltip = 'Show Commit';
break;
case StatusBarCommand.ShowQuickCommitFileDetails:
this._blameStatusBarItem.tooltip = 'Show Commit (file)';
this._statusBarBlame.tooltip = 'Show Commit (file)';
break;
case StatusBarCommand.ShowQuickFileHistory:
this._blameStatusBarItem.tooltip = 'Show File History';
this._statusBarBlame.tooltip = 'Show File History';
break;
case StatusBarCommand.ShowQuickCurrentBranchHistory:
this._blameStatusBarItem.tooltip = 'Show Branch History';
this._statusBarBlame.tooltip = 'Show Branch History';
break;
}
this._blameStatusBarItem.show();
this._statusBarBlame.show();
}
private async getPullRequest(commit: GitBlameCommit, { timeout }: { timeout?: number } = {}) {
const remote = await Container.git.getRemoteWithApiProvider(commit.repoPath);
if (remote?.provider == null) return undefined;
const { provider } = remote;
try {
return await Container.git.getPullRequestForCommit(commit.ref, provider, { timeout: timeout });
} catch (ex) {
return ex;
}
}
private async waitForPendingPullRequest(
editor: TextEditor,
commit: GitBlameCommit,
pr: PullRequest | Promises.CancellationError<Promise<PullRequest | undefined>> | undefined,
cancellationToken: CancellationToken,
timeout: number,
cc: LogCorrelationContext | undefined,
) {
if (cancellationToken.isCancellationRequested || !(pr instanceof Promises.CancellationError)) return;
// If the PR timed out, refresh the status bar once it completes
Logger.debug(cc, `${GlyphChars.Dot} pull request query took too long (over ${timeout} ms)`);
pr = await pr.promise;
if (cancellationToken.isCancellationRequested) return;
Logger.debug(cc, `${GlyphChars.Dot} pull request query completed; refreshing...`);
void this.updateBlame(editor, commit, { pr: pr });
}
}

+ 4
- 2
src/system/promise.ts View File

@ -15,13 +15,15 @@ export class CancellationErrorWithId extends CancellationErro
}
export function cancellable<T>(
promise: Thenable<T>,
timeoutOrToken: number | CancellationToken,
promise: Promise<T>,
timeoutOrToken?: number | CancellationToken,
options: {
cancelMessage?: string;
onDidCancel?(resolve: (value?: T | PromiseLike<T> | undefined) => void, reject: (reason?: any) => void): void;
} = {},
): Promise<T> {
if (timeoutOrToken == null) return promise;
return new Promise((resolve, reject) => {
let fulfilled = false;
let timer: NodeJS.Timer | undefined;

+ 3
- 3
src/webviews/apps/settings/partials/blame.html View File

@ -56,16 +56,16 @@
id="blame.format"
name="blame.format"
type="text"
placeholder="&#36;&#123;message|40?&#125; &#36;&#123;agoOrDate|14-&#125;"
placeholder="${message|50?} ${agoOrDate|14-}"
data-setting
data-default-value="&#36;&#123;message|40?&#125; &#36;&#123;agoOrDate|14-&#125;"
data-default-value="${message|50?} ${agoOrDate|14-}"
data-popup-trigger
/>
<label for="blame.format" title="See available tokens">
<i class="icon icon__chevron-down"></i>
</label>
<div id="blame.format.popup" class="popup hidden"></div>
</div>
<div id="blame.format.popup" class="popup hidden"></div>
</div>
<div class="setting">

+ 2
- 2
src/webviews/apps/settings/partials/current-line.html View File

@ -64,9 +64,9 @@
id="currentLine.format"
name="currentLine.format"
type="text"
placeholder="&#36;&#123;authorAgoOrDate&#125; &bull; &#36;&#123;message&#125;"
placeholder="${author, }${agoOrDate}${' via 'pullRequest}${ • message|50?}"
data-setting
data-default-value="&#36;&#123;authorAgoOrDate&#125; &bull; &#36;&#123;message&#125;"
data-default-value="${author, }${agoOrDate}${' via 'pullRequest}${ • message|50?}"
data-popup-trigger
disabled
/>

+ 23
- 3
src/webviews/apps/settings/partials/status-bar.html View File

@ -20,23 +20,43 @@
<div class="section__content">
<div class="settings settings--fixed ml-1">
<div class="setting" data-enablement="statusBar.enabled">
<div class="setting__input">
<input
id="statusBar.pullRequests.enabled"
name="statusBar.pullRequests.enabled"
type="checkbox"
data-setting
disabled
/>
<label for="statusBar.pullRequests.enabled"
>Show the Pull Request (if any) that introduced the commit</label
>
</div>
<p class="setting__hint hidden" data-visibility="statusBar.pullRequests.enabled">
<i class="icon icon__info"></i>Requires a connection to a supported remote service (e.g.
GitHub)
</p>
</div>
<div class="setting" data-enablement="statusBar.enabled">
<div class="setting__input setting__input--format">
<label for="statusBar.format">Annotation&nbsp;format</label>
<input
id="statusBar.format"
name="statusBar.format"
type="text"
placeholder="&#36;&#123;authorAgoOrDate&#125;"
placeholder="${author}, ${agoOrDate}${' via 'pullRequest}"
data-setting
data-default-value="&#36;&#123;authorAgoOrDate&#125;"
data-default-value="${author}, ${agoOrDate}${' via 'pullRequest}"
data-popup-trigger
disabled
/>
<label for="statusBar.format" title="See available tokens">
<i class="icon icon__chevron-down"></i>
</label>
<div id="statusBar.format.popup" class="popup hidden"></div>
</div>
<div id="statusBar.format.popup" class="popup hidden"></div>
</div>
<div class="setting" data-enablement="statusBar.enabled">

Loading…
Cancel
Save