|
|
- 'use strict';
- import { MarkdownString } from 'vscode';
- import { DiffWithCommand, ShowQuickCommitCommand } from '../commands';
- import { GlyphChars } from '../constants';
- import { Container } from '../container';
- import {
- CommitFormatter,
- GitBlameCommit,
- GitCommit,
- GitDiffHunk,
- GitDiffHunkLine,
- GitLogCommit,
- GitRemote,
- GitRevision,
- } from '../git/git';
- import { GitUri } from '../git/gitUri';
- import { Logger, TraceLevel } from '../logger';
- import { Iterables, Promises, Strings } from '../system';
-
- export namespace Hovers {
- export async function changesMessage(
- commit: GitBlameCommit | GitLogCommit,
- uri: GitUri,
- editorLine: number,
- ): Promise<MarkdownString | undefined> {
- const documentRef = uri.sha;
-
- let hunkLine;
- if (GitBlameCommit.is(commit)) {
- // TODO: Figure out how to optimize this
- let ref;
- if (commit.isUncommitted) {
- if (GitRevision.isUncommittedStaged(documentRef)) {
- ref = documentRef;
- }
- } else {
- ref = documentRef ? commit.previousSha : commit.sha;
- }
-
- const line = editorLine + 1;
- const commitLine = commit.lines.find(l => l.line === line) ?? commit.lines[0];
-
- let originalFileName = commit.originalFileName;
- if (originalFileName == null) {
- if (uri.fsPath !== commit.uri.fsPath) {
- originalFileName = commit.fileName;
- }
- }
-
- editorLine = commitLine.originalLine - 1;
- // TODO: Doesn't work with dirty files -- pass in editor? or contents?
- hunkLine = await Container.git.getDiffForLine(uri, editorLine, ref, uri.sha, originalFileName);
-
- // If we didn't find a diff & ref is undefined (meaning uncommitted), check for a staged diff
- if (hunkLine == null && ref == null) {
- hunkLine = await Container.git.getDiffForLine(
- uri,
- editorLine,
- undefined,
- GitRevision.uncommittedStaged,
- originalFileName,
- );
- }
- }
-
- if (hunkLine == null || commit.previousSha == null) return undefined;
-
- const diff = getDiffFromHunkLine(hunkLine);
-
- let message;
- let previous;
- let current;
- if (commit.isUncommitted) {
- const diffUris = await commit.getPreviousLineDiffUris(uri, editorLine, documentRef);
- if (diffUris == null || diffUris.previous == null) {
- return undefined;
- }
-
- message = `[$(compare-changes)](${DiffWithCommand.getMarkdownCommandArgs({
- lhs: {
- sha: diffUris.previous.sha ?? '',
- uri: diffUris.previous.documentUri(),
- },
- rhs: {
- sha: diffUris.current.sha ?? '',
- uri: diffUris.current.documentUri(),
- },
- repoPath: commit.repoPath,
- line: editorLine,
- })} "Open Changes")`;
-
- previous =
- diffUris.previous.sha == null || diffUris.previous.isUncommitted
- ? `_${GitRevision.shorten(diffUris.previous.sha, {
- strings: {
- working: 'Working Tree',
- },
- })}_`
- : `[$(git-commit) ${GitRevision.shorten(
- diffUris.previous.sha || '',
- )}](${ShowQuickCommitCommand.getMarkdownCommandArgs(diffUris.previous.sha || '')} "Show Commit")`;
-
- current =
- diffUris.current.sha == null || diffUris.current.isUncommitted
- ? `_${GitRevision.shorten(diffUris.current.sha, {
- strings: {
- working: 'Working Tree',
- },
- })}_`
- : `[$(git-commit) ${GitRevision.shorten(
- diffUris.current.sha || '',
- )}](${ShowQuickCommitCommand.getMarkdownCommandArgs(diffUris.current.sha || '')} "Show Commit")`;
- } else {
- message = `[$(compare-changes)](${DiffWithCommand.getMarkdownCommandArgs(
- commit,
- editorLine,
- )} "Open Changes")`;
-
- previous = `[$(git-commit) ${commit.previousShortSha}](${ShowQuickCommitCommand.getMarkdownCommandArgs(
- commit.previousSha,
- )} "Show Commit")`;
-
- current = `[$(git-commit) ${commit.shortSha}](${ShowQuickCommitCommand.getMarkdownCommandArgs(
- commit.sha,
- )} "Show Commit")`;
- }
-
- message = `${diff}\n---\n\nChanges ${previous} ${GlyphChars.ArrowLeftRightLong} ${current} | ${message}`;
-
- const markdown = new MarkdownString(message, true);
- markdown.isTrusted = true;
- return markdown;
- }
-
- export function localChangesMessage(
- fromCommit: GitLogCommit | undefined,
- uri: GitUri,
- editorLine: number,
- hunk: GitDiffHunk,
- ): MarkdownString {
- const diff = getDiffFromHunk(hunk);
-
- let message;
- let previous;
- let current;
- if (fromCommit == null) {
- previous = '_Working Tree_';
- current = '_Unsaved_';
- } else {
- const file = fromCommit.findFile(uri.fsPath)!;
-
- message = `[$(compare-changes)](${DiffWithCommand.getMarkdownCommandArgs({
- lhs: {
- sha: fromCommit.sha,
- uri: GitUri.fromFile(file, uri.repoPath!, undefined, true).toFileUri(),
- },
- rhs: {
- sha: '',
- uri: uri.toFileUri(),
- },
- repoPath: uri.repoPath!,
- line: editorLine,
- })} "Open Changes")`;
-
- previous = `[$(git-commit) ${fromCommit.shortSha}](${ShowQuickCommitCommand.getMarkdownCommandArgs(
- fromCommit.sha,
- )} "Show Commit")`;
-
- current = '_Working Tree_';
- }
- message = `${diff}\n---\n\nLocal Changes ${previous} ${
- GlyphChars.ArrowLeftRightLong
- } ${current}${message == null ? '' : ` | ${message}`}`;
-
- const markdown = new MarkdownString(message, true);
- markdown.isTrusted = true;
- return markdown;
- }
-
- export async function detailsMessage(
- commit: GitCommit,
- uri: GitUri,
- editorLine: number,
- dateFormat: string | null,
- ): Promise<MarkdownString> {
- if (dateFormat === null) {
- dateFormat = 'MMMM Do, YYYY h:mma';
- }
-
- const remotes = await Container.git.getRemotes(commit.repoPath, { sort: true });
-
- const [previousLineDiffUris, autolinkedIssuesOrPullRequests, pr, presence] = await Promise.all([
- commit.isUncommitted ? commit.getPreviousLineDiffUris(uri, editorLine, uri.sha) : undefined,
- getAutoLinkedIssuesOrPullRequests(commit.message, remotes),
- getPullRequestForCommit(commit.ref, remotes),
- Container.vsls.maybeGetPresence(commit.email).catch(() => undefined),
- ]);
-
- const details = await CommitFormatter.fromTemplateAsync(Container.config.hovers.detailsMarkdownFormat, commit, {
- autolinkedIssuesOrPullRequests: autolinkedIssuesOrPullRequests,
- dateFormat: dateFormat,
- line: editorLine,
- markdown: true,
- messageAutolinks: Container.config.hovers.autolinks.enabled,
- pullRequestOrRemote: pr,
- presence: presence,
- previousLineDiffUris: previousLineDiffUris,
- remotes: remotes,
- });
-
- const markdown = new MarkdownString(details, true);
- markdown.isTrusted = true;
- return markdown;
- }
-
- function getDiffFromHunk(hunk: GitDiffHunk): string {
- return `\`\`\`diff\n${hunk.diff.trim()}\n\`\`\``;
- }
-
- function getDiffFromHunkLine(hunkLine: GitDiffHunkLine, diffStyle?: 'line' | 'hunk'): string {
- if (diffStyle === 'hunk' || (diffStyle == null && Container.config.hovers.changesDiff === 'hunk')) {
- return getDiffFromHunk(hunkLine.hunk);
- }
-
- return `\`\`\`diff${hunkLine.previous == null ? '' : `\n-${hunkLine.previous.line.trim()}`}${
- hunkLine.current == null ? '' : `\n+${hunkLine.current.line.trim()}`
- }\n\`\`\``;
- }
-
- async function getAutoLinkedIssuesOrPullRequests(message: string, remotes: GitRemote[]) {
- const cc = Logger.getNewCorrelationContext('Hovers.getAutoLinkedIssues');
- Logger.debug(cc, `${GlyphChars.Dash} message=<message>`);
-
- const start = process.hrtime();
-
- if (
- !Container.config.hovers.autolinks.enabled ||
- !Container.config.hovers.autolinks.enhanced ||
- !CommitFormatter.has(Container.config.hovers.detailsMarkdownFormat, 'message')
- ) {
- Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
-
- return undefined;
- }
-
- const remote = await Container.git.getRemoteWithApiProvider(remotes);
- if (remote?.provider == null) {
- Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
-
- return undefined;
- }
-
- // TODO: Make this configurable?
- const timeout = 250;
-
- try {
- const autolinks = await Container.autolinks.getIssueOrPullRequestLinks(message, remote, {
- timeout: timeout,
- });
-
- if (autolinks != null && (Logger.level === TraceLevel.Debug || Logger.isDebugging)) {
- // If there are any issues/PRs that timed out, log it
- const count = Iterables.count(autolinks.values(), pr => pr instanceof Promises.CancellationError);
- if (count !== 0) {
- Logger.debug(
- cc,
- `timed out ${
- GlyphChars.Dash
- } ${count} issue/pull request queries took too long (over ${timeout} ms) ${
- GlyphChars.Dot
- } ${Strings.getDurationMilliseconds(start)} ms`,
- );
-
- // const pending = [
- // ...Iterables.map(autolinks.values(), issueOrPullRequest =>
- // issueOrPullRequest instanceof Promises.CancellationError
- // ? issueOrPullRequest.promise
- // : undefined,
- // ),
- // ];
- // void Promise.all(pending).then(() => {
- // Logger.debug(
- // cc,
- // `${GlyphChars.Dot} ${count} issue/pull request queries completed; refreshing...`,
- // );
- // void commands.executeCommand('editor.action.showHover');
- // });
-
- return autolinks;
- }
- }
-
- Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
-
- return autolinks;
- } catch (ex) {
- Logger.error(ex, cc, `failed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
-
- return undefined;
- }
- }
-
- async function getPullRequestForCommit(ref: string, remotes: GitRemote[]) {
- const cc = Logger.getNewCorrelationContext('Hovers.getPullRequestForCommit');
- Logger.debug(cc, `${GlyphChars.Dash} ref=${ref}`);
-
- const start = process.hrtime();
-
- if (
- !Container.config.hovers.pullRequests.enabled ||
- !CommitFormatter.has(
- Container.config.hovers.detailsMarkdownFormat,
- 'pullRequest',
- 'pullRequestAgo',
- 'pullRequestAgoOrDate',
- 'pullRequestDate',
- 'pullRequestState',
- )
- ) {
- Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
-
- return undefined;
- }
-
- const remote = await Container.git.getRemoteWithApiProvider(remotes, { includeDisconnected: true });
- if (remote?.provider == null) {
- Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
-
- return undefined;
- }
-
- const { provider } = remote;
- const connected = provider.maybeConnected ?? (await provider.isConnected());
- if (!connected) {
- Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
-
- return remote;
- }
-
- try {
- const pr = await Container.git.getPullRequestForCommit(ref, provider, { timeout: 250 });
-
- Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
-
- return pr;
- } catch (ex) {
- if (ex instanceof Promises.CancellationError) {
- Logger.debug(cc, `timed out ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
-
- return ex;
- }
-
- Logger.error(ex, cc, `failed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
-
- return undefined;
- }
- }
- }
|