mirror of
https://github.com/ckaczor/vscode-gitlens.git
synced 2026-01-17 01:35:37 -05:00
Major refactor/rework -- many new features and breaking changes
Adds all-new, beautiful, highly customizable and themeable, file blame annotations Adds all-new configurability and themeability to the current line blame annotations Adds all-new configurability to the status bar blame information Adds all-new configurability over which commands are added to which menus via the `gitlens.advanced.menus` setting Adds better configurability over where Git code lens will be shown -- both by default and per language Adds an all-new `changes` (diff) hover annotation to the current line - provides instant access to the line's previous version Adds `Toggle Line Blame Annotations` command (`gitlens.toggleLineBlame`) - toggles the current line blame annotations on and off Adds `Show Line Blame Annotations` command (`gitlens.showLineBlame`) - shows the current line blame annotations Adds `Toggle File Blame Annotations` command (`gitlens.toggleFileBlame`) - toggles the file blame annotations on and off Adds `Show File Blame Annotations` command (`gitlens.showFileBlame`) - shows the file blame annotations Adds `Open File in Remote` command (`gitlens.openFileInRemote`) to the `editor/title` context menu Adds `Open Repo in Remote` command (`gitlens.openRepoInRemote`) to the `editor/title` context menu Changes the position of the `Open File in Remote` command (`gitlens.openFileInRemote`) in the context menus - now in the `navigation` group Changes the `Toggle Git Code Lens` command (`gitlens.toggleCodeLens`) to always toggle the Git code lens on and off Removes the on-demand `trailing` file blame annotations -- didn't work out and just ended up with a ton of visual noise Removes `Toggle Blame Annotations` command (`gitlens.toggleBlame`) - replaced by the `Toggle File Blame Annotations` command (`gitlens.toggleFileBlame`) Removes `Show Blame Annotations` command (`gitlens.showBlame`) - replaced by the `Show File Blame Annotations` command (`gitlens.showFileBlame`)
This commit is contained in:
172
README.md
172
README.md
@@ -5,56 +5,67 @@
|
||||
|
||||
# GitLens
|
||||
|
||||
GitLens **supercharges** the built-in Visual Studio Code Git capabilities. It helps you to **visualize code authorship** at a glance via inline Git blame annotations and code lens, **seamlessly navigate and explore** the history of a file or branch, **gain valuable insights** via powerful comparision commands, and so much more.
|
||||
GitLens **supercharges** the built-in Visual Studio Code Git capabilities. It helps you to **visualize code authorship** at a glance via Git blame annotations and code lens, **seamlessly navigate and explore** the history of a file or branch, **gain valuable insights** via powerful comparision commands, and so much more.
|
||||
|
||||
GitLens provides an unobtrusive blame annotation at the end of the selected line, a status bar item showing the commit author and date of the selected line, code lens showing the most recent commit and # of authors of the file and/or code block, and many commands for exploring commits and histories, comparing and navigating revisions, stash access, repository status, and more. GitLens is also [highly customizable](#extension-settings) to meet your specific needs — find code lens intrusive or the selected line blame annotation distracting — no problem, it is easy to [turn them off or change how they behave](#extension-settings).
|
||||
GitLens provides an unobtrusive blame annotation at the end of the current line, a status bar item showing the commit author and date of the current line, code lens showing the most recent commit and # of authors of the file and/or code block, and many commands for exploring commits and histories, comparing and navigating revisions, stash access, repository status, and more. GitLens is also [highly customizable](#extension-settings) to meet your specific needs — find code lens intrusive or the current line blame annotation distracting — no problem, it is easy to [turn them off or change how they behave](#extension-settings).
|
||||
|
||||
## Previews
|
||||
#### Featuring code lens, whole file inline blame annotations, and navigation and exploration via quick pick menus
|
||||
#### Featuring code lens, file blame annotations, and navigation and exploration via quick pick menus
|
||||

