Browse Source

Reworks menu contributions

main
Eric Amodio 6 years ago
parent
commit
1fae4e871f
9 changed files with 896 additions and 904 deletions
  1. +7
    -0
      CHANGELOG.md
  2. +1
    -1
      README.md
  3. +158
    -236
      package.json
  4. +345
    -342
      src/commands/common.ts
  5. +83
    -83
      src/commands/openCommitInRemote.ts
  6. +68
    -68
      src/commands/openFileInRemote.ts
  7. +144
    -144
      src/commands/showQuickCommitFileDetails.ts
  8. +64
    -1
      src/extension.ts
  9. +26
    -29
      src/ui/config.ts

+ 7
- 0
CHANGELOG.md View File

@ -9,6 +9,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- Adds a tree layout option to tags in the *GitLens* explorer — closes [#358](https://github.com/eamodio/vscode-gitlens/issues/358)
- Adds an icon for the *Compare File with Previous Revision* command (`gitlens.diffWithPrevious`) and moves it into the editor toolbar
- Adds an icon for the *Compare File with Next Revision* command (`gitlens.diffWithNext`) and moves it into the editor toolbar
- Adds a *Show Line Commit Details* command (`gitlens.showQuickLineCommitFileDetails`) — behaves the same as the *Show Commit File Details* command (`gitlens.showQuickCommitFileDetails`) but with a more accurate menu name
- Adds a *Open Line Commit in Remote* command (`gitlens.openLineCommitInRemote`) — behaves the same as the *Open Commit in Remote* command (`gitlens.openCommitInRemote`) but with a more accurate menu name
- Adds a *Open Line in Remote* command (`gitlens.openLineInRemote`) — behaves the same as the *Open File in Remote* command (`gitlens.openFileInRemote`) but with a more accurate menu name
### Changed
- Renames the `gitlens.advanced.menus` setting to `gitlens.menus`
- Reworks GitLens menu contributions and configuration
### Fixed
- Fixes [#155](https://github.com/eamodio/vscode-gitlens/issues/155) - Navigating file diffs with `alt+,` gets stuck

+ 1
- 1
README.md View File

@ -570,6 +570,7 @@ GitLens is highly customizable and provides many configuration settings to allow
|`gitlens.defaultGravatarsStyle`|Specifies the style of the gravatar default (fallback) images<br />`identicon` - a geometric pattern<br />`mm` - (mystery-man) a simple, cartoon-style silhouetted outline of a person (does not vary by email hash)<br />`monsterid` - a monster with different colors, faces, etc<br />`retro` - 8-bit arcade-style pixelated faces<br />`robohash` - a robot with different colors, faces, etc<br />`wavatar` - faces with differing features and backgrounds
|`gitlens.insiders`|Opts into the insiders channel &mdash; provides access to upcoming features
|`gitlens.keymap`|Specifies the keymap to use for GitLens shortcut keys<br />`alternate` - adds an alternate set of shortcut keys that start with `Alt` (&#x2325; on macOS)<br />`chorded` - adds a chorded set of shortcut keys that start with `Ctrl+Shift+G` (<code>&#x2325;&#x2318;G</code> on macOS)<br />`none` - no shortcut keys will be added
|`gitlens.menus`|Specifies which commands will be added to which menus
|`gitlens.outputLevel`|Specifies how much (if any) output will be sent to the GitLens output channel
|`gitlens.showWhatsNewAfterUpgrades`|Specifies whether to show What's New after upgrading to new feature releases
@ -705,7 +706,6 @@ See also [Explorer Settings](#explorer-settings "Jump to the Explorer settings")
|`gitlens.advanced.git`|Specifies the path to the git executable to use. Use as a last resort as GitLens will use the built-in `git.path` setting first
|`gitlens.advanced.fileHistoryFollowsRenames`|Specifies whether file histories will follow renames -- will affect how merge commits are shown in histories
|`gitlens.advanced.maxListItems`|Specifies the maximum number of items to show in a list. Use 0 to specify no maximum
|`gitlens.advanced.menus`|Specifies which commands will be added to which menus
|`gitlens.advanced.messages`|Specifies which messages should be suppressed
|`gitlens.advanced.quickPick.closeOnFocusOut`|Specifies whether to close QuickPick menus when focus is lost
|`gitlens.advanced.repositorySearchDepth`|Specifies how many folders deep to search for repositories

+ 158
- 236
package.json View File

@ -576,6 +576,100 @@
"description": "Specifies the keymap to use for GitLens shortcut keys\n `alternate` - adds an alternate set of shortcut keys that start with `Alt` (\u2325 on macOS)\n `chorded` - adds a chorded set of shortcut keys that start with `Ctrl+Alt+G` (`\u2325\u2318G` on macOS)\n `none` - no shortcut keys will be added",
"scope": "window"
},
"gitlens.menus": {
"anyOf": [
{ "enum": [false] },
{
"type": "object",
"properties": {
"editor": {
"anyOf": [
{ "enum": [false] },
{
"type": "object",
"properties": {
"blame": { "type": "boolean" },
"clipboard": { "type": "boolean" },
"compare": { "type": "boolean" },
"details": { "type": "boolean" },
"history": { "type": "boolean" },
"remote": { "type": "boolean" }
}
}
]
},
"explorer": {
"anyOf": [
{ "enum": [false] },
{
"type": "object",
"properties": {
"compare": { "type": "boolean" },
"history": { "type": "boolean" },
"remote": { "type": "boolean" }
}
}
]
},
"tab": {
"anyOf": [
{ "enum": [false] },
{
"type": "object",
"properties": {
"compare": { "type": "boolean" },
"history": { "type": "boolean" },
"remote": { "type": "boolean" }
}
}
]
},
"tabGroup": {
"anyOf": [
{ "enum": [false] },
{
"type": "object",
"properties": {
"blame": { "type": "boolean" },
"compare": { "type": "boolean" },
"history": { "type": "boolean" },
"remote": { "type": "boolean" }
}
}
]
}
}
}
],
"default": {
"editor": {
"blame": false,
"clipboard": true,
"compare": true,
"details": true,
"history": false,
"remote": true
},
"explorer": {
"compare": true,
"history": true,
"remote": true
},
"tab": {
"compare": false,
"history": false,
"remote": true
},
"tabGroup": {
"blame": true,
"compare": true,
"history": false,
"remote": false
}
},
"description": "Specifies which commands will be added to which menus",
"scope": "window"
},
"gitlens.outputLevel": {
"type": "string",
"default": "silent",
@ -854,165 +948,6 @@
"description": "Specifies the maximum number of items to show in a list. Use 0 to specify no maximum",
"scope": "window"
},
"gitlens.advanced.menus": {
"type": "object",
"default": {
"editorContext": {
"blame": true,
"copy": true,
"details": true,
"fileDiff": true,
"history": true,
"lineDiff": true,
"remote": true
},
"editorTitle": {
"blame": true,
"fileDiff": false,
"history": false,
"remote": false,
"status": false
},
"editorTitleContext": {
"blame": false,
"fileDiff": false,
"history": false,
"remote": false
},
"explorerContext": {
"fileDiff": true,
"history": true,
"remote": true
}
},
"properties": {
"editorContext": {
"type": "object",
"default": {
"blame": true,
"copy": true,
"details": true,
"fileDiff": true,
"history": true,
"lineDiff": true,
"remote": true
},
"properties": {
"blame": {
"type": "boolean",
"default": true
},
"copy": {
"type": "boolean",
"default": true
},
"details": {
"type": "boolean",
"default": true
},
"fileDiff": {
"type": "boolean",
"default": true
},
"history": {
"type": "boolean",
"default": true
},
"lineDiff": {
"type": "boolean",
"default": true
},
"remote": {
"type": "boolean",
"default": true
}
}
},
"editorTitle": {
"type": "object",
"default": {
"blame": true,
"fileDiff": true,
"history": true,
"remote": true,
"status": true
},
"properties": {
"blame": {
"type": "boolean",
"default": true
},
"fileDiff": {
"type": "boolean",
"default": true
},
"history": {
"type": "boolean",
"default": true
},
"remote": {
"type": "boolean",
"default": true
},
"status": {
"type": "boolean",
"default": true
}
}
},
"editorTitleContext": {
"type": "object",
"default": {
"blame": true,
"fileDiff": true,
"history": true,
"remote": true
},
"properties": {
"blame": {
"type": "boolean",
"default": true
},
"fileDiff": {
"type": "boolean",
"default": true
},
"history": {
"type": "boolean",
"default": true
},
"remote": {
"type": "boolean",
"default": true
}
}
},
"explorerContext": {
"type": "object",
"default": {
"fileDiff": true,
"history": true,
"remote": true
},
"properties": {
"fileDiff": {
"type": "boolean",
"default": true
},
"history": {
"type": "boolean",
"default": true
},
"remote": {
"type": "boolean",
"default": true
}
}
}
},
"description": "Specifies which commands will be added to which menus",
"scope": "window"
},
"gitlens.advanced.messages": {
"type": "object",
"default": {
@ -1311,6 +1246,11 @@
"category": "GitLens"
},
{
"command": "gitlens.showQuickLineCommitFileDetails",
"title": "Show Line Commit Details",
"category": "GitLens"
},
{
"command": "gitlens.showQuickFileHistory",
"title": "Show File History",
"category": "GitLens"
@ -1381,6 +1321,16 @@
"category": "GitLens"
},
{
"command": "gitlens.openLineCommitInRemote",
"title": "Open Line Commit in Remote",
"category": "GitLens"
},
{
"command": "gitlens.openLineInRemote",
"title": "Open Line in Remote",
"category": "GitLens"
},
{
"command": "gitlens.openRepoInRemote",
"title": "Open Repository in Remote",
"category": "GitLens"
@ -1890,6 +1840,10 @@
},
{
"command": "gitlens.showQuickCommitFileDetails",
"when": "false"
},
{
"command": "gitlens.showQuickLineCommitFileDetails",
"when": "gitlens:activeIsBlameable"
},
{
@ -1945,6 +1899,14 @@
"when": "gitlens:activeIsTracked && gitlens:activeHasRemote"
},
{
"command": "gitlens.openLineCommitInRemote",
"when": "gitlens:activeIsTracked && gitlens:activeHasRemote"
},
{
"command": "gitlens.openLineInRemote",
"when": "gitlens:activeIsTracked && gitlens:activeHasRemote"
},
{
"command": "gitlens.openFileRevision",
"when": "gitlens:activeIsTracked"
},
@ -2219,53 +2181,48 @@
],
"editor/context": [
{
"command": "gitlens.openFileInRemote",
"when": "editorTextFocus && gitlens:activeHasRemote && config.gitlens.advanced.menus.editorContext.remote",
"group": "navigation@100"
},
{
"command": "gitlens.diffLineWithPrevious",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.lineDiff",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.menus.editor.compare",
"group": "1_gitlens@1"
},
{
"command": "gitlens.diffLineWithWorking",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.lineDiff",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.menus.editor.compare",
"group": "1_gitlens@2"
},
{
"command": "gitlens.showQuickCommitFileDetails",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.details",
"group": "1_gitlens@3"
},
{
"command": "gitlens.diffWithPrevious",
"when": "editorTextFocus && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorContext.fileDiff",
"command": "gitlens.openLineInRemote",
"when": "editorTextFocus && gitlens:activeHasRemote && config.gitlens.menus.editor.remote",
"group": "1_gitlens_1@1"
},
{
"command": "gitlens.diffWithWorking",
"when": "editorTextFocus && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorContext.fileDiff",
"command": "gitlens.openLineCommitInRemote",
"when": "editorTextFocus && gitlens:activeHasRemote && config.gitlens.menus.editor.remote",
"group": "1_gitlens_1@2"
},
{
"command": "gitlens.showQuickLineCommitFileDetails",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.menus.editor.details",
"group": "1_gitlens_1@3"
},
{
"command": "gitlens.showQuickFileHistory",
"when": "gitlens:activeIsTracked && config.gitlens.advanced.menus.editorContext.history",
"when": "gitlens:activeIsTracked && config.gitlens.menus.editor.history",
"group": "3_gitlens@1"
},
{
"command": "gitlens.toggleFileBlame",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.blame",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.menus.editor.blame",
"group": "3_gitlens@2"
},
{
"command": "gitlens.copyShaToClipboard",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.copy",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.menus.editor.clipboard",
"group": "9_gitlens@1"
},
{
"command": "gitlens.copyMessageToClipboard",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.copy",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.menus.editor.clipboard",
"group": "9_gitlens@2"
}
],
@ -2282,118 +2239,83 @@
},
{
"command": "gitlens.diffWithPrevious",
"when": "editorTextFocus && !isInDiffEditor && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorTitle.fileDiff",
"when": "editorTextFocus && !isInDiffEditor && gitlens:activeIsTracked && config.gitlens.menus.tabGroup.compare",
"group": "navigation@98"
},
{
"command": "gitlens.diffWithPreviousInDiff",
"when": "isInDiffEditor && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorTitle.fileDiff",
"when": "isInDiffEditor && gitlens:activeIsTracked && config.gitlens.menus.tabGroup.compare",
"group": "navigation@98"
},
{
"command": "gitlens.diffWithNext",
"when": "editorTextFocus && gitlens:activeIsTracked && gitlens:activeIsTracked && gitlens:activeIsRevision && config.gitlens.advanced.menus.editorTitle.fileDiff",
"when": "editorTextFocus && gitlens:activeIsTracked && gitlens:activeIsTracked && gitlens:activeIsRevision && config.gitlens.menus.tabGroup.compare",
"group": "navigation@99"
},
{
"command": "gitlens.toggleFileBlame",
"alt": "gitlens.toggleFileRecentChanges",
"when": "gitlens:activeIsBlameable && !gitlens:annotationStatus && config.gitlens.advanced.menus.editorTitle.blame",
"when": "gitlens:activeIsBlameable && !gitlens:annotationStatus && config.gitlens.menus.tabGroup.blame",
"group": "navigation@100"
},
{
"command": "gitlens.computingFileAnnotations",
"when": "gitlens:activeIsBlameable && gitlens:annotationStatus == computing && config.gitlens.advanced.menus.editorTitle.blame",
"when": "gitlens:activeIsBlameable && gitlens:annotationStatus == computing && config.gitlens.menus.tabGroup.blame",
"group": "navigation@100"
},
{
"command": "gitlens.clearFileAnnotations",
"when": "gitlens:activeIsBlameable && gitlens:annotationStatus == computed && config.gitlens.advanced.menus.editorTitle.blame",
"when": "gitlens:activeIsBlameable && gitlens:annotationStatus == computed && config.gitlens.menus.tabGroup.blame",
"group": "navigation@100"
},
{
"command": "gitlens.openFileInRemote",
"when": "gitlens:enabled && gitlens:activeHasRemote && config.gitlens.advanced.menus.editorTitle.remote",
"group": "2_gitlens"
},
{
"command": "gitlens.openRepoInRemote",
"when": "gitlens:enabled && gitlens:activeHasRemote && config.gitlens.advanced.menus.editorTitle.remote",
"group": "2_gitlens"
},
{
"command": "gitlens.diffWithPrevious",
"when": "editorTextFocus && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorTitle.fileDiff",
"group": "2_gitlens_1"
},
{
"command": "gitlens.diffWithWorking",
"when": "editorTextFocus && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorTitle.fileDiff",
"group": "2_gitlens_1"
"when": "gitlens:enabled && gitlens:activeHasRemote && config.gitlens.menus.tabGroup.remote",
"group": "4_gitlens"
},
{
"command": "gitlens.showQuickFileHistory",
"when": "editorFocus && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorTitle.history",
"group": "2_gitlens_2"
},
{
"command": "gitlens.showQuickRepoHistory",
"when": "!editorFocus && gitlens:enabled && config.gitlens.advanced.menus.editorTitle.history",
"group": "2_gitlens_2"
},
{
"command": "gitlens.showQuickRepoStatus",
"when": "gitlens:enabled && config.gitlens.advanced.menus.editorTitle.status",
"group": "2_gitlens_2"
"when": "editorFocus && gitlens:activeIsTracked && config.gitlens.menus.tabGroup.history",
"group": "4_gitlens"
}
],
"editor/title/context": [
{
"command": "gitlens.openFileInRemote",
"when": "gitlens:enabled && gitlens:activeHasRemote && config.gitlens.advanced.menus.editorTitleContext.remote",
"group": "1_gitlens"
"when": "gitlens:enabled && gitlens:activeHasRemote && config.gitlens.menus.tab.remote",
"group": "2_files@100"
},
{
"command": "gitlens.diffWithPrevious",
"when": "gitlens:enabled && config.gitlens.advanced.menus.editorTitleContext.fileDiff",
"when": "gitlens:enabled && config.gitlens.menus.tab.compare",
"group": "1_gitlens_1@1"
},
{
"command": "gitlens.diffWithWorking",
"when": "gitlens:enabled && config.gitlens.advanced.menus.editorTitleContext.fileDiff",
"when": "gitlens:enabled && config.gitlens.menus.tab.compare",
"group": "1_gitlens_1@2"
},
{
"command": "gitlens.showQuickFileHistory",
"when": "gitlens:enabled && config.gitlens.advanced.menus.editorTitleContext.history",
"when": "gitlens:enabled && config.gitlens.menus.tab.history",
"group": "1_gitlens_2@1"
},
{
"command": "gitlens.toggleFileBlame",
"when": "gitlens:enabled && config.gitlens.advanced.menus.editorTitleContext.blame",
"group": "1_gitlens_2@2"
}
],
"explorer/context": [
{
"command": "gitlens.openFileInRemote",
"when": "!explorerResourceIsRoot && !explorerResourceIsFolder && gitlens:enabled && gitlens:hasRemotes && config.gitlens.advanced.menus.explorerContext.remote",
"when": "!explorerResourceIsRoot && !explorerResourceIsFolder && gitlens:enabled && gitlens:hasRemotes && config.gitlens.menus.explorer.remote",
"group": "navigation@100"
},
{
"command": "gitlens.diffWithPrevious",
"when": "!explorerResourceIsRoot && !explorerResourceIsFolder && gitlens:enabled && config.gitlens.advanced.menus.explorerContext.fileDiff",
"group": "1_gitlens@1"
},
{
"command": "gitlens.diffWithWorking",
"when": "!explorerResourceIsRoot && !explorerResourceIsFolder && gitlens:enabled && config.gitlens.advanced.menus.explorerContext.fileDiff",
"group": "1_gitlens@2"
},
{
"command": "gitlens.showQuickFileHistory",
"when": "!explorerResourceIsRoot && !explorerResourceIsFolder && gitlens:enabled && config.gitlens.advanced.menus.explorerContext.history",
"when": "!explorerResourceIsRoot && !explorerResourceIsFolder && gitlens:enabled && config.gitlens.menus.explorer.history",
"group": "1_gitlens_1@1"
},
{
"command": "gitlens.diffWithPrevious",
"when": "!explorerResourceIsRoot && !explorerResourceIsFolder && gitlens:enabled && config.gitlens.menus.explorer.compare",
"group": "3_compare@1"
}
],
"scm/resourceGroup/context": [

+ 345
- 342
src/commands/common.ts View File

@ -1,344 +1,347 @@
'use strict';
import { commands, Disposable, SourceControlResourceGroup, SourceControlResourceState, TextDocumentShowOptions, TextEditor, TextEditorEdit, Uri, ViewColumn, window, workspace } from 'vscode';
import { BuiltInCommands, DocumentSchemes, ImageExtensions } from '../constants';
import { Container } from '../container';
import { ExplorerNode, ExplorerRefNode } from '../views/explorerNodes';
import { GitBranch, GitCommit, GitRemote, GitUri } from '../gitService';
import { Logger } from '../logger';
// import { Telemetry } from '../telemetry';
import * as path from 'path';
export enum Commands {
ClearFileAnnotations = 'gitlens.clearFileAnnotations',
CloseUnchangedFiles = 'gitlens.closeUnchangedFiles',
ComputingFileAnnotations = 'gitlens.computingFileAnnotations',
CopyMessageToClipboard = 'gitlens.copyMessageToClipboard',
CopyShaToClipboard = 'gitlens.copyShaToClipboard',
DiffDirectory = 'gitlens.diffDirectory',
DiffHeadWithBranch = 'gitlens.diffHeadWithBranch',
DiffWorkingWithBranch = 'gitlens.diffWorkingWithBranch',
ExternalDiffAll = 'gitlens.externalDiffAll',
DiffWith = 'gitlens.diffWith',
DiffWithBranch = 'gitlens.diffWithBranch',
DiffWithNext = 'gitlens.diffWithNext',
DiffWithPrevious = 'gitlens.diffWithPrevious',
'use strict';
import { commands, Disposable, SourceControlResourceGroup, SourceControlResourceState, TextDocumentShowOptions, TextEditor, TextEditorEdit, Uri, ViewColumn, window, workspace } from 'vscode';
import { BuiltInCommands, DocumentSchemes, ImageExtensions } from '../constants';
import { Container } from '../container';
import { ExplorerNode, ExplorerRefNode } from '../views/explorerNodes';
import { GitBranch, GitCommit, GitRemote, GitUri } from '../gitService';
import { Logger } from '../logger';
// import { Telemetry } from '../telemetry';
import * as path from 'path';
export enum Commands {
ClearFileAnnotations = 'gitlens.clearFileAnnotations',
CloseUnchangedFiles = 'gitlens.closeUnchangedFiles',
ComputingFileAnnotations = 'gitlens.computingFileAnnotations',
CopyMessageToClipboard = 'gitlens.copyMessageToClipboard',
CopyShaToClipboard = 'gitlens.copyShaToClipboard',
DiffDirectory = 'gitlens.diffDirectory',
DiffHeadWithBranch = 'gitlens.diffHeadWithBranch',
DiffWorkingWithBranch = 'gitlens.diffWorkingWithBranch',
ExternalDiffAll = 'gitlens.externalDiffAll',
DiffWith = 'gitlens.diffWith',
DiffWithBranch = 'gitlens.diffWithBranch',
DiffWithNext = 'gitlens.diffWithNext',
DiffWithPrevious = 'gitlens.diffWithPrevious',
DiffWithPreviousInDiff = 'gitlens.diffWithPreviousInDiff',
DiffLineWithPrevious = 'gitlens.diffLineWithPrevious',
DiffWithRevision = 'gitlens.diffWithRevision',
DiffWithWorking = 'gitlens.diffWithWorking',
DiffLineWithWorking = 'gitlens.diffLineWithWorking',
ExternalDiff = 'gitlens.externalDiff',
ExplorersOpenDirectoryDiff = 'gitlens.explorers.openDirectoryDiff',
ExplorersOpenDirectoryDiffWithWorking = 'gitlens.explorers.openDirectoryDiffWithWorking',
OpenChangedFiles = 'gitlens.openChangedFiles',
OpenBranchesInRemote = 'gitlens.openBranchesInRemote',
OpenBranchInRemote = 'gitlens.openBranchInRemote',
OpenCommitInRemote = 'gitlens.openCommitInRemote',
OpenFileInRemote = 'gitlens.openFileInRemote',
OpenFileRevision = 'gitlens.openFileRevision',
OpenInRemote = 'gitlens.openInRemote',
OpenRepoInRemote = 'gitlens.openRepoInRemote',
OpenWorkingFile = 'gitlens.openWorkingFile',
ResetSuppressedWarnings = 'gitlens.resetSuppressedWarnings',
ShowCommitSearch = 'gitlens.showCommitSearch',
ShowLastQuickPick = 'gitlens.showLastQuickPick',
ShowQuickCommitDetails = 'gitlens.showQuickCommitDetails',
ShowQuickCommitFileDetails = 'gitlens.showQuickCommitFileDetails',
ShowQuickFileHistory = 'gitlens.showQuickFileHistory',
ShowQuickBranchHistory = 'gitlens.showQuickBranchHistory',
ShowQuickCurrentBranchHistory = 'gitlens.showQuickRepoHistory',
ShowQuickRepoStatus = 'gitlens.showQuickRepoStatus',
ShowQuickStashList = 'gitlens.showQuickStashList',
ShowSettingsPage = 'gitlens.showSettingsPage',
ShowWelcomePage = 'gitlens.showWelcomePage',
StashApply = 'gitlens.stashApply',
StashDelete = 'gitlens.stashDelete',
StashSave = 'gitlens.stashSave',
ToggleCodeLens = 'gitlens.toggleCodeLens',
ToggleFileBlame = 'gitlens.toggleFileBlame',
ToggleFileHeatmap = 'gitlens.toggleFileHeatmap',
ToggleFileRecentChanges = 'gitlens.toggleFileRecentChanges',
ToggleLineBlame = 'gitlens.toggleLineBlame'
}
export function getCommandUri(uri?: Uri, editor?: TextEditor): Uri | undefined {
if (uri instanceof Uri) return uri;
if (editor === undefined || editor.document === undefined) return undefined;
return editor.document.uri;
}
export interface CommandContextParsingOptions {
editor: boolean;
uri: boolean;
}
export interface CommandBaseContext {
command: string;
editor?: TextEditor;
uri?: Uri;
}
export interface CommandScmGroupsContext extends CommandBaseContext {
type: 'scm-groups';
scmResourceGroups: SourceControlResourceGroup[];
}
export interface CommandScmStatesContext extends CommandBaseContext {
type: 'scm-states';
scmResourceStates: SourceControlResourceState[];
}
export interface CommandUnknownContext extends CommandBaseContext {
type: 'unknown';
}
export interface CommandUriContext extends CommandBaseContext {
type: 'uri';
}
export interface CommandViewContext extends CommandBaseContext {
type: 'view';
node: ExplorerNode;
}
export function isCommandViewContextWithBranch(context: CommandContext): context is CommandViewContext & { node: (ExplorerNode & { branch: GitBranch }) } {
return context.type === 'view' && (context.node as any).branch && (context.node as any).branch instanceof GitBranch;
}
export function isCommandViewContextWithCommit<T extends GitCommit>(context: CommandContext): context is CommandViewContext & { node: (ExplorerNode & { commit: T }) } {
return context.type === 'view' && (context.node as any).commit && (context.node as any).commit instanceof GitCommit;
}
export function isCommandViewContextWithRef(context: CommandContext): context is CommandViewContext & { node: (ExplorerNode & { ref: string }) } {
return context.type === 'view' && (context.node instanceof ExplorerRefNode);
}
export function isCommandViewContextWithRemote(context: CommandContext): context is CommandViewContext & { node: (ExplorerNode & { remote: GitRemote }) } {
return context.type === 'view' && (context.node as any).remote && (context.node as any).remote instanceof GitRemote;
}
export type CommandContext = CommandScmGroupsContext | CommandScmStatesContext | CommandUnknownContext | CommandUriContext | CommandViewContext;
function isScmResourceGroup(group: any): group is SourceControlResourceGroup {
if (group === undefined) return false;
return (group as SourceControlResourceGroup).id !== undefined && (group.handle !== undefined || (group as SourceControlResourceGroup).label !== undefined || (group as SourceControlResourceGroup).resourceStates !== undefined);
}
function isScmResourceState(state: any): state is SourceControlResourceState {
if (state === undefined) return false;
return (state as SourceControlResourceState).resourceUri !== undefined;
}
function isTextEditor(editor: any): editor is TextEditor {
if (editor === undefined) return false;
return editor.id !== undefined && ((editor as TextEditor).edit !== undefined || (editor as TextEditor).document !== undefined);
}
export abstract class Command extends Disposable {
static getMarkdownCommandArgsCore<T>(command: Commands, args: T): string {
return `command:${command}?${encodeURIComponent(JSON.stringify(args))}`;
}
protected readonly contextParsingOptions: CommandContextParsingOptions = { editor: false, uri: false };
private _disposable: Disposable;
constructor(command: Commands | Commands[]) {
super(() => this.dispose());
if (typeof command === 'string') {
this._disposable = commands.registerCommand(command, (...args: any[]) => this._execute(command, ...args), this);
return;
}
const subscriptions = command.map(cmd => commands.registerCommand(cmd, (...args: any[]) => this._execute(cmd, ...args), this));
this._disposable = Disposable.from(...subscriptions);
}
dispose() {
this._disposable && this._disposable.dispose();
}
protected async preExecute(context: CommandContext, ...args: any[]): Promise<any> {
return this.execute(...args);
}
abstract execute(...args: any[]): any;
protected _execute(command: string, ...args: any[]): any {
// Telemetry.trackEvent(command);
const [context, rest] = Command.parseContext(command, this.contextParsingOptions, ...args);
return this.preExecute(context, ...rest);
}
private static parseContext(command: string, options: CommandContextParsingOptions, ...args: any[]): [CommandContext, any[]] {
let editor: TextEditor | undefined = undefined;
let firstArg = args[0];
if (options.editor && (firstArg === undefined || isTextEditor(firstArg))) {
editor = firstArg;
args = args.slice(1);
firstArg = args[0];
}
if (options.uri && (firstArg === undefined || firstArg instanceof Uri)) {
const [uri, ...rest] = args as [Uri, any];
return [{ command: command, type: 'uri', editor: editor, uri: uri }, rest];
}
if (firstArg instanceof ExplorerNode) {
const [node, ...rest] = args as [ExplorerNode, any];
return [{ command: command, type: 'view', node: node, uri: node.uri }, rest];
}
if (isScmResourceState(firstArg)) {
const states = [];
let count = 0;
for (const arg of args) {
if (!isScmResourceState(arg)) break;
count++;
states.push(arg);
}
return [{ command: command, type: 'scm-states', scmResourceStates: states, uri: states[0].resourceUri }, args.slice(count)];
}
if (isScmResourceGroup(firstArg)) {
const groups = [];
let count = 0;
for (const arg of args) {
if (!isScmResourceGroup(arg)) break;
count++;
groups.push(arg);
}
return [{ command: command, type: 'scm-groups', scmResourceGroups: groups }, args.slice(count)];
}
return [{ command: command, type: 'unknown', editor: editor }, args];
}
}
export abstract class ActiveEditorCommand extends Command {
protected readonly contextParsingOptions: CommandContextParsingOptions = { editor: true, uri: true };
constructor(command: Commands | Commands[]) {
super(command);
}
protected async preExecute(context: CommandContext, ...args: any[]): Promise<any> {
return this.execute(context.editor, context.uri, ...args);
}
protected _execute(command: string, ...args: any[]): any {
return super._execute(command, window.activeTextEditor, ...args);
}
abstract execute(editor?: TextEditor, ...args: any[]): any;
}
let lastCommand: { command: string, args: any[] } | undefined = undefined;
export function getLastCommand() {
return lastCommand;
}
export abstract class ActiveEditorCachedCommand extends ActiveEditorCommand {
constructor(command: Commands | Commands[]) {
super(command);
}
protected _execute(command: string, ...args: any[]): any {
lastCommand = {
command: command,
args: args
};
return super._execute(command, ...args);
}
abstract execute(editor: TextEditor, ...args: any[]): any;
}
export abstract class EditorCommand extends Disposable {
private _disposable: Disposable;
constructor(command: Commands | Commands[]) {
super(() => this.dispose());
if (!Array.isArray(command)) {
command = [command];
}
const subscriptions = [];
for (const cmd of command) {
subscriptions.push(commands.registerTextEditorCommand(cmd, (editor: TextEditor, edit: TextEditorEdit, ...args: any[]) => this.executeCore(cmd, editor, edit, ...args), this));
}
this._disposable = Disposable.from(...subscriptions);
}
dispose() {
this._disposable && this._disposable.dispose();
}
private executeCore(command: string, editor: TextEditor, edit: TextEditorEdit, ...args: any[]): any {
// Telemetry.trackEvent(command);
return this.execute(editor, edit, ...args);
}
abstract execute(editor: TextEditor, edit: TextEditorEdit, ...args: any[]): any;
}
export async function openEditor(uri: Uri, options: TextDocumentShowOptions & { rethrow?: boolean } = {}): Promise<TextEditor | undefined> {
const { rethrow, ...opts } = options;
try {
if (uri instanceof GitUri) {
uri = uri.fileUri({ noSha: true });
}
// TODO: revist this
// This is a bit of an ugly hack, but I added it because there a bunch of call sites and toRevisionUri can't be easily made async because of its use in ctors
if (uri.scheme === DocumentSchemes.GitLensGit) {
const gitUri = GitUri.fromRevisionUri(uri);
if (ImageExtensions.includes(path.extname(gitUri.fsPath))) {
const fileName = await Container.git.getVersionedFile(gitUri.repoPath, gitUri.fsPath, gitUri.sha);
if (fileName !== undefined) {
uri = Uri.file(fileName);
await commands.executeCommand(BuiltInCommands.Open, uri);
return undefined;
}
}
}
const document = await workspace.openTextDocument(uri);
return window.showTextDocument(document, {
preserveFocus: false,
preview: true,
viewColumn: ViewColumn.Active,
...opts
});
}
catch (ex) {
const msg = ex.toString();
if (msg.includes('File seems to be binary and cannot be opened as text')) {
await commands.executeCommand(BuiltInCommands.Open, uri);
return undefined;
}
if (rethrow) throw ex;
Logger.error(ex, 'openEditor');
return undefined;
}
DiffLineWithPrevious = 'gitlens.diffLineWithPrevious',
DiffWithRevision = 'gitlens.diffWithRevision',
DiffWithWorking = 'gitlens.diffWithWorking',
DiffLineWithWorking = 'gitlens.diffLineWithWorking',
ExternalDiff = 'gitlens.externalDiff',
ExplorersOpenDirectoryDiff = 'gitlens.explorers.openDirectoryDiff',
ExplorersOpenDirectoryDiffWithWorking = 'gitlens.explorers.openDirectoryDiffWithWorking',
OpenChangedFiles = 'gitlens.openChangedFiles',
OpenBranchesInRemote = 'gitlens.openBranchesInRemote',
OpenBranchInRemote = 'gitlens.openBranchInRemote',
OpenCommitInRemote = 'gitlens.openCommitInRemote',
OpenFileInRemote = 'gitlens.openFileInRemote',
OpenFileRevision = 'gitlens.openFileRevision',
OpenInRemote = 'gitlens.openInRemote',
OpenLineCommitInRemote = 'gitlens.openLineCommitInRemote',
OpenLineInRemote = 'gitlens.openLineInRemote',
OpenRepoInRemote = 'gitlens.openRepoInRemote',
OpenWorkingFile = 'gitlens.openWorkingFile',
ResetSuppressedWarnings = 'gitlens.resetSuppressedWarnings',
ShowCommitSearch = 'gitlens.showCommitSearch',
ShowLastQuickPick = 'gitlens.showLastQuickPick',
ShowQuickCommitDetails = 'gitlens.showQuickCommitDetails',
ShowQuickCommitFileDetails = 'gitlens.showQuickCommitFileDetails',
ShowQuickLineCommitFileDetails = 'gitlens.showQuickLineCommitFileDetails',
ShowQuickFileHistory = 'gitlens.showQuickFileHistory',
ShowQuickBranchHistory = 'gitlens.showQuickBranchHistory',
ShowQuickCurrentBranchHistory = 'gitlens.showQuickRepoHistory',
ShowQuickRepoStatus = 'gitlens.showQuickRepoStatus',
ShowQuickStashList = 'gitlens.showQuickStashList',
ShowSettingsPage = 'gitlens.showSettingsPage',
ShowWelcomePage = 'gitlens.showWelcomePage',
StashApply = 'gitlens.stashApply',
StashDelete = 'gitlens.stashDelete',
StashSave = 'gitlens.stashSave',
ToggleCodeLens = 'gitlens.toggleCodeLens',
ToggleFileBlame = 'gitlens.toggleFileBlame',
ToggleFileHeatmap = 'gitlens.toggleFileHeatmap',
ToggleFileRecentChanges = 'gitlens.toggleFileRecentChanges',
ToggleLineBlame = 'gitlens.toggleLineBlame'
}
export function getCommandUri(uri?: Uri, editor?: TextEditor): Uri | undefined {
if (uri instanceof Uri) return uri;
if (editor === undefined || editor.document === undefined) return undefined;
return editor.document.uri;
}
export interface CommandContextParsingOptions {
editor: boolean;
uri: boolean;
}
export interface CommandBaseContext {
command: string;
editor?: TextEditor;
uri?: Uri;
}
export interface CommandScmGroupsContext extends CommandBaseContext {
type: 'scm-groups';
scmResourceGroups: SourceControlResourceGroup[];
}
export interface CommandScmStatesContext extends CommandBaseContext {
type: 'scm-states';
scmResourceStates: SourceControlResourceState[];
}
export interface CommandUnknownContext extends CommandBaseContext {
type: 'unknown';
}
export interface CommandUriContext extends CommandBaseContext {
type: 'uri';
}
export interface CommandViewContext extends CommandBaseContext {
type: 'view';
node: ExplorerNode;
}
export function isCommandViewContextWithBranch(context: CommandContext): context is CommandViewContext & { node: (ExplorerNode & { branch: GitBranch }) } {
return context.type === 'view' && (context.node as any).branch && (context.node as any).branch instanceof GitBranch;
}
export function isCommandViewContextWithCommit<T extends GitCommit>(context: CommandContext): context is CommandViewContext & { node: (ExplorerNode & { commit: T }) } {
return context.type === 'view' && (context.node as any).commit && (context.node as any).commit instanceof GitCommit;
}
export function isCommandViewContextWithRef(context: CommandContext): context is CommandViewContext & { node: (ExplorerNode & { ref: string }) } {
return context.type === 'view' && (context.node instanceof ExplorerRefNode);
}
export function isCommandViewContextWithRemote(context: CommandContext): context is CommandViewContext & { node: (ExplorerNode & { remote: GitRemote }) } {
return context.type === 'view' && (context.node as any).remote && (context.node as any).remote instanceof GitRemote;
}
export type CommandContext = CommandScmGroupsContext | CommandScmStatesContext | CommandUnknownContext | CommandUriContext | CommandViewContext;
function isScmResourceGroup(group: any): group is SourceControlResourceGroup {
if (group === undefined) return false;
return (group as SourceControlResourceGroup).id !== undefined && (group.handle !== undefined || (group as SourceControlResourceGroup).label !== undefined || (group as SourceControlResourceGroup).resourceStates !== undefined);
}
function isScmResourceState(state: any): state is SourceControlResourceState {
if (state === undefined) return false;
return (state as SourceControlResourceState).resourceUri !== undefined;
}
function isTextEditor(editor: any): editor is TextEditor {
if (editor === undefined) return false;
return editor.id !== undefined && ((editor as TextEditor).edit !== undefined || (editor as TextEditor).document !== undefined);
}
export abstract class Command extends Disposable {
static getMarkdownCommandArgsCore<T>(command: Commands, args: T): string {
return `command:${command}?${encodeURIComponent(JSON.stringify(args))}`;
}
protected readonly contextParsingOptions: CommandContextParsingOptions = { editor: false, uri: false };
private _disposable: Disposable;
constructor(command: Commands | Commands[]) {
super(() => this.dispose());
if (typeof command === 'string') {
this._disposable = commands.registerCommand(command, (...args: any[]) => this._execute(command, ...args), this);
return;
}
const subscriptions = command.map(cmd => commands.registerCommand(cmd, (...args: any[]) => this._execute(cmd, ...args), this));
this._disposable = Disposable.from(...subscriptions);
}
dispose() {
this._disposable && this._disposable.dispose();
}
protected async preExecute(context: CommandContext, ...args: any[]): Promise<any> {
return this.execute(...args);
}
abstract execute(...args: any[]): any;
protected _execute(command: string, ...args: any[]): any {
// Telemetry.trackEvent(command);
const [context, rest] = Command.parseContext(command, this.contextParsingOptions, ...args);
return this.preExecute(context, ...rest);
}
private static parseContext(command: string, options: CommandContextParsingOptions, ...args: any[]): [CommandContext, any[]] {
let editor: TextEditor | undefined = undefined;
let firstArg = args[0];
if (options.editor && (firstArg === undefined || isTextEditor(firstArg))) {
editor = firstArg;
args = args.slice(1);
firstArg = args[0];
}
if (options.uri && (firstArg === undefined || firstArg instanceof Uri)) {
const [uri, ...rest] = args as [Uri, any];
return [{ command: command, type: 'uri', editor: editor, uri: uri }, rest];
}
if (firstArg instanceof ExplorerNode) {
const [node, ...rest] = args as [ExplorerNode, any];
return [{ command: command, type: 'view', node: node, uri: node.uri }, rest];
}
if (isScmResourceState(firstArg)) {
const states = [];
let count = 0;
for (const arg of args) {
if (!isScmResourceState(arg)) break;
count++;
states.push(arg);
}
return [{ command: command, type: 'scm-states', scmResourceStates: states, uri: states[0].resourceUri }, args.slice(count)];
}
if (isScmResourceGroup(firstArg)) {
const groups = [];
let count = 0;
for (const arg of args) {
if (!isScmResourceGroup(arg)) break;
count++;
groups.push(arg);
}
return [{ command: command, type: 'scm-groups', scmResourceGroups: groups }, args.slice(count)];
}
return [{ command: command, type: 'unknown', editor: editor }, args];
}
}
export abstract class ActiveEditorCommand extends Command {
protected readonly contextParsingOptions: CommandContextParsingOptions = { editor: true, uri: true };
constructor(command: Commands | Commands[]) {
super(command);
}
protected async preExecute(context: CommandContext, ...args: any[]): Promise<any> {
return this.execute(context.editor, context.uri, ...args);
}
protected _execute(command: string, ...args: any[]): any {
return super._execute(command, window.activeTextEditor, ...args);
}
abstract execute(editor?: TextEditor, ...args: any[]): any;
}
let lastCommand: { command: string, args: any[] } | undefined = undefined;
export function getLastCommand() {
return lastCommand;
}
export abstract class ActiveEditorCachedCommand extends ActiveEditorCommand {
constructor(command: Commands | Commands[]) {
super(command);
}
protected _execute(command: string, ...args: any[]): any {
lastCommand = {
command: command,
args: args
};
return super._execute(command, ...args);
}
abstract execute(editor: TextEditor, ...args: any[]): any;
}
export abstract class EditorCommand extends Disposable {
private _disposable: Disposable;
constructor(command: Commands | Commands[]) {
super(() => this.dispose());
if (!Array.isArray(command)) {
command = [command];
}
const subscriptions = [];
for (const cmd of command) {
subscriptions.push(commands.registerTextEditorCommand(cmd, (editor: TextEditor, edit: TextEditorEdit, ...args: any[]) => this.executeCore(cmd, editor, edit, ...args), this));
}
this._disposable = Disposable.from(...subscriptions);
}
dispose() {
this._disposable && this._disposable.dispose();
}
private executeCore(command: string, editor: TextEditor, edit: TextEditorEdit, ...args: any[]): any {
// Telemetry.trackEvent(command);
return this.execute(editor, edit, ...args);
}
abstract execute(editor: TextEditor, edit: TextEditorEdit, ...args: any[]): any;
}
export async function openEditor(uri: Uri, options: TextDocumentShowOptions & { rethrow?: boolean } = {}): Promise<TextEditor | undefined> {
const { rethrow, ...opts } = options;
try {
if (uri instanceof GitUri) {
uri = uri.fileUri({ noSha: true });
}
// TODO: revist this
// This is a bit of an ugly hack, but I added it because there a bunch of call sites and toRevisionUri can't be easily made async because of its use in ctors
if (uri.scheme === DocumentSchemes.GitLensGit) {
const gitUri = GitUri.fromRevisionUri(uri);
if (ImageExtensions.includes(path.extname(gitUri.fsPath))) {
const fileName = await Container.git.getVersionedFile(gitUri.repoPath, gitUri.fsPath, gitUri.sha);
if (fileName !== undefined) {
uri = Uri.file(fileName);
await commands.executeCommand(BuiltInCommands.Open, uri);
return undefined;
}
}
}
const document = await workspace.openTextDocument(uri);
return window.showTextDocument(document, {
preserveFocus: false,
preview: true,
viewColumn: ViewColumn.Active,
...opts
});
}
catch (ex) {
const msg = ex.toString();
if (msg.includes('File seems to be binary and cannot be opened as text')) {
await commands.executeCommand(BuiltInCommands.Open, uri);
return undefined;
}
if (rethrow) throw ex;
Logger.error(ex, 'openEditor');
return undefined;
}
}

+ 83
- 83
src/commands/openCommitInRemote.ts View File

@ -1,84 +1,84 @@
'use strict';
import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithCommit } from './common';
import { Container } from '../container';
import { GitUri } from '../gitService';
import { Logger } from '../logger';
import { Messages } from '../messages';
import { OpenInRemoteCommandArgs } from './openInRemote';
export interface OpenCommitInRemoteCommandArgs {
sha?: string;
}
export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
static getMarkdownCommandArgs(sha: string): string;
static getMarkdownCommandArgs(args: OpenCommitInRemoteCommandArgs): string;
static getMarkdownCommandArgs(argsOrSha: OpenCommitInRemoteCommandArgs | string): string {
const args = typeof argsOrSha === 'string'
? { sha: argsOrSha }
: argsOrSha;
return super.getMarkdownCommandArgsCore<OpenCommitInRemoteCommandArgs>(Commands.OpenCommitInRemote, args);
}
constructor() {
super(Commands.OpenCommitInRemote);
}
protected async preExecute(context: CommandContext, args: OpenCommitInRemoteCommandArgs = {}): Promise<any> {
if (isCommandViewContextWithCommit(context)) {
args = { ...args };
args.sha = context.node.commit.sha;
return this.execute(context.editor, context.node.commit.uri, args);
}
return this.execute(context.editor, context.uri, args);
}
async execute(editor?: TextEditor, uri?: Uri, args: OpenCommitInRemoteCommandArgs = {}) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
const gitUri = await GitUri.fromUri(uri);
if (!gitUri.repoPath) return undefined;
try {
if (args.sha === undefined) {
const blameline = editor === undefined ? 0 : editor.selection.active.line;
if (blameline < 0) return undefined;
const blame = editor && editor.document && editor.document.isDirty
? await Container.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await Container.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open commit in remote provider');
let commit = blame.commit;
// If the line is uncommitted, find the previous commit
if (commit.isUncommitted) {
commit = commit.with({
sha: commit.previousSha,
fileName: commit.previousFileName,
previousSha: null,
previousFileName: null
});
}
args.sha = commit.sha;
}
const remotes = await Container.git.getRemotes(gitUri.repoPath);
return commands.executeCommand(Commands.OpenInRemote, uri, {
resource: {
type: 'commit',
sha: args.sha
},
remotes
} as OpenInRemoteCommandArgs);
}
catch (ex) {
Logger.error(ex, 'OpenCommitInRemoteCommand');
return window.showErrorMessage(`Unable to open commit in remote provider. See output channel for more details`);
}
}
'use strict';
import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithCommit } from './common';
import { Container } from '../container';
import { GitUri } from '../gitService';
import { Logger } from '../logger';
import { Messages } from '../messages';
import { OpenInRemoteCommandArgs } from './openInRemote';
export interface OpenCommitInRemoteCommandArgs {
sha?: string;
}
export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
static getMarkdownCommandArgs(sha: string): string;
static getMarkdownCommandArgs(args: OpenCommitInRemoteCommandArgs): string;
static getMarkdownCommandArgs(argsOrSha: OpenCommitInRemoteCommandArgs | string): string {
const args = typeof argsOrSha === 'string'
? { sha: argsOrSha }
: argsOrSha;
return super.getMarkdownCommandArgsCore<OpenCommitInRemoteCommandArgs>(Commands.OpenCommitInRemote, args);
}
constructor() {
super([Commands.OpenCommitInRemote, Commands.OpenLineCommitInRemote]);
}
protected async preExecute(context: CommandContext, args: OpenCommitInRemoteCommandArgs = {}): Promise<any> {
if (isCommandViewContextWithCommit(context)) {
args = { ...args };
args.sha = context.node.commit.sha;
return this.execute(context.editor, context.node.commit.uri, args);
}
return this.execute(context.editor, context.uri, args);
}
async execute(editor?: TextEditor, uri?: Uri, args: OpenCommitInRemoteCommandArgs = {}) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
const gitUri = await GitUri.fromUri(uri);
if (!gitUri.repoPath) return undefined;
try {
if (args.sha === undefined) {
const blameline = editor === undefined ? 0 : editor.selection.active.line;
if (blameline < 0) return undefined;
const blame = editor && editor.document && editor.document.isDirty
? await Container.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await Container.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open commit in remote provider');
let commit = blame.commit;
// If the line is uncommitted, find the previous commit
if (commit.isUncommitted) {
commit = commit.with({
sha: commit.previousSha,
fileName: commit.previousFileName,
previousSha: null,
previousFileName: null
});
}
args.sha = commit.sha;
}
const remotes = await Container.git.getRemotes(gitUri.repoPath);
return commands.executeCommand(Commands.OpenInRemote, uri, {
resource: {
type: 'commit',
sha: args.sha
},
remotes
} as OpenInRemoteCommandArgs);
}
catch (ex) {
Logger.error(ex, 'OpenCommitInRemoteCommand');
return window.showErrorMessage(`Unable to open commit in remote provider. See output channel for more details`);
}
}
}

