diff --git a/src/commands/showQuickBranchHistory.ts b/src/commands/showQuickBranchHistory.ts index 6081e6a..c4630f3 100644 --- a/src/commands/showQuickBranchHistory.ts +++ b/src/commands/showQuickBranchHistory.ts @@ -6,7 +6,9 @@ import { executeGitCommand } from './gitCommands'; import { GitUri } from '../git/gitUri'; export interface ShowQuickBranchHistoryCommandArgs { + repoPath?: string; branch?: string; + tag?: string; } @command() @@ -29,24 +31,27 @@ export class ShowQuickBranchHistoryCommand extends ActiveEditorCachedCommand { const gitUri = uri && (await GitUri.fromUri(uri)); + const repoPath = args?.repoPath ?? gitUri?.repoPath; + + let ref: GitReference | 'HEAD' | undefined; + if (repoPath != null) { + if (args?.branch != null) { + ref = + args.branch === 'HEAD' + ? 'HEAD' + : GitReference.create(args.branch, repoPath, { + refType: 'branch', + name: args.branch, + remote: false, + }); + } else if (args?.tag != null) { + ref = GitReference.create(args.tag, repoPath, { refType: 'tag', name: args.tag }); + } + } + return executeGitCommand({ command: 'log', - state: - gitUri?.repoPath != null - ? { - repo: gitUri.repoPath, - reference: - args?.branch != null - ? args.branch === 'HEAD' - ? 'HEAD' - : GitReference.create(args.branch, gitUri.repoPath, { - refType: 'branch', - name: args.branch, - remote: false, - }) - : undefined, - } - : {}, + state: repoPath != null ? { repo: repoPath, reference: ref } : {}, }); } } diff --git a/src/commands/showQuickCommit.ts b/src/commands/showQuickCommit.ts index 07e6451..0d39018 100644 --- a/src/commands/showQuickCommit.ts +++ b/src/commands/showQuickCommit.ts @@ -55,12 +55,31 @@ export class ShowQuickCommitCommand extends ActiveEditorCachedCommand { } async execute(editor?: TextEditor, uri?: Uri, args?: ShowQuickCommitCommandArgs) { - uri = getCommandUri(uri, editor); - if (uri == null && args?.repoPath == null) return; + let gitUri; + let repoPath; + if (args?.commit == null) { + if (args?.repoPath != null && args.sha != null) { + repoPath = args.repoPath; + gitUri = GitUri.fromRepoPath(repoPath); + } else { + uri = getCommandUri(uri, editor); + if (uri == null) return; + + gitUri = await GitUri.fromUri(uri); + repoPath = gitUri.repoPath; + } + } else { + if (args.sha == null) { + args.sha = args.commit.sha; + } - const gitUri = uri != null ? await GitUri.fromUri(uri) : GitUri.fromRepoPath(args!.repoPath!); + gitUri = args.commit.toGitUri(); + repoPath = args.commit.repoPath; - let repoPath = gitUri.repoPath; + if (uri == null) { + uri = args.commit.uri; + } + } args = { ...args }; if (args.sha == null) { diff --git a/src/container.ts b/src/container.ts index b3c74c1..566346e 100644 --- a/src/container.ts +++ b/src/container.ts @@ -14,6 +14,7 @@ import { LineHoverController } from './hovers/lineHoverController'; import { Keyboard } from './keyboard'; import { Logger } from './logger'; import { StatusBarController } from './statusbar/statusBarController'; +import { GitTerminalLinkProvider } from './terminal/linkProvider'; import { GitDocumentTracker } from './trackers/gitDocumentTracker'; import { GitLineTracker } from './trackers/gitLineTracker'; import { CompareView } from './views/compareView'; @@ -111,6 +112,7 @@ export class Container { } context.subscriptions.push(new RebaseEditorProvider()); + context.subscriptions.push(new GitTerminalLinkProvider()); context.subscriptions.push(new GitFileSystemProvider()); context.subscriptions.push(configuration.onWillChange(this.onConfigurationChanging, this)); diff --git a/src/terminal/linkProvider.ts b/src/terminal/linkProvider.ts new file mode 100644 index 0000000..6c91747 --- /dev/null +++ b/src/terminal/linkProvider.ts @@ -0,0 +1,110 @@ +'use strict'; +import { commands, Disposable, TerminalLink, TerminalLinkContext, TerminalLinkProvider, window } from 'vscode'; +import { Commands, ShowQuickBranchHistoryCommandArgs, ShowQuickCommitCommandArgs } from '../commands'; +import { Container } from '../container'; + +const shaRegex = /^[0-9a-f]{7,40}$/; +const refRegex = /\b((?!\S*\/\.)(?!\S*\.\.)(?!\/)(?!\S*\/\/)(?!\S*@\{)(?!@$)(?!\S*\\)[^\000-\037\177 ~^:?*[]+(? extends TerminalLink { + command: { + command: Commands; + args: T; + }; +} + +export class GitTerminalLinkProvider implements Disposable, TerminalLinkProvider { + private disposable: Disposable; + + constructor() { + this.disposable = window.registerTerminalLinkProvider(this); + } + + dispose() { + this.disposable.dispose(); + } + + async provideTerminalLinks(context: TerminalLinkContext): Promise { + if (context.line.trim().length === 0) return []; + + const repoPath = Container.git.getHighlanderRepoPath(); + if (repoPath == null) return []; + + const links: GitTerminalLink[] = []; + + const branchesAndTags = await Container.git.getBranchesAndOrTags(repoPath, { include: 'all' }); + + // Don't use the shared regex instance directly, because we can be called reentrantly (because of the awaits below) + const regex = new RegExp(refRegex, 'gi'); + + let match; + do { + match = regex.exec(context.line); + if (match == null) break; + + const [_, ref] = match; + + if (ref.toUpperCase() === 'HEAD') { + const link: GitTerminalLink = { + startIndex: match.index, + length: ref.length, + tooltip: 'Show HEAD', + command: { + command: Commands.ShowQuickBranchHistory, + args: { + branch: 'HEAD', + repoPath: repoPath, + }, + }, + }; + links.push(link); + + continue; + } + + const branchOrTag = branchesAndTags?.find(r => r.name === ref); + if (branchOrTag != null) { + const link: GitTerminalLink = { + startIndex: match.index, + length: ref.length, + tooltip: branchOrTag.refType === 'branch' ? 'Show Branch' : 'Show Tag', + command: { + command: Commands.ShowQuickBranchHistory, + args: { + branch: branchOrTag.refType === 'branch' ? branchOrTag.name : undefined, + tag: branchOrTag.refType === 'tag' ? branchOrTag.name : undefined, + repoPath: repoPath, + }, + }, + }; + links.push(link); + + continue; + } + + if (!shaRegex.test(ref)) continue; + + if (await Container.git.validateReference(repoPath, ref)) { + const link: GitTerminalLink = { + startIndex: match.index, + length: ref.length, + tooltip: 'Show Commit', + command: { + command: Commands.ShowQuickCommit, + args: { + repoPath: repoPath, + sha: ref, + }, + }, + }; + links.push(link); + } + } while (true); + + return links; + } + + handleTerminalLink(link: GitTerminalLink): void { + void commands.executeCommand(link.command.command, link.command.args); + } +}