|
||||
|
||||
#### Featuring selected line blame annotation and hovers, status bar commit details, quick pick menus, compare with previous, and more
|
||||
#### Featuring current line blame annotation and hovers, status bar commit details, quick pick menus, compare with previous, and more
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
#### Git Blame Annotations
|
||||
|
||||
- Adds a **blame annotation** to the end of the selected line showing the commit id and message, with more details (including the line's previous version) in a hover popup ([optional](#extension-settings), on by default)
|
||||
- Adds a [customizable](#line-blame-annotation-settings) **Git blame annotation** to the end of the current line ([optional](#line-blame-annotation-settings), on by default)
|
||||
- Contains the commit author, date, and message, by [default](#line-blame-annotation-settings)
|
||||
- Commit details, including the changes from the line's previous version, are provided in a hover popup ([optional](#line-blame-annotation-settings), on by default)
|
||||
|
||||
- Adds a `Toggle Blame Annotations` command (`gitlens.toggleBlame`) with a shortcut of `alt+b` to toggle **inline Git blame annotations** for a whole file with multiple styles — compact, expanded, and trailing
|
||||
- Also adds a `Show Blame Annotations` command (`gitlens.showBlame`)
|
||||
- Adds on-demand, highly [customizable](#file-blame-annotation-settings) **Git blame annotations** of the whole file
|
||||
- Choose between `gutter` (default) and `hover` [annotation styles](#file-blame-annotation-settings)
|
||||
- Contains the commit message and date, by [default](#file-blame-annotation-settings)
|
||||
- Commit details are also provided in a hover popup ([optional](#file-blame-annotation-settings), on by default)
|
||||
|
||||
- Adds **author and date blame information** about the selected line to the **status bar** ([optional](#extension-settings), on by default)
|
||||
- By default clicking on the status bar shows a **commit details quick pick menu** with commands for comparing, navigating and exploring commits, and more
|
||||
|
||||
- Provides [customizable](#extension-settings) click behavior of the status bar — choose between one of the following
|
||||
- Toggle whole file blame annotations on and off
|
||||
- Toggle code lens on and off — only available if [`"gitlens.codeLens.visibility": "ondemand"`](#extension-settings) is set
|
||||
- Compare the file with the previous commit
|
||||
- Show a quick pick menu with details and commands for the commit
|
||||
- Adds [customizable](#status-bar-settings) **blame information** about the current line to the **status bar** ([optional](#status-bar-settings), on by default)
|
||||
- Contains the commit author and date, by [default](#status-bar-settings)
|
||||
- Clicking the status bar item will, by [default](#status-bar-settings), show a **commit details quick pick menu** with commands for comparing, navigating and exploring commits, and more
|
||||
- Provides [customizable](#status-bar-settings) click behavior — choose between one of the following
|
||||
- Toggle file blame annotations on and off
|
||||
- Toggle code lens on and off
|
||||
- Compare the line commit with the previous commit
|
||||
- Compare the line commit with the working tree
|
||||
- Show a quick pick menu with details and commands for the commit (default)
|
||||
- Show a quick pick menu with file details and commands for the commit
|
||||
- Show a quick pick menu with the commit history of the file
|
||||
- Show a quick pick menu with the commit history of the current branch
|
||||
|
||||
- Adds a `Toggle File Blame Annotations` command (`gitlens.toggleFileBlame`) with a shortcut of `alt+b` to toggle the file blame annotations on and off
|
||||
- Also adds a `Show File Blame Annotations` command (`gitlens.showFileBlame`)
|
||||
|
||||
- Adds a `Toggle Line Blame Annotations` command (`gitlens.toggleLineBlame`) to toggle the current line blame annotations on and off
|
||||
- Also adds a `Show Line Blame Annotations` command (`gitlens.showLineBlame`)
|
||||
|
||||
#### Git Code Lens
|
||||
|
||||
- Adds **code lens** to the top of the file and on code blocks ([optional](#extension-settings), on by default)
|
||||
- Adds **code lens** to the top of the file and on code blocks ([optional](#code-lens-settings), on by default)
|
||||
- **Recent Change** — author and date of the most recent commit for the file or code block
|
||||
- By default, clicking on the code lens shows a **commit file details quick pick menu** with commands for comparing, navigating and exploring commits, and more
|
||||
- Clicking the code lens will, by [default](#code-lens-settings), show a **commit file details quick pick menu** with commands for comparing, navigating and exploring commits, and more
|
||||
- **Authors** — number of authors of the file or code block and the most prominent author (if there is more than one)
|
||||
- By default, clicking on the code lens toggles the inline Git blame annotations on and off for the whole file
|
||||
- Clicking the code lens will, by [default](#code-lens-settings), toggle the file Git blame annotations on and off of the whole file
|
||||
- Will be hidden if the author of the most recent commit is also the only author of the file or block, to avoid duplicate information and reduce visual noise
|
||||
|
||||
- Provides [customizable](#extension-settings) click behavior for each code lens — choose between one of the following
|
||||
- Toggle whole file blame annotations on and off
|
||||
- Compare the file with the previous commit
|
||||
- Provides [customizable](#code-lens-settings) click behavior for each code lens — choose between one of the following
|
||||
- Toggle file blame annotations on and off
|
||||
- Compare the commit with the previous commit
|
||||
- Show a quick pick menu with details and commands for the commit
|
||||
- Show a quick pick menu with file details and commands for the commit
|
||||
- Show a quick pick menu with the commit history of the file
|
||||
- Show a quick pick menu with the commit history of the current branch
|
||||
|
||||
- Adds a `Toggle Git Code Lens` command (`gitlens.toggleCodeLens`) with a shortcut of `shift+alt+b` to toggle the code lens on and off — only available if [`"gitlens.codeLens.visibility": "ondemand"`](#extension-settings) is set
|
||||
- Adds a `Toggle Git Code Lens` command (`gitlens.toggleCodeLens`) with a shortcut of `shift+alt+b` to toggle the code lens on and off
|
||||
|
||||
#### Powerful Comparison Tools
|
||||
|
||||
@@ -180,40 +191,109 @@ GitLens provides an unobtrusive blame annotation at the end of the selected line
|
||||
|
||||
## Insiders
|
||||
|
||||
Add [`"gitlens.insiders": true`](#extension-settings) to your settings to join the insiders channel and get early access to upcoming features. Be aware that because this provides early access expect there to be issues.
|
||||
Add [`"gitlens.insiders": true`](#general-extension-settings) to your settings to join the insiders channel and get early access to upcoming features. Be aware that because this provides early access expect there to be issues.
|
||||
|
||||
## Extension Settings
|
||||
|
||||
GitLens is highly customizable and provides many configuration settings to allow the personalization of almost all features
|
||||
|
||||
### General Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.insiders`|Opts into the insiders channel -- provides access to upcoming features
|
||||
|`gitlens.outputLevel`|Specifies how much (if any) output will be sent to the GitLens output channel
|
||||
|`gitlens.blame.annotation.activeLine`|Specifies whether and how to show blame annotations on the active line. `off` - no annotation. `inline` - adds a trailing annotation to the active line. `hover` - adds hover annotation to the active line. `both` - adds both `inline` and `hover` annotations
|
||||
|`gitlens.blame.annotation.activeLineDarkColor`|Specifies the color of the active line blame annotation to use with a dark theme. Must be a valid css color
|
||||
|`gitlens.blame.annotation.activeLineLightColor`|Specifies the color of the active line blame annotation to use with a light theme. Must be a valid css color
|
||||
|`gitlens.blame.annotation.highlight`|Specifies whether and how to highlight blame annotations. `none` - no highlight. `gutter` - adds a gutter icon. `line` - adds a full-line highlight. `both` - adds both `gutter` and `line` highlights
|
||||
|`gitlens.blame.annotation.style`|Specifies the style of the blame annotations. `compact` - groups annotations to limit the repetition and also adds author and date when possible. `expanded` - shows an annotation on every line
|
||||
|`gitlens.blame.annotation.author`|Specifies whether the committer will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles
|
||||
|`gitlens.blame.annotation.date`|Specifies whether and how the commit date will be shown in the blame annotations. `off` - no date. `relative` - relative date (e.g. 1 day ago). `absolute` - date format specified by `gitlens.blame.annotation.dateFormat`. Applies only to the `expanded` & `trailing` annotation styles
|
||||
|`gitlens.blame.annotation.dateFormat`|Specifies the date format of how absolute dates will be shown in the blame annotations. See https://momentjs.com/docs/#/displaying/format/ for valid formats
|
||||
|`gitlens.blame.annotation.message`|Specifies whether the commit message will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles
|
||||
|`gitlens.blame.annotation.sha`|Specifies whether the commit sha will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles
|
||||
|`gitlens.codeLens.visibility`|Specifies when code lens will be shown in the active document. `auto` - always shown. `ondemand` - never shown, unless toggled via the `gitlens.toggleCodeLens` command. `off` - never shown
|
||||
|`gitlens.codeLens.authors.enabled`|Specifies whether the authors code lens is shown
|
||||
|`gitlens.codeLens.authors.command`|Specifies the command executed when the authors code lens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current committed file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick
|
||||
|`gitlens.codeLens.recentChange.enabled`|Specifies whether the recent change code lens is shown
|
||||
|`gitlens.codeLens.recentChange.command`|"Specifies the command executed when the recent change code lens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current committed file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick
|
||||
|`gitlens.codeLens.location`|Specifies where code lens will be rendered in the active document. `all` - render at the top of the document, on container-like (classes, modules, etc), and on member-like (methods, functions, properties, etc) lines. `document+containers` - render at the top of the document and on container-like lines. `document` - only render at the top of the document. `custom` - rendering controlled by `gitlens.codeLens.locationCustomSymbols`
|
||||
|`gitlens.codeLens.locationCustomSymbols`|Specifies the set of document symbols to render active document code lens on. Must be a member of `SymbolKind`
|
||||
|`gitlens.codeLens.languageLocations`|Specifies where code lens will be rendered in the active document for the specified languages
|
||||
|`gitlens.menus.diff.enabled`|Specifies whether diff commands will be added to the context menus
|
||||
|`gitlens.statusBar.enabled`|Specifies whether blame information is shown in the status bar
|
||||
|
||||
### Blame Annotation Settings
|
||||
|
||||
#### File Blame Annotation Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.blame.file.annotationType`|Specifies the type of blame annotations that will be shown for the current file. `gutter` - adds an annotation to the beginning of each line. `hover` - shows annotations when hovering over each line
|
||||
|`gitlens.blame.file.lineHighlight.enabled`|Specifies whether or not to highlight lines associated with the current line
|
||||
|`gitlens.blame.file.lineHighlight.locations`|Specifies where the associated line highlights will be shown. `gutter` - adds a gutter glyph. `line` - adds a full-line highlight background color. `overviewRuler` - adds a decoration to the overviewRuler (scroll bar)
|
||||
|`gitlens.annotations.file.gutter.format`|Specifies the format of the gutter blame annotations. Available tokens: `${id}` - commit id, `${author}` - commit author, `${message}` - commit message, `${ago}` - relative commit date (e.g. 1 day ago), `${date}` - formatted commit date (format specified by `gitlens.annotations.file.dateFormat`), `${authorAgo}` - commit author, relative commit date
|
||||
|`gitlens.annotations.file.gutter.dateFormat`|Specifies how to format absolute dates (using the `${date}` token) in gutter blame annotations. See https://momentjs.com/docs/#/displaying/format/ for valid formats
|
||||
|`gitlens.annotations.file.gutter.compact`|Specifies whether or not to compact (deduplicate) matching adjacent gutter blame annotations
|
||||
|`gitlens.annotations.file.gutter.heatmap.enabled`|Specifies whether or not to provide a heatmap indicator in the gutter blame annotations
|
||||
|`gitlens.annotations.file.gutter.heatmap.location`|Specifies where the heatmap indicators will be shown in the gutter blame annotations. `left` - adds a heatmap indicator on the left edge of the gutter blame annotations. `right` - adds a heatmap indicator on the right edge of the gutter blame annotations
|
||||
|`gitlens.annotations.file.gutter.hover.details`|Specifies whether or not to provide a commit details hover annotation over the gutter blame annotations
|
||||
|`gitlens.annotations.file.gutter.hover.wholeLine`|Specifies whether or not to trigger hover annotations over the whole line
|
||||
|`gitlens.annotations.file.hover.heatmap.enabled`|Specifies whether or not to provide heatmap indicators on the left edge of each line
|
||||
|`gitlens.annotations.file.hover.wholeLine`|Specifies whether or not to trigger hover annotations over the whole line
|
||||
|
||||
#### Line Blame Annotation Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.blame.line.enabled`|Specifies whether or not to provide a blame annotation for the current line
|
||||
|`gitlens.blame.line.annotationType`|Specifies the type of blame annotations that will be shown for the current line. `trailing` - adds an annotation to the end of the current line. `hover` - shows annotations when hovering over the current line
|
||||
|`gitlens.annotations.line.trailing.format`|Specifies the format of the trailing blame annotations. Available tokens: `${id}` - commit id, `${author}` - commit author, `${message}` - commit message, `${ago}` - relative commit date (e.g. 1 day ago), `${date}` - formatted commit date (format specified by `gitlens.annotations.currentLine.dateFormat`), `${authorAgo}` - commit author, relative commit date
|
||||
|`gitlens.annotations.line.trailing.dateFormat`|Specifies how to format absolute dates (using the `${date}` token) in trailing blame annotations. See https://momentjs.com/docs/#/displaying/format/ for valid formats
|
||||
|`gitlens.annotations.line.trailing.hover.details`|Specifies whether or not to provide a commit details hover annotation over the trailing blame annotations
|
||||
|`gitlens.annotations.line.trailing.hover.changes`|Specifies whether or not to provide a changes (diff) hover annotation over the trailing blame annotations
|
||||
|`gitlens.annotations.line.trailing.hover.wholeLine`|Specifies whether or not to trigger hover annotations over the whole line
|
||||
|`gitlens.annotations.line.hover.details`|Specifies whether or not to provide a commit details hover annotation for the current line
|
||||
|`gitlens.annotations.line.hover.changes`|Specifies whether or not to provide a changes (diff) hover annotation for the current line
|
||||
|
||||
### Code Lens Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.codeLens.enabled`|Specifies whether or not to provide any Git code lens
|
||||
|`gitlens.codeLens.recentChange.enabled`|Specifies whether or not to show a `recent change` code lens showing the author and date of the most recent commit for the file or code block
|
||||
|`gitlens.codeLens.recentChange.command`|Specifies the command to be executed when the `recent change` code lens is clicked. `gitlens.toggleFileBlame` - toggles file blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current committed file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick
|
||||
|`gitlens.codeLens.authors.enabled`|Specifies whether or not to show an `authors` code lens showing number of authors of the file or code block and the most prominent author (if there is more than one)
|
||||
|`gitlens.codeLens.authors.command`|Specifies the command to be executed when the `authors` code lens is clicked. `gitlens.toggleFileBlame` - toggles file blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current committed file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick
|
||||
|`gitlens.codeLens.locations`|Specifies where Git code lens will be shown in the document. `document` - adds code lens at the top of the document. `containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc). `blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines. `custom` - adds code lens at the start of symbols contained in `gitlens.codeLens.locationCustomSymbols`
|
||||
|`gitlens.codeLens.customLocationSymbols`|Specifies the set of document symbols where Git code lens will be shown in the document
|
||||
|`gitlens.codeLens.perLanguageLocations`|Specifies where Git code lens will be shown in the document for the specified languages
|
||||
|
||||
### Status Bar Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.statusBar.enabled`|Specifies whether or not to provide blame information on the status bar
|
||||
|`gitlens.statusBar.alignment`|Specifies the blame alignment in the status bar. `left` - align to the left, `right` - align to the right
|
||||
|`gitlens.statusBar.command`|Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current line commit with the previous. `gitlens.diffWithWorking` - compares the current line commit with the working tree. `gitlens.toggleCodeLens` - toggles Git code lens. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick
|
||||
|`gitlens.statusBar.date`|Specifies whether and how the commit date will be shown in the blame status bar. `off` - no date. `relative` - relative date (e.g. 1 day ago). `absolute` - date format specified by `gitlens.statusBar.dateFormat`
|
||||
|`gitlens.statusBar.dateFormat`|Specifies the date format of how absolute dates will be shown in the blame status bar. See https://momentjs.com/docs/#/displaying/format/ for valid formats
|
||||
|`gitlens.statusBar.format`|Specifies the format of the blame information on the status bar. Available tokens: `${id}` - commit id, `${author}` - commit author, `${message}` - commit message, `${ago}` - relative commit date (e.g. 1 day ago), `${date}` - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)
|
||||
|`gitlens.statusBar.command`|Specifies the command to be executed when the blame status bar item is clicked. `gitlens.toggleFileBlame` - toggles file blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current line commit with the previous. `gitlens.diffWithWorking` - compares the current line commit with the working tree. `gitlens.toggleCodeLens` - toggles Git code lens. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick
|
||||
|`gitlens.statusBar.format`|Specifies the format of the status bar blame information. Available tokens: `${id}` - commit id, `${author}` - commit author, `${message}` - commit message, `${ago}` - relative commit date (e.g. 1 day ago), `${date}` - formatted commit date (format specified by `gitlens.statusBar.dateFormat`), `${authorAgo}` - commit author, relative commit date
|
||||
|`gitlens.statusBar.dateFormat`|Specifies the date format of absolute dates shown in the blame information on the status bar. See https://momentjs.com/docs/#/displaying/format/ for valid formats
|
||||
|
||||
### Theme Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.theme.annotations.file.gutter.separateLines`|Specifies whether or not gutter blame annotations will be separated by a small gap
|
||||
|`gitlens.theme.annotations.file.gutter.dark.backgroundColor`|Specifies the dark theme background color of the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.gutter.light.backgroundColor`|Specifies the light theme background color of the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.gutter.dark.foregroundColor`|Specifies the dark theme foreground color of the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.gutter.light.foregroundColor`|Specifies the light theme foreground color of the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.gutter.dark.uncommittedForegroundColor`|Specifies the dark theme foreground color of an uncommitted line in the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.gutter.light.uncommittedForegroundColor`|Specifies the light theme foreground color of an uncommitted line in the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.hover.separateLines`|Specifies whether or not hover blame annotations will be separated by a small gap (if heatmap is enabled)
|
||||
|`gitlens.theme.annotations.line.trailing.dark.backgroundColor`|Specifies the dark theme background color of the trailing blame annotation
|
||||
|`gitlens.theme.annotations.line.trailing.light.backgroundColor`|Specifies the light theme background color of the trailing blame annotation
|
||||
|`gitlens.theme.annotations.line.trailing.dark.foregroundColor`|Specifies the dark theme foreground color of the trailing blame annotation
|
||||
|`gitlens.theme.annotations.line.trailing.light.foregroundColor`|Specifies the light theme foreground color of the trailing blame annotation
|
||||
|`gitlens.theme.lineHighlight.dark.backgroundColor`|Specifies the dark theme background color of the associated line highlights in blame annotations. Must be a valid css color
|
||||
|`gitlens.theme.lineHighlight.light.backgroundColor`|Specifies the light theme background color of the associated line highlights in blame annotations. Must be a valid css color
|
||||
|`gitlens.theme.lineHighlight.dark.overviewRulerColor`|Specifies the dark theme overview ruler color of the associated line highlights in blame annotations
|
||||
|`gitlens.theme.lineHighlight.light.overviewRulerColor`|Specifies the light theme overview ruler color of the associated line highlights in blame annotations
|
||||
|
||||
### Advanced Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.advanced.toggleWhitespace.enabled`|Specifies whether or not to toggle whitespace off then showing blame annotations (*may* be required by certain fonts/themes)
|
||||
|`gitlens.advanced.menus`|Specifies which commands will be added to the menus
|
||||
|`gitlens.advanced.caching.enabled`|Specifies whether git output will be cached
|
||||
|`gitlens.advanced.caching.maxLines`|Specifies the threshold for caching larger documents
|
||||
|`gitlens.advanced.git`|Specifies the git path to use
|
||||
|`gitlens.advanced.gitignore.enabled`|Specifies whether or not to parse the root .gitignore file for better performance (i.e. avoids blaming excluded files)
|
||||
|`gitlens.advanced.maxQuickHistory`|Specifies the maximum number of QuickPick history entries to show
|
||||
|`gitlens.advanced.quickPick.closeOnFocusOut`|Specifies whether or not to close the QuickPick menu when focus is lost
|
||||
|
||||
## Known Issues
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="18px" height="18px" viewBox="0 0 18 18" xml:space="preserve">
|
||||
<g>
|
||||
<rect fill="#FFFFFF" fill-opacity="0.75" x="1" y="0" width="4" height="18"/>
|
||||
<rect fill="#00bcf2" fill-opacity="0.6" x="7" y="0" width="3" height="18"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 313 B After Width: | Height: | Size: 312 B |
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="18px" height="18px" viewBox="0 0 18 18" xml:space="preserve">
|
||||
<g>
|
||||
<rect fill="#000000" fill-opacity="0.75" x="1" y="0" width="4" height="18"/>
|
||||
<rect fill="#00bcf2" fill-opacity="0.6" x="7" y="0" width="3" height="18"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 314 B After Width: | Height: | Size: 313 B |
754
package.json
754
package.json
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"displayName": "Git Lens \u2014 git blame annotations, code lens, and more",
|
||||
"description": "Supercharge Visual Studio Code's Git capabilities \u2014 Visualize code authorship at a glance via inline Git blame annotations and code lens, seamlessly navigate and explore the history of a file or branch, gain valuable insights via powerful comparision commands, and so much more",
|
||||
"description": "Supercharge Visual Studio Code's Git capabilities \u2014 Visualize code authorship at a glance via Git blame annotations and code lens, seamlessly navigate and explore the history of a file or branch, gain valuable insights via powerful comparision commands, and so much more",
|
||||
"badges": [
|
||||
{
|
||||
"url": "https://badges.gitter.im/vscode-gitlens/Lobby.svg",
|
||||
@@ -73,118 +73,153 @@
|
||||
],
|
||||
"description": "Specifies how much (if any) output will be sent to the GitLens output channel"
|
||||
},
|
||||
"gitlens.blame.annotation.activeLine": {
|
||||
"gitlens.annotations.file.gutter.format": {
|
||||
"type": "string",
|
||||
"default": "both",
|
||||
"enum": [
|
||||
"off",
|
||||
"inline",
|
||||
"hover",
|
||||
"both"
|
||||
],
|
||||
"description": "Specifies whether and how to show blame annotations on the active line. `off` - no annotation. `inline` - adds a trailing annotation to the active line. `hover` - adds hover annotation to the active line. `both` - adds both `inline` and `hover` annotations"
|
||||
"default": "${message|40?} ${ago|14-}",
|
||||
"description": "Specifies the format of the gutter blame annotations. Available tokens: `${id}` - commit id, `${author}` - commit author, `${message}` - commit message, `${ago}` - relative commit date (e.g. 1 day ago), `${date}` - formatted commit date (format specified by `gitlens.annotations.file.dateFormat`), `${authorAgo}` - commit author, relative commit date"
|
||||
},
|
||||
"gitlens.blame.annotation.activeLineDarkColor": {
|
||||
"type": "string",
|
||||
"default": "rgba(153, 153, 153, 0.35)",
|
||||
"description": "Specifies the color of the active line blame annotation to use with a dark theme. Must be a valid css color"
|
||||
},
|
||||
"gitlens.blame.annotation.activeLineLightColor": {
|
||||
"type": "string",
|
||||
"default": "rgba(153, 153, 153, 0.35)",
|
||||
"description": "Specifies the color of the active line blame annotation to use with a light theme. Must be a valid css color"
|
||||
},
|
||||
"gitlens.blame.annotation.highlight": {
|
||||
"type": "string",
|
||||
"default": "both",
|
||||
"enum": [
|
||||
"none",
|
||||
"gutter",
|
||||
"line",
|
||||
"both"
|
||||
],
|
||||
"description": "Specifies whether and how to highlight blame annotations. `none` - no highlight. `gutter` - adds a gutter icon. `line` - adds a full-line highlight. `both` - adds both `gutter` and `line` highlights"
|
||||
},
|
||||
"gitlens.blame.annotation.style": {
|
||||
"type": "string",
|
||||
"default": "expanded",
|
||||
"enum": [
|
||||
"compact",
|
||||
"expanded",
|
||||
"trailing"
|
||||
],
|
||||
"description": "Specifies the style of the blame annotations. `compact` - groups annotations to limit the repetition and also adds author and date when possible. `expanded` - shows an annotation before every line. `trailing` - shows an annotation after every line"
|
||||
},
|
||||
"gitlens.blame.annotation.author": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether the committer will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles"
|
||||
},
|
||||
"gitlens.blame.annotation.date": {
|
||||
"type": "string",
|
||||
"default": "off",
|
||||
"enum": [
|
||||
"off",
|
||||
"relative",
|
||||
"absolute"
|
||||
],
|
||||
"description": "Specifies whether and how the commit date will be shown in the blame annotations. `off` - no date. `relative` - relative date (e.g. 1 day ago). `absolute` - date format specified by `gitlens.blame.annotation.dateFormat`. Applies only to the `expanded` & `trailing` annotation styles"
|
||||
},
|
||||
"gitlens.blame.annotation.dateFormat": {
|
||||
"gitlens.annotations.file.gutter.dateFormat": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "Specifies the date format of how absolute dates will be shown in the blame annotations. See https://momentjs.com/docs/#/displaying/format/ for valid formats"
|
||||
"description": "Specifies how to format absolute dates (using the `${date}` token) in gutter blame annotations. See https://momentjs.com/docs/#/displaying/format/ for valid formats"
|
||||
},
|
||||
"gitlens.blame.annotation.message": {
|
||||
"gitlens.annotations.file.gutter.compact": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether or not to compact (deduplicate) matching adjacent gutter blame annotations"
|
||||
},
|
||||
"gitlens.annotations.file.gutter.heatmap.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether or not to provide a heatmap indicator in the gutter blame annotations"
|
||||
},
|
||||
"gitlens.annotations.file.gutter.heatmap.location": {
|
||||
"type": "string",
|
||||
"default": "right",
|
||||
"enum": [
|
||||
"left",
|
||||
"right"
|
||||
],
|
||||
"description": "Specifies where the heatmap indicators will be shown in the gutter blame annotations. `left` - adds a heatmap indicator on the left edge of the gutter blame annotations. `right` - adds a heatmap indicator on the right edge of the gutter blame annotations"
|
||||
},
|
||||
"gitlens.annotations.file.gutter.hover.details": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether or not to provide a commit details hover annotation over the gutter blame annotations"
|
||||
},
|
||||
"gitlens.annotations.file.gutter.hover.wholeLine": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Specifies whether the commit message will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles"
|
||||
"description": "Specifies whether or not to trigger hover annotations over the whole line"
|
||||
},
|
||||
"gitlens.blame.annotation.sha": {
|
||||
"gitlens.annotations.file.hover.heatmap.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether the commit id (sha) will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles"
|
||||
"description": "Specifies whether or not to provide heatmap indicators on the left edge of each line"
|
||||
},
|
||||
"gitlens.codeLens.visibility": {
|
||||
"type": "string",
|
||||
"default": "auto",
|
||||
"enum": [
|
||||
"auto",
|
||||
"ondemand",
|
||||
"off"
|
||||
],
|
||||
"description": "Specifies when code lens will be shown in the active document. `auto` - always shown. `ondemand` - never shown, unless toggled via the `gitlens.toggleCodeLens` command. `off` - never shown"
|
||||
},
|
||||
"gitlens.codeLens.authors.enabled": {
|
||||
"gitlens.annotations.file.hover.wholeLine": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether the authors code lens is shown"
|
||||
"description": "Specifies whether or not to trigger hover annotations over the whole line"
|
||||
},
|
||||
"gitlens.codeLens.authors.command": {
|
||||
"gitlens.annotations.line.hover.details": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether or not to provide a commit details hover annotation for the current line"
|
||||
},
|
||||
"gitlens.annotations.line.hover.changes": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether or not to provide a changes (diff) hover annotation for the current line"
|
||||
},
|
||||
"gitlens.annotations.line.trailing.format": {
|
||||
"type": "string",
|
||||
"default": "gitlens.toggleBlame",
|
||||
"default": "${authorAgo} \u2022 ${message}",
|
||||
"description": "Specifies the format of the trailing blame annotations. Available tokens: `${id}` - commit id, `${author}` - commit author, `${message}` - commit message, `${ago}` - relative commit date (e.g. 1 day ago), `${date}` - formatted commit date (format specified by `gitlens.annotations.currentLine.dateFormat`), `${authorAgo}` - commit author, relative commit date"
|
||||
},
|
||||
"gitlens.annotations.line.trailing.dateFormat": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "Specifies how to format absolute dates (using the `${date}` token) in trailing blame annotations. See https://momentjs.com/docs/#/displaying/format/ for valid formats"
|
||||
},
|
||||
"gitlens.annotations.line.trailing.hover.details": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether or not to provide a commit details hover annotation over the trailing blame annotations"
|
||||
},
|
||||
"gitlens.annotations.line.trailing.hover.changes": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether or not to provide a changes (diff) hover annotation over the trailing blame annotations"
|
||||
},
|
||||
"gitlens.annotations.line.trailing.hover.wholeLine": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Specifies whether or not to trigger hover annotations over the whole line"
|
||||
},
|
||||
"gitlens.blame.file.annotationType": {
|
||||
"type": "string",
|
||||
"default": "gutter",
|
||||
"enum": [
|
||||
"gitlens.toggleBlame",
|
||||
"gitlens.showBlameHistory",
|
||||
"gitlens.showFileHistory",
|
||||
"gitlens.diffWithPrevious",
|
||||
"gitlens.showQuickCommitDetails",
|
||||
"gitlens.showQuickCommitFileDetails",
|
||||
"gitlens.showQuickFileHistory",
|
||||
"gitlens.showQuickRepoHistory"
|
||||
"gutter",
|
||||
"hover"
|
||||
],
|
||||
"description": "Specifies the command executed when the authors code lens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current committed file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick"
|
||||
"description": "Specifies the type of blame annotations that will be shown for the current file. `gutter` - adds an annotation to the beginning of each line. `hover` - shows annotations when hovering over each line"
|
||||
},
|
||||
"gitlens.blame.file.lineHighlight.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether or not to highlight lines associated with the current line"
|
||||
},
|
||||
"gitlens.blame.file.lineHighlight.locations": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
"gutter",
|
||||
"line",
|
||||
"overviewRuler"
|
||||
],
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"gutter",
|
||||
"line",
|
||||
"overviewRuler"
|
||||
]
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 3,
|
||||
"uniqueItems": true,
|
||||
"description": "Specifies where the associated line highlights will be shown. `gutter` - adds a gutter glyph. `line` - adds a full-line highlight background color. `overviewRuler` - adds a decoration to the overviewRuler (scroll bar)"
|
||||
},
|
||||
"gitlens.blame.line.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether or not to provide a blame annotation for the current line"
|
||||
},
|
||||
"gitlens.blame.line.annotationType": {
|
||||
"type": "string",
|
||||
"default": "trailing",
|
||||
"enum": [
|
||||
"trailing",
|
||||
"hover"
|
||||
],
|
||||
"description": "Specifies the type of blame annotations that will be shown for the current line. `trailing` - adds an annotation to the end of the current line. `hover` - shows annotations when hovering over the current line"
|
||||
},
|
||||
"gitlens.codeLens.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether or not to provide any Git code lens"
|
||||
},
|
||||
"gitlens.codeLens.recentChange.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether the recent change code lens is shown"
|
||||
"description": "Specifies whether or not to show a `recent change` code lens showing the author and date of the most recent commit for the file or code block"
|
||||
},
|
||||
"gitlens.codeLens.recentChange.command": {
|
||||
"type": "string",
|
||||
"default": "gitlens.showQuickCommitFileDetails",
|
||||
"enum": [
|
||||
"gitlens.toggleBlame",
|
||||
"gitlens.toggleFileBlame",
|
||||
"gitlens.showBlameHistory",
|
||||
"gitlens.showFileHistory",
|
||||
"gitlens.diffWithPrevious",
|
||||
@@ -193,103 +228,149 @@
|
||||
"gitlens.showQuickFileHistory",
|
||||
"gitlens.showQuickRepoHistory"
|
||||
],
|
||||
"description": "Specifies the command executed when the recent change code lens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current committed file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick"
|
||||
"description": "Specifies the command to be executed when the `recent change` code lens is clicked. `gitlens.toggleFileBlame` - toggles file blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current committed file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick"
|
||||
},
|
||||
"gitlens.codeLens.location": {
|
||||
"gitlens.codeLens.authors.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether or not to show an `authors` code lens showing number of authors of the file or code block and the most prominent author (if there is more than one)"
|
||||
},
|
||||
"gitlens.codeLens.authors.command": {
|
||||
"type": "string",
|
||||
"default": "document+containers",
|
||||
"default": "gitlens.toggleFileBlame",
|
||||
"enum": [
|
||||
"all",
|
||||
"document+containers",
|
||||
"document",
|
||||
"custom"
|
||||
"gitlens.toggleFileBlame",
|
||||
"gitlens.showBlameHistory",
|
||||
"gitlens.showFileHistory",
|
||||
"gitlens.diffWithPrevious",
|
||||
"gitlens.showQuickCommitDetails",
|
||||
"gitlens.showQuickCommitFileDetails",
|
||||
"gitlens.showQuickFileHistory",
|
||||
"gitlens.showQuickRepoHistory"
|
||||
],
|
||||
"description": "Specifies where code lens will be rendered in the active document. `all` - render at the top of the document, on container-like (classes, modules, etc), and on member-like (methods, functions, properties, etc) lines. `document+containers` - render at the top of the document and on container-like lines. `document` - only render at the top of the document. `custom` - rendering controlled by `gitlens.codeLens.locationCustomSymbols`"
|
||||
"description": "Specifies the command to be executed when the `authors` code lens is clicked. `gitlens.toggleFileBlame` - toggles file blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current committed file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick"
|
||||
},
|
||||
"gitlens.codeLens.locationCustomSymbols": {
|
||||
"gitlens.codeLens.locations": {
|
||||
"type": "array",
|
||||
"description": "Specifies the set of document symbols to render active document code lens on. Must be a member of `SymbolKind`"
|
||||
"default": [
|
||||
"document",
|
||||
"containers"
|
||||
],
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"document",
|
||||
"containers",
|
||||
"blocks",
|
||||
"custom"
|
||||
]
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 4,
|
||||
"uniqueItems": true,
|
||||
"description": "Specifies where Git code lens will be shown in the document. `document` - adds code lens at the top of the document. `containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc). `blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines. `custom` - adds code lens at the start of symbols contained in `gitlens.codeLens.locationCustomSymbols`"
|
||||
},
|
||||
"gitlens.codeLens.languageLocations": {
|
||||
"gitlens.codeLens.customLocationSymbols": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"uniqueItems": true,
|
||||
"description": "Specifies the set of document symbols where Git code lens will be shown in the document. Must be a member of `SymbolKind`"
|
||||
},
|
||||
"gitlens.codeLens.perLanguageLocations": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
{
|
||||
"language": "css",
|
||||
"location": "document"
|
||||
"locations": [
|
||||
"document"
|
||||
]
|
||||
},
|
||||
{
|
||||
"language": "html",
|
||||
"location": "document"
|
||||
"locations": [
|
||||
"document"
|
||||
]
|
||||
},
|
||||
{
|
||||
"language": "json",
|
||||
"location": "document"
|
||||
"locations": [
|
||||
"document"
|
||||
]
|
||||
},
|
||||
{
|
||||
"language": "less",
|
||||
"location": "document"
|
||||
"locations": [
|
||||
"document"
|
||||
]
|
||||
},
|
||||
{
|
||||
"language": "scss",
|
||||
"location": "document"
|
||||
"locations": [
|
||||
"document"
|
||||
]
|
||||
},
|
||||
{
|
||||
"language": "vue",
|
||||
"location": "document"
|
||||
"locations": [
|
||||
"document"
|
||||
]
|
||||
}
|
||||
],
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"language",
|
||||
"location"
|
||||
"locations"
|
||||
],
|
||||
"properties": {
|
||||
"language": {
|
||||
"type": "string",
|
||||
"description": "Specifies the language to which this code lens override applies"
|
||||
},
|
||||
"location": {
|
||||
"type": "string",
|
||||
"default": "document+containers",
|
||||
"enum": [
|
||||
"all",
|
||||
"document+containers",
|
||||
"locations": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
"document",
|
||||
"custom",
|
||||
"none"
|
||||
"containers"
|
||||
],
|
||||
"description": "Specifies where code lens will be rendered in the active document for the specified language. `all` - render at the top of the document, on container-like (classes, modules, etc), and on member-like (methods, functions, properties, etc) lines. `document+containers` - render at the top of the document and on container-like lines. `document` - only render at the top of the document. `custom` - rendering controlled by `customSymbols`"
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"document",
|
||||
"containers",
|
||||
"blocks",
|
||||
"custom"
|
||||
]
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 4,
|
||||
"uniqueItems": true,
|
||||
"description": "Specifies where Git code lens will be shown in the document for the specified language. `document` - adds code lens at the top of the document. `containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc). `blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines. `custom` - adds code lens at the start of symbols contained in `customSymbols`"
|
||||
},
|
||||
"customSymbols": {
|
||||
"type": "string",
|
||||
"description": "Specifies the set of document symbols to render active document code lens on. Must be a member of `SymbolKind`"
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"uniqueItems": true,
|
||||
"description": "Specifies the set of document symbols where Git code lens will be shown in the document for the specified language. Must be a member of `SymbolKind`"
|
||||
}
|
||||
}
|
||||
},
|
||||
"uniqueItems": true,
|
||||
"enum": [
|
||||
"all",
|
||||
"document+containers",
|
||||
"document",
|
||||
"custom"
|
||||
],
|
||||
"description": "Specifies where code lens will be rendered in the active document for the specified languages"
|
||||
"description": "Specifies where Git code lens will be shown in the document for the specified languages"
|
||||
},
|
||||
"gitlens.codeLens.debug": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Specifies whether or not to show debug information in code lens"
|
||||
},
|
||||
"gitlens.menus.diff.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether diff commands will be added to the context menus"
|
||||
},
|
||||
"gitlens.statusBar.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether blame information is shown in the status bar"
|
||||
"description": "Specifies whether or not to provide blame information on the status bar"
|
||||
},
|
||||
"gitlens.statusBar.alignment": {
|
||||
"type": "string",
|
||||
@@ -304,7 +385,7 @@
|
||||
"type": "string",
|
||||
"default": "gitlens.showQuickCommitDetails",
|
||||
"enum": [
|
||||
"gitlens.toggleBlame",
|
||||
"gitlens.toggleFileBlame",
|
||||
"gitlens.showBlameHistory",
|
||||
"gitlens.showFileHistory",
|
||||
"gitlens.diffWithPrevious",
|
||||
@@ -315,37 +396,112 @@
|
||||
"gitlens.showQuickFileHistory",
|
||||
"gitlens.showQuickRepoHistory"
|
||||
],
|
||||
"description": "Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current line commit with the previous. `gitlens.diffWithWorking` - compares the current line commit with the working tree. `gitlens.toggleCodeLens` - toggles Git code lens. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick"
|
||||
"description": "Specifies the command to be executed when the blame status bar item is clicked. `gitlens.toggleFileBlame` - toggles file blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current line commit with the previous. `gitlens.diffWithWorking` - compares the current line commit with the working tree. `gitlens.toggleCodeLens` - toggles Git code lens. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick"
|
||||
},
|
||||
"gitlens.statusBar.date": {
|
||||
"gitlens.statusBar.format": {
|
||||
"type": "string",
|
||||
"default": "relative",
|
||||
"enum": [
|
||||
"off",
|
||||
"relative",
|
||||
"absolute"
|
||||
],
|
||||
"description": "Specifies whether and how the commit date will be shown in the blame status bar. `off` - no date. `relative` - relative date (e.g. 1 day ago). `absolute` - date format specified by `gitlens.statusBar.dateFormat`"
|
||||
"default": "${authorAgo}",
|
||||
"description": "Specifies the format of the status bar blame information. Available tokens: `${id}` - commit id, `${author}` - commit author, `${message}` - commit message, `${ago}` - relative commit date (e.g. 1 day ago), `${date}` - formatted commit date (format specified by `gitlens.statusBar.dateFormat`), `${authorAgo}` - commit author, relative commit date"
|
||||
},
|
||||
"gitlens.statusBar.dateFormat": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "Specifies the date format of how absolute dates will be shown in the blame status bar. See https://momentjs.com/docs/#/displaying/format/ for valid formats"
|
||||
"description": "Specifies the date format of absolute dates shown in the blame information on the status bar. See https://momentjs.com/docs/#/displaying/format/ for valid formats"
|
||||
},
|
||||
"gitlens.theme.annotations.file.gutter.separateLines": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether or not gutter blame annotations will be separated by a small gap"
|
||||
},
|
||||
"gitlens.theme.annotations.file.gutter.dark.backgroundColor": {
|
||||
"type": "string",
|
||||
"default": "rgba(255, 255, 255, 0.075)",
|
||||
"description": "Specifies the dark theme background color of the gutter blame annotations. Must be a valid css color"
|
||||
},
|
||||
"gitlens.theme.annotations.file.gutter.light.backgroundColor": {
|
||||
"type": "string",
|
||||
"default": "rgba(0, 0, 0, 0.05)",
|
||||
"description": "Specifies the light theme background color of the gutter blame annotations. Must be a valid css color"
|
||||
},
|
||||
"gitlens.theme.annotations.file.gutter.dark.foregroundColor": {
|
||||
"type": "string",
|
||||
"default": "rgb(190, 190, 190)",
|
||||
"description": "Specifies the dark theme foreground color of the gutter blame annotations. Must be a valid css color"
|
||||
},
|
||||
"gitlens.theme.annotations.file.gutter.light.foregroundColor": {
|
||||
"type": "string",
|
||||
"default": "rgb(116, 116, 116)",
|
||||
"description": "Specifies the light theme foreground color of the gutter blame annotations. Must be a valid css color"
|
||||
},
|
||||
"gitlens.theme.annotations.file.gutter.dark.uncommittedForegroundColor": {
|
||||
"type": "string",
|
||||
"default": "rgba(0, 188, 242, 0.6)",
|
||||
"description": "Specifies the dark theme foreground color of an uncommitted line in the gutter blame annotations. Must be a valid css color"
|
||||
},
|
||||
"gitlens.theme.annotations.file.gutter.light.uncommittedForegroundColor": {
|
||||
"type": "string",
|
||||
"default": "rgba(0, 188, 242, 0.6)",
|
||||
"description": "Specifies the light theme foreground color of an uncommitted line in the gutter blame annotations. Must be a valid css color"
|
||||
},
|
||||
"gitlens.theme.annotations.file.hover.separateLines": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Specifies whether or not hover blame annotations will be separated by a small gap (if heatmap is enabled)"
|
||||
},
|
||||
"gitlens.theme.annotations.line.trailing.dark.backgroundColor": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "Specifies the dark theme background color of the trailing blame annotation. Must be a valid css color"
|
||||
},
|
||||
"gitlens.theme.annotations.line.trailing.light.backgroundColor": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "Specifies the light theme background color of the trailing blame annotation. Must be a valid css color"
|
||||
},
|
||||
"gitlens.theme.annotations.line.trailing.dark.foregroundColor": {
|
||||
"type": "string",
|
||||
"default": "rgba(153, 153, 153, 0.35)",
|
||||
"description": "Specifies the dark theme foreground color of the trailing blame annotation. Must be a valid css color"
|
||||
},
|
||||
"gitlens.theme.annotations.line.trailing.light.foregroundColor": {
|
||||
"type": "string",
|
||||
"default": "rgba(153, 153, 153, 0.35)",
|
||||
"description": "Specifies the light theme foreground color of the trailing blame annotation. Must be a valid css color"
|
||||
},
|
||||
"gitlens.theme.lineHighlight.dark.backgroundColor": {
|
||||
"type": "string",
|
||||
"default": "rgba(0, 188, 242, 0.2)",
|
||||
"description": "Specifies the dark theme background color of the associated line highlights in blame annotations. Must be a valid css color"
|
||||
},
|
||||
"gitlens.theme.lineHighlight.light.backgroundColor": {
|
||||
"type": "string",
|
||||
"default": "rgba(0, 188, 242, 0.2)",
|
||||
"description": "Specifies the light theme background color of the associated line highlights in blame annotations. Must be a valid css color"
|
||||
},
|
||||
"gitlens.theme.lineHighlight.dark.overviewRulerColor": {
|
||||
"type": "string",
|
||||
"default": "rgba(0, 188, 242, 0.6)",
|
||||
"description": "Specifies the dark theme overview ruler color of the associated line highlights in blame annotations. Must be a valid css color"
|
||||
},
|
||||
"gitlens.theme.lineHighlight.light.overviewRulerColor": {
|
||||
"type": "string",
|
||||
"default": "rgba(0, 188, 242, 0.6)",
|
||||
"description": "Specifies the light theme overview ruler color of the associated line highlights in blame annotations. Must be a valid css color"
|
||||
},
|
||||
"gitlens.advanced.caching.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether git blame output will be cached"
|
||||
"description": "Specifies whether git output will be cached"
|
||||
},
|
||||
"gitlens.advanced.caching.statusBar.maxLines": {
|
||||
"gitlens.advanced.caching.maxLines": {
|
||||
"type": "number",
|
||||
"default": 0,
|
||||
"description": "Specifies whether status bar git blame output will be cached for larger documents"
|
||||
"description": "Specifies the threshold for caching larger documents"
|
||||
},
|
||||
"gitlens.advanced.git": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "Specifies a git path to use"
|
||||
"description": "Specifies the git path to use"
|
||||
},
|
||||
"gitlens.advanced.gitignore.enabled": {
|
||||
"type": "boolean",
|
||||
@@ -357,6 +513,156 @@
|
||||
"default": 200,
|
||||
"description": "Specifies the maximum number of QuickPick history entries to show"
|
||||
},
|
||||
"gitlens.advanced.menus": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"explorerContext": {
|
||||
"fileDiff": true,
|
||||
"history": true,
|
||||
"remote": true
|
||||
},
|
||||
"editorContext": {
|
||||
"blame": true,
|
||||
"copy": true,
|
||||
"fileDiff": true,
|
||||
"history": true,
|
||||
"lineDiff": true,
|
||||
"remote": true
|
||||
},
|
||||
"editorTitle": {
|
||||
"blame": true,
|
||||
"fileDiff": true,
|
||||
"history": true,
|
||||
"status": true
|
||||
},
|
||||
"editorTitleContext": {
|
||||
"blame": true,
|
||||
"fileDiff": true,
|
||||
"history": true,
|
||||
"remote": true
|
||||
}
|
||||
},
|
||||
"description": "Specifies which commands will be added to which menus",
|
||||
"properties": {
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"editorContext": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"blame": true,
|
||||
"copy": 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,
|
||||
"status": true
|
||||
},
|
||||
"properties": {
|
||||
"blame": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"fileDiff": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"history": {
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"gitlens.advanced.quickPick.closeOnFocusOut": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
@@ -406,19 +712,29 @@
|
||||
"category": "GitLens"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showBlame",
|
||||
"title": "Show Blame Annotations",
|
||||
"command": "gitlens.showFileBlame",
|
||||
"title": "Show File Blame Annotations",
|
||||
"category": "GitLens"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.toggleBlame",
|
||||
"title": "Toggle Blame Annotations",
|
||||
"command": "gitlens.showLineBlame",
|
||||
"title": "Show Line Blame Annotations",
|
||||
"category": "GitLens"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.toggleFileBlame",
|
||||
"title": "Toggle File Blame Annotations",
|
||||
"category": "GitLens",
|
||||
"icon": {
|
||||
"dark": "images/git-icon-dark.svg",
|
||||
"light": "images/git-icon-light.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "gitlens.toggleLineBlame",
|
||||
"title": "Toggle Line Blame Annotations",
|
||||
"category": "GitLens"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.toggleCodeLens",
|
||||
"title": "Toggle Git Code Lens",
|
||||
@@ -561,11 +877,19 @@
|
||||
"when": "gitlens:isBlameable"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showBlame",
|
||||
"command": "gitlens.showFileBlame",
|
||||
"when": "gitlens:isBlameable"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.toggleBlame",
|
||||
"command": "gitlens.showLineBlame",
|
||||
"when": "gitlens:isBlameable"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.toggleFileBlame",
|
||||
"when": "gitlens:isBlameable"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.toggleLineBlame",
|
||||
"when": "gitlens:isBlameable"
|
||||
},
|
||||
{
|
||||
@@ -654,135 +978,145 @@
|
||||
}
|
||||
],
|
||||
"explorer/context": [
|
||||
{
|
||||
"command": "gitlens.diffWithPrevious",
|
||||
"when": "gitlens:enabled && config.gitlens.menus.diff.enabled",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.diffWithWorking",
|
||||
"when": "gitlens:enabled && config.gitlens.menus.diff.enabled",
|
||||
"group": "1_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showQuickFileHistory",
|
||||
"when": "gitlens:enabled",
|
||||
"group": "1_gitlens_1@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openFileInRemote",
|
||||
"when": "gitlens:enabled",
|
||||
"group": "1_gitlens_1@2"
|
||||
}
|
||||
],
|
||||
"editor/title": [
|
||||
{
|
||||
"command": "gitlens.toggleBlame",
|
||||
"when": "gitlens:isBlameable",
|
||||
"when": "gitlens:enabled && config.gitlens.advanced.menus.explorerContext.remote",
|
||||
"group": "navigation@100"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.diffWithPrevious",
|
||||
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.menus.diff.enabled",
|
||||
"when": "gitlens:enabled && config.gitlens.advanced.menus.explorerContext.fileDiff",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.diffWithWorking",
|
||||
"when": "gitlens:enabled && config.gitlens.advanced.menus.explorerContext.fileDiff",
|
||||
"group": "1_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showQuickFileHistory",
|
||||
"when": "gitlens:enabled && config.gitlens.advanced.menus.explorerContext.history",
|
||||
"group": "1_gitlens_1@1"
|
||||
}
|
||||
],
|
||||
"editor/title": [
|
||||
{
|
||||
"command": "gitlens.toggleFileBlame",
|
||||
"when": "gitlens:isBlameable && config.gitlens.advanced.menus.editorTitle.blame",
|
||||
"group": "navigation@100"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openFileInRemote",
|
||||
"when": "gitlens:enabled && config.gitlens.advanced.menus.editorTitleContext.remote",
|
||||
"group": "1_gitlens"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openRepoInRemote",
|
||||
"when": "gitlens:enabled && config.gitlens.advanced.menus.editorTitleContext.remote",
|
||||
"group": "1_gitlens"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.diffWithPrevious",
|
||||
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorTitle.fileDiff",
|
||||
"group": "2_gitlens"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.diffWithWorking",
|
||||
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.menus.diff.enabled",
|
||||
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorTitle.fileDiff",
|
||||
"group": "2_gitlens"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showQuickFileHistory",
|
||||
"when": "editorFocus && gitlens:isTracked",
|
||||
"when": "editorFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorTitle.history",
|
||||
"group": "2_gitlens_1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showQuickRepoHistory",
|
||||
"when": "!editorFocus && gitlens:enabled",
|
||||
"when": "!editorFocus && gitlens:enabled && config.gitlens.advanced.menus.editorTitle.history",
|
||||
"group": "2_gitlens_1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showQuickRepoStatus",
|
||||
"when": "gitlens:enabled",
|
||||
"when": "gitlens:enabled && config.gitlens.advanced.menus.editorTitle.status",
|
||||
"group": "2_gitlens_1"
|
||||
}
|
||||
],
|
||||
"editor/title/context": [
|
||||
{
|
||||
"command": "gitlens.openFileInRemote",
|
||||
"when": "gitlens:enabled && config.gitlens.advanced.menus.editorTitleContext.remote",
|
||||
"group": "1_gitlens"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.diffWithPrevious",
|
||||
"when": "gitlens:enabled && config.gitlens.menus.diff.enabled",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.diffWithWorking",
|
||||
"when": "gitlens:enabled && config.gitlens.menus.diff.enabled",
|
||||
"group": "1_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showQuickFileHistory",
|
||||
"when": "gitlens:enabled",
|
||||
"when": "gitlens:enabled && config.gitlens.advanced.menus.editorTitleContext.fileDiff",
|
||||
"group": "1_gitlens_1@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.toggleBlame",
|
||||
"when": "gitlens:enabled",
|
||||
"command": "gitlens.diffWithWorking",
|
||||
"when": "gitlens:enabled && config.gitlens.advanced.menus.editorTitleContext.fileDiff",
|
||||
"group": "1_gitlens_1@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openFileInRemote",
|
||||
"when": "gitlens:enabled",
|
||||
"group": "1_gitlens_1@3"
|
||||
"command": "gitlens.showQuickFileHistory",
|
||||
"when": "gitlens:enabled && config.gitlens.advanced.menus.editorTitleContext.history",
|
||||
"group": "1_gitlens_2@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.toggleFileBlame",
|
||||
"when": "gitlens:enabled && config.gitlens.advanced.menus.editorTitleContext.blame",
|
||||
"group": "1_gitlens_2@2"
|
||||
}
|
||||
],
|
||||
"editor/context": [
|
||||
{
|
||||
"command": "gitlens.openFileInRemote",
|
||||
"when": "editorTextFocus && gitlens:isTracked && gitlens:hasRemotes && config.gitlens.advanced.menus.editorContext.remote",
|
||||
"group": "navigation@100"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.diffLineWithPrevious",
|
||||
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.menus.diff.enabled",
|
||||
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.lineDiff",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.diffLineWithWorking",
|
||||
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.menus.diff.enabled",
|
||||
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.lineDiff",
|
||||
"group": "1_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showQuickCommitFileDetails",
|
||||
"when": "editorTextFocus && gitlens:isBlameable",
|
||||
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.details",
|
||||
"group": "1_gitlens@3"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.diffWithPrevious",
|
||||
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.menus.diff.enabled",
|
||||
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorContext.fileDiff",
|
||||
"group": "1_gitlens_1@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.diffWithWorking",
|
||||
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.menus.diff.enabled",
|
||||
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorContext.fileDiff",
|
||||
"group": "1_gitlens_1@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showQuickFileHistory",
|
||||
"when": "gitlens:isTracked",
|
||||
"when": "gitlens:isTracked && config.gitlens.advanced.menus.editorContext.history",
|
||||
"group": "3_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.toggleBlame",
|
||||
"when": "editorTextFocus && gitlens:isBlameable",
|
||||
"command": "gitlens.toggleFileBlame",
|
||||
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.blame",
|
||||
"group": "3_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openFileInRemote",
|
||||
"when": "editorTextFocus && gitlens:isTracked && gitlens:hasRemotes",
|
||||
"group": "3_gitlens@3"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.copyShaToClipboard",
|
||||
"when": "editorTextFocus && gitlens:isBlameable",
|
||||
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.copy",
|
||||
"group": "9_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.copyMessageToClipboard",
|
||||
"when": "editorTextFocus && gitlens:isBlameable",
|
||||
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.copy",
|
||||
"group": "9_gitlens@2"
|
||||
}
|
||||
]
|
||||
@@ -809,7 +1143,7 @@
|
||||
"when": "gitlens:key:."
|
||||
},
|
||||
{
|
||||
"command": "gitlens.toggleBlame",
|
||||
"command": "gitlens.toggleFileBlame",
|
||||
"key": "alt+b",
|
||||
"mac": "alt+b",
|
||||
"when": "editorTextFocus && gitlens:isTracked"
|
||||
|
||||
282
src/annotations/annotationController.ts
Normal file
282
src/annotations/annotationController.ts
Normal file
@@ -0,0 +1,282 @@
|
||||
'use strict';
|
||||
import { Functions, Objects } from '../system';
|
||||
import { DecorationRenderOptions, Disposable, Event, EventEmitter, ExtensionContext, OverviewRulerLane, TextDocument, TextDocumentChangeEvent, TextEditor, TextEditorDecorationType, TextEditorViewColumnChangeEvent, window, workspace } from 'vscode';
|
||||
import { AnnotationProviderBase } from './annotationProvider';
|
||||
import { TextDocumentComparer, TextEditorComparer } from '../comparers';
|
||||
import { BlameLineHighlightLocations, ExtensionKey, FileAnnotationType, IConfig, themeDefaults } from '../configuration';
|
||||
import { BlameabilityChangeEvent, GitContextTracker, GitService, GitUri } from '../gitService';
|
||||
import { GutterBlameAnnotationProvider } from './gutterBlameAnnotationProvider';
|
||||
import { HoverBlameAnnotationProvider } from './hoverBlameAnnotationProvider';
|
||||
import { Logger } from '../logger';
|
||||
import { WhitespaceController } from './whitespaceController';
|
||||
|
||||
export const Decorations = {
|
||||
annotation: window.createTextEditorDecorationType({
|
||||
isWholeLine: true
|
||||
} as DecorationRenderOptions),
|
||||
highlight: undefined as TextEditorDecorationType | undefined
|
||||
};
|
||||
|
||||
export class AnnotationController extends Disposable {
|
||||
|
||||
private _onDidToggleAnnotations = new EventEmitter<void>();
|
||||
get onDidToggleAnnotations(): Event<void> {
|
||||
return this._onDidToggleAnnotations.event;
|
||||
}
|
||||
|
||||
private _annotationsDisposable: Disposable | undefined;
|
||||
private _annotationProviders: Map<number, AnnotationProviderBase> = new Map();
|
||||
private _config: IConfig;
|
||||
private _disposable: Disposable;
|
||||
private _whitespaceController: WhitespaceController | undefined;
|
||||
|
||||
constructor(private context: ExtensionContext, private git: GitService, private gitContextTracker: GitContextTracker) {
|
||||
super(() => this.dispose());
|
||||
|
||||
this._onConfigurationChanged();
|
||||
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
|
||||
|
||||
this._disposable = Disposable.from(...subscriptions);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._annotationProviders.forEach(async (p, i) => await this.clear(i));
|
||||
|
||||
Decorations.annotation && Decorations.annotation.dispose();
|
||||
Decorations.highlight && Decorations.highlight.dispose();
|
||||
|
||||
this._annotationsDisposable && this._annotationsDisposable.dispose();
|
||||
this._whitespaceController && this._whitespaceController.dispose();
|
||||
this._disposable && this._disposable.dispose();
|
||||
}
|
||||
|
||||
private _onConfigurationChanged() {
|
||||
let toggleWhitespace = workspace.getConfiguration(`${ExtensionKey}.advanced.toggleWhitespace`).get<boolean>('enabled');
|
||||
if (!toggleWhitespace) {
|
||||
// Until https://github.com/Microsoft/vscode/issues/11485 is fixed we need to toggle whitespace for non-monospace fonts and ligatures
|
||||
// TODO: detect monospace font
|
||||
toggleWhitespace = workspace.getConfiguration('editor').get<boolean>('fontLigatures');
|
||||
}
|
||||
|
||||
if (toggleWhitespace && !this._whitespaceController) {
|
||||
this._whitespaceController = new WhitespaceController();
|
||||
}
|
||||
else if (!toggleWhitespace && this._whitespaceController) {
|
||||
this._whitespaceController.dispose();
|
||||
this._whitespaceController = undefined;
|
||||
}
|
||||
|
||||
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
|
||||
const cfgHighlight = cfg.blame.file.lineHighlight;
|
||||
const cfgTheme = cfg.theme.lineHighlight;
|
||||
|
||||
let changed = false;
|
||||
|
||||
if (!Objects.areEquivalent(cfgHighlight, this._config && this._config.blame.file.lineHighlight) ||
|
||||
!Objects.areEquivalent(cfgTheme, this._config && this._config.theme.lineHighlight)) {
|
||||
changed = true;
|
||||
|
||||
Decorations.highlight && Decorations.highlight.dispose();
|
||||
|
||||
if (cfgHighlight.enabled) {
|
||||
Decorations.highlight = window.createTextEditorDecorationType({
|
||||
gutterIconSize: 'contain',
|
||||
isWholeLine: true,
|
||||
overviewRulerLane: OverviewRulerLane.Right,
|
||||
dark: {
|
||||
backgroundColor: cfgHighlight.locations.includes(BlameLineHighlightLocations.Line)
|
||||
? cfgTheme.dark.backgroundColor || themeDefaults.lineHighlight.dark.backgroundColor
|
||||
: undefined,
|
||||
gutterIconPath: cfgHighlight.locations.includes(BlameLineHighlightLocations.Gutter)
|
||||
? this.context.asAbsolutePath('images/blame-dark.svg')
|
||||
: undefined,
|
||||
overviewRulerColor: cfgHighlight.locations.includes(BlameLineHighlightLocations.OverviewRuler)
|
||||
? cfgTheme.dark.overviewRulerColor || themeDefaults.lineHighlight.dark.overviewRulerColor
|
||||
: undefined
|
||||
},
|
||||
light: {
|
||||
backgroundColor: cfgHighlight.locations.includes(BlameLineHighlightLocations.Line)
|
||||
? cfgTheme.light.backgroundColor || themeDefaults.lineHighlight.light.backgroundColor
|
||||
: undefined,
|
||||
gutterIconPath: cfgHighlight.locations.includes(BlameLineHighlightLocations.Gutter)
|
||||
? this.context.asAbsolutePath('images/blame-light.svg')
|
||||
: undefined,
|
||||
overviewRulerColor: cfgHighlight.locations.includes(BlameLineHighlightLocations.OverviewRuler)
|
||||
? cfgTheme.light.overviewRulerColor || themeDefaults.lineHighlight.light.overviewRulerColor
|
||||
: undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
Decorations.highlight = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Objects.areEquivalent(cfg.blame.file, this._config && this._config.blame.file) ||
|
||||
!Objects.areEquivalent(cfg.annotations, this._config && this._config.annotations) ||
|
||||
!Objects.areEquivalent(cfg.theme.annotations, this._config && this._config.theme.annotations)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
this._config = cfg;
|
||||
|
||||
if (changed) {
|
||||
// Since the configuration has changed -- reset any visible annotations
|
||||
for (const provider of this._annotationProviders.values()) {
|
||||
if (provider === undefined) continue;
|
||||
|
||||
provider.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async clear(column: number) {
|
||||
const provider = this._annotationProviders.get(column);
|
||||
if (!provider) return;
|
||||
|
||||
this._annotationProviders.delete(column);
|
||||
await provider.dispose();
|
||||
|
||||
if (this._annotationProviders.size === 0) {
|
||||
Logger.log(`Remove listener registrations for annotations`);
|
||||
this._annotationsDisposable && this._annotationsDisposable.dispose();
|
||||
this._annotationsDisposable = undefined;
|
||||
}
|
||||
|
||||
this._onDidToggleAnnotations.fire();
|
||||
}
|
||||
|
||||
getAnnotationType(editor: TextEditor): FileAnnotationType | undefined {
|
||||
const provider = this.getProvider(editor);
|
||||
return provider === undefined ? undefined : provider.annotationType;
|
||||
}
|
||||
|
||||
getProvider(editor: TextEditor): AnnotationProviderBase | undefined {
|
||||
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return undefined;
|
||||
|
||||
return this._annotationProviders.get(editor.viewColumn || -1);
|
||||
}
|
||||
|
||||
async showAnnotations(editor: TextEditor, type: FileAnnotationType, shaOrLine?: string | number): Promise<boolean> {
|
||||
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false;
|
||||
|
||||
const currentProvider = this._annotationProviders.get(editor.viewColumn || -1);
|
||||
if (currentProvider && TextEditorComparer.equals(currentProvider.editor, editor)) {
|
||||
await currentProvider.selection(shaOrLine);
|
||||
return true;
|
||||
}
|
||||
|
||||
const gitUri = await GitUri.fromUri(editor.document.uri, this.git);
|
||||
|
||||
let provider: AnnotationProviderBase | undefined = undefined;
|
||||
switch (type) {
|
||||
case FileAnnotationType.Gutter:
|
||||
provider = new GutterBlameAnnotationProvider(this.context, editor, Decorations.annotation, Decorations.highlight, this._whitespaceController, this.git, gitUri);
|
||||
break;
|
||||
case FileAnnotationType.Hover:
|
||||
provider = new HoverBlameAnnotationProvider(this.context, editor, Decorations.annotation, Decorations.highlight, this._whitespaceController, this.git, gitUri);
|
||||
break;
|
||||
}
|
||||
if (provider === undefined || !(await provider.validate())) return false;
|
||||
|
||||
if (currentProvider) {
|
||||
await this.clear(currentProvider.editor.viewColumn || -1);
|
||||
}
|
||||
|
||||
if (!this._annotationsDisposable && this._annotationProviders.size === 0) {
|
||||
Logger.log(`Add listener registrations for annotations`);
|
||||
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
subscriptions.push(window.onDidChangeVisibleTextEditors(Functions.debounce(this._onVisibleTextEditorsChanged, 100), this));
|
||||
subscriptions.push(window.onDidChangeTextEditorViewColumn(this._onTextEditorViewColumnChanged, this));
|
||||
subscriptions.push(workspace.onDidChangeTextDocument(this._onTextDocumentChanged, this));
|
||||
subscriptions.push(workspace.onDidCloseTextDocument(this._onTextDocumentClosed, this));
|
||||
subscriptions.push(this.gitContextTracker.onDidBlameabilityChange(this._onBlameabilityChanged, this));
|
||||
|
||||
this._annotationsDisposable = Disposable.from(...subscriptions);
|
||||
}
|
||||
|
||||
this._annotationProviders.set(editor.viewColumn || -1, provider);
|
||||
if (await provider.provideAnnotation(shaOrLine)) {
|
||||
this._onDidToggleAnnotations.fire();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async toggleAnnotations(editor: TextEditor, type: FileAnnotationType, shaOrLine?: string | number): Promise<boolean> {
|
||||
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false;
|
||||
|
||||
const provider = this._annotationProviders.get(editor.viewColumn || -1);
|
||||
if (provider === undefined) return this.showAnnotations(editor, type, shaOrLine);
|
||||
|
||||
await this.clear(provider.editor.viewColumn || -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
private _onBlameabilityChanged(e: BlameabilityChangeEvent) {
|
||||
if (e.blameable || !e.editor) return;
|
||||
|
||||
for (const [key, p] of this._annotationProviders) {
|
||||
if (!TextDocumentComparer.equals(p.document, e.editor.document)) continue;
|
||||
|
||||
Logger.log('BlameabilityChanged:', `Clear annotations for column ${key}`);
|
||||
this.clear(key);
|
||||
}
|
||||
}
|
||||
|
||||
private _onTextDocumentChanged(e: TextDocumentChangeEvent) {
|
||||
for (const [key, p] of this._annotationProviders) {
|
||||
if (!TextDocumentComparer.equals(p.document, e.document)) continue;
|
||||
|
||||
// We have to defer because isDirty is not reliable inside this event
|
||||
setTimeout(() => {
|
||||
// If the document is dirty all is fine, just kick out since the GitContextTracker will handle it
|
||||
if (e.document.isDirty) return;
|
||||
|
||||
// If the document isn't dirty, it is very likely this event was triggered by an outside edit of this document
|
||||
// Which means the document has been reloaded and the annotations have been removed, so we need to update (clear) our state tracking
|
||||
Logger.log('TextDocumentChanged:', `Clear annotations for column ${key}`);
|
||||
this.clear(key);
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private _onTextDocumentClosed(e: TextDocument) {
|
||||
for (const [key, p] of this._annotationProviders) {
|
||||
if (!TextDocumentComparer.equals(p.document, e)) continue;
|
||||
|
||||
Logger.log('TextDocumentClosed:', `Clear annotations for column ${key}`);
|
||||
this.clear(key);
|
||||
}
|
||||
}
|
||||
|
||||
private async _onTextEditorViewColumnChanged(e: TextEditorViewColumnChangeEvent) {
|
||||
const viewColumn = e.viewColumn || -1;
|
||||
|
||||
Logger.log('TextEditorViewColumnChanged:', `Clear annotations for column ${viewColumn}`);
|
||||
await this.clear(viewColumn);
|
||||
|
||||
for (const [key, p] of this._annotationProviders) {
|
||||
if (!TextEditorComparer.equals(p.editor, e.textEditor)) continue;
|
||||
|
||||
Logger.log('TextEditorViewColumnChanged:', `Clear annotations for column ${key}`);
|
||||
await this.clear(key);
|
||||
}
|
||||
}
|
||||
|
||||
private async _onVisibleTextEditorsChanged(e: TextEditor[]) {
|
||||
if (e.every(_ => _.document.uri.scheme === 'inmemory')) return;
|
||||
|
||||
for (const [key, p] of this._annotationProviders) {
|
||||
if (e.some(_ => TextEditorComparer.equals(p.editor, _))) continue;
|
||||
|
||||
Logger.log('VisibleTextEditorsChanged:', `Clear annotations for column ${key}`);
|
||||
this.clear(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
src/annotations/annotationProvider.ts
Normal file
74
src/annotations/annotationProvider.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
import { Functions } from '../system';
|
||||
import { Disposable, ExtensionContext, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
||||
import { TextDocumentComparer } from '../comparers';
|
||||
import { ExtensionKey, FileAnnotationType, IConfig } from '../configuration';
|
||||
import { WhitespaceController } from './whitespaceController';
|
||||
|
||||
export abstract class AnnotationProviderBase extends Disposable {
|
||||
|
||||
public annotationType: FileAnnotationType;
|
||||
public document: TextDocument;
|
||||
|
||||
protected _config: IConfig;
|
||||
protected _disposable: Disposable;
|
||||
|
||||
constructor(context: ExtensionContext, public editor: TextEditor, protected decoration: TextEditorDecorationType, protected highlightDecoration: TextEditorDecorationType | undefined, protected whitespaceController: WhitespaceController | undefined) {
|
||||
super(() => this.dispose());
|
||||
|
||||
this.document = this.editor.document;
|
||||
|
||||
this._config = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
|
||||
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
subscriptions.push(window.onDidChangeTextEditorSelection(this._onTextEditorSelectionChanged, this));
|
||||
|
||||
this._disposable = Disposable.from(...subscriptions);
|
||||
}
|
||||
|
||||
async dispose() {
|
||||
await this.clear();
|
||||
|
||||
this._disposable && this._disposable.dispose();
|
||||
}
|
||||
|
||||
private async _onTextEditorSelectionChanged(e: TextEditorSelectionChangeEvent) {
|
||||
if (!TextDocumentComparer.equals(this.document, e.textEditor && e.textEditor.document)) return;
|
||||
|
||||
return this.selection(e.selections[0].active.line);
|
||||
}
|
||||
|
||||
async clear() {
|
||||
if (this.editor !== undefined) {
|
||||
try {
|
||||
this.editor.setDecorations(this.decoration, []);
|
||||
this.highlightDecoration && this.editor.setDecorations(this.highlightDecoration, []);
|
||||
// I have no idea why the decorators sometimes don't get removed, but if they don't try again with a tiny delay
|
||||
if (this.highlightDecoration !== undefined) {
|
||||
await Functions.wait(1);
|
||||
|
||||
if (this.highlightDecoration === undefined) return;
|
||||
|
||||
this.editor.setDecorations(this.highlightDecoration, []);
|
||||
}
|
||||
}
|
||||
catch (ex) { }
|
||||
}
|
||||
|
||||
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- restore whitespace
|
||||
this.whitespaceController && await this.whitespaceController.restore();
|
||||
}
|
||||
|
||||
async reset() {
|
||||
await this.clear();
|
||||
|
||||
this._config = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
|
||||
|
||||
await this.provideAnnotation(this.editor === undefined ? undefined : this.editor.selection.active.line);
|
||||
}
|
||||
|
||||
abstract async provideAnnotation(shaOrLine?: string | number): Promise<boolean>;
|
||||
abstract async selection(shaOrLine?: string | number): Promise<void>;
|
||||
abstract async validate(): Promise<boolean>;
|
||||
}
|
||||
189
src/annotations/annotations.ts
Normal file
189
src/annotations/annotations.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { DecorationInstanceRenderOptions, DecorationOptions, ThemableDecorationRenderOptions } from 'vscode';
|
||||
import { IThemeConfig, themeDefaults } from '../configuration';
|
||||
import { CommitFormatter, GitCommit, GitService, GitUri, ICommitFormatOptions } from '../gitService';
|
||||
import * as moment from 'moment';
|
||||
|
||||
interface IHeatmapConfig {
|
||||
enabled: boolean;
|
||||
location?: 'left' | 'right';
|
||||
}
|
||||
|
||||
interface IRenderOptions {
|
||||
uncommittedForegroundColor?: {
|
||||
dark: string;
|
||||
light: string;
|
||||
};
|
||||
|
||||
before?: DecorationInstanceRenderOptions & ThemableDecorationRenderOptions & { height?: string };
|
||||
dark?: DecorationInstanceRenderOptions;
|
||||
light?: DecorationInstanceRenderOptions;
|
||||
}
|
||||
|
||||
export const endOfLineIndex = 1000000;
|
||||
|
||||
export class Annotations {
|
||||
|
||||
static applyHeatmap(decoration: DecorationOptions, date: Date, now: moment.Moment) {
|
||||
const color = this._getHeatmapColor(now, date);
|
||||
(decoration.renderOptions!.before! as any).borderColor = color;
|
||||
}
|
||||
|
||||
private static _getHeatmapColor(now: moment.Moment, date: Date) {
|
||||
const days = now.diff(moment(date), 'days');
|
||||
|
||||
if (days <= 2) return '#ffeca7';
|
||||
if (days <= 7) return '#ffdd8c';
|
||||
if (days <= 14) return '#ffdd7c';
|
||||
if (days <= 30) return '#fba447';
|
||||
if (days <= 60) return '#f68736';
|
||||
if (days <= 90) return '#f37636';
|
||||
if (days <= 180) return '#ca6632';
|
||||
if (days <= 365) return '#c0513f';
|
||||
if (days <= 730) return '#a2503a';
|
||||
return '#793738';
|
||||
}
|
||||
|
||||
static async changesHover(commit: GitCommit, line: number, uri: GitUri, git: GitService): Promise<DecorationOptions> {
|
||||
let message: string | undefined = undefined;
|
||||
if (commit.isUncommitted) {
|
||||
const [previous, current] = await git.getDiffForLine(uri, line + uri.offset);
|
||||
message = CommitFormatter.toHoverDiff(commit, previous, current);
|
||||
}
|
||||
else if (commit.previousSha !== undefined) {
|
||||
const [previous, current] = await git.getDiffForLine(uri, line + uri.offset, commit.previousSha);
|
||||
message = CommitFormatter.toHoverDiff(commit, previous, current);
|
||||
}
|
||||
|
||||
return {
|
||||
hoverMessage: message
|
||||
} as DecorationOptions;
|
||||
}
|
||||
|
||||
static detailsHover(commit: GitCommit): DecorationOptions {
|
||||
const message = CommitFormatter.toHoverAnnotation(commit);
|
||||
return {
|
||||
hoverMessage: message
|
||||
} as DecorationOptions;
|
||||
}
|
||||
|
||||
static gutter(commit: GitCommit, format: string, dateFormatOrFormatOptions: string | null | ICommitFormatOptions, renderOptions: IRenderOptions, compact: boolean): DecorationOptions {
|
||||
let content = `\u00a0${CommitFormatter.fromTemplate(format, commit, dateFormatOrFormatOptions)}\u00a0`;
|
||||
if (compact) {
|
||||
content = '\u00a0'.repeat(content.length);
|
||||
}
|
||||
|
||||
return {
|
||||
renderOptions: {
|
||||
before: {
|
||||
...renderOptions.before,
|
||||
...{
|
||||
contentText: content,
|
||||
margin: '0 26px 0 0'
|
||||
}
|
||||
},
|
||||
dark: {
|
||||
before: commit.isUncommitted
|
||||
? { ...renderOptions.dark, ...{ color: renderOptions.uncommittedForegroundColor!.dark } }
|
||||
: { ...renderOptions.dark }
|
||||
},
|
||||
light: {
|
||||
before: commit.isUncommitted
|
||||
? { ...renderOptions.light, ...{ color: renderOptions.uncommittedForegroundColor!.light } }
|
||||
: { ...renderOptions.light }
|
||||
}
|
||||
} as DecorationInstanceRenderOptions
|
||||
} as DecorationOptions;
|
||||
}
|
||||
|
||||
static gutterRenderOptions(cfgTheme: IThemeConfig, heatmap: IHeatmapConfig): IRenderOptions {
|
||||
const cfgFileTheme = cfgTheme.annotations.file.gutter;
|
||||
|
||||
let borderStyle = undefined;
|
||||
let borderWidth = undefined;
|
||||
if (heatmap.enabled) {
|
||||
borderStyle = 'solid';
|
||||
borderWidth = heatmap.location === 'left' ? '0 0 0 2px' : '0 2px 0 0';
|
||||
}
|
||||
|
||||
return {
|
||||
uncommittedForegroundColor: {
|
||||
dark: cfgFileTheme.dark.uncommittedForegroundColor || cfgFileTheme.dark.foregroundColor || themeDefaults.annotations.file.gutter.dark.foregroundColor,
|
||||
light: cfgFileTheme.light.uncommittedForegroundColor || cfgFileTheme.light.foregroundColor || themeDefaults.annotations.file.gutter.light.foregroundColor
|
||||
},
|
||||
before: {
|
||||
borderStyle: borderStyle,
|
||||
borderWidth: borderWidth,
|
||||
height: cfgFileTheme.separateLines ? 'calc(100% - 1px)' : '100%'
|
||||
},
|
||||
dark: {
|
||||
backgroundColor: cfgFileTheme.dark.backgroundColor || undefined,
|
||||
color: cfgFileTheme.dark.foregroundColor || themeDefaults.annotations.file.gutter.dark.foregroundColor
|
||||
} as DecorationInstanceRenderOptions,
|
||||
light: {
|
||||
backgroundColor: cfgFileTheme.light.backgroundColor || undefined,
|
||||
color: cfgFileTheme.light.foregroundColor || themeDefaults.annotations.file.gutter.light.foregroundColor
|
||||
} as DecorationInstanceRenderOptions
|
||||
};
|
||||
}
|
||||
|
||||
static hover(commit: GitCommit, renderOptions: IRenderOptions, heatmap: boolean): DecorationOptions {
|
||||
return {
|
||||
hoverMessage: CommitFormatter.toHoverAnnotation(commit),
|
||||
renderOptions: heatmap ? { before: { ...renderOptions.before } } : undefined
|
||||
} as DecorationOptions;
|
||||
}
|
||||
|
||||
static hoverRenderOptions(cfgTheme: IThemeConfig, heatmap: IHeatmapConfig): IRenderOptions {
|
||||
if (!heatmap.enabled) return { before: undefined };
|
||||
|
||||
return {
|
||||
before: {
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '0 0 0 2px',
|
||||
contentText: '\u200B',
|
||||
height: cfgTheme.annotations.file.hover.separateLines ? 'calc(100% - 1px)' : '100%',
|
||||
margin: '0 26px 0 0'
|
||||
}
|
||||
} as IRenderOptions;
|
||||
}
|
||||
|
||||
static trailing(commit: GitCommit, format: string, dateFormat: string | null, cfgTheme: IThemeConfig): DecorationOptions {
|
||||
const message = CommitFormatter.fromTemplate(format, commit, dateFormat);
|
||||
return {
|
||||
renderOptions: {
|
||||
after: {
|
||||
contentText: `\u00a0${message}\u00a0`
|
||||
},
|
||||
dark: {
|
||||
after: {
|
||||
backgroundColor: cfgTheme.annotations.line.trailing.dark.backgroundColor || undefined,
|
||||
color: cfgTheme.annotations.line.trailing.dark.foregroundColor || themeDefaults.annotations.line.trailing.dark.foregroundColor
|
||||
}
|
||||
},
|
||||
light: {
|
||||
after: {
|
||||
backgroundColor: cfgTheme.annotations.line.trailing.light.backgroundColor || undefined,
|
||||
color: cfgTheme.annotations.line.trailing.light.foregroundColor || themeDefaults.annotations.line.trailing.light.foregroundColor
|
||||
}
|
||||
}
|
||||
} as DecorationInstanceRenderOptions
|
||||
} as DecorationOptions;
|
||||
}
|
||||
|
||||
static withRange(decoration: DecorationOptions, start?: number, end?: number): DecorationOptions {
|
||||
let range = decoration.range;
|
||||
if (start !== undefined) {
|
||||
range = range.with({
|
||||
start: range.start.with({ character: start })
|
||||
});
|
||||
}
|
||||
|
||||
if (end !== undefined) {
|
||||
range = range.with({
|
||||
end: range.end.with({ character: end })
|
||||
});
|
||||
}
|
||||
|
||||
return { ...decoration, ...{ range: range } };
|
||||
}
|
||||
}
|
||||
82
src/annotations/blameAnnotationProvider.ts
Normal file
82
src/annotations/blameAnnotationProvider.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
'use strict';
|
||||
import { Iterables } from '../system';
|
||||
import { ExtensionContext, Range, TextEditor, TextEditorDecorationType } from 'vscode';
|
||||
import { AnnotationProviderBase } from './annotationProvider';
|
||||
import { GitService, GitUri, IGitBlame } from '../gitService';
|
||||
import { WhitespaceController } from './whitespaceController';
|
||||
|
||||
export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase {
|
||||
|
||||
protected _blame: Promise<IGitBlame>;
|
||||
|
||||
constructor(context: ExtensionContext, editor: TextEditor, decoration: TextEditorDecorationType, highlightDecoration: TextEditorDecorationType | undefined, whitespaceController: WhitespaceController | undefined, protected git: GitService, protected uri: GitUri) {
|
||||
super(context, editor, decoration, highlightDecoration, whitespaceController);
|
||||
|
||||
this._blame = this.git.getBlameForFile(this.uri);
|
||||
}
|
||||
|
||||
async selection(shaOrLine?: string | number, blame?: IGitBlame) {
|
||||
if (!this.highlightDecoration) return;
|
||||
|
||||
if (blame === undefined) {
|
||||
blame = await this._blame;
|
||||
if (!blame || !blame.lines.length) return;
|
||||
}
|
||||
|
||||
const offset = this.uri.offset;
|
||||
|
||||
let sha: string | undefined = undefined;
|
||||
if (typeof shaOrLine === 'string') {
|
||||
sha = shaOrLine;
|
||||
}
|
||||
else if (typeof shaOrLine === 'number') {
|
||||
const line = shaOrLine - offset;
|
||||
if (line >= 0) {
|
||||
const commitLine = blame.lines[line];
|
||||
sha = commitLine && commitLine.sha;
|
||||
}
|
||||
}
|
||||
else {
|
||||
sha = Iterables.first(blame.commits.values()).sha;
|
||||
}
|
||||
|
||||
if (!sha) {
|
||||
this.editor.setDecorations(this.highlightDecoration, []);
|
||||
return;
|
||||
}
|
||||
|
||||
const highlightDecorationRanges = blame.lines
|
||||
.filter(l => l.sha === sha)
|
||||
.map(l => this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)));
|
||||
|
||||
this.editor.setDecorations(this.highlightDecoration, highlightDecorationRanges);
|
||||
}
|
||||
|
||||
async validate(): Promise<boolean> {
|
||||
const blame = await this._blame;
|
||||
return blame !== undefined && blame.lines.length !== 0;
|
||||
}
|
||||
|
||||
protected async getBlame(requiresWhitespaceHack: boolean): Promise<IGitBlame | undefined> {
|
||||
let whitespacePromise: Promise<void> | undefined;
|
||||
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- override whitespace (turn off)
|
||||
if (requiresWhitespaceHack) {
|
||||
whitespacePromise = this.whitespaceController && this.whitespaceController.override();
|
||||
}
|
||||
|
||||
let blame: IGitBlame;
|
||||
if (whitespacePromise) {
|
||||
[blame] = await Promise.all([this._blame, whitespacePromise]);
|
||||
}
|
||||
else {
|
||||
blame = await this._blame;
|
||||
}
|
||||
|
||||
if (!blame || !blame.lines.length) {
|
||||
this.whitespaceController && await this.whitespaceController.restore();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return blame;
|
||||
}
|
||||
}
|
||||
69
src/annotations/diffAnnotationProvider.ts
Normal file
69
src/annotations/diffAnnotationProvider.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
'use strict';
|
||||
import { DecorationOptions, ExtensionContext, Position, Range, TextEditor, TextEditorDecorationType } from 'vscode';
|
||||
import { AnnotationProviderBase } from './annotationProvider';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
import { WhitespaceController } from './whitespaceController';
|
||||
|
||||
export class DiffAnnotationProvider extends AnnotationProviderBase {
|
||||
|
||||
constructor(context: ExtensionContext, editor: TextEditor, decoration: TextEditorDecorationType, highlightDecoration: TextEditorDecorationType | undefined, whitespaceController: WhitespaceController | undefined, private git: GitService, private uri: GitUri) {
|
||||
super(context, editor, decoration, highlightDecoration, whitespaceController);
|
||||
}
|
||||
|
||||
async provideAnnotation(shaOrLine?: string | number): Promise<boolean> {
|
||||
// let sha1: string | undefined = undefined;
|
||||
// let sha2: string | undefined = undefined;
|
||||
// if (shaOrLine === undefined) {
|
||||
// const commit = await this.git.getLogCommit(this.uri.repoPath, this.uri.fsPath, { previous: true });
|
||||
// if (commit === undefined) return false;
|
||||
|
||||
// sha1 = commit.previousSha;
|
||||
// }
|
||||
// else if (typeof shaOrLine === 'string') {
|
||||
// sha1 = shaOrLine;
|
||||
// }
|
||||
// else {
|
||||
// const blame = await this.git.getBlameForLine(this.uri, shaOrLine);
|
||||
// if (blame === undefined) return false;
|
||||
|
||||
// sha1 = blame.commit.previousSha;
|
||||
// sha2 = blame.commit.sha;
|
||||
// }
|
||||
|
||||
// if (sha1 === undefined) return false;
|
||||
|
||||
const commit = await this.git.getLogCommit(this.uri.repoPath, this.uri.fsPath, { previous: true });
|
||||
if (commit === undefined) return false;
|
||||
|
||||
const diff = await this.git.getDiffForFile(this.uri, commit.previousSha);
|
||||
if (diff === undefined) return false;
|
||||
|
||||
const decorators: DecorationOptions[] = [];
|
||||
|
||||
for (const chunk of diff.chunks) {
|
||||
let count = chunk.currentStart - 2;
|
||||
for (const change of chunk.current) {
|
||||
if (change === undefined) continue;
|
||||
|
||||
count++;
|
||||
|
||||
if (change.state === 'unchanged') continue;
|
||||
|
||||
decorators.push({
|
||||
range: new Range(new Position(count, 0), new Position(count, 0))
|
||||
} as DecorationOptions);
|
||||
}
|
||||
}
|
||||
|
||||
this.editor.setDecorations(this.decoration, decorators);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async selection(shaOrLine?: string | number): Promise<void> {
|
||||
}
|
||||
|
||||
async validate(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
76
src/annotations/gutterBlameAnnotationProvider.ts
Normal file
76
src/annotations/gutterBlameAnnotationProvider.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
'use strict';
|
||||
import { Strings } from '../system';
|
||||
import { DecorationOptions, Range } from 'vscode';
|
||||
import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
|
||||
import { Annotations, endOfLineIndex } from './annotations';
|
||||
import { FileAnnotationType } from '../configuration';
|
||||
import { ICommitFormatOptions } from '../gitService';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
|
||||
|
||||
async provideAnnotation(shaOrLine?: string | number, type?: FileAnnotationType): Promise<boolean> {
|
||||
this.annotationType = FileAnnotationType.Gutter;
|
||||
|
||||
const blame = await this.getBlame(true);
|
||||
if (blame === undefined) return false;
|
||||
|
||||
const cfg = this._config.annotations.file.gutter;
|
||||
|
||||
// Precalculate the formatting options so we don't need to do it on each iteration
|
||||
const tokenOptions = Strings.getTokensFromTemplate(cfg.format)
|
||||
.reduce((map, token) => {
|
||||
map[token.key] = token.options;
|
||||
return map;
|
||||
}, {} as { [token: string]: ICommitFormatOptions });
|
||||
|
||||
const options: ICommitFormatOptions = {
|
||||
dateFormat: cfg.dateFormat,
|
||||
tokenOptions: tokenOptions
|
||||
};
|
||||
|
||||
const now = moment();
|
||||
const offset = this.uri.offset;
|
||||
let previousLine: string | undefined = undefined;
|
||||
const renderOptions = Annotations.gutterRenderOptions(this._config.theme, cfg.heatmap);
|
||||
|
||||
const decorations: DecorationOptions[] = [];
|
||||
|
||||
for (const l of blame.lines) {
|
||||
const commit = blame.commits.get(l.sha);
|
||||
if (commit === undefined) continue;
|
||||
|
||||
const line = l.line + offset;
|
||||
|
||||
const gutter = Annotations.gutter(commit, cfg.format, options, renderOptions, cfg.compact && previousLine === l.sha);
|
||||
|
||||
if (cfg.compact) {
|
||||
const isEmptyOrWhitespace = this.document.lineAt(line).isEmptyOrWhitespace;
|
||||
previousLine = isEmptyOrWhitespace ? undefined : l.sha;
|
||||
}
|
||||
|
||||
if (cfg.heatmap.enabled) {
|
||||
Annotations.applyHeatmap(gutter, commit.date, now);
|
||||
}
|
||||
|
||||
const firstNonWhitespace = this.editor.document.lineAt(line).firstNonWhitespaceCharacterIndex;
|
||||
gutter.range = this.editor.document.validateRange(new Range(line, 0, line, firstNonWhitespace));
|
||||
decorations.push(gutter);
|
||||
|
||||
if (cfg.hover.details) {
|
||||
const details = Annotations.detailsHover(commit);
|
||||
details.range = cfg.hover.wholeLine
|
||||
? this.editor.document.validateRange(new Range(line, 0, line, endOfLineIndex))
|
||||
: gutter.range;
|
||||
decorations.push(details);
|
||||
}
|
||||
}
|
||||
|
||||
if (decorations.length) {
|
||||
this.editor.setDecorations(this.decoration, decorations);
|
||||
}
|
||||
|
||||
this.selection(shaOrLine, blame);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
49
src/annotations/hoverBlameAnnotationProvider.ts
Normal file
49
src/annotations/hoverBlameAnnotationProvider.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
import { DecorationOptions, Range } from 'vscode';
|
||||
import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
|
||||
import { Annotations, endOfLineIndex } from './annotations';
|
||||
import { FileAnnotationType } from '../configuration';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase {
|
||||
|
||||
async provideAnnotation(shaOrLine?: string | number): Promise<boolean> {
|
||||
this.annotationType = FileAnnotationType.Hover;
|
||||
|
||||
const blame = await this.getBlame(this._config.annotations.file.hover.heatmap.enabled);
|
||||
if (blame === undefined) return false;
|
||||
|
||||
const cfg = this._config.annotations.file.hover;
|
||||
|
||||
const now = moment();
|
||||
const offset = this.uri.offset;
|
||||
const renderOptions = Annotations.hoverRenderOptions(this._config.theme, cfg.heatmap);
|
||||
|
||||
const decorations: DecorationOptions[] = [];
|
||||
|
||||
for (const l of blame.lines) {
|
||||
const commit = blame.commits.get(l.sha);
|
||||
if (commit === undefined) continue;
|
||||
|
||||
const line = l.line + offset;
|
||||
|
||||
const hover = Annotations.hover(commit, renderOptions, cfg.heatmap.enabled);
|
||||
|
||||
const endIndex = cfg.wholeLine ? endOfLineIndex : this.editor.document.lineAt(line).firstNonWhitespaceCharacterIndex;
|
||||
hover.range = this.editor.document.validateRange(new Range(line, 0, line, endIndex));
|
||||
|
||||
if (cfg.heatmap.enabled) {
|
||||
Annotations.applyHeatmap(hover, commit.date, now);
|
||||
}
|
||||
|
||||
decorations.push(hover);
|
||||
}
|
||||
|
||||
if (decorations.length) {
|
||||
this.editor.setDecorations(this.decoration, decorations);
|
||||
}
|
||||
|
||||
this.selection(shaOrLine, blame);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
import { Disposable, workspace } from 'vscode';
|
||||
import { Logger } from './logger';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
interface ConfigurationInspection {
|
||||
key: string;
|
||||
@@ -118,8 +118,6 @@ export class WhitespaceController extends Disposable {
|
||||
if (this._count === 1 && this._configuration.overrideRequired) {
|
||||
// Override whitespace (turn off)
|
||||
await this._overrideWhitespace();
|
||||
// Add a delay to give the editor time to turn off the whitespace
|
||||
await new Promise((resolve, reject) => setTimeout(resolve, 250));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,391 +0,0 @@
|
||||
'use strict';
|
||||
import { Functions, Objects } from './system';
|
||||
import { DecorationInstanceRenderOptions, DecorationOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
||||
import { BlameAnnotationController } from './blameAnnotationController';
|
||||
import { BlameAnnotationFormat, BlameAnnotationFormatter } from './blameAnnotationFormatter';
|
||||
import { Commands } from './commands';
|
||||
import { TextEditorComparer } from './comparers';
|
||||
import { IBlameConfig, IConfig, StatusBarCommand } from './configuration';
|
||||
import { DocumentSchemes, ExtensionKey } from './constants';
|
||||
import { BlameabilityChangeEvent, GitCommit, GitContextTracker, GitService, GitUri, IGitCommitLine } from './gitService';
|
||||
import * as moment from 'moment';
|
||||
|
||||
const activeLineDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
|
||||
after: {
|
||||
margin: '0 0 0 4em'
|
||||
}
|
||||
} as DecorationRenderOptions);
|
||||
|
||||
export class BlameActiveLineController extends Disposable {
|
||||
|
||||
private _activeEditorLineDisposable: Disposable | undefined;
|
||||
private _blameable: boolean;
|
||||
private _config: IConfig;
|
||||
private _currentLine: number = -1;
|
||||
private _disposable: Disposable;
|
||||
private _editor: TextEditor | undefined;
|
||||
private _statusBarItem: StatusBarItem | undefined;
|
||||
private _updateBlameDebounced: (line: number, editor: TextEditor) => Promise<void>;
|
||||
private _uri: GitUri;
|
||||
|
||||
constructor(context: ExtensionContext, private git: GitService, private gitContextTracker: GitContextTracker, private annotationController: BlameAnnotationController) {
|
||||
super(() => this.dispose());
|
||||
|
||||
this._updateBlameDebounced = Functions.debounce(this._updateBlame, 250);
|
||||
|
||||
this._onConfigurationChanged();
|
||||
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
|
||||
subscriptions.push(git.onDidChangeGitCache(this._onGitCacheChanged, this));
|
||||
subscriptions.push(annotationController.onDidToggleBlameAnnotations(this._onBlameAnnotationToggled, this));
|
||||
|
||||
this._disposable = Disposable.from(...subscriptions);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._editor && this._editor.setDecorations(activeLineDecoration, []);
|
||||
|
||||
this._activeEditorLineDisposable && this._activeEditorLineDisposable.dispose();
|
||||
this._statusBarItem && this._statusBarItem.dispose();
|
||||
this._disposable && this._disposable.dispose();
|
||||
}
|
||||
|
||||
private _onConfigurationChanged() {
|
||||
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
|
||||
|
||||
let changed = false;
|
||||
|
||||
if (!Objects.areEquivalent(cfg.statusBar, this._config && this._config.statusBar)) {
|
||||
changed = true;
|
||||
if (cfg.statusBar.enabled) {
|
||||
const alignment = cfg.statusBar.alignment !== 'left' ? StatusBarAlignment.Right : StatusBarAlignment.Left;
|
||||
if (this._statusBarItem !== undefined && this._statusBarItem.alignment !== alignment) {
|
||||
this._statusBarItem.dispose();
|
||||
this._statusBarItem = undefined;
|
||||
}
|
||||
|
||||
this._statusBarItem = this._statusBarItem || window.createStatusBarItem(alignment, alignment === StatusBarAlignment.Right ? 1000 : 0);
|
||||
this._statusBarItem.command = cfg.statusBar.command;
|
||||
}
|
||||
else if (!cfg.statusBar.enabled && this._statusBarItem) {
|
||||
this._statusBarItem.dispose();
|
||||
this._statusBarItem = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Objects.areEquivalent(cfg.blame.annotation.activeLine, this._config && this._config.blame.annotation.activeLine)) {
|
||||
changed = true;
|
||||
if (cfg.blame.annotation.activeLine !== 'off' && this._editor) {
|
||||
this._editor.setDecorations(activeLineDecoration, []);
|
||||
}
|
||||
}
|
||||
if (!Objects.areEquivalent(cfg.blame.annotation.activeLineDarkColor, this._config && this._config.blame.annotation.activeLineDarkColor) ||
|
||||
!Objects.areEquivalent(cfg.blame.annotation.activeLineLightColor, this._config && this._config.blame.annotation.activeLineLightColor)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
this._config = cfg;
|
||||
|
||||
if (!changed) return;
|
||||
|
||||
const trackActiveLine = cfg.statusBar.enabled || cfg.blame.annotation.activeLine !== 'off';
|
||||
if (trackActiveLine && !this._activeEditorLineDisposable) {
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
|
||||
subscriptions.push(window.onDidChangeTextEditorSelection(this._onTextEditorSelectionChanged, this));
|
||||
subscriptions.push(this.gitContextTracker.onDidBlameabilityChange(this._onBlameabilityChanged, this));
|
||||
|
||||
this._activeEditorLineDisposable = Disposable.from(...subscriptions);
|
||||
}
|
||||
else if (!trackActiveLine && this._activeEditorLineDisposable) {
|
||||
this._activeEditorLineDisposable.dispose();
|
||||
this._activeEditorLineDisposable = undefined;
|
||||
}
|
||||
|
||||
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||
}
|
||||
|
||||
private isEditorBlameable(editor: TextEditor | undefined): boolean {
|
||||
if (editor === undefined || editor.document === undefined) return false;
|
||||
|
||||
if (!this.git.isTrackable(editor.document.uri)) return false;
|
||||
if (editor.document.isUntitled && editor.document.uri.scheme === DocumentSchemes.File) return false;
|
||||
|
||||
return this.git.isEditorBlameable(editor);
|
||||
}
|
||||
|
||||
private async _onActiveTextEditorChanged(editor: TextEditor | undefined) {
|
||||
this._currentLine = -1;
|
||||
|
||||
const previousEditor = this._editor;
|
||||
previousEditor && previousEditor.setDecorations(activeLineDecoration, []);
|
||||
|
||||
if (editor === undefined || !this.isEditorBlameable(editor)) {
|
||||
this.clear(editor);
|
||||
|
||||
this._editor = undefined;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._blameable = editor !== undefined && editor.document !== undefined && !editor.document.isDirty;
|
||||
this._editor = editor;
|
||||
this._uri = await GitUri.fromUri(editor.document.uri, this.git);
|
||||
|
||||
const maxLines = this._config.advanced.caching.statusBar.maxLines;
|
||||
// If caching is on and the file is small enough -- kick off a blame for the whole file
|
||||
if (this._config.advanced.caching.enabled && (maxLines <= 0 || editor.document.lineCount <= maxLines)) {
|
||||
this.git.getBlameForFile(this._uri);
|
||||
}
|
||||
|
||||
this._updateBlameDebounced(editor.selection.active.line, editor);
|
||||
}
|
||||
|
||||
private _onBlameabilityChanged(e: BlameabilityChangeEvent) {
|
||||
this._blameable = e.blameable;
|
||||
if (!e.blameable || !this._editor) {
|
||||
this.clear(e.editor);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure this is for the editor we are tracking
|
||||
if (!TextEditorComparer.equals(this._editor, e.editor)) return;
|
||||
|
||||
this._updateBlameDebounced(this._editor.selection.active.line, this._editor);
|
||||
}
|
||||
|
||||
private _onBlameAnnotationToggled() {
|
||||
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||
}
|
||||
|
||||
private _onGitCacheChanged() {
|
||||
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||
}
|
||||
|
||||
private async _onTextEditorSelectionChanged(e: TextEditorSelectionChangeEvent): Promise<void> {
|
||||
// Make sure this is for the editor we are tracking
|
||||
if (!this._blameable || !TextEditorComparer.equals(this._editor, e.textEditor)) return;
|
||||
|
||||
const line = e.selections[0].active.line;
|
||||
if (line === this._currentLine) return;
|
||||
this._currentLine = line;
|
||||
|
||||
if (!this._uri && e.textEditor) {
|
||||
this._uri = await GitUri.fromUri(e.textEditor.document.uri, this.git);
|
||||
}
|
||||
|
||||
this._updateBlameDebounced(line, e.textEditor);
|
||||
}
|
||||
|
||||
private async _updateBlame(line: number, editor: TextEditor) {
|
||||
line = line - this._uri.offset;
|
||||
|
||||
let commit: GitCommit | undefined = undefined;
|
||||
let commitLine: IGitCommitLine | undefined = undefined;
|
||||
// Since blame information isn't valid when there are unsaved changes -- don't show any status
|
||||
if (this._blameable && line >= 0) {
|
||||
const blameLine = await this.git.getBlameForLine(this._uri, line);
|
||||
commitLine = blameLine === undefined ? undefined : blameLine.line;
|
||||
commit = blameLine === undefined ? undefined : blameLine.commit;
|
||||
}
|
||||
|
||||
if (commit !== undefined && commitLine !== undefined) {
|
||||
this.show(commit, commitLine, editor);
|
||||
}
|
||||
else {
|
||||
this.clear(editor);
|
||||
}
|
||||
}
|
||||
|
||||
clear(editor: TextEditor | undefined, previousEditor?: TextEditor) {
|
||||
editor && editor.setDecorations(activeLineDecoration, []);
|
||||
// I have no idea why the decorators sometimes don't get removed, but if they don't try again with a tiny delay
|
||||
if (editor) {
|
||||
setTimeout(() => editor.setDecorations(activeLineDecoration, []), 1);
|
||||
}
|
||||
|
||||
this._statusBarItem && this._statusBarItem.hide();
|
||||
}
|
||||
|
||||
async show(commit: GitCommit, blameLine: IGitCommitLine, editor: TextEditor) {
|
||||
// I have no idea why I need this protection -- but it happens
|
||||
if (!editor.document) return;
|
||||
|
||||
if (this._config.statusBar.enabled && this._statusBarItem !== undefined) {
|
||||
switch (this._config.statusBar.date) {
|
||||
case 'off':
|
||||
this._statusBarItem.text = `$(git-commit) ${commit.author}`;
|
||||
break;
|
||||
case 'absolute':
|
||||
const dateFormat = this._config.statusBar.dateFormat || 'MMMM Do, YYYY h:MMa';
|
||||
let date: string;
|
||||
try {
|
||||
date = moment(commit.date).format(dateFormat);
|
||||
} catch (ex) {
|
||||
date = moment(commit.date).format('MMMM Do, YYYY h:MMa');
|
||||
}
|
||||
this._statusBarItem.text = `$(git-commit) ${commit.author}, ${date}`;
|
||||
break;
|
||||
default:
|
||||
this._statusBarItem.text = `$(git-commit) ${commit.author}, ${moment(commit.date).fromNow()}`;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (this._config.statusBar.command) {
|
||||
case StatusBarCommand.BlameAnnotate:
|
||||
this._statusBarItem.tooltip = 'Toggle Blame Annotations';
|
||||
break;
|
||||
case StatusBarCommand.ShowBlameHistory:
|
||||
this._statusBarItem.tooltip = 'Open Blame History Explorer';
|
||||
break;
|
||||
case StatusBarCommand.ShowFileHistory:
|
||||
this._statusBarItem.tooltip = 'Open File History Explorer';
|
||||
break;
|
||||
case StatusBarCommand.DiffWithPrevious:
|
||||
this._statusBarItem.command = Commands.DiffLineWithPrevious;
|
||||
this._statusBarItem.tooltip = 'Compare File with Previous';
|
||||
break;
|
||||
case StatusBarCommand.DiffWithWorking:
|
||||
this._statusBarItem.command = Commands.DiffLineWithWorking;
|
||||
this._statusBarItem.tooltip = 'Compare File with Working Tree';
|
||||
break;
|
||||
case StatusBarCommand.ToggleCodeLens:
|
||||
this._statusBarItem.tooltip = 'Toggle Git CodeLens';
|
||||
break;
|
||||
case StatusBarCommand.ShowQuickCommitDetails:
|
||||
this._statusBarItem.tooltip = 'Show Commit Details';
|
||||
break;
|
||||
case StatusBarCommand.ShowQuickCommitFileDetails:
|
||||
this._statusBarItem.tooltip = 'Show Line Commit Details';
|
||||
break;
|
||||
case StatusBarCommand.ShowQuickFileHistory:
|
||||
this._statusBarItem.tooltip = 'Show File History';
|
||||
break;
|
||||
case StatusBarCommand.ShowQuickCurrentBranchHistory:
|
||||
this._statusBarItem.tooltip = 'Show Branch History';
|
||||
break;
|
||||
}
|
||||
|
||||
this._statusBarItem.show();
|
||||
}
|
||||
|
||||
if (this._config.blame.annotation.activeLine !== 'off') {
|
||||
const activeLine = this._config.blame.annotation.activeLine;
|
||||
const offset = this._uri.offset;
|
||||
|
||||
const cfg = {
|
||||
annotation: {
|
||||
sha: true,
|
||||
author: this._config.statusBar.enabled ? false : this._config.blame.annotation.author,
|
||||
date: this._config.statusBar.enabled ? 'off' : this._config.blame.annotation.date,
|
||||
message: true
|
||||
}
|
||||
} as IBlameConfig;
|
||||
|
||||
const annotation = BlameAnnotationFormatter.getAnnotation(cfg, commit, BlameAnnotationFormat.Unconstrained);
|
||||
|
||||
// Get the full commit message -- since blame only returns the summary
|
||||
let logCommit: GitCommit | undefined = undefined;
|
||||
if (!commit.isUncommitted) {
|
||||
logCommit = await this.git.getLogCommit(this._uri.repoPath, this._uri.fsPath, commit.sha);
|
||||
}
|
||||
|
||||
// I have no idea why I need this protection -- but it happens
|
||||
if (!editor.document) return;
|
||||
|
||||
let hoverMessage: string | string[] | undefined = undefined;
|
||||
if (activeLine !== 'inline') {
|
||||
// If the messages match (or we couldn't find the log), then this is a possible duplicate annotation
|
||||
const possibleDuplicate = !logCommit || logCommit.message === commit.message;
|
||||
// If we don't have a possible dupe or we aren't showing annotations get the hover message
|
||||
if (!commit.isUncommitted && (!possibleDuplicate || !this.annotationController.isAnnotating(editor))) {
|
||||
hoverMessage = BlameAnnotationFormatter.getAnnotationHover(cfg, blameLine, logCommit || commit);
|
||||
|
||||
if (commit.previousSha !== undefined) {
|
||||
const changes = await this.git.getDiffForLine(this._uri, blameLine.line + offset, commit.previousSha);
|
||||
if (changes !== undefined) {
|
||||
let previous = changes[0];
|
||||
if (previous !== undefined) {
|
||||
previous = previous.replace(/\n/g, '\`\n>\n> \`').trim();
|
||||
hoverMessage += `\n\n---\n\`\`\`\n${previous}\n\`\`\``;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (commit.isUncommitted) {
|
||||
const changes = await this.git.getDiffForLine(this._uri, blameLine.line + offset);
|
||||
if (changes !== undefined) {
|
||||
let previous = changes[0];
|
||||
if (previous !== undefined) {
|
||||
previous = previous.replace(/\n/g, '\`\n>\n> \`').trim();
|
||||
hoverMessage = `\`${'0'.repeat(8)}\` __Uncommitted change__\n\n---\n\`\`\`\n${previous}\n\`\`\``;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let decorationOptions: [DecorationOptions] | undefined = undefined;
|
||||
switch (activeLine) {
|
||||
case 'both':
|
||||
case 'inline':
|
||||
const range = editor.document.validateRange(new Range(blameLine.line + offset, 0, blameLine.line + offset, 1000000));
|
||||
decorationOptions = [
|
||||
{
|
||||
range: range.with({
|
||||
start: range.start.with({
|
||||
character: range.end.character
|
||||
})
|
||||
}),
|
||||
hoverMessage: hoverMessage,
|
||||
renderOptions: {
|
||||
after: {
|
||||
contentText: annotation
|
||||
},
|
||||
dark: {
|
||||
after: {
|
||||
color: this._config.blame.annotation.activeLineDarkColor || 'rgba(153, 153, 153, 0.35)'
|
||||
}
|
||||
},
|
||||
light: {
|
||||
after: {
|
||||
color: this._config.blame.annotation.activeLineLightColor || 'rgba(153, 153, 153, 0.35)'
|
||||
}
|
||||
}
|
||||
} as DecorationInstanceRenderOptions
|
||||
} as DecorationOptions
|
||||
];
|
||||
|
||||
if (activeLine === 'both') {
|
||||
// Add a hover decoration to the area between the start of the line and the first non-whitespace character
|
||||
decorationOptions.push({
|
||||
range: range.with({
|
||||
end: range.end.with({
|
||||
character: editor.document.lineAt(range.end.line).firstNonWhitespaceCharacterIndex
|
||||
})
|
||||
}),
|
||||
hoverMessage: hoverMessage
|
||||
} as DecorationOptions);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'hover':
|
||||
decorationOptions = [
|
||||
{
|
||||
range: editor.document.validateRange(new Range(blameLine.line + offset, 0, blameLine.line + offset, 1000000)),
|
||||
hoverMessage: hoverMessage
|
||||
} as DecorationOptions
|
||||
];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (decorationOptions !== undefined) {
|
||||
editor.setDecorations(activeLineDecoration, decorationOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
'use strict';
|
||||
import { Functions } from './system';
|
||||
import { DecorationRenderOptions, Disposable, Event, EventEmitter, ExtensionContext, OverviewRulerLane, TextDocument, TextDocumentChangeEvent, TextEditor, TextEditorDecorationType, TextEditorViewColumnChangeEvent, window, workspace } from 'vscode';
|
||||
import { BlameAnnotationProvider } from './blameAnnotationProvider';
|
||||
import { TextDocumentComparer, TextEditorComparer } from './comparers';
|
||||
import { IBlameConfig } from './configuration';
|
||||
import { ExtensionKey } from './constants';
|
||||
import { BlameabilityChangeEvent, GitContextTracker, GitService, GitUri } from './gitService';
|
||||
import { Logger } from './logger';
|
||||
import { WhitespaceController } from './whitespaceController';
|
||||
|
||||
export const BlameDecorations = {
|
||||
annotation: window.createTextEditorDecorationType({
|
||||
before: {
|
||||
margin: '0 1.75em 0 0'
|
||||
},
|
||||
after: {
|
||||
margin: '0 0 0 4em'
|
||||
}
|
||||
} as DecorationRenderOptions),
|
||||
highlight: undefined as TextEditorDecorationType | undefined
|
||||
};
|
||||
|
||||
export class BlameAnnotationController extends Disposable {
|
||||
|
||||
private _onDidToggleBlameAnnotations = new EventEmitter<void>();
|
||||
get onDidToggleBlameAnnotations(): Event<void> {
|
||||
return this._onDidToggleBlameAnnotations.event;
|
||||
}
|
||||
|
||||
private _annotationProviders: Map<number, BlameAnnotationProvider> = new Map();
|
||||
private _blameAnnotationsDisposable: Disposable | undefined;
|
||||
private _config: IBlameConfig;
|
||||
private _disposable: Disposable;
|
||||
private _whitespaceController: WhitespaceController | undefined;
|
||||
|
||||
constructor(private context: ExtensionContext, private git: GitService, private gitContextTracker: GitContextTracker) {
|
||||
super(() => this.dispose());
|
||||
|
||||
this._onConfigurationChanged();
|
||||
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
|
||||
|
||||
this._disposable = Disposable.from(...subscriptions);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._annotationProviders.forEach(async (p, i) => await this.clear(i));
|
||||
|
||||
BlameDecorations.annotation && BlameDecorations.annotation.dispose();
|
||||
BlameDecorations.highlight && BlameDecorations.highlight.dispose();
|
||||
|
||||
this._blameAnnotationsDisposable && this._blameAnnotationsDisposable.dispose();
|
||||
this._whitespaceController && this._whitespaceController.dispose();
|
||||
this._disposable && this._disposable.dispose();
|
||||
}
|
||||
|
||||
private _onConfigurationChanged() {
|
||||
let toggleWhitespace = workspace.getConfiguration(`${ExtensionKey}.advanced.toggleWhitespace`).get<boolean>('enabled');
|
||||
if (!toggleWhitespace) {
|
||||
// Until https://github.com/Microsoft/vscode/issues/11485 is fixed we need to toggle whitespace for non-monospace fonts and ligatures
|
||||
// TODO: detect monospace font
|
||||
toggleWhitespace = workspace.getConfiguration('editor').get<boolean>('fontLigatures');
|
||||
}
|
||||
|
||||
if (toggleWhitespace && !this._whitespaceController) {
|
||||
this._whitespaceController = new WhitespaceController();
|
||||
}
|
||||
else if (!toggleWhitespace && this._whitespaceController) {
|
||||
this._whitespaceController.dispose();
|
||||
this._whitespaceController = undefined;
|
||||
}
|
||||
|
||||
const cfg = workspace.getConfiguration(ExtensionKey).get<IBlameConfig>('blame')!;
|
||||
|
||||
if (cfg.annotation.highlight !== (this._config && this._config.annotation.highlight)) {
|
||||
BlameDecorations.highlight && BlameDecorations.highlight.dispose();
|
||||
|
||||
switch (cfg.annotation.highlight) {
|
||||
case 'gutter':
|
||||
BlameDecorations.highlight = window.createTextEditorDecorationType({
|
||||
dark: {
|
||||
gutterIconPath: this.context.asAbsolutePath('images/blame-dark.svg'),
|
||||
overviewRulerColor: 'rgba(255, 255, 255, 0.75)'
|
||||
},
|
||||
light: {
|
||||
gutterIconPath: this.context.asAbsolutePath('images/blame-light.svg'),
|
||||
overviewRulerColor: 'rgba(0, 0, 0, 0.75)'
|
||||
},
|
||||
gutterIconSize: 'contain',
|
||||
overviewRulerLane: OverviewRulerLane.Right
|
||||
});
|
||||
break;
|
||||
|
||||
case 'line':
|
||||
BlameDecorations.highlight = window.createTextEditorDecorationType({
|
||||
dark: {
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
||||
overviewRulerColor: 'rgba(255, 255, 255, 0.75)'
|
||||
},
|
||||
light: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.15)',
|
||||
overviewRulerColor: 'rgba(0, 0, 0, 0.75)'
|
||||
},
|
||||
overviewRulerLane: OverviewRulerLane.Right,
|
||||
isWholeLine: true
|
||||
});
|
||||
break;
|
||||
|
||||
case 'both':
|
||||
BlameDecorations.highlight = window.createTextEditorDecorationType({
|
||||
dark: {
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
||||
gutterIconPath: this.context.asAbsolutePath('images/blame-dark.svg'),
|
||||
overviewRulerColor: 'rgba(255, 255, 255, 0.75)'
|
||||
},
|
||||
light: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.15)',
|
||||
gutterIconPath: this.context.asAbsolutePath('images/blame-light.svg'),
|
||||
overviewRulerColor: 'rgba(0, 0, 0, 0.75)'
|
||||
},
|
||||
gutterIconSize: 'contain',
|
||||
overviewRulerLane: OverviewRulerLane.Right,
|
||||
isWholeLine: true
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
BlameDecorations.highlight = undefined;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this._config = cfg;
|
||||
}
|
||||
|
||||
async clear(column: number) {
|
||||
const provider = this._annotationProviders.get(column);
|
||||
if (!provider) return;
|
||||
|
||||
this._annotationProviders.delete(column);
|
||||
await provider.dispose();
|
||||
|
||||
if (this._annotationProviders.size === 0) {
|
||||
Logger.log(`Remove listener registrations for blame annotations`);
|
||||
this._blameAnnotationsDisposable && this._blameAnnotationsDisposable.dispose();
|
||||
this._blameAnnotationsDisposable = undefined;
|
||||
}
|
||||
|
||||
this._onDidToggleBlameAnnotations.fire();
|
||||
}
|
||||
|
||||
async showBlameAnnotation(editor: TextEditor, shaOrLine?: string | number): Promise<boolean> {
|
||||
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false;
|
||||
|
||||
const currentProvider = this._annotationProviders.get(editor.viewColumn || -1);
|
||||
if (currentProvider && TextEditorComparer.equals(currentProvider.editor, editor)) {
|
||||
await currentProvider.setSelection(shaOrLine);
|
||||
return true;
|
||||
}
|
||||
|
||||
const gitUri = await GitUri.fromUri(editor.document.uri, this.git);
|
||||
const provider = new BlameAnnotationProvider(this.context, this.git, this._whitespaceController, editor, gitUri);
|
||||
if (!await provider.supportsBlame()) return false;
|
||||
|
||||
if (currentProvider) {
|
||||
await this.clear(currentProvider.editor.viewColumn || -1);
|
||||
}
|
||||
|
||||
if (!this._blameAnnotationsDisposable && this._annotationProviders.size === 0) {
|
||||
Logger.log(`Add listener registrations for blame annotations`);
|
||||
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
subscriptions.push(window.onDidChangeVisibleTextEditors(Functions.debounce(this._onVisibleTextEditorsChanged, 100), this));
|
||||
subscriptions.push(window.onDidChangeTextEditorViewColumn(this._onTextEditorViewColumnChanged, this));
|
||||
subscriptions.push(workspace.onDidChangeTextDocument(this._onTextDocumentChanged, this));
|
||||
subscriptions.push(workspace.onDidCloseTextDocument(this._onTextDocumentClosed, this));
|
||||
subscriptions.push(this.gitContextTracker.onDidBlameabilityChange(this._onBlameabilityChanged, this));
|
||||
|
||||
this._blameAnnotationsDisposable = Disposable.from(...subscriptions);
|
||||
}
|
||||
|
||||
this._annotationProviders.set(editor.viewColumn || -1, provider);
|
||||
if (await provider.provideBlameAnnotation(shaOrLine)) {
|
||||
this._onDidToggleBlameAnnotations.fire();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isAnnotating(editor: TextEditor): boolean {
|
||||
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false;
|
||||
|
||||
return !!this._annotationProviders.get(editor.viewColumn || -1);
|
||||
}
|
||||
|
||||
async toggleBlameAnnotation(editor: TextEditor, shaOrLine?: string | number): Promise<boolean> {
|
||||
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false;
|
||||
|
||||
const provider = this._annotationProviders.get(editor.viewColumn || -1);
|
||||
if (!provider) return this.showBlameAnnotation(editor, shaOrLine);
|
||||
|
||||
await this.clear(provider.editor.viewColumn || -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
private _onBlameabilityChanged(e: BlameabilityChangeEvent) {
|
||||
if (e.blameable || !e.editor) return;
|
||||
|
||||
for (const [key, p] of this._annotationProviders) {
|
||||
if (!TextDocumentComparer.equals(p.document, e.editor.document)) continue;
|
||||
|
||||
Logger.log('BlameabilityChanged:', `Clear blame annotations for column ${key}`);
|
||||
this.clear(key);
|
||||
}
|
||||
}
|
||||
|
||||
private _onTextDocumentChanged(e: TextDocumentChangeEvent) {
|
||||
for (const [key, p] of this._annotationProviders) {
|
||||
if (!TextDocumentComparer.equals(p.document, e.document)) continue;
|
||||
|
||||
// We have to defer because isDirty is not reliable inside this event
|
||||
setTimeout(() => {
|
||||
// If the document is dirty all is fine, just kick out since the GitContextTracker will handle it
|
||||
if (e.document.isDirty) return;
|
||||
|
||||
// If the document isn't dirty, it is very likely this event was triggered by an outside edit of this document
|
||||
// Which means the document has been reloaded and the blame annotations have been removed, so we need to update (clear) our state tracking
|
||||
Logger.log('TextDocumentChanged:', `Clear blame annotations for column ${key}`);
|
||||
this.clear(key);
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private _onTextDocumentClosed(e: TextDocument) {
|
||||
for (const [key, p] of this._annotationProviders) {
|
||||
if (!TextDocumentComparer.equals(p.document, e)) continue;
|
||||
|
||||
Logger.log('TextDocumentClosed:', `Clear blame annotations for column ${key}`);
|
||||
this.clear(key);
|
||||
}
|
||||
}
|
||||
|
||||
private async _onTextEditorViewColumnChanged(e: TextEditorViewColumnChangeEvent) {
|
||||
const viewColumn = e.viewColumn || -1;
|
||||
|
||||
Logger.log('TextEditorViewColumnChanged:', `Clear blame annotations for column ${viewColumn}`);
|
||||
await this.clear(viewColumn);
|
||||
|
||||
for (const [key, p] of this._annotationProviders) {
|
||||
if (!TextEditorComparer.equals(p.editor, e.textEditor)) continue;
|
||||
|
||||
Logger.log('TextEditorViewColumnChanged:', `Clear blame annotations for column ${key}`);
|
||||
await this.clear(key);
|
||||
}
|
||||
}
|
||||
|
||||
private async _onVisibleTextEditorsChanged(e: TextEditor[]) {
|
||||
if (e.every(_ => _.document.uri.scheme === 'inmemory')) return;
|
||||
|
||||
for (const [key, p] of this._annotationProviders) {
|
||||
if (e.some(_ => TextEditorComparer.equals(p.editor, _))) continue;
|
||||
|
||||
Logger.log('VisibleTextEditorsChanged:', `Clear blame annotations for column ${key}`);
|
||||
this.clear(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
'use strict';
|
||||
import { IBlameConfig } from './configuration';
|
||||
import { GitCommit, IGitCommitLine } from './gitService';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export const defaultAbsoluteDateLength = 10;
|
||||
export const defaultRelativeDateLength = 13;
|
||||
export const defaultAuthorLength = 16;
|
||||
export const defaultMessageLength = 32;
|
||||
|
||||
export enum BlameAnnotationFormat {
|
||||
Constrained,
|
||||
Unconstrained
|
||||
}
|
||||
|
||||
export class BlameAnnotationFormatter {
|
||||
|
||||
static getAnnotation(config: IBlameConfig, commit: GitCommit, format: BlameAnnotationFormat) {
|
||||
const sha = commit.shortSha;
|
||||
let message = this.getMessage(config, commit, format === BlameAnnotationFormat.Unconstrained ? 0 : defaultMessageLength);
|
||||
|
||||
if (format === BlameAnnotationFormat.Unconstrained) {
|
||||
const authorAndDate = this.getAuthorAndDate(config, commit, config.annotation.dateFormat || 'MMMM Do, YYYY h:MMa');
|
||||
if (config.annotation.sha) {
|
||||
message = `${sha}${(authorAndDate ? `\u00a0\u2022\u00a0${authorAndDate}` : '')}${(message ? `\u00a0\u2022\u00a0${message}` : '')}`;
|
||||
}
|
||||
else if (config.annotation.author || config.annotation.date) {
|
||||
message = `${authorAndDate}${(message ? `\u00a0\u2022\u00a0${message}` : '')}`;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
const author = this.getAuthor(config, commit, defaultAuthorLength);
|
||||
const date = this.getDate(config, commit, config.annotation.dateFormat || 'MM/DD/YYYY', true);
|
||||
if (config.annotation.sha) {
|
||||
message = `${sha}${(author ? `\u00a0\u2022\u00a0${author}` : '')}${(date ? `\u00a0\u2022\u00a0${date}` : '')}${(message ? `\u00a0\u2022\u00a0${message}` : '')}`;
|
||||
}
|
||||
else if (config.annotation.author) {
|
||||
message = `${author}${(date ? `\u00a0\u2022\u00a0${date}` : '')}${(message ? `\u00a0\u2022\u00a0${message}` : '')}`;
|
||||
}
|
||||
else if (config.annotation.date) {
|
||||
message = `${date}${(message ? `\u00a0\u2022\u00a0${message}` : '')}`;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
static getAnnotationHover(config: IBlameConfig, line: IGitCommitLine, commit: GitCommit): string | string[] {
|
||||
const message = `> \`${commit.message.replace(/\n/g, '\`\n>\n> \`')}\``;
|
||||
if (commit.isUncommitted) {
|
||||
return `\`${'0'.repeat(8)}\` __Uncommitted change__`;
|
||||
}
|
||||
|
||||
return `\`${commit.shortSha}\` __${commit.author}__, ${moment(commit.date).fromNow()} _(${moment(commit.date).format(config.annotation.dateFormat || 'MMMM Do, YYYY h:MMa')})_ \n\n${message}`;
|
||||
}
|
||||
|
||||
static getAuthorAndDate(config: IBlameConfig, commit: GitCommit, format: string, force: boolean = false) {
|
||||
if (!force && !config.annotation.author && (!config.annotation.date || config.annotation.date === 'off')) return '';
|
||||
|
||||
if (!config.annotation.author) {
|
||||
return this.getDate(config, commit, format);
|
||||
}
|
||||
|
||||
if (!config.annotation.date || config.annotation.date === 'off') {
|
||||
return this.getAuthor(config, commit);
|
||||
}
|
||||
|
||||
return `${this.getAuthor(config, commit)}, ${this.getDate(config, commit, format)}`;
|
||||
}
|
||||
|
||||
static getAuthor(config: IBlameConfig, commit: GitCommit, truncateTo: number = 0, force: boolean = false) {
|
||||
if (!force && !config.annotation.author) return '';
|
||||
|
||||
const author = commit.isUncommitted ? 'Uncommitted' : commit.author;
|
||||
if (!truncateTo) return author;
|
||||
|
||||
if (author.length > truncateTo) {
|
||||
return `${author.substring(0, truncateTo - 1)}\u2026`;
|
||||
}
|
||||
|
||||
if (force) return author; // Don't pad when just asking for the value
|
||||
return author + '\u00a0'.repeat(truncateTo - author.length);
|
||||
}
|
||||
|
||||
static getDate(config: IBlameConfig, commit: GitCommit, format: string, truncate: boolean = false, force: boolean = false) {
|
||||
if (!force && (!config.annotation.date || config.annotation.date === 'off')) return '';
|
||||
|
||||
const date = config.annotation.date === 'relative'
|
||||
? moment(commit.date).fromNow()
|
||||
: moment(commit.date).format(format);
|
||||
if (!truncate) return date;
|
||||
|
||||
const truncateTo = config.annotation.date === 'relative' ? defaultRelativeDateLength : defaultAbsoluteDateLength;
|
||||
if (date.length > truncateTo) {
|
||||
return `${date.substring(0, truncateTo - 1)}\u2026`;
|
||||
}
|
||||
|
||||
if (force) return date; // Don't pad when just asking for the value
|
||||
return date + '\u00a0'.repeat(truncateTo - date.length);
|
||||
}
|
||||
|
||||
static getMessage(config: IBlameConfig, commit: GitCommit, truncateTo: number = 0, force: boolean = false) {
|
||||
if (!force && !config.annotation.message) return '';
|
||||
|
||||
const message = commit.isUncommitted ? 'Uncommitted change' : commit.message;
|
||||
if (truncateTo && message.length > truncateTo) {
|
||||
return `${message.substring(0, truncateTo - 1)}\u2026`;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
'use strict';
|
||||
import { Iterables } from './system';
|
||||
import { DecorationInstanceRenderOptions, DecorationOptions, Disposable, ExtensionContext, Range, TextDocument, TextEditor, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
||||
import { BlameAnnotationFormat, BlameAnnotationFormatter, defaultAuthorLength } from './blameAnnotationFormatter';
|
||||
import { BlameDecorations } from './blameAnnotationController';
|
||||
import { TextDocumentComparer } from './comparers';
|
||||
import { BlameAnnotationStyle, IBlameConfig } from './configuration';
|
||||
import { ExtensionKey } from './constants';
|
||||
import { GitService, GitUri, IGitBlame } from './gitService';
|
||||
import { WhitespaceController } from './whitespaceController';
|
||||
|
||||
export class BlameAnnotationProvider extends Disposable {
|
||||
|
||||
public document: TextDocument;
|
||||
|
||||
private _blame: Promise<IGitBlame>;
|
||||
private _config: IBlameConfig;
|
||||
private _disposable: Disposable;
|
||||
|
||||
constructor(context: ExtensionContext, private git: GitService, private whitespaceController: WhitespaceController | undefined, public editor: TextEditor, private uri: GitUri) {
|
||||
super(() => this.dispose());
|
||||
|
||||
this.document = this.editor.document;
|
||||
|
||||
this._blame = this.git.getBlameForFile(this.uri);
|
||||
|
||||
this._config = workspace.getConfiguration(ExtensionKey).get<IBlameConfig>('blame')!;
|
||||
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
subscriptions.push(window.onDidChangeTextEditorSelection(this._onActiveSelectionChanged, this));
|
||||
|
||||
this._disposable = Disposable.from(...subscriptions);
|
||||
}
|
||||
|
||||
async dispose() {
|
||||
if (this.editor) {
|
||||
try {
|
||||
this.editor.setDecorations(BlameDecorations.annotation, []);
|
||||
BlameDecorations.highlight && this.editor.setDecorations(BlameDecorations.highlight, []);
|
||||
// I have no idea why the decorators sometimes don't get removed, but if they don't try again with a tiny delay
|
||||
if (BlameDecorations.highlight !== undefined) {
|
||||
setTimeout(() => {
|
||||
if (BlameDecorations.highlight === undefined) return;
|
||||
|
||||
this.editor.setDecorations(BlameDecorations.highlight, []);
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
catch (ex) { }
|
||||
}
|
||||
|
||||
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- restore whitespace
|
||||
this.whitespaceController && await this.whitespaceController.restore();
|
||||
|
||||
this._disposable && this._disposable.dispose();
|
||||
}
|
||||
|
||||
private async _onActiveSelectionChanged(e: TextEditorSelectionChangeEvent) {
|
||||
if (!TextDocumentComparer.equals(this.document, e.textEditor && e.textEditor.document)) return;
|
||||
|
||||
return this.setSelection(e.selections[0].active.line);
|
||||
}
|
||||
|
||||
async supportsBlame(): Promise<boolean> {
|
||||
const blame = await this._blame;
|
||||
return !!(blame && blame.lines.length);
|
||||
}
|
||||
|
||||
async provideBlameAnnotation(shaOrLine?: string | number): Promise<boolean> {
|
||||
let whitespacePromise: Promise<void> | undefined;
|
||||
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- override whitespace (turn off)
|
||||
if (this._config.annotation.style !== BlameAnnotationStyle.Trailing) {
|
||||
whitespacePromise = this.whitespaceController && this.whitespaceController.override();
|
||||
}
|
||||
|
||||
let blame: IGitBlame;
|
||||
if (whitespacePromise) {
|
||||
[blame] = await Promise.all([this._blame, whitespacePromise]);
|
||||
}
|
||||
else {
|
||||
blame = await this._blame;
|
||||
}
|
||||
|
||||
if (!blame || !blame.lines.length) {
|
||||
this.whitespaceController && await this.whitespaceController.restore();
|
||||
return false;
|
||||
}
|
||||
|
||||
let blameDecorationOptions: DecorationOptions[] | undefined;
|
||||
switch (this._config.annotation.style) {
|
||||
case BlameAnnotationStyle.Compact:
|
||||
blameDecorationOptions = this._getCompactGutterDecorations(blame);
|
||||
break;
|
||||
case BlameAnnotationStyle.Expanded:
|
||||
blameDecorationOptions = this._getExpandedGutterDecorations(blame, false);
|
||||
break;
|
||||
case BlameAnnotationStyle.Trailing:
|
||||
blameDecorationOptions = this._getExpandedGutterDecorations(blame, true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (blameDecorationOptions) {
|
||||
this.editor.setDecorations(BlameDecorations.annotation, blameDecorationOptions);
|
||||
}
|
||||
|
||||
this._setSelection(blame, shaOrLine);
|
||||
return true;
|
||||
}
|
||||
|
||||
async setSelection(shaOrLine?: string | number) {
|
||||
const blame = await this._blame;
|
||||
if (!blame || !blame.lines.length) return;
|
||||
|
||||
return this._setSelection(blame, shaOrLine);
|
||||
}
|
||||
|
||||
private _setSelection(blame: IGitBlame, shaOrLine?: string | number) {
|
||||
if (!BlameDecorations.highlight) return;
|
||||
|
||||
const offset = this.uri.offset;
|
||||
|
||||
let sha: string | undefined = undefined;
|
||||
if (typeof shaOrLine === 'string') {
|
||||
sha = shaOrLine;
|
||||
}
|
||||
else if (typeof shaOrLine === 'number') {
|
||||
const line = shaOrLine - offset;
|
||||
if (line >= 0) {
|
||||
const commitLine = blame.lines[line];
|
||||
sha = commitLine && commitLine.sha;
|
||||
}
|
||||
}
|
||||
else {
|
||||
sha = Iterables.first(blame.commits.values()).sha;
|
||||
}
|
||||
|
||||
if (!sha) {
|
||||
this.editor.setDecorations(BlameDecorations.highlight, []);
|
||||
return;
|
||||
}
|
||||
|
||||
const highlightDecorationRanges = blame.lines
|
||||
.filter(l => l.sha === sha)
|
||||
.map(l => this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)));
|
||||
|
||||
this.editor.setDecorations(BlameDecorations.highlight, highlightDecorationRanges);
|
||||
}
|
||||
|
||||
private _getCompactGutterDecorations(blame: IGitBlame): DecorationOptions[] {
|
||||
const offset = this.uri.offset;
|
||||
|
||||
let count = 0;
|
||||
let lastSha: string;
|
||||
return blame.lines.map(l => {
|
||||
const commit = blame.commits.get(l.sha);
|
||||
if (commit === undefined) throw new Error(`Cannot find sha ${l.sha}`);
|
||||
|
||||
let color: string;
|
||||
if (commit.isUncommitted) {
|
||||
color = 'rgba(0, 188, 242, 0.6)';
|
||||
}
|
||||
else {
|
||||
color = l.previousSha ? '#999999' : '#6b6b6b';
|
||||
}
|
||||
|
||||
let gutter = '';
|
||||
if (lastSha !== l.sha) {
|
||||
count = -1;
|
||||
}
|
||||
|
||||
const isEmptyOrWhitespace = this.document.lineAt(l.line).isEmptyOrWhitespace;
|
||||
if (!isEmptyOrWhitespace) {
|
||||
switch (++count) {
|
||||
case 0:
|
||||
gutter = commit.shortSha;
|
||||
break;
|
||||
case 1:
|
||||
gutter = `\u2759 ${BlameAnnotationFormatter.getAuthor(this._config, commit, defaultAuthorLength, true)}`;
|
||||
break;
|
||||
case 2:
|
||||
gutter = `\u2759 ${BlameAnnotationFormatter.getDate(this._config, commit, this._config.annotation.dateFormat || 'MM/DD/YYYY', true, true)}`;
|
||||
break;
|
||||
default:
|
||||
gutter = `\u2759`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const hoverMessage = BlameAnnotationFormatter.getAnnotationHover(this._config, l, commit);
|
||||
|
||||
lastSha = l.sha;
|
||||
|
||||
return {
|
||||
range: this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)),
|
||||
hoverMessage: hoverMessage,
|
||||
renderOptions: {
|
||||
before: {
|
||||
color: color,
|
||||
contentText: gutter,
|
||||
width: '11em'
|
||||
}
|
||||
}
|
||||
} as DecorationOptions;
|
||||
});
|
||||
}
|
||||
|
||||
private _getExpandedGutterDecorations(blame: IGitBlame, trailing: boolean = false): DecorationOptions[] {
|
||||
const offset = this.uri.offset;
|
||||
|
||||
let width = 0;
|
||||
if (!trailing) {
|
||||
if (this._config.annotation.sha) {
|
||||
width += 5;
|
||||
}
|
||||
if (this._config.annotation.date && this._config.annotation.date !== 'off') {
|
||||
if (width > 0) {
|
||||
width += 7;
|
||||
}
|
||||
else {
|
||||
width += 6;
|
||||
}
|
||||
|
||||
if (this._config.annotation.date === 'relative') {
|
||||
width += 2;
|
||||
}
|
||||
}
|
||||
if (this._config.annotation.author) {
|
||||
if (width > 5 + 6) {
|
||||
width += 12;
|
||||
}
|
||||
else if (width > 0) {
|
||||
width += 11;
|
||||
}
|
||||
else {
|
||||
width += 10;
|
||||
}
|
||||
}
|
||||
if (this._config.annotation.message) {
|
||||
if (width > 5 + 6 + 10) {
|
||||
width += 21;
|
||||
}
|
||||
else if (width > 5 + 6) {
|
||||
width += 21;
|
||||
}
|
||||
else if (width > 0) {
|
||||
width += 21;
|
||||
}
|
||||
else {
|
||||
width += 19;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blame.lines.map(l => {
|
||||
const commit = blame.commits.get(l.sha);
|
||||
if (commit === undefined) throw new Error(`Cannot find sha ${l.sha}`);
|
||||
|
||||
let color: string;
|
||||
if (commit.isUncommitted) {
|
||||
color = 'rgba(0, 188, 242, 0.6)';
|
||||
}
|
||||
else {
|
||||
if (trailing) {
|
||||
color = l.previousSha ? 'rgba(153, 153, 153, 0.5)' : 'rgba(107, 107, 107, 0.5)';
|
||||
}
|
||||
else {
|
||||
color = l.previousSha ? 'rgb(153, 153, 153)' : 'rgb(107, 107, 107)';
|
||||
}
|
||||
}
|
||||
|
||||
const format = trailing ? BlameAnnotationFormat.Unconstrained : BlameAnnotationFormat.Constrained;
|
||||
const gutter = BlameAnnotationFormatter.getAnnotation(this._config, commit, format);
|
||||
const hoverMessage = BlameAnnotationFormatter.getAnnotationHover(this._config, l, commit);
|
||||
|
||||
let renderOptions: DecorationInstanceRenderOptions;
|
||||
if (trailing) {
|
||||
renderOptions = {
|
||||
after: {
|
||||
color: color,
|
||||
contentText: gutter
|
||||
}
|
||||
} as DecorationInstanceRenderOptions;
|
||||
}
|
||||
else {
|
||||
renderOptions = {
|
||||
before: {
|
||||
color: color,
|
||||
contentText: gutter,
|
||||
width: `${width}em`
|
||||
}
|
||||
} as DecorationInstanceRenderOptions;
|
||||
}
|
||||
|
||||
return {
|
||||
range: this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)),
|
||||
hoverMessage: hoverMessage,
|
||||
renderOptions: renderOptions
|
||||
} as DecorationOptions;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -19,10 +19,11 @@ export * from './commands/openCommitInRemote';
|
||||
export * from './commands/openFileInRemote';
|
||||
export * from './commands/openInRemote';
|
||||
export * from './commands/openRepoInRemote';
|
||||
export * from './commands/showBlame';
|
||||
export * from './commands/showBlameHistory';
|
||||
export * from './commands/showFileBlame';
|
||||
export * from './commands/showFileHistory';
|
||||
export * from './commands/showLastQuickPick';
|
||||
export * from './commands/showLineBlame';
|
||||
export * from './commands/showQuickCommitDetails';
|
||||
export * from './commands/showQuickCommitFileDetails';
|
||||
export * from './commands/showCommitSearch';
|
||||
@@ -34,5 +35,6 @@ export * from './commands/showQuickStashList';
|
||||
export * from './commands/stashApply';
|
||||
export * from './commands/stashDelete';
|
||||
export * from './commands/stashSave';
|
||||
export * from './commands/toggleBlame';
|
||||
export * from './commands/toggleCodeLens';
|
||||
export * from './commands/toggleCodeLens';
|
||||
export * from './commands/toggleFileBlame';
|
||||
export * from './commands/toggleLineBlame';
|
||||
@@ -7,13 +7,13 @@ import { Telemetry } from '../telemetry';
|
||||
export type Commands = 'gitlens.closeUnchangedFiles' | 'gitlens.copyMessageToClipboard' | 'gitlens.copyShaToClipboard' |
|
||||
'gitlens.diffDirectory' | 'gitlens.diffWithBranch' | 'gitlens.diffWithNext' | 'gitlens.diffWithPrevious' | 'gitlens.diffLineWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.diffLineWithWorking' |
|
||||
'gitlens.openChangedFiles' | 'gitlens.openBranchInRemote' | 'gitlens.openCommitInRemote' | 'gitlens.openFileInRemote' | 'gitlens.openInRemote' | 'gitlens.openRepoInRemote' |
|
||||
'gitlens.showBlame' | 'gitlens.showBlameHistory' | 'gitlens.showCommitSearch' | 'gitlens.showFileHistory' |
|
||||
'gitlens.showLastQuickPick' | 'gitlens.showQuickBranchHistory' |
|
||||
'gitlens.showBlameHistory' | 'gitlens.showCommitSearch' | 'gitlens.showFileBlame' | 'gitlens.showFileHistory' |
|
||||
'gitlens.showLastQuickPick' | 'gitlens.showLineBlame' | 'gitlens.showQuickBranchHistory' |
|
||||
'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' |
|
||||
'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory' |
|
||||
'gitlens.showQuickRepoStatus' | 'gitlens.showQuickStashList' |
|
||||
'gitlens.stashApply' | 'gitlens.stashDelete' | 'gitlens.stashSave' |
|
||||
'gitlens.toggleBlame' | 'gitlens.toggleCodeLens';
|
||||
'gitlens.toggleCodeLens' | 'gitlens.toggleFileBlame' | 'gitlens.toggleLineBlame';
|
||||
export const Commands = {
|
||||
CloseUnchangedFiles: 'gitlens.closeUnchangedFiles' as Commands,
|
||||
CopyMessageToClipboard: 'gitlens.copyMessageToClipboard' as Commands,
|
||||
@@ -31,7 +31,8 @@ export const Commands = {
|
||||
OpenFileInRemote: 'gitlens.openFileInRemote' as Commands,
|
||||
OpenInRemote: 'gitlens.openInRemote' as Commands,
|
||||
OpenRepoInRemote: 'gitlens.openRepoInRemote' as Commands,
|
||||
ShowBlame: 'gitlens.showBlame' as Commands,
|
||||
ShowFileBlame: 'gitlens.showFileBlame' as Commands,
|
||||
ShowLineBlame: 'gitlens.showLineBlame' as Commands,
|
||||
ShowBlameHistory: 'gitlens.showBlameHistory' as Commands,
|
||||
ShowCommitSearch: 'gitlens.showCommitSearch' as Commands,
|
||||
ShowFileHistory: 'gitlens.showFileHistory' as Commands,
|
||||
@@ -46,7 +47,8 @@ export const Commands = {
|
||||
StashApply: 'gitlens.stashApply' as Commands,
|
||||
StashDelete: 'gitlens.stashDelete' as Commands,
|
||||
StashSave: 'gitlens.stashSave' as Commands,
|
||||
ToggleBlame: 'gitlens.toggleBlame' as Commands,
|
||||
ToggleFileBlame: 'gitlens.toggleFileBlame' as Commands,
|
||||
ToggleLineBlame: 'gitlens.toggleLineBlame' as Commands,
|
||||
ToggleCodeLens: 'gitlens.toggleCodeLens' as Commands
|
||||
};
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
'use strict';
|
||||
import { TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||
import { BlameAnnotationController } from '../blameAnnotationController';
|
||||
import { Commands, EditorCommand } from './common';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export interface ShowBlameCommandArgs {
|
||||
sha?: string;
|
||||
}
|
||||
|
||||
export class ShowBlameCommand extends EditorCommand {
|
||||
|
||||
constructor(private annotationController: BlameAnnotationController) {
|
||||
super(Commands.ShowBlame);
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowBlameCommandArgs = {}): Promise<any> {
|
||||
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
|
||||
|
||||
try {
|
||||
return this.annotationController.showBlameAnnotation(editor, args.sha !== undefined ? args.sha : editor.selection.active.line);
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error(ex, 'ShowBlameCommand');
|
||||
return window.showErrorMessage(`Unable to show blame annotations. See output channel for more details`);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/commands/showFileBlame.ts
Normal file
35
src/commands/showFileBlame.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
import { TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
|
||||
import { AnnotationController } from '../annotations/annotationController';
|
||||
import { Commands, EditorCommand } from './common';
|
||||
import { ExtensionKey, FileAnnotationType, IConfig } from '../configuration';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export interface ShowFileBlameCommandArgs {
|
||||
sha?: string;
|
||||
type?: FileAnnotationType;
|
||||
}
|
||||
|
||||
export class ShowFileBlameCommand extends EditorCommand {
|
||||
|
||||
constructor(private annotationController: AnnotationController) {
|
||||
super(Commands.ShowFileBlame);
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowFileBlameCommandArgs = {}): Promise<any> {
|
||||
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
|
||||
|
||||
try {
|
||||
if (args.type === undefined) {
|
||||
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
|
||||
args.type = cfg.blame.file.annotationType;
|
||||
}
|
||||
|
||||
return this.annotationController.showAnnotations(editor, args.type, args.sha !== undefined ? args.sha : editor.selection.active.line);
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error(ex, 'ShowFileBlameCommand');
|
||||
return window.showErrorMessage(`Unable to show file blame annotations. See output channel for more details`);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/commands/showLineBlame.ts
Normal file
34
src/commands/showLineBlame.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
import { TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
|
||||
import { CurrentLineController } from '../currentLineController';
|
||||
import { Commands, EditorCommand } from './common';
|
||||
import { ExtensionKey, IConfig, LineAnnotationType } from '../configuration';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export interface ShowLineBlameCommandArgs {
|
||||
type?: LineAnnotationType;
|
||||
}
|
||||
|
||||
export class ShowLineBlameCommand extends EditorCommand {
|
||||
|
||||
constructor(private currentLineController: CurrentLineController) {
|
||||
super(Commands.ShowLineBlame);
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowLineBlameCommandArgs = {}): Promise<any> {
|
||||
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
|
||||
|
||||
try {
|
||||
if (args.type === undefined) {
|
||||
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
|
||||
args.type = cfg.blame.line.annotationType;
|
||||
}
|
||||
|
||||
return this.currentLineController.showAnnotations(editor, args.type);
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error(ex, 'ShowLineBlameCommand');
|
||||
return window.showErrorMessage(`Unable to show line blame annotations. See output channel for more details`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
'use strict';
|
||||
import { TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||
import { BlameAnnotationController } from '../blameAnnotationController';
|
||||
import { Commands, EditorCommand } from './common';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export interface ToggleBlameCommandArgs {
|
||||
sha?: string;
|
||||
}
|
||||
|
||||
export class ToggleBlameCommand extends EditorCommand {
|
||||
|
||||
constructor(private annotationController: BlameAnnotationController) {
|
||||
super(Commands.ToggleBlame);
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ToggleBlameCommandArgs = {}): Promise<any> {
|
||||
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
|
||||
|
||||
try {
|
||||
return this.annotationController.toggleBlameAnnotation(editor, args.sha !== undefined ? args.sha : editor.selection.active.line);
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error(ex, 'ToggleBlameCommand');
|
||||
return window.showErrorMessage(`Unable to show blame annotations. See output channel for more details`);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/commands/toggleFileBlame.ts
Normal file
35
src/commands/toggleFileBlame.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
import { TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
|
||||
import { AnnotationController } from '../annotations/annotationController';
|
||||
import { Commands, EditorCommand } from './common';
|
||||
import { ExtensionKey, FileAnnotationType, IConfig } from '../configuration';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export interface ToggleFileBlameCommandArgs {
|
||||
sha?: string;
|
||||
type?: FileAnnotationType;
|
||||
}
|
||||
|
||||
export class ToggleFileBlameCommand extends EditorCommand {
|
||||
|
||||
constructor(private annotationController: AnnotationController) {
|
||||
super(Commands.ToggleFileBlame);
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ToggleFileBlameCommandArgs = {}): Promise<any> {
|
||||
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
|
||||
|
||||
try {
|
||||
if (args.type === undefined) {
|
||||
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
|
||||
args.type = cfg.blame.file.annotationType;
|
||||
}
|
||||
|
||||
return this.annotationController.toggleAnnotations(editor, args.type, args.sha !== undefined ? args.sha : editor.selection.active.line);
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error(ex, 'ToggleFileBlameCommand');
|
||||
return window.showErrorMessage(`Unable to toggle file blame annotations. See output channel for more details`);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/commands/toggleLineBlame.ts
Normal file
34
src/commands/toggleLineBlame.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
import { TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
|
||||
import { CurrentLineController } from '../currentLineController';
|
||||
import { Commands, EditorCommand } from './common';
|
||||
import { ExtensionKey, IConfig, LineAnnotationType } from '../configuration';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export interface ToggleLineBlameCommandArgs {
|
||||
type?: LineAnnotationType;
|
||||
}
|
||||
|
||||
export class ToggleLineBlameCommand extends EditorCommand {
|
||||
|
||||
constructor(private currentLineController: CurrentLineController) {
|
||||
super(Commands.ToggleLineBlame);
|
||||
}
|
||||
|
||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ToggleLineBlameCommandArgs = {}): Promise<any> {
|
||||
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
|
||||
|
||||
try {
|
||||
if (args.type === undefined) {
|
||||
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
|
||||
args.type = cfg.blame.line.annotationType;
|
||||
}
|
||||
|
||||
return this.currentLineController.toggleAnnotations(editor, args.type);
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error(ex, 'ToggleLineBlameCommand');
|
||||
return window.showErrorMessage(`Unable to toggle line blame annotations. See output channel for more details`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,31 +2,18 @@
|
||||
import { Commands } from './commands';
|
||||
import { OutputLevel } from './logger';
|
||||
|
||||
export type BlameAnnotationStyle = 'compact' | 'expanded' | 'trailing';
|
||||
export const BlameAnnotationStyle = {
|
||||
Compact: 'compact' as BlameAnnotationStyle,
|
||||
Expanded: 'expanded' as BlameAnnotationStyle,
|
||||
Trailing: 'trailing' as BlameAnnotationStyle
|
||||
export { ExtensionKey } from './constants';
|
||||
|
||||
export type BlameLineHighlightLocations = 'gutter' | 'line' | 'overviewRuler';
|
||||
export const BlameLineHighlightLocations = {
|
||||
Gutter: 'gutter' as BlameLineHighlightLocations,
|
||||
Line: 'line' as BlameLineHighlightLocations,
|
||||
OverviewRuler: 'overviewRuler' as BlameLineHighlightLocations
|
||||
};
|
||||
|
||||
export interface IBlameConfig {
|
||||
annotation: {
|
||||
style: BlameAnnotationStyle;
|
||||
highlight: 'none' | 'gutter' | 'line' | 'both';
|
||||
sha: boolean;
|
||||
author: boolean;
|
||||
date: 'off' | 'relative' | 'absolute';
|
||||
dateFormat: string;
|
||||
message: boolean;
|
||||
activeLine: 'off' | 'inline' | 'hover' | 'both';
|
||||
activeLineDarkColor: string;
|
||||
activeLineLightColor: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type CodeLensCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.diffWithPrevious' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory';
|
||||
export type CodeLensCommand = 'gitlens.toggleFileBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.diffWithPrevious' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory';
|
||||
export const CodeLensCommand = {
|
||||
BlameAnnotate: Commands.ToggleBlame as CodeLensCommand,
|
||||
BlameAnnotate: Commands.ToggleFileBlame as CodeLensCommand,
|
||||
ShowBlameHistory: Commands.ShowBlameHistory as CodeLensCommand,
|
||||
ShowFileHistory: Commands.ShowFileHistory as CodeLensCommand,
|
||||
DiffWithPrevious: Commands.DiffWithPrevious as CodeLensCommand,
|
||||
@@ -36,46 +23,29 @@ export const CodeLensCommand = {
|
||||
ShowQuickCurrentBranchHistory: Commands.ShowQuickCurrentBranchHistory as CodeLensCommand
|
||||
};
|
||||
|
||||
export type CodeLensLocation = 'all' | 'document+containers' | 'document' | 'custom' | 'none';
|
||||
export const CodeLensLocation = {
|
||||
All: 'all' as CodeLensLocation,
|
||||
DocumentAndContainers: 'document+containers' as CodeLensLocation,
|
||||
Document: 'document' as CodeLensLocation,
|
||||
Custom: 'custom' as CodeLensLocation,
|
||||
None: 'none' as CodeLensLocation
|
||||
export type CodeLensLocations = 'document' | 'containers' | 'blocks' | 'custom';
|
||||
export const CodeLensLocations = {
|
||||
Document: 'document' as CodeLensLocations,
|
||||
Containers: 'containers' as CodeLensLocations,
|
||||
Blocks: 'blocks' as CodeLensLocations,
|
||||
Custom: 'custom' as CodeLensLocations
|
||||
};
|
||||
|
||||
export type CodeLensVisibility = 'auto' | 'ondemand' | 'off';
|
||||
export const CodeLensVisibility = {
|
||||
Auto: 'auto' as CodeLensVisibility,
|
||||
OnDemand: 'ondemand' as CodeLensVisibility,
|
||||
Off: 'off' as CodeLensVisibility
|
||||
export type FileAnnotationType = 'gutter' | 'hover';
|
||||
export const FileAnnotationType = {
|
||||
Gutter: 'gutter' as FileAnnotationType,
|
||||
Hover: 'hover' as FileAnnotationType
|
||||
};
|
||||
|
||||
export interface ICodeLensConfig {
|
||||
enabled: boolean;
|
||||
command: CodeLensCommand;
|
||||
}
|
||||
export type LineAnnotationType = 'trailing' | 'hover';
|
||||
export const LineAnnotationType = {
|
||||
Trailing: 'trailing' as LineAnnotationType,
|
||||
Hover: 'hover' as LineAnnotationType
|
||||
};
|
||||
|
||||
export interface ICodeLensLanguageLocation {
|
||||
language: string | undefined;
|
||||
location: CodeLensLocation;
|
||||
customSymbols?: string[];
|
||||
}
|
||||
|
||||
export interface ICodeLensesConfig {
|
||||
debug: boolean;
|
||||
visibility: CodeLensVisibility;
|
||||
location: CodeLensLocation;
|
||||
locationCustomSymbols: string[];
|
||||
languageLocations: ICodeLensLanguageLocation[];
|
||||
recentChange: ICodeLensConfig;
|
||||
authors: ICodeLensConfig;
|
||||
}
|
||||
|
||||
export type StatusBarCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.toggleCodeLens' | 'gitlens.diffWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory';
|
||||
export type StatusBarCommand = 'gitlens.toggleFileBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.toggleCodeLens' | 'gitlens.diffWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory';
|
||||
export const StatusBarCommand = {
|
||||
BlameAnnotate: Commands.ToggleBlame as StatusBarCommand,
|
||||
BlameAnnotate: Commands.ToggleFileBlame as StatusBarCommand,
|
||||
ShowBlameHistory: Commands.ShowBlameHistory as StatusBarCommand,
|
||||
ShowFileHistory: Commands.ShowFileHistory as CodeLensCommand,
|
||||
DiffWithPrevious: Commands.DiffWithPrevious as StatusBarCommand,
|
||||
@@ -87,26 +57,44 @@ export const StatusBarCommand = {
|
||||
ShowQuickCurrentBranchHistory: Commands.ShowQuickCurrentBranchHistory as StatusBarCommand
|
||||
};
|
||||
|
||||
export interface IStatusBarConfig {
|
||||
enabled: boolean;
|
||||
command: StatusBarCommand;
|
||||
date: 'off' | 'relative' | 'absolute';
|
||||
dateFormat: string;
|
||||
alignment: 'left' | 'right';
|
||||
}
|
||||
|
||||
export interface IAdvancedConfig {
|
||||
caching: {
|
||||
enabled: boolean;
|
||||
statusBar: {
|
||||
maxLines: number;
|
||||
}
|
||||
maxLines: number;
|
||||
};
|
||||
git: string;
|
||||
gitignore: {
|
||||
enabled: boolean;
|
||||
};
|
||||
maxQuickHistory: 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;
|
||||
};
|
||||
};
|
||||
quickPick: {
|
||||
closeOnFocusOut: boolean;
|
||||
};
|
||||
@@ -115,12 +103,192 @@ export interface IAdvancedConfig {
|
||||
};
|
||||
}
|
||||
|
||||
export interface ICodeLensLanguageLocation {
|
||||
language: string | undefined;
|
||||
locations: CodeLensLocations[];
|
||||
customSymbols?: string[];
|
||||
}
|
||||
|
||||
export interface IThemeConfig {
|
||||
annotations: {
|
||||
file: {
|
||||
gutter: {
|
||||
separateLines: boolean;
|
||||
dark: {
|
||||
backgroundColor: string | null;
|
||||
foregroundColor: string;
|
||||
uncommittedForegroundColor: string | null;
|
||||
};
|
||||
light: {
|
||||
backgroundColor: string | null;
|
||||
foregroundColor: string;
|
||||
uncommittedForegroundColor: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
hover: {
|
||||
separateLines: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
line: {
|
||||
trailing: {
|
||||
dark: {
|
||||
backgroundColor: string | null;
|
||||
foregroundColor: string;
|
||||
};
|
||||
light: {
|
||||
backgroundColor: string | null;
|
||||
foregroundColor: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
lineHighlight: {
|
||||
dark: {
|
||||
backgroundColor: string;
|
||||
overviewRulerColor: string;
|
||||
};
|
||||
light: {
|
||||
backgroundColor: string;
|
||||
overviewRulerColor: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const themeDefaults: IThemeConfig = {
|
||||
annotations: {
|
||||
file: {
|
||||
gutter: {
|
||||
separateLines: true,
|
||||
dark: {
|
||||
backgroundColor: null,
|
||||
foregroundColor: 'rgb(190, 190, 190)',
|
||||
uncommittedForegroundColor: null
|
||||
},
|
||||
light: {
|
||||
backgroundColor: null,
|
||||
foregroundColor: 'rgb(116, 116, 116)',
|
||||
uncommittedForegroundColor: null
|
||||
}
|
||||
},
|
||||
hover: {
|
||||
separateLines: false
|
||||
}
|
||||
},
|
||||
line: {
|
||||
trailing: {
|
||||
dark: {
|
||||
backgroundColor: null,
|
||||
foregroundColor: 'rgba(153, 153, 153, 0.35)'
|
||||
},
|
||||
light: {
|
||||
backgroundColor: null,
|
||||
foregroundColor: 'rgba(153, 153, 153, 0.35)'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
lineHighlight: {
|
||||
dark: {
|
||||
backgroundColor: 'rgba(0, 188, 242, 0.2)',
|
||||
overviewRulerColor: 'rgba(0, 188, 242, 0.6)'
|
||||
},
|
||||
light: {
|
||||
backgroundColor: 'rgba(0, 188, 242, 0.2)',
|
||||
overviewRulerColor: 'rgba(0, 188, 242, 0.6)'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export interface IConfig {
|
||||
annotations: {
|
||||
file: {
|
||||
gutter: {
|
||||
format: string;
|
||||
dateFormat: string;
|
||||
compact: boolean;
|
||||
heatmap: {
|
||||
enabled: boolean;
|
||||
location: 'left' | 'right';
|
||||
};
|
||||
hover: {
|
||||
details: boolean;
|
||||
wholeLine: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
hover: {
|
||||
heatmap: {
|
||||
enabled: boolean;
|
||||
};
|
||||
wholeLine: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
line: {
|
||||
hover: {
|
||||
details: boolean;
|
||||
changes: boolean;
|
||||
};
|
||||
|
||||
trailing: {
|
||||
format: string;
|
||||
dateFormat: string;
|
||||
hover: {
|
||||
changes: boolean;
|
||||
details: boolean;
|
||||
wholeLine: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
blame: {
|
||||
file: {
|
||||
annotationType: FileAnnotationType;
|
||||
lineHighlight: {
|
||||
enabled: boolean;
|
||||
locations: BlameLineHighlightLocations[];
|
||||
};
|
||||
};
|
||||
|
||||
line: {
|
||||
enabled: boolean;
|
||||
annotationType: LineAnnotationType;
|
||||
};
|
||||
};
|
||||
|
||||
codeLens: {
|
||||
enabled: boolean;
|
||||
recentChange: {
|
||||
enabled: boolean;
|
||||
command: CodeLensCommand;
|
||||
};
|
||||
authors: {
|
||||
enabled: boolean;
|
||||
command: CodeLensCommand;
|
||||
};
|
||||
locations: CodeLensLocations[];
|
||||
customLocationSymbols: string[];
|
||||
perLanguageLocations: ICodeLensLanguageLocation[];
|
||||
debug: boolean;
|
||||
};
|
||||
|
||||
statusBar: {
|
||||
enabled: boolean;
|
||||
alignment: 'left' | 'right';
|
||||
command: StatusBarCommand;
|
||||
format: string;
|
||||
dateFormat: string;
|
||||
};
|
||||
|
||||
theme: IThemeConfig;
|
||||
|
||||
debug: boolean;
|
||||
outputLevel: OutputLevel;
|
||||
blame: IBlameConfig;
|
||||
codeLens: ICodeLensesConfig;
|
||||
statusBar: IStatusBarConfig;
|
||||
advanced: IAdvancedConfig;
|
||||
insiders: boolean;
|
||||
outputLevel: OutputLevel;
|
||||
|
||||
advanced: IAdvancedConfig;
|
||||
}
|
||||
437
src/currentLineController.ts
Normal file
437
src/currentLineController.ts
Normal file
@@ -0,0 +1,437 @@
|
||||
'use strict';
|
||||
import { Functions, Objects } from './system';
|
||||
import { DecorationOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
||||
import { AnnotationController } from './annotations/annotationController';
|
||||
import { Annotations, endOfLineIndex } from './annotations/annotations';
|
||||
import { Commands } from './commands';
|
||||
import { TextEditorComparer } from './comparers';
|
||||
import { FileAnnotationType, IConfig, LineAnnotationType, StatusBarCommand } from './configuration';
|
||||
import { DocumentSchemes, ExtensionKey } from './constants';
|
||||
import { BlameabilityChangeEvent, CommitFormatter, GitCommit, GitContextTracker, GitService, GitUri, IGitCommitLine } from './gitService';
|
||||
|
||||
const annotationDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
|
||||
after: {
|
||||
margin: '0 0 0 4em'
|
||||
}
|
||||
} as DecorationRenderOptions);
|
||||
|
||||
export class CurrentLineController extends Disposable {
|
||||
|
||||
private _activeEditorLineDisposable: Disposable | undefined;
|
||||
private _blameable: boolean;
|
||||
private _config: IConfig;
|
||||
private _currentLine: number = -1;
|
||||
private _disposable: Disposable;
|
||||
private _editor: TextEditor | undefined;
|
||||
private _statusBarItem: StatusBarItem | undefined;
|
||||
private _updateBlameDebounced: (line: number, editor: TextEditor) => Promise<void>;
|
||||
private _uri: GitUri;
|
||||
|
||||
constructor(context: ExtensionContext, private git: GitService, private gitContextTracker: GitContextTracker, private annotationController: AnnotationController) {
|
||||
super(() => this.dispose());
|
||||
|
||||
this._updateBlameDebounced = Functions.debounce(this._updateBlame, 250);
|
||||
|
||||
this._onConfigurationChanged();
|
||||
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
|
||||
subscriptions.push(git.onDidChangeGitCache(this._onGitCacheChanged, this));
|
||||
subscriptions.push(annotationController.onDidToggleAnnotations(this._onAnnotationsToggled, this));
|
||||
|
||||
this._disposable = Disposable.from(...subscriptions);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._editor && this._editor.setDecorations(annotationDecoration, []);
|
||||
|
||||
this._activeEditorLineDisposable && this._activeEditorLineDisposable.dispose();
|
||||
this._statusBarItem && this._statusBarItem.dispose();
|
||||
this._disposable && this._disposable.dispose();
|
||||
}
|
||||
|
||||
private _onConfigurationChanged() {
|
||||
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
|
||||
|
||||
let changed = false;
|
||||
|
||||
if (!Objects.areEquivalent(cfg.blame.line, this._config && this._config.blame.line) ||
|
||||
!Objects.areEquivalent(cfg.annotations.line.trailing, this._config && this._config.annotations.line.trailing) ||
|
||||
!Objects.areEquivalent(cfg.annotations.line.hover, this._config && this._config.annotations.line.hover) ||
|
||||
!Objects.areEquivalent(cfg.theme.annotations.line.trailing, this._config && this._config.theme.annotations.line.trailing)) {
|
||||
changed = true;
|
||||
if (this._editor) {
|
||||
this._editor.setDecorations(annotationDecoration, []);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Objects.areEquivalent(cfg.statusBar, this._config && this._config.statusBar)) {
|
||||
changed = true;
|
||||
if (cfg.statusBar.enabled) {
|
||||
const alignment = cfg.statusBar.alignment !== 'left' ? StatusBarAlignment.Right : StatusBarAlignment.Left;
|
||||
if (this._statusBarItem !== undefined && this._statusBarItem.alignment !== alignment) {
|
||||
this._statusBarItem.dispose();
|
||||
this._statusBarItem = undefined;
|
||||
}
|
||||
|
||||
this._statusBarItem = this._statusBarItem || window.createStatusBarItem(alignment, alignment === StatusBarAlignment.Right ? 1000 : 0);
|
||||
this._statusBarItem.command = cfg.statusBar.command;
|
||||
}
|
||||
else if (!cfg.statusBar.enabled && this._statusBarItem) {
|
||||
this._statusBarItem.dispose();
|
||||
this._statusBarItem = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
this._config = cfg;
|
||||
|
||||
if (!changed) return;
|
||||
|
||||
const trackCurrentLine = cfg.statusBar.enabled || cfg.blame.line.enabled;
|
||||
if (trackCurrentLine && !this._activeEditorLineDisposable) {
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
|
||||
subscriptions.push(window.onDidChangeTextEditorSelection(this._onTextEditorSelectionChanged, this));
|
||||
subscriptions.push(this.gitContextTracker.onDidBlameabilityChange(this._onBlameabilityChanged, this));
|
||||
|
||||
this._activeEditorLineDisposable = Disposable.from(...subscriptions);
|
||||
}
|
||||
else if (!trackCurrentLine && this._activeEditorLineDisposable) {
|
||||
this._activeEditorLineDisposable.dispose();
|
||||
this._activeEditorLineDisposable = undefined;
|
||||
}
|
||||
|
||||
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||
}
|
||||
|
||||
private isEditorBlameable(editor: TextEditor | undefined): boolean {
|
||||
if (editor === undefined || editor.document === undefined) return false;
|
||||
|
||||
if (!this.git.isTrackable(editor.document.uri)) return false;
|
||||
if (editor.document.isUntitled && editor.document.uri.scheme === DocumentSchemes.File) return false;
|
||||
|
||||
return this.git.isEditorBlameable(editor);
|
||||
}
|
||||
|
||||
private async _onActiveTextEditorChanged(editor: TextEditor | undefined) {
|
||||
this._currentLine = -1;
|
||||
|
||||
const previousEditor = this._editor;
|
||||
previousEditor && previousEditor.setDecorations(annotationDecoration, []);
|
||||
|
||||
if (editor === undefined || !this.isEditorBlameable(editor)) {
|
||||
this.clear(editor);
|
||||
|
||||
this._editor = undefined;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._blameable = editor !== undefined && editor.document !== undefined && !editor.document.isDirty;
|
||||
this._editor = editor;
|
||||
this._uri = await GitUri.fromUri(editor.document.uri, this.git);
|
||||
|
||||
const maxLines = this._config.advanced.caching.maxLines;
|
||||
// If caching is on and the file is small enough -- kick off a blame for the whole file
|
||||
if (this._config.advanced.caching.enabled && (maxLines <= 0 || editor.document.lineCount <= maxLines)) {
|
||||
this.git.getBlameForFile(this._uri);
|
||||
}
|
||||
|
||||
this._updateBlameDebounced(editor.selection.active.line, editor);
|
||||
}
|
||||
|
||||
private _onBlameabilityChanged(e: BlameabilityChangeEvent) {
|
||||
this._blameable = e.blameable;
|
||||
if (!e.blameable || !this._editor) {
|
||||
this.clear(e.editor);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure this is for the editor we are tracking
|
||||
if (!TextEditorComparer.equals(this._editor, e.editor)) return;
|
||||
|
||||
this._updateBlameDebounced(this._editor.selection.active.line, this._editor);
|
||||
}
|
||||
|
||||
private _onAnnotationsToggled() {
|
||||
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||
}
|
||||
|
||||
private _onGitCacheChanged() {
|
||||
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||
}
|
||||
|
||||
private async _onTextEditorSelectionChanged(e: TextEditorSelectionChangeEvent): Promise<void> {
|
||||
// Make sure this is for the editor we are tracking
|
||||
if (!this._blameable || !TextEditorComparer.equals(this._editor, e.textEditor)) return;
|
||||
|
||||
const line = e.selections[0].active.line;
|
||||
if (line === this._currentLine) return;
|
||||
this._currentLine = line;
|
||||
|
||||
if (!this._uri && e.textEditor) {
|
||||
this._uri = await GitUri.fromUri(e.textEditor.document.uri, this.git);
|
||||
}
|
||||
|
||||
this._updateBlameDebounced(line, e.textEditor);
|
||||
}
|
||||
|
||||
private async _updateBlame(line: number, editor: TextEditor) {
|
||||
line = line - this._uri.offset;
|
||||
|
||||
let commit: GitCommit | undefined = undefined;
|
||||
let commitLine: IGitCommitLine | undefined = undefined;
|
||||
// Since blame information isn't valid when there are unsaved changes -- don't show any status
|
||||
if (this._blameable && line >= 0) {
|
||||
const blameLine = await this.git.getBlameForLine(this._uri, line);
|
||||
commitLine = blameLine === undefined ? undefined : blameLine.line;
|
||||
commit = blameLine === undefined ? undefined : blameLine.commit;
|
||||
}
|
||||
|
||||
if (commit !== undefined && commitLine !== undefined) {
|
||||
this.show(commit, commitLine, editor);
|
||||
}
|
||||
else {
|
||||
this.clear(editor);
|
||||
}
|
||||
}
|
||||
|
||||
async clear(editor: TextEditor | undefined, previousEditor?: TextEditor) {
|
||||
this._clearAnnotations(editor, previousEditor);
|
||||
this._statusBarItem && this._statusBarItem.hide();
|
||||
}
|
||||
|
||||
private async _clearAnnotations(editor: TextEditor | undefined, previousEditor?: TextEditor) {
|
||||
editor && editor.setDecorations(annotationDecoration, []);
|
||||
// I have no idea why the decorators sometimes don't get removed, but if they don't try again with a tiny delay
|
||||
if (editor !== undefined) {
|
||||
await Functions.wait(1);
|
||||
editor.setDecorations(annotationDecoration, []);
|
||||
}
|
||||
}
|
||||
|
||||
async show(commit: GitCommit, blameLine: IGitCommitLine, editor: TextEditor) {
|
||||
// I have no idea why I need this protection -- but it happens
|
||||
if (editor.document === undefined) return;
|
||||
|
||||
this._updateStatusBar(commit);
|
||||
await this._updateAnnotations(commit, blameLine, editor);
|
||||
}
|
||||
|
||||
async showAnnotations(editor: TextEditor, type: LineAnnotationType) {
|
||||
if (editor === undefined) return;
|
||||
|
||||
const cfg = this._config.blame.line;
|
||||
if (!cfg.enabled || cfg.annotationType !== type) {
|
||||
cfg.enabled = true;
|
||||
cfg.annotationType = type;
|
||||
|
||||
await this._clearAnnotations(editor);
|
||||
await this._updateBlame(editor.selection.active.line, editor);
|
||||
}
|
||||
}
|
||||
|
||||
async toggleAnnotations(editor: TextEditor, type: LineAnnotationType) {
|
||||
if (editor === undefined) return;
|
||||
|
||||
const cfg = this._config.blame.line;
|
||||
cfg.enabled = !cfg.enabled;
|
||||
cfg.annotationType = type;
|
||||
|
||||
await this._clearAnnotations(editor);
|
||||
await this._updateBlame(editor.selection.active.line, editor);
|
||||
}
|
||||
|
||||
private async _updateAnnotations(commit: GitCommit, blameLine: IGitCommitLine, editor: TextEditor) {
|
||||
const cfg = this._config.blame.line;
|
||||
if (!cfg.enabled) return;
|
||||
|
||||
const line = blameLine.line + this._uri.offset;
|
||||
|
||||
const decorationOptions: DecorationOptions[] = [];
|
||||
|
||||
let showChanges = false;
|
||||
let showChangesStartIndex = 0;
|
||||
let showChangesInStartingWhitespace = false;
|
||||
|
||||
let showDetails = false;
|
||||
let showDetailsStartIndex = 0;
|
||||
let showDetailsInStartingWhitespace = false;
|
||||
|
||||
switch (cfg.annotationType) {
|
||||
case LineAnnotationType.Trailing: {
|
||||
const cfgAnnotations = this._config.annotations.line.trailing;
|
||||
|
||||
showChanges = cfgAnnotations.hover.changes;
|
||||
showDetails = cfgAnnotations.hover.details;
|
||||
|
||||
if (cfgAnnotations.hover.wholeLine) {
|
||||
showChangesStartIndex = 0;
|
||||
showChangesInStartingWhitespace = false;
|
||||
|
||||
showDetailsStartIndex = 0;
|
||||
showDetailsInStartingWhitespace = false;
|
||||
}
|
||||
else {
|
||||
showChangesStartIndex = endOfLineIndex;
|
||||
showChangesInStartingWhitespace = true;
|
||||
|
||||
showDetailsStartIndex = endOfLineIndex;
|
||||
showDetailsInStartingWhitespace = true;
|
||||
}
|
||||
|
||||
const decoration = Annotations.trailing(commit, cfgAnnotations.format, cfgAnnotations.dateFormat, this._config.theme);
|
||||
decoration.range = editor.document.validateRange(new Range(line, endOfLineIndex, line, endOfLineIndex));
|
||||
decorationOptions.push(decoration);
|
||||
|
||||
break;
|
||||
}
|
||||
case LineAnnotationType.Hover: {
|
||||
const cfgAnnotations = this._config.annotations.line.hover;
|
||||
|
||||
showChanges = cfgAnnotations.changes;
|
||||
showChangesStartIndex = 0;
|
||||
showChangesInStartingWhitespace = false;
|
||||
|
||||
showDetails = cfgAnnotations.details;
|
||||
showDetailsStartIndex = 0;
|
||||
showDetailsInStartingWhitespace = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (showDetails || showChanges) {
|
||||
const annotationType = this.annotationController.getAnnotationType(editor);
|
||||
|
||||
const firstNonWhitespace = editor.document.lineAt(line).firstNonWhitespaceCharacterIndex;
|
||||
|
||||
switch (annotationType) {
|
||||
case FileAnnotationType.Gutter: {
|
||||
const cfgHover = this._config.annotations.file.gutter.hover;
|
||||
if (cfgHover.details) {
|
||||
showDetailsInStartingWhitespace = false;
|
||||
if (cfgHover.wholeLine) {
|
||||
// Avoid double annotations if we are showing the whole-file hover blame annotations
|
||||
showDetails = false;
|
||||
}
|
||||
else {
|
||||
if (showDetailsStartIndex === 0) {
|
||||
showDetailsStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
|
||||
}
|
||||
if (showChangesStartIndex === 0) {
|
||||
showChangesInStartingWhitespace = true;
|
||||
showChangesStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case FileAnnotationType.Hover: {
|
||||
const cfgHover = this._config.annotations.file.hover;
|
||||
showDetailsInStartingWhitespace = false;
|
||||
if (cfgHover.wholeLine) {
|
||||
// Avoid double annotations if we are showing the whole-file hover blame annotations
|
||||
showDetails = false;
|
||||
showChangesStartIndex = 0;
|
||||
}
|
||||
else {
|
||||
if (showDetailsStartIndex === 0) {
|
||||
showDetailsStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
|
||||
}
|
||||
if (showChangesStartIndex === 0) {
|
||||
showChangesInStartingWhitespace = true;
|
||||
showChangesStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (showDetails) {
|
||||
// Get the full commit message -- since blame only returns the summary
|
||||
let logCommit: GitCommit | undefined = undefined;
|
||||
if (!commit.isUncommitted) {
|
||||
logCommit = await this.git.getLogCommit(this._uri.repoPath, this._uri.fsPath, commit.sha);
|
||||
}
|
||||
|
||||
// I have no idea why I need this protection -- but it happens
|
||||
if (editor.document === undefined) return;
|
||||
|
||||
const decoration = Annotations.detailsHover(logCommit || commit);
|
||||
decoration.range = editor.document.validateRange(new Range(line, showDetailsStartIndex, line, endOfLineIndex));
|
||||
decorationOptions.push(decoration);
|
||||
|
||||
if (showDetailsInStartingWhitespace && showDetailsStartIndex !== 0) {
|
||||
decorationOptions.push(Annotations.withRange(decoration, 0, firstNonWhitespace));
|
||||
}
|
||||
}
|
||||
|
||||
if (showChanges) {
|
||||
const decoration = await Annotations.changesHover(commit, line, this._uri, this.git);
|
||||
|
||||
// I have no idea why I need this protection -- but it happens
|
||||
if (editor.document === undefined) return;
|
||||
|
||||
decoration.range = editor.document.validateRange(new Range(line, showChangesStartIndex, line, endOfLineIndex));
|
||||
decorationOptions.push(decoration);
|
||||
|
||||
if (showChangesInStartingWhitespace && showChangesStartIndex !== 0) {
|
||||
decorationOptions.push(Annotations.withRange(decoration, 0, firstNonWhitespace));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (decorationOptions.length) {
|
||||
editor.setDecorations(annotationDecoration, decorationOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private _updateStatusBar(commit: GitCommit) {
|
||||
const cfg = this._config.statusBar;
|
||||
if (!cfg.enabled || this._statusBarItem === undefined) return;
|
||||
|
||||
this._statusBarItem.text = `$(git-commit) ${CommitFormatter.fromTemplate(cfg.format, commit, cfg.dateFormat)}`;
|
||||
|
||||
switch (cfg.command) {
|
||||
case StatusBarCommand.BlameAnnotate:
|
||||
this._statusBarItem.tooltip = 'Toggle Blame Annotations';
|
||||
break;
|
||||
case StatusBarCommand.ShowBlameHistory:
|
||||
this._statusBarItem.tooltip = 'Open Blame History Explorer';
|
||||
break;
|
||||
case StatusBarCommand.ShowFileHistory:
|
||||
this._statusBarItem.tooltip = 'Open File History Explorer';
|
||||
break;
|
||||
case StatusBarCommand.DiffWithPrevious:
|
||||
this._statusBarItem.command = Commands.DiffLineWithPrevious;
|
||||
this._statusBarItem.tooltip = 'Compare Line Commit with Previous';
|
||||
break;
|
||||
case StatusBarCommand.DiffWithWorking:
|
||||
this._statusBarItem.command = Commands.DiffLineWithWorking;
|
||||
this._statusBarItem.tooltip = 'Compare Line Commit with Working Tree';
|
||||
break;
|
||||
case StatusBarCommand.ToggleCodeLens:
|
||||
this._statusBarItem.tooltip = 'Toggle Git CodeLens';
|
||||
break;
|
||||
case StatusBarCommand.ShowQuickCommitDetails:
|
||||
this._statusBarItem.tooltip = 'Show Commit Details';
|
||||
break;
|
||||
case StatusBarCommand.ShowQuickCommitFileDetails:
|
||||
this._statusBarItem.tooltip = 'Show Line Commit Details';
|
||||
break;
|
||||
case StatusBarCommand.ShowQuickFileHistory:
|
||||
this._statusBarItem.tooltip = 'Show File History';
|
||||
break;
|
||||
case StatusBarCommand.ShowQuickCurrentBranchHistory:
|
||||
this._statusBarItem.tooltip = 'Show Branch History';
|
||||
break;
|
||||
}
|
||||
|
||||
this._statusBarItem.show();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
'use strict';
|
||||
import { Objects } from './system';
|
||||
import { commands, ExtensionContext, extensions, languages, Uri, window, workspace } from 'vscode';
|
||||
import { BlameActiveLineController } from './blameActiveLineController';
|
||||
import { BlameAnnotationController } from './blameAnnotationController';
|
||||
import { AnnotationController } from './annotations/annotationController';
|
||||
import { CommandContext, setCommandContext } from './commands';
|
||||
import { CloseUnchangedFilesCommand, OpenChangedFilesCommand } from './commands';
|
||||
import { OpenBranchInRemoteCommand, OpenCommitInRemoteCommand, OpenFileInRemoteCommand, OpenInRemoteCommand, OpenRepoInRemoteCommand } from './commands';
|
||||
import { CopyMessageToClipboardCommand, CopyShaToClipboardCommand } from './commands';
|
||||
import { DiffDirectoryCommand, DiffLineWithPreviousCommand, DiffLineWithWorkingCommand, DiffWithBranchCommand, DiffWithNextCommand, DiffWithPreviousCommand, DiffWithWorkingCommand} from './commands';
|
||||
import { ShowBlameCommand, ToggleBlameCommand } from './commands';
|
||||
import { ShowFileBlameCommand, ShowLineBlameCommand, ToggleFileBlameCommand, ToggleLineBlameCommand } from './commands';
|
||||
import { ShowBlameHistoryCommand, ShowFileHistoryCommand } from './commands';
|
||||
import { ShowLastQuickPickCommand } from './commands';
|
||||
import { ShowQuickBranchHistoryCommand, ShowQuickCurrentBranchHistoryCommand, ShowQuickFileHistoryCommand } from './commands';
|
||||
@@ -19,6 +18,7 @@ import { ToggleCodeLensCommand } from './commands';
|
||||
import { Keyboard } from './commands';
|
||||
import { IConfig } from './configuration';
|
||||
import { ApplicationInsightsKey, BuiltInCommands, ExtensionKey, QualifiedExtensionId, WorkspaceState } from './constants';
|
||||
import { CurrentLineController } from './currentLineController';
|
||||
import { GitContentProvider } from './gitContentProvider';
|
||||
import { GitContextTracker, GitService } from './gitService';
|
||||
import { GitRevisionCodeLensProvider } from './gitRevisionCodeLensProvider';
|
||||
@@ -74,11 +74,11 @@ export async function activate(context: ExtensionContext) {
|
||||
|
||||
context.subscriptions.push(languages.registerCodeLensProvider(GitRevisionCodeLensProvider.selector, new GitRevisionCodeLensProvider(context, git)));
|
||||
|
||||
const annotationController = new BlameAnnotationController(context, git, gitContextTracker);
|
||||
const annotationController = new AnnotationController(context, git, gitContextTracker);
|
||||
context.subscriptions.push(annotationController);
|
||||
|
||||
const activeLineController = new BlameActiveLineController(context, git, gitContextTracker, annotationController);
|
||||
context.subscriptions.push(activeLineController);
|
||||
const currentLineController = new CurrentLineController(context, git, gitContextTracker, annotationController);
|
||||
context.subscriptions.push(currentLineController);
|
||||
|
||||
context.subscriptions.push(new Keyboard());
|
||||
|
||||
@@ -98,8 +98,10 @@ export async function activate(context: ExtensionContext) {
|
||||
context.subscriptions.push(new OpenFileInRemoteCommand(git));
|
||||
context.subscriptions.push(new OpenInRemoteCommand());
|
||||
context.subscriptions.push(new OpenRepoInRemoteCommand(git));
|
||||
context.subscriptions.push(new ShowBlameCommand(annotationController));
|
||||
context.subscriptions.push(new ToggleBlameCommand(annotationController));
|
||||
context.subscriptions.push(new ShowFileBlameCommand(annotationController));
|
||||
context.subscriptions.push(new ShowLineBlameCommand(currentLineController));
|
||||
context.subscriptions.push(new ToggleFileBlameCommand(annotationController));
|
||||
context.subscriptions.push(new ToggleLineBlameCommand(currentLineController));
|
||||
context.subscriptions.push(new ShowBlameHistoryCommand(git));
|
||||
context.subscriptions.push(new ShowFileHistoryCommand(git));
|
||||
context.subscriptions.push(new ShowLastQuickPickCommand());
|
||||
|
||||
160
src/git/formatters/commit.ts
Normal file
160
src/git/formatters/commit.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
'use strict';
|
||||
import { Strings } from '../../system';
|
||||
import { GitCommit } from '../models/commit';
|
||||
import { IGitDiffLine } from '../models/diff';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export interface ICommitFormatOptions {
|
||||
dateFormat?: string | null;
|
||||
tokenOptions?: {
|
||||
ago?: Strings.ITokenOptions;
|
||||
author?: Strings.ITokenOptions;
|
||||
authorAgo?: Strings.ITokenOptions;
|
||||
date?: Strings.ITokenOptions;
|
||||
message?: Strings.ITokenOptions;
|
||||
};
|
||||
}
|
||||
|
||||
export class CommitFormatter {
|
||||
|
||||
private _options: ICommitFormatOptions;
|
||||
|
||||
constructor(private commit: GitCommit, options?: ICommitFormatOptions) {
|
||||
options = options || {};
|
||||
if (options.tokenOptions == null) {
|
||||
options.tokenOptions = {};
|
||||
}
|
||||
|
||||
if (options.dateFormat == null) {
|
||||
options.dateFormat = 'MMMM Do, YYYY h:MMa';
|
||||
}
|
||||
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
get ago() {
|
||||
const ago = moment(this.commit.date).fromNow();
|
||||
return this._padOrTruncate(ago, this._options.tokenOptions!.ago);
|
||||
}
|
||||
|
||||
get author() {
|
||||
const author = this.commit.author;
|
||||
return this._padOrTruncate(author, this._options.tokenOptions!.author);
|
||||
}
|
||||
|
||||
get authorAgo() {
|
||||
const authorAgo = `${this.commit.author}, ${moment(this.commit.date).fromNow()}`;
|
||||
return this._padOrTruncate(authorAgo, this._options.tokenOptions!.authorAgo);
|
||||
}
|
||||
|
||||
get date() {
|
||||
const date = moment(this.commit.date).format(this._options.dateFormat!);
|
||||
return this._padOrTruncate(date, this._options.tokenOptions!.date);
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.commit.shortSha;
|
||||
}
|
||||
|
||||
get message() {
|
||||
const message = this.commit.isUncommitted ? 'Uncommitted change' : this.commit.message;
|
||||
return this._padOrTruncate(message, this._options.tokenOptions!.message);
|
||||
}
|
||||
|
||||
get sha() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
private collapsableWhitespace: number = 0;
|
||||
|
||||
private _padOrTruncate(s: string, options: Strings.ITokenOptions | undefined) {
|
||||
// NOTE: the collapsable whitespace logic relies on the javascript template evaluation to be left to right
|
||||
if (options === undefined) {
|
||||
options = {
|
||||
truncateTo: undefined,
|
||||
padDirection: 'left',
|
||||
collapseWhitespace: false
|
||||
};
|
||||
}
|
||||
|
||||
let max = options.truncateTo;
|
||||
|
||||
if (max === undefined) {
|
||||
if (this.collapsableWhitespace === 0) return s;
|
||||
|
||||
// If we have left over whitespace make sure it gets re-added
|
||||
const diff = this.collapsableWhitespace - s.length;
|
||||
this.collapsableWhitespace = 0;
|
||||
|
||||
if (diff <= 0) return s;
|
||||
if (options.truncateTo === undefined) return s;
|
||||
return Strings.padLeft(s, diff);
|
||||
}
|
||||
|
||||
max += this.collapsableWhitespace;
|
||||
this.collapsableWhitespace = 0;
|
||||
|
||||
const diff = max - s.length;
|
||||
if (diff > 0) {
|
||||
if (options.collapseWhitespace) {
|
||||
this.collapsableWhitespace = diff;
|
||||
}
|
||||
|
||||
if (options.padDirection === 'left') return Strings.padLeft(s, max);
|
||||
|
||||
if (options.collapseWhitespace) {
|
||||
max -= diff;
|
||||
}
|
||||
return Strings.padRight(s, max);
|
||||
}
|
||||
|
||||
if (diff < 0) return Strings.truncate(s, max);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static fromTemplate(template: string, commit: GitCommit, dateFormat: string | null): string;
|
||||
static fromTemplate(template: string, commit: GitCommit, options?: ICommitFormatOptions): string;
|
||||
static fromTemplate(template: string, commit: GitCommit, dateFormatOrOptions?: string | null | ICommitFormatOptions): string;
|
||||
static fromTemplate(template: string, commit: GitCommit, dateFormatOrOptions?: string | null | ICommitFormatOptions): string {
|
||||
let options: ICommitFormatOptions | undefined = undefined;
|
||||
if (dateFormatOrOptions == null || typeof dateFormatOrOptions === 'string') {
|
||||
const tokenOptions = Strings.getTokensFromTemplate(template)
|
||||
.reduce((map, token) => {
|
||||
map[token.key] = token.options;
|
||||
return map;
|
||||
}, {} as { [token: string]: ICommitFormatOptions });
|
||||
|
||||
options = {
|
||||
dateFormat: dateFormatOrOptions,
|
||||
tokenOptions: tokenOptions
|
||||
};
|
||||
}
|
||||
else {
|
||||
options = dateFormatOrOptions;
|
||||
}
|
||||
|
||||
return Strings.interpolateLazy(template, new CommitFormatter(commit, options));
|
||||
}
|
||||
|
||||
static toHoverAnnotation(commit: GitCommit, dateFormat: string = 'MMMM Do, YYYY h:MMa'): string | string[] {
|
||||
const message = commit.isUncommitted ? '' : `\n\n> ${commit.message.replace(/\n/g, '\n>\n> ')}`;
|
||||
return `\`${commit.shortSha}\` __${commit.author}__, ${moment(commit.date).fromNow()} _(${moment(commit.date).format(dateFormat)})_${message}`;
|
||||
}
|
||||
|
||||
static toHoverDiff(commit: GitCommit, previous: IGitDiffLine | undefined, current: IGitDiffLine | undefined): string | undefined {
|
||||
if (previous === undefined && current === undefined) return undefined;
|
||||
|
||||
const codeDiff = this._getCodeDiff(previous, current);
|
||||
return commit.isUncommitted
|
||||
? `\`Changes\` \u2014 _uncommitted_\n${codeDiff}`
|
||||
: `\`Changes\` \u2014 \`${commit.previousShortSha}\` \u2194 \`${commit.shortSha}\`\n${codeDiff}`;
|
||||
}
|
||||
|
||||
private static _getCodeDiff(previous: IGitDiffLine | undefined, current: IGitDiffLine | undefined): string {
|
||||
return `\`\`\`
|
||||
- ${previous === undefined ? '' : previous.line.trim()}
|
||||
+ ${current === undefined ? '' : current.line.trim()}
|
||||
\`\`\``;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
export interface IGitDiffLine {
|
||||
line: string;
|
||||
state: 'added' | 'removed' | 'unchanged';
|
||||
}
|
||||
|
||||
export interface IGitDiffChunk {
|
||||
current: (string | undefined)[];
|
||||
current: (IGitDiffLine | undefined)[];
|
||||
currentStart: number;
|
||||
currentEnd: number;
|
||||
|
||||
previous: (string | undefined)[];
|
||||
previous: (IGitDiffLine | undefined)[];
|
||||
previousStart: number;
|
||||
previousEnd: number;
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ export class GitBlameParser {
|
||||
switch (lineParts[0]) {
|
||||
case 'author':
|
||||
entry.author = Git.isUncommitted(entry.sha)
|
||||
? 'Uncommitted'
|
||||
? 'You'
|
||||
: lineParts.slice(1).join(' ').trim();
|
||||
break;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
import { IGitDiff, IGitDiffChunk } from './../git';
|
||||
import { IGitDiff, IGitDiffChunk, IGitDiffLine } from './../git';
|
||||
|
||||
const unifiedDiffRegex = /^@@ -([\d]+),([\d]+) [+]([\d]+),([\d]+) @@([\s\S]*?)(?=^@@)/gm;
|
||||
|
||||
@@ -21,23 +21,29 @@ export class GitDiffParser {
|
||||
const chunk = match[5];
|
||||
const lines = chunk.split('\n').slice(1);
|
||||
|
||||
const current = [];
|
||||
const previous = [];
|
||||
const current: (IGitDiffLine | undefined)[] = [];
|
||||
const previous: (IGitDiffLine | undefined)[] = [];
|
||||
for (const l of lines) {
|
||||
switch (l[0]) {
|
||||
case '+':
|
||||
current.push(` ${l.substring(1)}`);
|
||||
current.push({
|
||||
line: ` ${l.substring(1)}`,
|
||||
state: 'added'
|
||||
});
|
||||
previous.push(undefined);
|
||||
break;
|
||||
|
||||
case '-':
|
||||
current.push(undefined);
|
||||
previous.push(` ${l.substring(1)}`);
|
||||
previous.push({
|
||||
line: ` ${l.substring(1)}`,
|
||||
state: 'removed'
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
current.push(l);
|
||||
previous.push(l);
|
||||
current.push({ line: l, state: 'unchanged' });
|
||||
previous.push({ line: l, state: 'unchanged' });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export class GitLogParser {
|
||||
switch (lineParts[0]) {
|
||||
case 'author':
|
||||
entry.author = Git.isUncommitted(entry.sha)
|
||||
? 'Uncommitted'
|
||||
? 'You'
|
||||
: lineParts.slice(1).join(' ').trim();
|
||||
break;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Functions, Iterables, Strings } from './system';
|
||||
import { CancellationToken, CodeLens, CodeLensProvider, Command, commands, DocumentSelector, Event, EventEmitter, ExtensionContext, Position, Range, SymbolInformation, SymbolKind, TextDocument, Uri, workspace } from 'vscode';
|
||||
import { Commands, DiffWithPreviousCommandArgs, ShowBlameHistoryCommandArgs, ShowFileHistoryCommandArgs, ShowQuickCommitDetailsCommandArgs, ShowQuickCommitFileDetailsCommandArgs, ShowQuickFileHistoryCommandArgs } from './commands';
|
||||
import { BuiltInCommands, DocumentSchemes, ExtensionKey } from './constants';
|
||||
import { CodeLensCommand, CodeLensLocation, ICodeLensLanguageLocation, IConfig } from './configuration';
|
||||
import { CodeLensCommand, CodeLensLocations, ICodeLensLanguageLocation, IConfig } from './configuration';
|
||||
import { GitCommit, GitService, GitUri, IGitBlame, IGitBlameLines } from './gitService';
|
||||
import { Logger } from './logger';
|
||||
import * as moment from 'moment';
|
||||
@@ -56,24 +56,22 @@ export class GitCodeLensProvider implements CodeLensProvider {
|
||||
async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
|
||||
this._documentIsDirty = document.isDirty;
|
||||
|
||||
let languageLocations = this._config.codeLens.languageLocations.find(_ => _.language !== undefined && _.language.toLowerCase() === document.languageId);
|
||||
let languageLocations = this._config.codeLens.perLanguageLocations.find(_ => _.language !== undefined && _.language.toLowerCase() === document.languageId);
|
||||
if (languageLocations == null) {
|
||||
languageLocations = {
|
||||
language: undefined,
|
||||
location: this._config.codeLens.location,
|
||||
customSymbols: this._config.codeLens.locationCustomSymbols
|
||||
locations: this._config.codeLens.locations,
|
||||
customSymbols: this._config.codeLens.customLocationSymbols
|
||||
} as ICodeLensLanguageLocation;
|
||||
}
|
||||
|
||||
const lenses: CodeLens[] = [];
|
||||
|
||||
if (languageLocations.location === CodeLensLocation.None) return lenses;
|
||||
|
||||
const gitUri = await GitUri.fromUri(document.uri, this.git);
|
||||
|
||||
const blamePromise = this.git.getBlameForFile(gitUri);
|
||||
let blame: IGitBlame | undefined;
|
||||
if (languageLocations.location === CodeLensLocation.Document) {
|
||||
if (languageLocations.locations.length === 1 && languageLocations.locations.includes(CodeLensLocations.Document)) {
|
||||
blame = await blamePromise;
|
||||
if (blame === undefined || !blame.lines.length) return lenses;
|
||||
}
|
||||
@@ -91,7 +89,8 @@ export class GitCodeLensProvider implements CodeLensProvider {
|
||||
symbols.forEach(sym => this._provideCodeLens(gitUri, document, sym, languageLocations!, blame!, lenses));
|
||||
}
|
||||
|
||||
if (languageLocations.location !== CodeLensLocation.Custom || (languageLocations.customSymbols || []).find(_ => _.toLowerCase() === 'file')) {
|
||||
if (languageLocations.locations.includes(CodeLensLocations.Document) ||
|
||||
(languageLocations.locations.includes(CodeLensLocations.Custom) && (languageLocations.customSymbols || []).find(_ => _.toLowerCase() === 'file'))) {
|
||||
// Check if we have a lens for the whole document -- if not add one
|
||||
if (!lenses.find(l => l.range.start.line === 0 && l.range.end.line === 0)) {
|
||||
const blameRange = document.validateRange(new Range(0, 1000000, 1000000, 1000000));
|
||||
@@ -117,55 +116,61 @@ export class GitCodeLensProvider implements CodeLensProvider {
|
||||
private _validateSymbolAndGetBlameRange(document: TextDocument, symbol: SymbolInformation, languageLocation: ICodeLensLanguageLocation): Range | undefined {
|
||||
let valid = false;
|
||||
let range: Range | undefined;
|
||||
switch (languageLocation.location) {
|
||||
case CodeLensLocation.All:
|
||||
case CodeLensLocation.DocumentAndContainers:
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind.File:
|
||||
valid = true;
|
||||
// Adjust the range to be the whole file
|
||||
range = document.validateRange(new Range(0, 1000000, 1000000, 1000000));
|
||||
break;
|
||||
case SymbolKind.Package:
|
||||
case SymbolKind.Module:
|
||||
// Adjust the range to be the whole file
|
||||
if (symbol.location.range.start.line === 0 && symbol.location.range.end.line === 0) {
|
||||
range = document.validateRange(new Range(0, 1000000, 1000000, 1000000));
|
||||
}
|
||||
valid = true;
|
||||
break;
|
||||
case SymbolKind.Namespace:
|
||||
case SymbolKind.Class:
|
||||
case SymbolKind.Interface:
|
||||
valid = true;
|
||||
break;
|
||||
case SymbolKind.Constructor:
|
||||
case SymbolKind.Method:
|
||||
case SymbolKind.Function:
|
||||
case SymbolKind.Property:
|
||||
case SymbolKind.Enum:
|
||||
valid = languageLocation.location === CodeLensLocation.All;
|
||||
break;
|
||||
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind.File:
|
||||
if (languageLocation.locations.includes(CodeLensLocations.Containers)) {
|
||||
valid = true;
|
||||
}
|
||||
else if (languageLocation.locations.includes(CodeLensLocations.Custom)) {
|
||||
valid = !!(languageLocation.customSymbols || []).find(_ => _.toLowerCase() === SymbolKind[symbol.kind].toLowerCase());
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
// Adjust the range to be for the whole file
|
||||
range = document.validateRange(new Range(0, 1000000, 1000000, 1000000));
|
||||
}
|
||||
break;
|
||||
case CodeLensLocation.Custom:
|
||||
valid = !!(languageLocation.customSymbols || []).find(_ => _.toLowerCase() === SymbolKind[symbol.kind].toLowerCase());
|
||||
|
||||
case SymbolKind.Package:
|
||||
if (languageLocation.locations.includes(CodeLensLocations.Containers)) {
|
||||
valid = true;
|
||||
}
|
||||
else if (languageLocation.locations.includes(CodeLensLocations.Custom)) {
|
||||
valid = !!(languageLocation.customSymbols || []).find(_ => _.toLowerCase() === SymbolKind[symbol.kind].toLowerCase());
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind.File:
|
||||
// Adjust the range to be the whole file
|
||||
range = document.validateRange(new Range(0, 1000000, 1000000, 1000000));
|
||||
break;
|
||||
case SymbolKind.Package:
|
||||
case SymbolKind.Module:
|
||||
// Adjust the range to be the whole file
|
||||
if (symbol.location.range.start.line === 0 && symbol.location.range.end.line === 0) {
|
||||
range = document.validateRange(new Range(0, 1000000, 1000000, 1000000));
|
||||
}
|
||||
break;
|
||||
// Adjust the range to be for the whole file
|
||||
if (symbol.location.range.start.line === 0 && symbol.location.range.end.line === 0) {
|
||||
range = document.validateRange(new Range(0, 1000000, 1000000, 1000000));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SymbolKind.Class:
|
||||
case SymbolKind.Interface:
|
||||
case SymbolKind.Module:
|
||||
case SymbolKind.Namespace:
|
||||
case SymbolKind.Struct:
|
||||
if (languageLocation.locations.includes(CodeLensLocations.Containers)) {
|
||||
valid = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case SymbolKind.Constructor:
|
||||
case SymbolKind.Enum:
|
||||
case SymbolKind.Function:
|
||||
case SymbolKind.Method:
|
||||
case SymbolKind.Property:
|
||||
if (languageLocation.locations.includes(CodeLensLocations.Blocks)) {
|
||||
valid = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!valid && languageLocation.locations.includes(CodeLensLocations.Custom)) {
|
||||
valid = !!(languageLocation.customSymbols || []).find(_ => _.toLowerCase() === SymbolKind[symbol.kind].toLowerCase());
|
||||
}
|
||||
|
||||
return valid ? range || symbol.location.range : undefined;
|
||||
@@ -302,7 +307,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
|
||||
_applyBlameAnnotateCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(title: string, lens: T, blame: IGitBlameLines): T {
|
||||
lens.command = {
|
||||
title: title,
|
||||
command: Commands.ToggleBlame,
|
||||
command: Commands.ToggleFileBlame,
|
||||
arguments: [Uri.file(lens.uri.fsPath)]
|
||||
};
|
||||
return lens;
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { Iterables, Objects } from './system';
|
||||
import { Disposable, Event, EventEmitter, ExtensionContext, FileSystemWatcher, languages, Location, Position, Range, TextDocument, TextDocumentChangeEvent, TextEditor, Uri, workspace } from 'vscode';
|
||||
import { CommandContext, setCommandContext } from './commands';
|
||||
import { CodeLensVisibility, IConfig } from './configuration';
|
||||
import { IConfig } from './configuration';
|
||||
import { DocumentSchemes, ExtensionKey } from './constants';
|
||||
import { Git, GitBlameParser, GitBranch, GitCommit, GitDiffParser, GitLogCommit, GitLogParser, GitRemote, GitStashParser, GitStatusFile, GitStatusParser, IGit, IGitAuthor, IGitBlame, IGitBlameLine, IGitBlameLines, IGitDiff, IGitLog, IGitStash, IGitStatus, setDefaultEncoding } from './git/git';
|
||||
import { Git, GitBlameParser, GitBranch, GitCommit, GitDiffParser, GitLogCommit, GitLogParser, GitRemote, GitStashParser, GitStatusFile, GitStatusParser, IGit, IGitAuthor, IGitBlame, IGitBlameLine, IGitBlameLines, IGitDiff, IGitDiffLine, IGitLog, IGitStash, IGitStatus, setDefaultEncoding } from './git/git';
|
||||
import { GitUri, IGitCommitInfo, IGitUriData } from './git/gitUri';
|
||||
import { GitCodeLensProvider } from './gitCodeLensProvider';
|
||||
import { Logger } from './logger';
|
||||
@@ -15,6 +15,7 @@ import * as path from 'path';
|
||||
|
||||
export { GitUri, IGitCommitInfo };
|
||||
export * from './git/models/models';
|
||||
export * from './git/formatters/commit';
|
||||
export { getNameFromRemoteResource, RemoteResource, RemoteProvider } from './git/remotes/provider';
|
||||
export * from './git/gitContextTracker';
|
||||
|
||||
@@ -139,7 +140,7 @@ export class GitService extends Disposable {
|
||||
|
||||
if (codeLensChanged) {
|
||||
Logger.log('CodeLens config changed; resetting CodeLens provider');
|
||||
if (cfg.codeLens.visibility === CodeLensVisibility.Auto && (cfg.codeLens.recentChange.enabled || cfg.codeLens.authors.enabled)) {
|
||||
if (cfg.codeLens.enabled && (cfg.codeLens.recentChange.enabled || cfg.codeLens.authors.enabled)) {
|
||||
if (this._codeLensProvider) {
|
||||
this._codeLensProvider.reset();
|
||||
}
|
||||
@@ -154,7 +155,7 @@ export class GitService extends Disposable {
|
||||
this._codeLensProvider = undefined;
|
||||
}
|
||||
|
||||
setCommandContext(CommandContext.CanToggleCodeLens, cfg.codeLens.visibility !== CodeLensVisibility.Off && (cfg.codeLens.recentChange.enabled || cfg.codeLens.authors.enabled));
|
||||
setCommandContext(CommandContext.CanToggleCodeLens, cfg.codeLens.recentChange.enabled || cfg.codeLens.authors.enabled);
|
||||
}
|
||||
|
||||
if (advancedChanged) {
|
||||
@@ -644,13 +645,13 @@ export class GitService extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
async getDiffForLine(uri: GitUri, line: number, sha1?: string, sha2?: string): Promise<[string | undefined, string | undefined] | undefined> {
|
||||
async getDiffForLine(uri: GitUri, line: number, sha1?: string, sha2?: string): Promise<[IGitDiffLine | undefined, IGitDiffLine | undefined]> {
|
||||
try {
|
||||
const diff = await this.getDiffForFile(uri, sha1, sha2);
|
||||
if (diff === undefined) return undefined;
|
||||
if (diff === undefined) return [undefined, undefined];
|
||||
|
||||
const chunk = diff.chunks.find(_ => _.currentStart <= line && _.currentEnd >= line);
|
||||
if (chunk === undefined) return undefined;
|
||||
if (chunk === undefined) return [undefined, undefined];
|
||||
|
||||
// Search for the line (skipping deleted lines -- since they don't currently exist in the editor)
|
||||
// Keep track of the deleted lines for the original version
|
||||
@@ -675,7 +676,7 @@ export class GitService extends Disposable {
|
||||
];
|
||||
}
|
||||
catch (ex) {
|
||||
return undefined;
|
||||
return [undefined, undefined];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1008,8 +1009,7 @@ export class GitService extends Disposable {
|
||||
}
|
||||
|
||||
toggleCodeLens(editor: TextEditor) {
|
||||
if (this.config.codeLens.visibility === CodeLensVisibility.Off ||
|
||||
(!this.config.codeLens.recentChange.enabled && !this.config.codeLens.authors.enabled)) return;
|
||||
if (!this.config.codeLens.recentChange.enabled && !this.config.codeLens.authors.enabled) return;
|
||||
|
||||
Logger.log(`toggleCodeLens()`);
|
||||
if (this._codeLensProviderDisposable) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
'use strict';
|
||||
import { CancellationTokenSource, commands, Disposable, QuickPickItem, QuickPickOptions, TextDocumentShowOptions, TextEditor, Uri, window, workspace } from 'vscode';
|
||||
import { Commands, Keyboard, KeyboardScope, KeyMapping, Keys, openEditor } from '../commands';
|
||||
import { IAdvancedConfig } from '../configuration';
|
||||
import { ExtensionKey } from '../constants';
|
||||
import { ExtensionKey, IAdvancedConfig } from '../configuration';
|
||||
import { GitCommit, GitLogCommit, GitStashCommit } from '../gitService';
|
||||
// import { Logger } from '../logger';
|
||||
import * as moment from 'moment';
|
||||
|
||||
@@ -15,4 +15,8 @@ export namespace Functions {
|
||||
export function once<T extends Function>(fn: T): T {
|
||||
return _once(fn);
|
||||
}
|
||||
|
||||
export async function wait(ms: number) {
|
||||
await new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
@@ -55,4 +55,10 @@ export namespace Objects {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function* values(o: any): IterableIterator<[any]> {
|
||||
for (const key in o) {
|
||||
yield [o[key]];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,84 @@
|
||||
'use strict';
|
||||
import { Objects } from './object';
|
||||
const _escapeRegExp = require('lodash.escaperegexp');
|
||||
|
||||
export namespace Strings {
|
||||
export function escapeRegExp(s: string): string {
|
||||
return _escapeRegExp(s);
|
||||
}
|
||||
|
||||
const TokenRegex = /\$\{([^|]*?)(?:\|(\d+)(\-|\?)?)?\}/g;
|
||||
const TokenSanitizeRegex = /\$\{(\w*?)(?:\W|\d)*?\}/g;
|
||||
|
||||
export interface ITokenOptions {
|
||||
padDirection: 'left' | 'right';
|
||||
truncateTo: number | undefined;
|
||||
collapseWhitespace: boolean;
|
||||
}
|
||||
|
||||
export function getTokensFromTemplate(template: string) {
|
||||
const tokens: { key: string, options: ITokenOptions }[] = [];
|
||||
|
||||
let match = TokenRegex.exec(template);
|
||||
while (match != null) {
|
||||
const truncateTo = match[2];
|
||||
const option = match[3];
|
||||
tokens.push({
|
||||
key: match[1],
|
||||
options: {
|
||||
truncateTo: truncateTo == null ? undefined : parseInt(truncateTo, 10),
|
||||
padDirection: option === '-' ? 'left' : 'right',
|
||||
collapseWhitespace: option === '?'
|
||||
}
|
||||
});
|
||||
match = TokenRegex.exec(template);
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
export function interpolate(template: string, tokens: { [key: string]: any }): string {
|
||||
return new Function(...Object.keys(tokens), `return \`${template}\`;`)(...Objects.values(tokens));
|
||||
}
|
||||
|
||||
export function interpolateLazy(template: string, context: object): string {
|
||||
template = template.replace(TokenSanitizeRegex, '$${c.$1}');
|
||||
return new Function('c', `return \`${template}\`;`)(context);
|
||||
}
|
||||
|
||||
export function padLeft(s: string, padTo: number, padding: string = '\u00a0') {
|
||||
const diff = padTo - s.length;
|
||||
return (diff <= 0) ? s : '\u00a0'.repeat(diff) + s;
|
||||
}
|
||||
|
||||
export function padLeftOrTruncate(s: string, max: number, padding?: string) {
|
||||
if (s.length < max) return padLeft(s, max, padding);
|
||||
if (s.length > max) return truncate(s, max);
|
||||
return s;
|
||||
}
|
||||
|
||||
export function padRight(s: string, padTo: number, padding: string = '\u00a0') {
|
||||
const diff = padTo - s.length;
|
||||
return (diff <= 0) ? s : s + '\u00a0'.repeat(diff);
|
||||
}
|
||||
|
||||
export function padOrTruncate(s: string, max: number, padding?: string) {
|
||||
const left = max < 0;
|
||||
max = Math.abs(max);
|
||||
|
||||
if (s.length < max) return left ? padLeft(s, max, padding) : padRight(s, max, padding);
|
||||
if (s.length > max) return truncate(s, max);
|
||||
return s;
|
||||
}
|
||||
|
||||
export function padRightOrTruncate(s: string, max: number, padding?: string) {
|
||||
if (s.length < max) return padRight(s, max, padding);
|
||||
if (s.length > max) return truncate(s, max);
|
||||
return s;
|
||||
}
|
||||
|
||||
export function truncate(s: string, truncateTo?: number) {
|
||||
if (!s || truncateTo === undefined || s.length <= truncateTo) return s;
|
||||
return `${s.substring(0, truncateTo - 1)}\u2026`;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"lib": [ "es2015" ],
|
||||
"lib": [ "es2015", "es2016" ],
|
||||
"module": "commonjs",
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitReturns": true,
|
||||
@@ -12,7 +12,7 @@
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"target": "es2015"
|
||||
"target": "es2016"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"ignore-properties"
|
||||
],
|
||||
"no-internal-module": true,
|
||||
"no-invalid-template-strings": true,
|
||||
// "no-invalid-template-strings": true,
|
||||
"no-irregular-whitespace": true,
|
||||
"no-reference": true,
|
||||
"no-string-throw": true,
|
||||
|
||||
Reference in New Issue
Block a user