+ 68
- 68
src/commands/openFileInRemote.ts View File

@ -1,69 +1,69 @@
'use strict';
import { commands, Range, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithBranch, isCommandViewContextWithCommit } from './common';
import { Container } from '../container';
import { GitUri } from '../gitService';
import { Logger } from '../logger';
import { OpenInRemoteCommandArgs } from './openInRemote';
export interface OpenFileInRemoteCommandArgs {
branch?: string;
range?: boolean;
}
export class OpenFileInRemoteCommand extends ActiveEditorCommand {
constructor() {
super(Commands.OpenFileInRemote);
}
protected async preExecute(context: CommandContext, args: OpenFileInRemoteCommandArgs = { range: true }): Promise<any> {
if (isCommandViewContextWithCommit(context)) {
args = { ...args };
args.range = false;
if (isCommandViewContextWithBranch(context)) {
args.branch = context.node.branch !== undefined ? context.node.branch.name : undefined;
}
return this.execute(context.editor, context.node.commit.uri, args);
}
return this.execute(context.editor, context.uri, args);
}
async execute(editor?: TextEditor, uri?: Uri, args: OpenFileInRemoteCommandArgs = { range: true }) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
const gitUri = await GitUri.fromUri(uri);
if (!gitUri.repoPath) return undefined;
if (args.branch === undefined) {
const branch = await Container.git.getBranch(gitUri.repoPath);
if (branch !== undefined) {
args.branch = branch.name;
}
}
try {
const remotes = await Container.git.getRemotes(gitUri.repoPath);
const range = (args.range && editor !== undefined)
? new Range(editor.selection.start.with({ line: editor.selection.start.line + 1 }), editor.selection.end.with({ line: editor.selection.end.line + 1 }))
: undefined;
return commands.executeCommand(Commands.OpenInRemote, uri, {
resource: {
type: gitUri.sha === undefined ? 'file' : 'revision',
branch: args.branch,
fileName: gitUri.getRelativePath(),
range: range,
sha: gitUri.sha
},
remotes
} as OpenInRemoteCommandArgs);
}
catch (ex) {
Logger.error(ex, 'OpenFileInRemoteCommand');
return window.showErrorMessage(`Unable to open file in remote provider. See output channel for more details`);
}
}
'use strict';
import { commands, Range, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithBranch, isCommandViewContextWithCommit } from './common';
import { Container } from '../container';
import { GitUri } from '../gitService';
import { Logger } from '../logger';
import { OpenInRemoteCommandArgs } from './openInRemote';
export interface OpenFileInRemoteCommandArgs {
branch?: string;
range?: boolean;
}
export class OpenFileInRemoteCommand extends ActiveEditorCommand {
constructor() {
super([Commands.OpenFileInRemote, Commands.OpenLineInRemote]);
}
protected async preExecute(context: CommandContext, args: OpenFileInRemoteCommandArgs = { range: true }): Promise<any> {
if (isCommandViewContextWithCommit(context)) {
args = { ...args };
args.range = false;
if (isCommandViewContextWithBranch(context)) {
args.branch = context.node.branch !== undefined ? context.node.branch.name : undefined;
}
return this.execute(context.editor, context.node.commit.uri, args);
}
return this.execute(context.editor, context.uri, args);
}
async execute(editor?: TextEditor, uri?: Uri, args: OpenFileInRemoteCommandArgs = { range: true }) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
const gitUri = await GitUri.fromUri(uri);
if (!gitUri.repoPath) return undefined;
if (args.branch === undefined) {
const branch = await Container.git.getBranch(gitUri.repoPath);
if (branch !== undefined) {
args.branch = branch.name;
}
}
try {
const remotes = await Container.git.getRemotes(gitUri.repoPath);
const range = (args.range && editor !== undefined)
? new Range(editor.selection.start.with({ line: editor.selection.start.line + 1 }), editor.selection.end.with({ line: editor.selection.end.line + 1 }))
: undefined;
return commands.executeCommand(Commands.OpenInRemote, uri, {
resource: {
type: gitUri.sha === undefined ? 'file' : 'revision',
branch: args.branch,
fileName: gitUri.getRelativePath(),
range: range,
sha: gitUri.sha
},
remotes
} as OpenInRemoteCommandArgs);
}
catch (ex) {
Logger.error(ex, 'OpenFileInRemoteCommand');
return window.showErrorMessage(`Unable to open file in remote provider. See output channel for more details`);
}
}
}

