From a4a30d94f56db7327b336420033680502106c6bd Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Tue, 9 May 2023 00:44:54 -0400 Subject: [PATCH] Adds new context menu for editor gutter --- CHANGELOG.md | 3 + package.json | 126 ++++++++++++++++++++++++- src/commands/base.ts | 25 +++++ src/commands/diffLineWithPrevious.ts | 9 ++ src/commands/diffLineWithWorking.ts | 9 ++ src/commands/openCommitOnRemote.ts | 11 ++- src/commands/openFileOnRemote.ts | 31 ++++-- src/commands/showQuickCommitFile.ts | 100 +++++++------------- src/config.ts | 7 ++ src/constants.ts | 1 + src/webviews/apps/settings/partials/menus.html | 60 ++++++++++++ 11 files changed, 301 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01a65d9..cbb273a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - Adds new _Commit Graph_ features and improvements - Unlocks columns in the _Commit Graph_ that were previously locked, including the Branch/Tag column, the Graph column, and the Commit Message column, allowing all columns to be rearranged - Column headers now show icons instead of text when their width is sufficiently small +- Adds _Share_, _Open Changes_, and _Open on Remote (Web)_ submenus to the new editor line numbers (gutter) context menu +- Adds an _Open Line Commit Details_ command to the _Open Changes_ submenus on editor context menus ### Changed @@ -26,6 +28,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - Consolidates existing file history commands into a _File History_ submenu - Consolidates existing "Open on Remote" commands into _Open on Remote (Web)_ submenu - Renames the _Commit Changes_ submenu to _Open Changes_ + - Renames _Show Commit_ command to _Quick Show Commit_ and _Show Line Commit_ command to _Quick Show Line Commit_ for better clarity as it opens a quick pick menu - Renames _Delete Stash..._ command to _Drop Stash..._ in the _Stashes_ view - Removes the commit icon when hiding avatars in the _Commits_ view to allow for a more compact layout - Limits Git CodeLens on docker files — closes [#2153](https://github.com/gitkraken/vscode-gitlens/issues/2153) diff --git a/package.json b/package.json index 29814b8..89c92c0 100644 --- a/package.json +++ b/package.json @@ -2827,6 +2827,29 @@ } ] }, + "editorGutter": { + "anyOf": [ + { + "enum": [ + false + ] + }, + { + "type": "object", + "properties": { + "compare": { + "type": "boolean" + }, + "remote": { + "type": "boolean" + }, + "share": { + "type": "boolean" + } + } + } + ] + }, "editorTab": { "anyOf": [ { @@ -3058,6 +3081,11 @@ "blame": true, "compare": true }, + "editorGutter": { + "compare": true, + "remote": true, + "share": true + }, "editorTab": { "clipboard": true, "compare": true, @@ -4840,6 +4868,12 @@ "icon": "$(eye)" }, { + "command": "gitlens.showLineCommitInView", + "title": "Open Line Commit Details", + "category": "GitLens", + "icon": "$(eye)" + }, + { "command": "gitlens.showInDetailsView", "title": "Open Details", "category": "GitLens", @@ -4867,12 +4901,12 @@ }, { "command": "gitlens.showQuickCommitDetails", - "title": "Show Commit", + "title": "Quick Show Commit", "category": "GitLens" }, { "command": "gitlens.showQuickCommitFileDetails", - "title": "Show Line Commit", + "title": "Quick Show Line Commit", "category": "GitLens" }, { @@ -7737,6 +7771,10 @@ "when": "false" }, { + "command": "gitlens.showLineCommitInView", + "when": "gitlens:activeFileStatus =~ /blameable/" + }, + { "command": "gitlens.showQuickCommitFileDetails", "when": "gitlens:activeFileStatus =~ /blameable/" }, @@ -9328,6 +9366,23 @@ "group": "3_gitlens@2" } ], + "editor/lineNumber/context": [ + { + "submenu": "gitlens/editor/lineNumber/context/share", + "when": "gitlens:hasRemotes && config.gitlens.menus.editorGutter.share && resourceScheme != output", + "group": "2_gitlens@2" + }, + { + "submenu": "gitlens/editor/lineNumber/context/changes", + "when": "config.gitlens.menus.editorGutter.compare && resourceScheme != output", + "group": "3_gitlens@1" + }, + { + "submenu": "gitlens/editor/lineNumber/context/openOn", + "when": "gitlens:hasRemotes && config.gitlens.menus.editorGutter.remote && resourceScheme != output", + "group": "3_gitlens@2" + } + ], "editor/title": [ { "command": "gitlens.diffWithWorking", @@ -12428,10 +12483,14 @@ "group": "2_gitlens@4" }, { - "command": "gitlens.showQuickCommitFileDetails", + "command": "gitlens.showLineCommitInView", "group": "3_gitlens@1" }, { + "command": "gitlens.showQuickCommitFileDetails", + "group": "3_gitlens@2" + }, + { "command": "gitlens.showQuickRevisionDetails", "when": "gitlens:activeFileStatus =~ /revision/ && !isInDiffEditor", "group": "3_gitlens@2" @@ -12509,6 +12568,55 @@ "alt": "gitlens.copyRemoteFileUrlFrom" } ], + "gitlens/editor/lineNumber/context/changes": [ + { + "command": "gitlens.diffLineWithPrevious", + "group": "1_gitlens@1" + }, + { + "command": "gitlens.diffLineWithWorking", + "group": "1_gitlens@2" + }, + { + "command": "gitlens.showLineCommitInView", + "group": "3_gitlens@1" + }, + { + "command": "gitlens.showQuickCommitFileDetails", + "group": "3_gitlens@2" + } + ], + "gitlens/editor/lineNumber/context/openOn": [ + { + "command": "gitlens.openFileOnRemote", + "group": "1_gitlens@2", + "alt": "gitlens.copyRemoteFileUrlToClipboard" + }, + { + "command": "gitlens.openFileOnRemoteFrom", + "group": "1_gitlens@3", + "alt": "gitlens.copyRemoteFileUrlFrom" + }, + { + "command": "gitlens.openCommitOnRemote", + "group": "1_gitlens_commit@1", + "alt": "gitlens.copyRemoteCommitUrl" + } + ], + "gitlens/editor/lineNumber/context/share": [ + { + "command": "gitlens.copyRemoteFileUrlToClipboard", + "group": "1_gitlens@2" + }, + { + "command": "gitlens.copyRemoteFileUrlFrom", + "group": "1_gitlens@3" + }, + { + "command": "gitlens.copyRemoteCommitUrl", + "group": "1_gitlens_commit@1" + } + ], "gitlens/explorer/changes": [ { "command": "gitlens.diffWithPrevious", @@ -12830,6 +12938,18 @@ "label": "Open on Remote (Web)" }, { + "id": "gitlens/editor/lineNumber/context/changes", + "label": "Open Changes" + }, + { + "id": "gitlens/editor/lineNumber/context/openOn", + "label": "Open on Remote (Web)" + }, + { + "id": "gitlens/editor/lineNumber/context/share", + "label": "Share" + }, + { "id": "gitlens/explorer/changes", "label": "Open Changes" }, diff --git a/src/commands/base.ts b/src/commands/base.ts index 499c2b8..707e268 100644 --- a/src/commands/base.ts +++ b/src/commands/base.ts @@ -40,6 +40,12 @@ export interface CommandBaseContext { uri?: Uri; } +export interface CommandEditorLineContext extends CommandBaseContext { + readonly type: 'editorLine'; + readonly line: number; + readonly uri: Uri; +} + export interface CommandGitTimelineItemContext extends CommandBaseContext { readonly type: 'timeline-item:git'; readonly item: GitTimelineItem; @@ -89,6 +95,10 @@ export interface CommandViewNodesContext extends CommandBaseContext { readonly nodes: ViewNode[]; } +export function isCommandContextEditorLine(context: CommandContext): context is CommandEditorLineContext { + return context.type === 'editorLine'; +} + export function isCommandContextGitTimelineItem(context: CommandContext): context is CommandGitTimelineItemContext { return context.type === 'timeline-item:git'; } @@ -188,6 +198,7 @@ export function isCommandContextViewNodeHasTag( } export type CommandContext = + | CommandEditorLineContext | CommandGitTimelineItemContext | CommandScmContext | CommandScmGroupsContext @@ -333,6 +344,20 @@ export function parseCommandContext( args = args.slice(1); } else if (editor == null) { + if (firstArg != null && typeof firstArg === 'object' && 'lineNumber' in firstArg && 'uri' in firstArg) { + const [, ...rest] = args; + return [ + { + command: command, + type: 'editorLine', + editor: undefined, + line: firstArg.lineNumber - 1, // convert to zero-based + uri: firstArg.uri, + }, + rest, + ]; + } + // If we are expecting an editor and we have no uri, then pass the active editor editor = window.activeTextEditor; } diff --git a/src/commands/diffLineWithPrevious.ts b/src/commands/diffLineWithPrevious.ts index e47082f..a361c87 100644 --- a/src/commands/diffLineWithPrevious.ts +++ b/src/commands/diffLineWithPrevious.ts @@ -6,6 +6,7 @@ import type { GitCommit } from '../git/models/commit'; import { showCommitHasNoPreviousCommitWarningMessage, showGenericErrorMessage } from '../messages'; import { command, executeCommand } from '../system/command'; import { Logger } from '../system/logger'; +import type { CommandContext } from './base'; import { ActiveEditorCommand, getCommandUri } from './base'; import type { DiffWithCommandArgs } from './diffWith'; @@ -22,6 +23,14 @@ export class DiffLineWithPreviousCommand extends ActiveEditorCommand { super(Commands.DiffLineWithPrevious); } + protected override preExecute(context: CommandContext, args?: DiffLineWithPreviousCommandArgs): Promise { + if (context.type === 'editorLine') { + args = { ...args, line: context.line }; + } + + return this.execute(context.editor, context.uri, args); + } + async execute(editor?: TextEditor, uri?: Uri, args?: DiffLineWithPreviousCommandArgs): Promise { uri = getCommandUri(uri, editor); if (uri == null) return; diff --git a/src/commands/diffLineWithWorking.ts b/src/commands/diffLineWithWorking.ts index 995b385..4a60613 100644 --- a/src/commands/diffLineWithWorking.ts +++ b/src/commands/diffLineWithWorking.ts @@ -8,6 +8,7 @@ import { uncommittedStaged } from '../git/models/constants'; import { showFileNotUnderSourceControlWarningMessage, showGenericErrorMessage } from '../messages'; import { command, executeCommand } from '../system/command'; import { Logger } from '../system/logger'; +import type { CommandContext } from './base'; import { ActiveEditorCommand, getCommandUri } from './base'; import type { DiffWithCommandArgs } from './diffWith'; @@ -24,6 +25,14 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand { super(Commands.DiffLineWithWorking); } + protected override preExecute(context: CommandContext, args?: DiffLineWithWorkingCommandArgs): Promise { + if (context.type === 'editorLine') { + args = { ...args, line: context.line }; + } + + return this.execute(context.editor, context.uri, args); + } + async execute(editor?: TextEditor, uri?: Uri, args?: DiffLineWithWorkingCommandArgs): Promise { uri = getCommandUri(uri, editor); if (uri == null) return; diff --git a/src/commands/openCommitOnRemote.ts b/src/commands/openCommitOnRemote.ts index 0051569..ebdb620 100644 --- a/src/commands/openCommitOnRemote.ts +++ b/src/commands/openCommitOnRemote.ts @@ -19,6 +19,7 @@ import type { OpenOnRemoteCommandArgs } from './openOnRemote'; export interface OpenCommitOnRemoteCommandArgs { clipboard?: boolean; + line?: number; sha?: string; } @@ -38,6 +39,10 @@ export class OpenCommitOnRemoteCommand extends ActiveEditorCommand { protected override preExecute(context: CommandContext, args?: OpenCommitOnRemoteCommandArgs) { let uri = context.uri; + if (context.type === 'editorLine') { + args = { ...args, line: context.line }; + } + if (isCommandContextViewNodeHasCommit(context)) { if (context.node.commit.isUncommitted) return Promise.resolve(undefined); @@ -79,10 +84,10 @@ export class OpenCommitOnRemoteCommand extends ActiveEditorCommand { try { if (args.sha == null) { - const blameline = editor == null ? 0 : editor.selection.active.line; - if (blameline < 0) return; + const blameLine = args.line ?? editor?.selection.active.line; + if (blameLine == null) return; - const blame = await this.container.git.getBlameForLine(gitUri, blameline, editor?.document); + const blame = await this.container.git.getBlameForLine(gitUri, blameLine, editor?.document); if (blame == null) { void showFileNotUnderSourceControlWarningMessage('Unable to open commit on remote provider'); diff --git a/src/commands/openFileOnRemote.ts b/src/commands/openFileOnRemote.ts index 9fcb329..8cc9deb 100644 --- a/src/commands/openFileOnRemote.ts +++ b/src/commands/openFileOnRemote.ts @@ -26,6 +26,7 @@ import type { OpenOnRemoteCommandArgs } from './openOnRemote'; export interface OpenFileOnRemoteCommandArgs { branchOrTag?: string; clipboard?: boolean; + line?: number; range?: boolean; sha?: string; pickBranchOrTag?: boolean; @@ -47,6 +48,10 @@ export class OpenFileOnRemoteCommand extends ActiveEditorCommand { protected override async preExecute(context: CommandContext, args?: OpenFileOnRemoteCommandArgs) { let uri = context.uri; + if (context.type === 'editorLine') { + args = { ...args, line: context.line, range: true }; + } + if (context.command === Commands.CopyRemoteFileUrlWithoutRange) { args = { ...args, range: false }; } @@ -100,7 +105,7 @@ export class OpenFileOnRemoteCommand extends ActiveEditorCommand { } if (context.command === Commands.OpenFileOnRemoteFrom || context.command === Commands.CopyRemoteFileUrlFrom) { - args = { ...args, pickBranchOrTag: true, range: false }; + args = { ...args, pickBranchOrTag: true, range: false }; // Override range since it can be wrong at a different commit } return this.execute(context.editor, uri, args); @@ -117,15 +122,21 @@ export class OpenFileOnRemoteCommand extends ActiveEditorCommand { try { let remotes = await this.container.git.getRemotesWithProviders(gitUri.repoPath); - const range = - args.range && editor != null && UriComparer.equals(editor.document.uri, uri) - ? new Range( - editor.selection.start.with({ line: editor.selection.start.line + 1 }), - editor.selection.end.with({ - line: editor.selection.end.line + (editor.selection.end.character === 0 ? 0 : 1), - }), - ) - : undefined; + + let range: Range | undefined; + if (args.range) { + if (editor != null && UriComparer.equals(editor.document.uri, uri)) { + range = new Range( + editor.selection.start.with({ line: editor.selection.start.line + 1 }), + editor.selection.end.with({ + line: editor.selection.end.line + (editor.selection.end.character === 0 ? 0 : 1), + }), + ); + } else if (args.line != null) { + range = new Range(args.line + 1, 0, args.line + 1, 0); + } + } + let sha = args.sha ?? gitUri.sha; if (args.branchOrTag == null && sha != null && !isSha(sha) && remotes.length !== 0) { diff --git a/src/commands/showQuickCommitFile.ts b/src/commands/showQuickCommitFile.ts index 9fcf8c5..5e6f8c7 100644 --- a/src/commands/showQuickCommitFile.ts +++ b/src/commands/showQuickCommitFile.ts @@ -13,16 +13,20 @@ import { showGenericErrorMessage, showLineUncommittedWarningMessage, } from '../messages'; -import { command } from '../system/command'; +import { command, executeCommand } from '../system/command'; import { Logger } from '../system/logger'; import type { CommandContext } from './base'; import { ActiveEditorCachedCommand, getCommandUri, isCommandContextViewNodeHasCommit } from './base'; +import type { ShowCommitsInViewCommandArgs } from './showCommitsInView'; export interface ShowQuickCommitFileCommandArgs { - sha?: string; commit?: GitCommit | GitStashCommit; + line?: number; fileLog?: GitLog; revisionUri?: string; + sha?: string; + + inView?: boolean; } @command() @@ -37,20 +41,27 @@ export class ShowQuickCommitFileCommand extends ActiveEditorCachedCommand { Commands.ShowQuickCommitRevision, Commands.ShowQuickCommitRevisionInDiffLeft, Commands.ShowQuickCommitRevisionInDiffRight, + Commands.ShowLineCommitInView, ]); } protected override async preExecute(context: CommandContext, args?: ShowQuickCommitFileCommandArgs) { - if (context.editor != null && context.command.startsWith(Commands.ShowQuickCommitRevision)) { - args = { ...args }; + if (context.type === 'editorLine') { + args = { ...args, line: context.line }; + } + + if (context.command === Commands.ShowLineCommitInView) { + args = { ...args, inView: true }; + } + if (context.editor != null && context.command.startsWith(Commands.ShowQuickCommitRevision)) { const gitUri = await GitUri.fromUri(context.editor.document.uri); - args.sha = gitUri.sha; + + args = { ...args, sha: gitUri.sha }; } if (context.type === 'viewItem') { - args = { ...args }; - args.sha = context.node.uri.sha; + args = { ...args, sha: context.node.uri.sha }; if (isCommandContextViewNodeHasCommit(context)) { args.commit = context.node.commit; @@ -75,10 +86,8 @@ export class ShowQuickCommitFileCommand extends ActiveEditorCachedCommand { } if (args.sha == null) { - if (editor == null) return; - - const blameLine = editor.selection.active.line; - if (blameLine < 0) return; + const blameLine = args.line ?? editor?.selection.active.line; + if (blameLine == null) return; try { const blame = await this.container.git.getBlameForLine(gitUri, blameLine); @@ -142,60 +151,21 @@ export class ShowQuickCommitFileCommand extends ActiveEditorCachedCommand { } } - // const shortSha = shorten(args.sha); - - // if (args.commit instanceof GitBlameCommit) { - // args.commit = (await this.container.git.getCommit(args.commit.repoPath, args.commit.ref))!; - // } - - await executeGitCommand({ - command: 'show', - state: { - repo: args.commit.repoPath, - reference: args.commit, - fileName: path, - }, - }); - - // if (args.goBackCommand === undefined) { - // const commandArgs: ShowQuickCommitCommandArgs = { - // commit: args.commit, - // sha: args.sha, - // }; - - // // Create a command to get back to the commit details - // args.goBackCommand = new CommandQuickPickItem( - // { - // label: `go back ${GlyphChars.ArrowBack}`, - // description: `to details of ${GlyphChars.Space}$(git-commit) ${shortSha}`, - // }, - // Commands.ShowQuickCommit, - // [args.commit.toGitUri(), commandArgs], - // ); - // } - - // // Create a command to get back to where we are right now - // const currentCommand = new CommandQuickPickItem( - // { - // label: `go back ${GlyphChars.ArrowBack}`, - // description: `to details of ${args.commit.getFormattedPath()} from ${ - // GlyphChars.Space - // }$(git-commit) ${shortSha}`, - // }, - // Commands.ShowQuickCommitFile, - // [args.commit.toGitUri(), args], - // ); - - // const pick = await CommitFileQuickPick.show(args.commit as GitCommit, uri, { - // goBackCommand: args.goBackCommand, - // currentCommand: currentCommand, - // fileLog: args.fileLog, - // }); - // if (pick === undefined) return undefined; - - // if (pick instanceof CommandQuickPickItem) return pick.execute(); - - // return undefined; + if (args.inView) { + await executeCommand(Commands.ShowCommitsInView, { + refs: [args.commit.sha], + repoPath: args.commit.repoPath, + }); + } else { + await executeGitCommand({ + command: 'show', + state: { + repo: args.commit.repoPath, + reference: args.commit, + fileName: path, + }, + }); + } } catch (ex) { Logger.error(ex, 'ShowQuickCommitFileDetailsCommand'); void showGenericErrorMessage('Unable to show commit file details'); diff --git a/src/config.ts b/src/config.ts index 1f24fe0..964178c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -482,6 +482,13 @@ export interface MenuConfig { blame: boolean; compare: boolean; }; + editorGutter: + | false + | { + compare: boolean; + remote: boolean; + share: boolean; + }; editorTab: | false | { diff --git a/src/constants.ts b/src/constants.ts index f31efe3..6668d73 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -242,6 +242,7 @@ export const enum Commands { ShowInCommitGraph = 'gitlens.showInCommitGraph', ShowInDetailsView = 'gitlens.showInDetailsView', ShowLastQuickPick = 'gitlens.showLastQuickPick', + ShowLineCommitInView = 'gitlens.showLineCommitInView', ShowLineHistoryView = 'gitlens.showLineHistoryView', ShowQuickBranchHistory = 'gitlens.showQuickBranchHistory', ShowQuickCommit = 'gitlens.showQuickCommitDetails', diff --git a/src/webviews/apps/settings/partials/menus.html b/src/webviews/apps/settings/partials/menus.html index 71b2056..a7c385e 100644 --- a/src/webviews/apps/settings/partials/menus.html +++ b/src/webviews/apps/settings/partials/menus.html @@ -103,6 +103,66 @@
+ +
+ + +
+
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+
+ +
+
+
+