+ 144
- 144
src/commands/showQuickCommitFileDetails.ts View File

@ -1,145 +1,145 @@
'use strict';
import { Strings } from '../system';
import { TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithCommit } from './common';
import { GlyphChars } from '../constants';
import { GitCommit, GitLog, GitLogCommit, GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import { CommandQuickPickItem, CommitFileQuickPick } from '../quickPicks/quickPicks';
import { ShowQuickCommitDetailsCommandArgs } from './showQuickCommitDetails';
import { Messages } from '../messages';
import * as path from 'path';
import { Container } from '../container';
export interface ShowQuickCommitFileDetailsCommandArgs {
sha?: string;
commit?: GitCommit | GitLogCommit;
fileLog?: GitLog;
goBackCommand?: CommandQuickPickItem;
}
export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCachedCommand {
static getMarkdownCommandArgs(sha: string): string;
static getMarkdownCommandArgs(args: ShowQuickCommitFileDetailsCommandArgs): string;
static getMarkdownCommandArgs(argsOrSha: ShowQuickCommitFileDetailsCommandArgs | string): string {
const args = typeof argsOrSha === 'string'
? { sha: argsOrSha }
: argsOrSha;
return super.getMarkdownCommandArgsCore<ShowQuickCommitFileDetailsCommandArgs>(Commands.ShowQuickCommitFileDetails, args);
}
constructor() {
super(Commands.ShowQuickCommitFileDetails);
}
protected async preExecute(context: CommandContext, args: ShowQuickCommitFileDetailsCommandArgs = {}): Promise<any> {
if (context.type === 'view') {
args = { ...args };
args.sha = context.node.uri.sha;
if (isCommandViewContextWithCommit(context)) {
args.commit = context.node.commit;
}
}
return this.execute(context.editor, context.uri, args);
}
async execute(editor?: TextEditor, uri?: Uri, args: ShowQuickCommitFileDetailsCommandArgs = {}) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
let workingFileName = args.commit && args.commit.workingFileName;
const gitUri = await GitUri.fromUri(uri);
args = { ...args };
if (args.sha === undefined) {
if (editor === undefined) return undefined;
const blameline = editor.selection.active.line;
if (blameline < 0) return undefined;
try {
const blame = await Container.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to show commit file details');
// Because the previous sha of an uncommitted file isn't trust worthy we just have to kick out
if (blame.commit.isUncommitted) return Messages.showLineUncommittedWarningMessage('Unable to show commit file details');
args.sha = blame.commit.sha;
args.commit = blame.commit;
workingFileName = path.relative(args.commit.repoPath, gitUri.fsPath);
}
catch (ex) {
Logger.error(ex, 'ShowQuickCommitFileDetailsCommand', `getBlameForLine(${blameline})`);
return window.showErrorMessage(`Unable to show commit file details. See output channel for more details`);
}
}
try {
if (args.commit === undefined || !args.commit.isFile) {
if (args.commit !== undefined) {
workingFileName = undefined;
}
if (args.fileLog !== undefined) {
args.commit = args.fileLog.commits.get(args.sha!);
// If we can't find the commit, kill the fileLog
if (args.commit === undefined) {
args.fileLog = undefined;
}
}
if (args.fileLog === undefined) {
args.commit = await Container.git.getLogCommitForFile(args.commit === undefined ? gitUri.repoPath : args.commit.repoPath, gitUri.fsPath, { ref: args.sha });
if (args.commit === undefined) return Messages.showCommitNotFoundWarningMessage(`Unable to show commit file details`);
}
}
if (args.commit === undefined) return Messages.showCommitNotFoundWarningMessage(`Unable to show commit file details`);
// Attempt to the most recent commit -- so that we can find the real working filename if there was a rename
args.commit.workingFileName = workingFileName;
[args.commit.workingFileName] = await Container.git.findWorkingFileName(args.commit);
const shortSha = GitService.shortenSha(args.sha!);
if (args.goBackCommand === undefined) {
// Create a command to get back to the commit details
args.goBackCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to details of ${GlyphChars.Space}$(git-commit) ${shortSha}`
}, Commands.ShowQuickCommitDetails, [
args.commit.toGitUri(),
{
commit: args.commit,
sha: args.sha
} as ShowQuickCommitDetailsCommandArgs
]);
}
// Create a command to get back to where we are right now
const currentCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to details of ${GlyphChars.Space}$(file-text) ${path.basename(args.commit.fileName)} in ${GlyphChars.Space}$(git-commit) ${shortSha}`
}, Commands.ShowQuickCommitFileDetails, [
args.commit.toGitUri(),
args
]);
const pick = await CommitFileQuickPick.show(args.commit as GitLogCommit, uri, args.goBackCommand, currentCommand, args.fileLog);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
return undefined;
}
catch (ex) {
Logger.error(ex, 'ShowQuickCommitFileDetailsCommand');
return window.showErrorMessage(`Unable to show commit file details. See output channel for more details`);
}
}
'use strict';
import { Strings } from '../system';
import { TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithCommit } from './common';
import { GlyphChars } from '../constants';
import { GitCommit, GitLog, GitLogCommit, GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import { CommandQuickPickItem, CommitFileQuickPick } from '../quickPicks/quickPicks';
import { ShowQuickCommitDetailsCommandArgs } from './showQuickCommitDetails';
import { Messages } from '../messages';
import * as path from 'path';
import { Container } from '../container';
export interface ShowQuickCommitFileDetailsCommandArgs {
sha?: string;
commit?: GitCommit | GitLogCommit;
fileLog?: GitLog;
goBackCommand?: CommandQuickPickItem;
}
export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCachedCommand {
static getMarkdownCommandArgs(sha: string): string;
static getMarkdownCommandArgs(args: ShowQuickCommitFileDetailsCommandArgs): string;
static getMarkdownCommandArgs(argsOrSha: ShowQuickCommitFileDetailsCommandArgs | string): string {
const args = typeof argsOrSha === 'string'
? { sha: argsOrSha }
: argsOrSha;
return super.getMarkdownCommandArgsCore<ShowQuickCommitFileDetailsCommandArgs>(Commands.ShowQuickCommitFileDetails, args);
}
constructor() {
super([Commands.ShowQuickCommitFileDetails, Commands.ShowQuickLineCommitFileDetails]);
}
protected async preExecute(context: CommandContext, args: ShowQuickCommitFileDetailsCommandArgs = {}): Promise<any> {
if (context.type === 'view') {
args = { ...args };
args.sha = context.node.uri.sha;
if (isCommandViewContextWithCommit(context)) {
args.commit = context.node.commit;
}
}
return this.execute(context.editor, context.uri, args);
}
async execute(editor?: TextEditor, uri?: Uri, args: ShowQuickCommitFileDetailsCommandArgs = {}) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
let workingFileName = args.commit && args.commit.workingFileName;
const gitUri = await GitUri.fromUri(uri);
args = { ...args };
if (args.sha === undefined) {
if (editor === undefined) return undefined;
const blameline = editor.selection.active.line;
if (blameline < 0) return undefined;
try {
const blame = await Container.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to show commit file details');
// Because the previous sha of an uncommitted file isn't trust worthy we just have to kick out
if (blame.commit.isUncommitted) return Messages.showLineUncommittedWarningMessage('Unable to show commit file details');
args.sha = blame.commit.sha;
args.commit = blame.commit;
workingFileName = path.relative(args.commit.repoPath, gitUri.fsPath);
}
catch (ex) {
Logger.error(ex, 'ShowQuickCommitFileDetailsCommand', `getBlameForLine(${blameline})`);
return window.showErrorMessage(`Unable to show commit file details. See output channel for more details`);
}
}
try {
if (args.commit === undefined || !args.commit.isFile) {
if (args.commit !== undefined) {
workingFileName = undefined;
}
if (args.fileLog !== undefined) {
args.commit = args.fileLog.commits.get(args.sha!);
// If we can't find the commit, kill the fileLog
if (args.commit === undefined) {
args.fileLog = undefined;
}
}
if (args.fileLog === undefined) {
args.commit = await Container.git.getLogCommitForFile(args.commit === undefined ? gitUri.repoPath : args.commit.repoPath, gitUri.fsPath, { ref: args.sha });
if (args.commit === undefined) return Messages.showCommitNotFoundWarningMessage(`Unable to show commit file details`);
}
}
if (args.commit === undefined) return Messages.showCommitNotFoundWarningMessage(`Unable to show commit file details`);
// Attempt to the most recent commit -- so that we can find the real working filename if there was a rename
args.commit.workingFileName = workingFileName;
[args.commit.workingFileName] = await Container.git.findWorkingFileName(args.commit);
const shortSha = GitService.shortenSha(args.sha!);
if (args.goBackCommand === undefined) {
// Create a command to get back to the commit details
args.goBackCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to details of ${GlyphChars.Space}$(git-commit) ${shortSha}`
}, Commands.ShowQuickCommitDetails, [
args.commit.toGitUri(),
{
commit: args.commit,
sha: args.sha
} as ShowQuickCommitDetailsCommandArgs
]);
}
// Create a command to get back to where we are right now
const currentCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to details of ${GlyphChars.Space}$(file-text) ${path.basename(args.commit.fileName)} in ${GlyphChars.Space}$(git-commit) ${shortSha}`
}, Commands.ShowQuickCommitFileDetails, [
args.commit.toGitUri(),
args
]);
const pick = await CommitFileQuickPick.show(args.commit as GitLogCommit, uri, args.goBackCommand, currentCommand, args.fileLog);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
return undefined;
}
catch (ex) {
Logger.error(ex, 'ShowQuickCommitFileDetailsCommand');
return window.showErrorMessage(`Unable to show commit file details. See output channel for more details`);
}
}
}

+ 64
- 1
src/extension.ts View File

@ -1,7 +1,7 @@
'use strict';
import { Versions } from './system';
import { commands, ExtensionContext, extensions, window, workspace } from 'vscode';
import { CodeLensLanguageScope, CodeLensScopes, configuration, Configuration, HighlightLocations, IConfig, KeyMap, OutputLevel } from './configuration';
import { CodeLensLanguageScope, CodeLensScopes, configuration, Configuration, HighlightLocations, IConfig, IMenuConfig, KeyMap, OutputLevel } from './configuration';
import { CommandContext, ExtensionKey, GlobalState, QualifiedExtensionId, setCommandContext } from './constants';
import { Commands, configureCommands } from './commands';
import { Container } from './container';
@ -229,6 +229,69 @@ async function migrateSettings(context: ExtensionContext, previousVersion: strin
migrationFn: v => v === 'standard' ? KeyMap.Alternate : v as KeyMap
});
}
if (Versions.compare(previous, Versions.from(8, 2, 4)) !== 1) {
await configuration.migrate<{
explorerContext: {
fileDiff: boolean,
history: boolean,
remote: boolean
},
editorContext: {
blame: boolean,
copy: boolean,
details: boolean,
fileDiff: boolean,
history: boolean,
lineDiff: boolean,
remote: boolean
},
editorTitle: {
blame: boolean,
fileDiff: boolean,
history: boolean,
remote: boolean,
status: boolean
},
editorTitleContext: {
blame: boolean,
fileDiff: boolean,
history: boolean,
remote: boolean
}
},
IMenuConfig>(
'advanced.menus', configuration.name('menus').value, {
migrationFn: m => {
return {
editor: {
blame: !!m.editorContext.blame,
clipboard: !!m.editorContext.copy,
compare: !!m.editorContext.lineDiff,
details: !!m.editorContext.details,
history: !!m.editorContext.history,
remote: !!m.editorContext.remote
},
explorer: {
compare: !!m.explorerContext.fileDiff,
history: !!m.explorerContext.history,
remote: !!m.explorerContext.remote
},
tab: {
compare: !!m.editorTitleContext.fileDiff,
history: !!m.editorTitleContext.history,
remote: !!m.editorTitleContext.remote
},
tabGroup: {
blame: !!m.editorTitle.blame,
compare: !!m.editorTitle.fileDiff,
history: !!m.editorTitle.history,
remote: !!m.editorTitle.remote
}
} as IMenuConfig;
}
});
}
}
catch (ex) {
Logger.error(ex, 'migrateSettings');

+ 26
- 29
src/ui/config.ts View File

@ -116,35 +116,6 @@ export interface IAdvancedConfig {
fileHistoryFollowsRenames: boolean;
maxListItems: number;
menus: {
explorerContext: {
fileDiff: boolean;
history: boolean;
remote: boolean;
};
editorContext: {
blame: boolean;
copy: boolean;
details: boolean;
fileDiff: boolean;
history: boolean;
lineDiff: boolean;
remote: boolean;
};
editorTitle: {
blame: boolean;
fileDiff: boolean;
history: boolean;
status: boolean;
};
editorTitleContext: {
blame: boolean;
fileDiff: boolean;
history: boolean;
remote: boolean;
};
};
messages: {
suppressCommitHasNoPreviousCommitWarning: boolean;
suppressCommitNotFoundWarning: boolean;
@ -228,6 +199,31 @@ export interface IHistoryExplorerConfig {
enabled: boolean;
}
export interface IMenuConfig {
editor: boolean | {
blame: boolean;
clipboard: boolean;
compare: boolean;
details: boolean;
history: boolean;
remote: boolean;
};
explorer: boolean | {
compare: boolean;
history: boolean;
remote: boolean;
};
tab: boolean | {
compare: boolean;
history: boolean;
remote: boolean;
};
tabGroup: boolean | {
compare: boolean;
history: boolean;
};
}
export interface IResultsExplorerConfig {
files: IExplorersFilesConfig;
}
@ -315,6 +311,7 @@ export interface IConfig {
insiders: boolean;
keymap: KeyMap;
menus: boolean | IMenuConfig;
outputLevel: OutputLevel;
recentChanges: {

Loading…
Cancel
Save