diff --git a/README.md b/README.md
index f11b2ac..67009e2 100644
--- a/README.md
+++ b/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
![GitLens preview 1](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/master/images/gitlens-preview1.gif)
-#### 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
![GitLens preview 2](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/master/images/gitlens-preview2.gif)
## 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 `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 **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 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 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 [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
diff --git a/images/blame-dark.svg b/images/blame-dark.svg
index bb26282..7e49611 100644
--- a/images/blame-dark.svg
+++ b/images/blame-dark.svg
@@ -1,6 +1,6 @@
\ No newline at end of file
diff --git a/images/blame-light.svg b/images/blame-light.svg
index 5fabefb..b60013b 100644
--- a/images/blame-light.svg
+++ b/images/blame-light.svg
@@ -1,6 +1,6 @@
diff --git a/package.json b/package.json
index 2cea74d..271109b 100644
--- a/package.json
+++ b/package.json
@@ -1,929 +1,1263 @@
-{
- "name": "gitlens",
- "version": "3.6.1",
- "author": {
- "name": "Eric Amodio",
- "email": "eamodio@gmail.com"
- },
- "publisher": "eamodio",
- "engines": {
- "vscode": "^1.12.0"
- },
- "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",
- "badges": [
- {
- "url": "https://badges.gitter.im/vscode-gitlens/Lobby.svg",
- "href": "https://gitter.im/vscode-gitlens/Lobby",
- "description": "Chat at https://gitter.im/vscode-gitlens/Lobby"
- }
- ],
- "categories": [
- "Other"
- ],
- "keywords": [
- "git",
- "code lens",
- "blame",
- "history",
- "annotation",
- "log",
- "inline blame",
- "compare",
- "diff"
- ],
- "galleryBanner": {
- "color": "#56098c",
- "theme": "dark"
- },
- "icon": "images/gitlens-icon.svg",
- "preview": false,
- "homepage": "https://github.com/eamodio/vscode-gitlens/blob/master/README.md",
- "bugs": {
- "url": "https://github.com/eamodio/vscode-gitlens/issues"
- },
- "repository": {
- "type": "git",
- "url": "https://github.com/eamodio/vscode-gitlens.git"
- },
- "main": "./out/src/extension",
- "contributes": {
- "configuration": {
- "type": "object",
- "title": "GitLens configuration",
- "properties": {
- "gitlens.debug": {
- "type": "boolean",
- "default": false,
- "description": "Specifies debug mode"
- },
- "gitlens.insiders": {
- "type": "boolean",
- "default": false,
- "description": "Specifies whether or not to enable new experimental features (expect there to be issues)"
- },
- "gitlens.outputLevel": {
- "type": "string",
- "default": "silent",
- "enum": [
- "silent",
- "errors",
- "verbose"
- ],
- "description": "Specifies how much (if any) output will be sent to the GitLens output channel"
- },
- "gitlens.blame.annotation.activeLine": {
- "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"
- },
- "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": {
- "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"
- },
- "gitlens.blame.annotation.message": {
- "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"
- },
- "gitlens.blame.annotation.sha": {
- "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"
- },
- "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": {
- "type": "boolean",
- "default": true,
- "description": "Specifies whether the authors code lens is shown"
- },
- "gitlens.codeLens.authors.command": {
- "type": "string",
- "default": "gitlens.toggleBlame",
- "enum": [
- "gitlens.toggleBlame",
- "gitlens.showBlameHistory",
- "gitlens.showFileHistory",
- "gitlens.diffWithPrevious",
- "gitlens.showQuickCommitDetails",
- "gitlens.showQuickCommitFileDetails",
- "gitlens.showQuickFileHistory",
- "gitlens.showQuickRepoHistory"
- ],
- "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"
- },
- "gitlens.codeLens.recentChange.enabled": {
- "type": "boolean",
- "default": true,
- "description": "Specifies whether the recent change code lens is shown"
- },
- "gitlens.codeLens.recentChange.command": {
- "type": "string",
- "default": "gitlens.showQuickCommitFileDetails",
- "enum": [
- "gitlens.toggleBlame",
- "gitlens.showBlameHistory",
- "gitlens.showFileHistory",
- "gitlens.diffWithPrevious",
- "gitlens.showQuickCommitDetails",
- "gitlens.showQuickCommitFileDetails",
- "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"
- },
- "gitlens.codeLens.location": {
- "type": "string",
- "default": "document+containers",
- "enum": [
- "all",
- "document+containers",
- "document",
- "custom"
- ],
- "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`"
- },
- "gitlens.codeLens.locationCustomSymbols": {
- "type": "array",
- "description": "Specifies the set of document symbols to render active document code lens on. Must be a member of `SymbolKind`"
- },
- "gitlens.codeLens.languageLocations": {
- "type": "array",
- "default": [
- {
- "language": "css",
- "location": "document"
- },
- {
- "language": "html",
- "location": "document"
- },
- {
- "language": "json",
- "location": "document"
- },
- {
- "language": "less",
- "location": "document"
- },
- {
- "language": "scss",
- "location": "document"
- },
- {
- "language": "vue",
- "location": "document"
- }
- ],
- "items": {
- "type": "object",
- "required": [
- "language",
- "location"
- ],
- "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",
- "document",
- "custom",
- "none"
- ],
- "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`"
- },
- "customSymbols": {
- "type": "string",
- "description": "Specifies the set of document symbols to render active document code lens on. 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"
- },
- "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"
- },
- "gitlens.statusBar.alignment": {
- "type": "string",
- "default": "right",
- "enum": [
- "left",
- "right"
- ],
- "description": "Specifies the blame alignment in the status bar. `left` - align to the left, `right` - align to the right"
- },
- "gitlens.statusBar.command": {
- "type": "string",
- "default": "gitlens.showQuickCommitDetails",
- "enum": [
- "gitlens.toggleBlame",
- "gitlens.showBlameHistory",
- "gitlens.showFileHistory",
- "gitlens.diffWithPrevious",
- "gitlens.diffWithWorking",
- "gitlens.toggleCodeLens",
- "gitlens.showQuickCommitDetails",
- "gitlens.showQuickCommitFileDetails",
- "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"
- },
- "gitlens.statusBar.date": {
- "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`"
- },
- "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"
- },
- "gitlens.advanced.caching.enabled": {
- "type": "boolean",
- "default": true,
- "description": "Specifies whether git blame output will be cached"
- },
- "gitlens.advanced.caching.statusBar.maxLines": {
- "type": "number",
- "default": 0,
- "description": "Specifies whether status bar git blame output will be cached for larger documents"
- },
- "gitlens.advanced.git": {
- "type": "string",
- "default": null,
- "description": "Specifies a git path to use"
- },
- "gitlens.advanced.gitignore.enabled": {
- "type": "boolean",
- "default": true,
- "description": "Specifies whether or not to parse the root .gitignore file for better performance (i.e. avoids blaming excluded files)"
- },
- "gitlens.advanced.maxQuickHistory": {
- "type": "number",
- "default": 200,
- "description": "Specifies the maximum number of QuickPick history entries to show"
- },
- "gitlens.advanced.quickPick.closeOnFocusOut": {
- "type": "boolean",
- "default": true,
- "description": "Specifies whether or not to close the QuickPick menu when focus is lost"
- },
- "gitlens.advanced.toggleWhitespace.enabled": {
- "type": "boolean",
- "default": false,
- "description": "Specifies whether or not to toggle whitespace off then showing blame annotations (*may* be required by certain fonts/themes)"
- }
- }
- },
- "commands": [
- {
- "command": "gitlens.diffDirectory",
- "title": "Directory Compare",
- "category": "GitLens"
- },
- {
- "command": "gitlens.diffWithBranch",
- "title": "Compare File with...",
- "category": "GitLens"
- },
- {
- "command": "gitlens.diffWithNext",
- "title": "Compare File with Next Commit",
- "category": "GitLens"
- },
- {
- "command": "gitlens.diffWithPrevious",
- "title": "Compare File with Previous",
- "category": "GitLens"
- },
- {
- "command": "gitlens.diffLineWithPrevious",
- "title": "Compare Line Commit with Previous",
- "category": "GitLens"
- },
- {
- "command": "gitlens.diffWithWorking",
- "title": "Compare File with Working Tree",
- "category": "GitLens"
- },
- {
- "command": "gitlens.diffLineWithWorking",
- "title": "Compare Line Commit with Working Tree",
- "category": "GitLens"
- },
- {
- "command": "gitlens.showBlame",
- "title": "Show Blame Annotations",
- "category": "GitLens"
- },
- {
- "command": "gitlens.toggleBlame",
- "title": "Toggle Blame Annotations",
- "category": "GitLens",
- "icon": {
- "dark": "images/git-icon-dark.svg",
- "light": "images/git-icon-light.svg"
- }
- },
- {
- "command": "gitlens.toggleCodeLens",
- "title": "Toggle Git Code Lens",
- "category": "GitLens"
- },
- {
- "command": "gitlens.showBlameHistory",
- "title": "Open Blame History Explorer",
- "category": "GitLens"
- },
- {
- "command": "gitlens.showCommitSearch",
- "title": "Search Commits",
- "category": "GitLens"
- },
- {
- "command": "gitlens.showFileHistory",
- "title": "Open File History Explorer",
- "category": "GitLens"
- },
- {
- "command": "gitlens.showLastQuickPick",
- "title": "Show Last Opened Quick Pick",
- "category": "GitLens"
- },
- {
- "command": "gitlens.showQuickCommitDetails",
- "title": "Show Commit Details",
- "category": "GitLens"
- },
- {
- "command": "gitlens.showQuickCommitFileDetails",
- "title": "Show Line Commit Details",
- "category": "GitLens"
- },
- {
- "command": "gitlens.showQuickFileHistory",
- "title": "Show File History",
- "category": "GitLens"
- },
- {
- "command": "gitlens.showQuickBranchHistory",
- "title": "Show Branch History",
- "category": "GitLens"
- },
- {
- "command": "gitlens.showQuickRepoHistory",
- "title": "Show Current Branch History",
- "category": "GitLens"
- },
- {
- "command": "gitlens.showQuickRepoStatus",
- "title": "Show Repository Status",
- "category": "GitLens"
- },
- {
- "command": "gitlens.showQuickStashList",
- "title": "Show Stashed Changes",
- "category": "GitLens"
- },
- {
- "command": "gitlens.copyShaToClipboard",
- "title": "Copy Commit ID to Clipboard",
- "category": "GitLens"
- },
- {
- "command": "gitlens.copyMessageToClipboard",
- "title": "Copy Commit Message to Clipboard",
- "category": "GitLens"
- },
- {
- "command": "gitlens.closeUnchangedFiles",
- "title": "Close Unchanged Files",
- "category": "GitLens"
- },
- {
- "command": "gitlens.openChangedFiles",
- "title": "Open Changed Files",
- "category": "GitLens"
- },
- {
- "command": "gitlens.openBranchInRemote",
- "title": "Open Branch in Remote",
- "category": "GitLens"
- },
- {
- "command": "gitlens.openCommitInRemote",
- "title": "Open Line Commit in Remote",
- "category": "GitLens"
- },
- {
- "command": "gitlens.openFileInRemote",
- "title": "Open File in Remote",
- "category": "GitLens"
- },
- {
- "command": "gitlens.openRepoInRemote",
- "title": "Open Repository in Remote",
- "category": "GitLens"
- },
- {
- "command": "gitlens.stashApply",
- "title": "Apply Stashed Changes",
- "category": "GitLens"
- },
- {
- "command": "gitlens.stashSave",
- "title": "Stash Changes",
- "category": "GitLens"
- }
- ],
- "menus": {
- "commandPalette": [
- {
- "command": "gitlens.diffDirectory",
- "when": "gitlens:enabled"
- },
- {
- "command": "gitlens.diffWithBranch",
- "when": "gitlens:isTracked"
- },
- {
- "command": "gitlens.diffWithNext",
- "when": "gitlens:isTracked"
- },
- {
- "command": "gitlens.diffWithPrevious",
- "when": "gitlens:isTracked"
- },
- {
- "command": "gitlens.diffLineWithPrevious",
- "when": "gitlens:isBlameable"
- },
- {
- "command": "gitlens.diffWithWorking",
- "when": "gitlens:isTracked"
- },
- {
- "command": "gitlens.diffLineWithWorking",
- "when": "gitlens:isBlameable"
- },
- {
- "command": "gitlens.showBlame",
- "when": "gitlens:isBlameable"
- },
- {
- "command": "gitlens.toggleBlame",
- "when": "gitlens:isBlameable"
- },
- {
- "command": "gitlens.toggleCodeLens",
- "when": "gitlens:isTracked && gitlens:canToggleCodeLens"
- },
- {
- "command": "gitlens.showBlameHistory",
- "when": "gitlens:isBlameable"
- },
- {
- "command": "gitlens.showFileHistory",
- "when": "gitlens:isTracked"
- },
- {
- "command": "gitlens.showLastQuickPick",
- "when": "gitlens:enabled"
- },
- {
- "command": "gitlens.showQuickCommitDetails",
- "when": "gitlens:isBlameable"
- },
- {
- "command": "gitlens.showQuickCommitFileDetails",
- "when": "gitlens:isBlameable"
- },
- {
- "command": "gitlens.showQuickFileHistory",
- "when": "gitlens:isTracked"
- },
- {
- "command": "gitlens.showQuickBranchHistory",
- "when": "gitlens:enabled"
- },
- {
- "command": "gitlens.showQuickRepoHistory",
- "when": "gitlens:enabled"
- },
- {
- "command": "gitlens.showQuickRepoStatus",
- "when": "gitlens:enabled"
- },
- {
- "command": "gitlens.showQuickStashList",
- "when": "gitlens:enabled"
- },
- {
- "command": "gitlens.copyShaToClipboard",
- "when": "gitlens:isBlameable"
- },
- {
- "command": "gitlens.copyMessageToClipboard",
- "when": "gitlens:isBlameable"
- },
- {
- "command": "gitlens.closeUnchangedFiles",
- "when": "gitlens:enabled"
- },
- {
- "command": "gitlens.openChangedFiles",
- "when": "gitlens:enabled"
- },
- {
- "command": "gitlens.openBranchInRemote",
- "when": "gitlens:hasRemotes"
- },
- {
- "command": "gitlens.openCommitInRemote",
- "when": "gitlens:isBlameable && gitlens:hasRemotes"
- },
- {
- "command": "gitlens.openFileInRemote",
- "when": "gitlens:isTracked && gitlens:hasRemotes"
- },
- {
- "command": "gitlens.openRepoInRemote",
- "when": "gitlens:hasRemotes"
- },
- {
- "command": "gitlens.stashApply",
- "when": "gitlens:enabled"
- },
- {
- "command": "gitlens.stashSave",
- "when": "gitlens:enabled"
- }
- ],
- "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",
- "group": "navigation@100"
- },
- {
- "command": "gitlens.diffWithPrevious",
- "when": "editorTextFocus && gitlens:isTracked && config.gitlens.menus.diff.enabled",
- "group": "2_gitlens"
- },
- {
- "command": "gitlens.diffWithWorking",
- "when": "editorTextFocus && gitlens:isTracked && config.gitlens.menus.diff.enabled",
- "group": "2_gitlens"
- },
- {
- "command": "gitlens.showQuickFileHistory",
- "when": "editorFocus && gitlens:isTracked",
- "group": "2_gitlens_1"
- },
- {
- "command": "gitlens.showQuickRepoHistory",
- "when": "!editorFocus && gitlens:enabled",
- "group": "2_gitlens_1"
- },
- {
- "command": "gitlens.showQuickRepoStatus",
- "when": "gitlens:enabled",
- "group": "2_gitlens_1"
- }
- ],
- "editor/title/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.toggleBlame",
- "when": "gitlens:enabled",
- "group": "1_gitlens_1@2"
- },
- {
- "command": "gitlens.openFileInRemote",
- "when": "gitlens:enabled",
- "group": "1_gitlens_1@3"
- }
- ],
- "editor/context": [
- {
- "command": "gitlens.diffLineWithPrevious",
- "when": "editorTextFocus && gitlens:isBlameable && config.gitlens.menus.diff.enabled",
- "group": "1_gitlens@1"
- },
- {
- "command": "gitlens.diffLineWithWorking",
- "when": "editorTextFocus && gitlens:isBlameable && config.gitlens.menus.diff.enabled",
- "group": "1_gitlens@2"
- },
- {
- "command": "gitlens.showQuickCommitFileDetails",
- "when": "editorTextFocus && gitlens:isBlameable",
- "group": "1_gitlens@3"
- },
- {
- "command": "gitlens.diffWithPrevious",
- "when": "editorTextFocus && gitlens:isTracked && config.gitlens.menus.diff.enabled",
- "group": "1_gitlens_1@1"
- },
- {
- "command": "gitlens.diffWithWorking",
- "when": "editorTextFocus && gitlens:isTracked && config.gitlens.menus.diff.enabled",
- "group": "1_gitlens_1@2"
- },
- {
- "command": "gitlens.showQuickFileHistory",
- "when": "gitlens:isTracked",
- "group": "3_gitlens@1"
- },
- {
- "command": "gitlens.toggleBlame",
- "when": "editorTextFocus && gitlens:isBlameable",
- "group": "3_gitlens@2"
- },
- {
- "command": "gitlens.openFileInRemote",
- "when": "editorTextFocus && gitlens:isTracked && gitlens:hasRemotes",
- "group": "3_gitlens@3"
- },
- {
- "command": "gitlens.copyShaToClipboard",
- "when": "editorTextFocus && gitlens:isBlameable",
- "group": "9_gitlens@1"
- },
- {
- "command": "gitlens.copyMessageToClipboard",
- "when": "editorTextFocus && gitlens:isBlameable",
- "group": "9_gitlens@2"
- }
- ]
- },
- "keybindings": [
- {
- "command": "gitlens.key.left",
- "key": "alt+left",
- "when": "gitlens:key:left"
- },
- {
- "command": "gitlens.key.right",
- "key": "alt+right",
- "when": "gitlens:key:right"
- },
- {
- "command": "gitlens.key.,",
- "key": "alt+,",
- "when": "gitlens:key:,"
- },
- {
- "command": "gitlens.key..",
- "key": "alt+.",
- "when": "gitlens:key:."
- },
- {
- "command": "gitlens.toggleBlame",
- "key": "alt+b",
- "mac": "alt+b",
- "when": "editorTextFocus && gitlens:isTracked"
- },
- {
- "command": "gitlens.toggleCodeLens",
- "key": "shift+alt+b",
- "mac": "shift+alt+b",
- "when": "editorTextFocus && gitlens:isTracked && gitlens:canToggleCodeLens"
- },
- {
- "command": "gitlens.showLastQuickPick",
- "key": "alt+-",
- "mac": "alt+-",
- "when": "gitlens:enabled"
- },
- {
- "command": "gitlens.showCommitSearch",
- "key": "alt+/",
- "mac": "alt+/",
- "when": "gitlens:enabled"
- },
- {
- "command": "gitlens.showQuickFileHistory",
- "key": "alt+h",
- "mac": "alt+h",
- "when": "gitlens:enabled"
- },
- {
- "command": "gitlens.showQuickRepoHistory",
- "key": "shift+alt+h",
- "mac": "shift+alt+h",
- "when": "gitlens:enabled"
- },
- {
- "command": "gitlens.showQuickRepoStatus",
- "key": "alt+s",
- "mac": "alt+s",
- "when": "gitlens:enabled"
- },
- {
- "command": "gitlens.showQuickCommitFileDetails",
- "key": "alt+c",
- "mac": "alt+c",
- "when": "editorTextFocus && gitlens:enabled"
- },
- {
- "command": "gitlens.diffWithNext",
- "key": "alt+.",
- "mac": "alt+.",
- "when": "editorTextFocus && gitlens:isTracked"
- },
- {
- "command": "gitlens.diffLineWithPrevious",
- "key": "shift+alt+,",
- "mac": "shift+alt+,",
- "when": "editorTextFocus && gitlens:isTracked"
- },
- {
- "command": "gitlens.diffWithPrevious",
- "key": "alt+,",
- "mac": "alt+,",
- "when": "editorTextFocus && gitlens:isTracked"
- },
- {
- "command": "gitlens.diffLineWithWorking",
- "key": "alt+w",
- "mac": "alt+w",
- "when": "editorTextFocus && gitlens:isTracked"
- },
- {
- "command": "gitlens.diffWithWorking",
- "key": "shift+alt+w",
- "mac": "shift+alt+w",
- "when": "editorTextFocus && gitlens:isTracked"
- }
- ]
- },
- "activationEvents": [
- "*"
- ],
- "scripts": {
- "clean": "git clean -xdf",
- "compile": "tslint --project tslint.json && tsc -p ./",
- "watch": "tsc -watch -p ./",
- "lint": "tslint --project tslint.json",
- "pack": "git clean -xdf && vsce package",
- "postinstall": "node ./node_modules/vscode/bin/install",
- "pub": "git clean -xdf && vsce publish",
- "reset": "git clean -xdf && npm install",
- "vscode:prepublish": "npm install --no-save && npm run compile"
- },
- "dependencies": {
- "applicationinsights": "0.20.1",
- "copy-paste": "1.3.0",
- "iconv-lite": "0.4.17",
- "ignore": "3.3.3",
- "lodash.debounce": "4.0.8",
- "lodash.escaperegexp": "4.1.2",
- "lodash.isequal": "4.5.0",
- "lodash.once": "4.1.1",
- "moment": "2.18.1",
- "spawn-rx": "2.0.11",
- "tmp": "0.0.31"
- },
- "devDependencies": {
- "@types/copy-paste": "1.1.30",
- "@types/iconv-lite": "0.0.1",
- "@types/mocha": "2.2.41",
- "@types/node": "7.0.28",
- "@types/tmp": "0.0.33",
- "mocha": "3.4.2",
- "tslint": "5.4.3",
- "typescript": "2.3.4",
- "vscode": "1.1.0"
- }
-}
+{
+ "name": "gitlens",
+ "version": "3.6.1",
+ "author": {
+ "name": "Eric Amodio",
+ "email": "eamodio@gmail.com"
+ },
+ "publisher": "eamodio",
+ "engines": {
+ "vscode": "^1.12.0"
+ },
+ "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 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",
+ "href": "https://gitter.im/vscode-gitlens/Lobby",
+ "description": "Chat at https://gitter.im/vscode-gitlens/Lobby"
+ }
+ ],
+ "categories": [
+ "Other"
+ ],
+ "keywords": [
+ "git",
+ "code lens",
+ "blame",
+ "history",
+ "annotation",
+ "log",
+ "inline blame",
+ "compare",
+ "diff"
+ ],
+ "galleryBanner": {
+ "color": "#56098c",
+ "theme": "dark"
+ },
+ "icon": "images/gitlens-icon.svg",
+ "preview": false,
+ "homepage": "https://github.com/eamodio/vscode-gitlens/blob/master/README.md",
+ "bugs": {
+ "url": "https://github.com/eamodio/vscode-gitlens/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/eamodio/vscode-gitlens.git"
+ },
+ "main": "./out/src/extension",
+ "contributes": {
+ "configuration": {
+ "type": "object",
+ "title": "GitLens configuration",
+ "properties": {
+ "gitlens.debug": {
+ "type": "boolean",
+ "default": false,
+ "description": "Specifies debug mode"
+ },
+ "gitlens.insiders": {
+ "type": "boolean",
+ "default": false,
+ "description": "Specifies whether or not to enable new experimental features (expect there to be issues)"
+ },
+ "gitlens.outputLevel": {
+ "type": "string",
+ "default": "silent",
+ "enum": [
+ "silent",
+ "errors",
+ "verbose"
+ ],
+ "description": "Specifies how much (if any) output will be sent to the GitLens output channel"
+ },
+ "gitlens.annotations.file.gutter.format": {
+ "type": "string",
+ "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.annotations.file.gutter.dateFormat": {
+ "type": "string",
+ "default": null,
+ "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.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 or not to trigger hover annotations over the whole line"
+ },
+ "gitlens.annotations.file.hover.heatmap.enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Specifies whether or not to provide heatmap indicators on the left edge of each line"
+ },
+ "gitlens.annotations.file.hover.wholeLine": {
+ "type": "boolean",
+ "default": true,
+ "description": "Specifies whether or not to trigger hover annotations over the whole line"
+ },
+ "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": "${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": [
+ "gutter",
+ "hover"
+ ],
+ "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 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.toggleFileBlame",
+ "gitlens.showBlameHistory",
+ "gitlens.showFileHistory",
+ "gitlens.diffWithPrevious",
+ "gitlens.showQuickCommitDetails",
+ "gitlens.showQuickCommitFileDetails",
+ "gitlens.showQuickFileHistory",
+ "gitlens.showQuickRepoHistory"
+ ],
+ "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.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": "gitlens.toggleFileBlame",
+ "enum": [
+ "gitlens.toggleFileBlame",
+ "gitlens.showBlameHistory",
+ "gitlens.showFileHistory",
+ "gitlens.diffWithPrevious",
+ "gitlens.showQuickCommitDetails",
+ "gitlens.showQuickCommitFileDetails",
+ "gitlens.showQuickFileHistory",
+ "gitlens.showQuickRepoHistory"
+ ],
+ "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.locations": {
+ "type": "array",
+ "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.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",
+ "locations": [
+ "document"
+ ]
+ },
+ {
+ "language": "html",
+ "locations": [
+ "document"
+ ]
+ },
+ {
+ "language": "json",
+ "locations": [
+ "document"
+ ]
+ },
+ {
+ "language": "less",
+ "locations": [
+ "document"
+ ]
+ },
+ {
+ "language": "scss",
+ "locations": [
+ "document"
+ ]
+ },
+ {
+ "language": "vue",
+ "locations": [
+ "document"
+ ]
+ }
+ ],
+ "items": {
+ "type": "object",
+ "required": [
+ "language",
+ "locations"
+ ],
+ "properties": {
+ "language": {
+ "type": "string",
+ "description": "Specifies the language to which this code lens override applies"
+ },
+ "locations": {
+ "type": "array",
+ "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 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": "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,
+ "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.statusBar.enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Specifies whether or not to provide blame information on the status bar"
+ },
+ "gitlens.statusBar.alignment": {
+ "type": "string",
+ "default": "right",
+ "enum": [
+ "left",
+ "right"
+ ],
+ "description": "Specifies the blame alignment in the status bar. `left` - align to the left, `right` - align to the right"
+ },
+ "gitlens.statusBar.command": {
+ "type": "string",
+ "default": "gitlens.showQuickCommitDetails",
+ "enum": [
+ "gitlens.toggleFileBlame",
+ "gitlens.showBlameHistory",
+ "gitlens.showFileHistory",
+ "gitlens.diffWithPrevious",
+ "gitlens.diffWithWorking",
+ "gitlens.toggleCodeLens",
+ "gitlens.showQuickCommitDetails",
+ "gitlens.showQuickCommitFileDetails",
+ "gitlens.showQuickFileHistory",
+ "gitlens.showQuickRepoHistory"
+ ],
+ "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.format": {
+ "type": "string",
+ "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 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 output will be cached"
+ },
+ "gitlens.advanced.caching.maxLines": {
+ "type": "number",
+ "default": 0,
+ "description": "Specifies the threshold for caching larger documents"
+ },
+ "gitlens.advanced.git": {
+ "type": "string",
+ "default": null,
+ "description": "Specifies the git path to use"
+ },
+ "gitlens.advanced.gitignore.enabled": {
+ "type": "boolean",
+ "default": true,
+ "description": "Specifies whether or not to parse the root .gitignore file for better performance (i.e. avoids blaming excluded files)"
+ },
+ "gitlens.advanced.maxQuickHistory": {
+ "type": "number",
+ "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,
+ "description": "Specifies whether or not to close the QuickPick menu when focus is lost"
+ },
+ "gitlens.advanced.toggleWhitespace.enabled": {
+ "type": "boolean",
+ "default": false,
+ "description": "Specifies whether or not to toggle whitespace off then showing blame annotations (*may* be required by certain fonts/themes)"
+ }
+ }
+ },
+ "commands": [
+ {
+ "command": "gitlens.diffDirectory",
+ "title": "Directory Compare",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.diffWithBranch",
+ "title": "Compare File with...",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.diffWithNext",
+ "title": "Compare File with Next Commit",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.diffWithPrevious",
+ "title": "Compare File with Previous",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.diffLineWithPrevious",
+ "title": "Compare Line Commit with Previous",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.diffWithWorking",
+ "title": "Compare File with Working Tree",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.diffLineWithWorking",
+ "title": "Compare Line Commit with Working Tree",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.showFileBlame",
+ "title": "Show File Blame Annotations",
+ "category": "GitLens"
+ },
+ {
+ "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",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.showBlameHistory",
+ "title": "Open Blame History Explorer",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.showCommitSearch",
+ "title": "Search Commits",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.showFileHistory",
+ "title": "Open File History Explorer",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.showLastQuickPick",
+ "title": "Show Last Opened Quick Pick",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.showQuickCommitDetails",
+ "title": "Show Commit Details",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.showQuickCommitFileDetails",
+ "title": "Show Line Commit Details",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.showQuickFileHistory",
+ "title": "Show File History",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.showQuickBranchHistory",
+ "title": "Show Branch History",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.showQuickRepoHistory",
+ "title": "Show Current Branch History",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.showQuickRepoStatus",
+ "title": "Show Repository Status",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.showQuickStashList",
+ "title": "Show Stashed Changes",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.copyShaToClipboard",
+ "title": "Copy Commit ID to Clipboard",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.copyMessageToClipboard",
+ "title": "Copy Commit Message to Clipboard",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.closeUnchangedFiles",
+ "title": "Close Unchanged Files",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.openChangedFiles",
+ "title": "Open Changed Files",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.openBranchInRemote",
+ "title": "Open Branch in Remote",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.openCommitInRemote",
+ "title": "Open Line Commit in Remote",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.openFileInRemote",
+ "title": "Open File in Remote",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.openRepoInRemote",
+ "title": "Open Repository in Remote",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.stashApply",
+ "title": "Apply Stashed Changes",
+ "category": "GitLens"
+ },
+ {
+ "command": "gitlens.stashSave",
+ "title": "Stash Changes",
+ "category": "GitLens"
+ }
+ ],
+ "menus": {
+ "commandPalette": [
+ {
+ "command": "gitlens.diffDirectory",
+ "when": "gitlens:enabled"
+ },
+ {
+ "command": "gitlens.diffWithBranch",
+ "when": "gitlens:isTracked"
+ },
+ {
+ "command": "gitlens.diffWithNext",
+ "when": "gitlens:isTracked"
+ },
+ {
+ "command": "gitlens.diffWithPrevious",
+ "when": "gitlens:isTracked"
+ },
+ {
+ "command": "gitlens.diffLineWithPrevious",
+ "when": "gitlens:isBlameable"
+ },
+ {
+ "command": "gitlens.diffWithWorking",
+ "when": "gitlens:isTracked"
+ },
+ {
+ "command": "gitlens.diffLineWithWorking",
+ "when": "gitlens:isBlameable"
+ },
+ {
+ "command": "gitlens.showFileBlame",
+ "when": "gitlens:isBlameable"
+ },
+ {
+ "command": "gitlens.showLineBlame",
+ "when": "gitlens:isBlameable"
+ },
+ {
+ "command": "gitlens.toggleFileBlame",
+ "when": "gitlens:isBlameable"
+ },
+ {
+ "command": "gitlens.toggleLineBlame",
+ "when": "gitlens:isBlameable"
+ },
+ {
+ "command": "gitlens.toggleCodeLens",
+ "when": "gitlens:isTracked && gitlens:canToggleCodeLens"
+ },
+ {
+ "command": "gitlens.showBlameHistory",
+ "when": "gitlens:isBlameable"
+ },
+ {
+ "command": "gitlens.showFileHistory",
+ "when": "gitlens:isTracked"
+ },
+ {
+ "command": "gitlens.showLastQuickPick",
+ "when": "gitlens:enabled"
+ },
+ {
+ "command": "gitlens.showQuickCommitDetails",
+ "when": "gitlens:isBlameable"
+ },
+ {
+ "command": "gitlens.showQuickCommitFileDetails",
+ "when": "gitlens:isBlameable"
+ },
+ {
+ "command": "gitlens.showQuickFileHistory",
+ "when": "gitlens:isTracked"
+ },
+ {
+ "command": "gitlens.showQuickBranchHistory",
+ "when": "gitlens:enabled"
+ },
+ {
+ "command": "gitlens.showQuickRepoHistory",
+ "when": "gitlens:enabled"
+ },
+ {
+ "command": "gitlens.showQuickRepoStatus",
+ "when": "gitlens:enabled"
+ },
+ {
+ "command": "gitlens.showQuickStashList",
+ "when": "gitlens:enabled"
+ },
+ {
+ "command": "gitlens.copyShaToClipboard",
+ "when": "gitlens:isBlameable"
+ },
+ {
+ "command": "gitlens.copyMessageToClipboard",
+ "when": "gitlens:isBlameable"
+ },
+ {
+ "command": "gitlens.closeUnchangedFiles",
+ "when": "gitlens:enabled"
+ },
+ {
+ "command": "gitlens.openChangedFiles",
+ "when": "gitlens:enabled"
+ },
+ {
+ "command": "gitlens.openBranchInRemote",
+ "when": "gitlens:hasRemotes"
+ },
+ {
+ "command": "gitlens.openCommitInRemote",
+ "when": "gitlens:isBlameable && gitlens:hasRemotes"
+ },
+ {
+ "command": "gitlens.openFileInRemote",
+ "when": "gitlens:isTracked && gitlens:hasRemotes"
+ },
+ {
+ "command": "gitlens.openRepoInRemote",
+ "when": "gitlens:hasRemotes"
+ },
+ {
+ "command": "gitlens.stashApply",
+ "when": "gitlens:enabled"
+ },
+ {
+ "command": "gitlens.stashSave",
+ "when": "gitlens:enabled"
+ }
+ ],
+ "explorer/context": [
+ {
+ "command": "gitlens.openFileInRemote",
+ "when": "gitlens:enabled && config.gitlens.advanced.menus.explorerContext.remote",
+ "group": "navigation@100"
+ },
+ {
+ "command": "gitlens.diffWithPrevious",
+ "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.advanced.menus.editorTitle.fileDiff",
+ "group": "2_gitlens"
+ },
+ {
+ "command": "gitlens.showQuickFileHistory",
+ "when": "editorFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorTitle.history",
+ "group": "2_gitlens_1"
+ },
+ {
+ "command": "gitlens.showQuickRepoHistory",
+ "when": "!editorFocus && gitlens:enabled && config.gitlens.advanced.menus.editorTitle.history",
+ "group": "2_gitlens_1"
+ },
+ {
+ "command": "gitlens.showQuickRepoStatus",
+ "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.advanced.menus.editorTitleContext.fileDiff",
+ "group": "1_gitlens_1@1"
+ },
+ {
+ "command": "gitlens.diffWithWorking",
+ "when": "gitlens:enabled && config.gitlens.advanced.menus.editorTitleContext.fileDiff",
+ "group": "1_gitlens_1@2"
+ },
+ {
+ "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.advanced.menus.editorContext.lineDiff",
+ "group": "1_gitlens@1"
+ },
+ {
+ "command": "gitlens.diffLineWithWorking",
+ "when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.lineDiff",
+ "group": "1_gitlens@2"
+ },
+ {
+ "command": "gitlens.showQuickCommitFileDetails",
+ "when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.details",
+ "group": "1_gitlens@3"
+ },
+ {
+ "command": "gitlens.diffWithPrevious",
+ "when": "editorTextFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorContext.fileDiff",
+ "group": "1_gitlens_1@1"
+ },
+ {
+ "command": "gitlens.diffWithWorking",
+ "when": "editorTextFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorContext.fileDiff",
+ "group": "1_gitlens_1@2"
+ },
+ {
+ "command": "gitlens.showQuickFileHistory",
+ "when": "gitlens:isTracked && config.gitlens.advanced.menus.editorContext.history",
+ "group": "3_gitlens@1"
+ },
+ {
+ "command": "gitlens.toggleFileBlame",
+ "when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.blame",
+ "group": "3_gitlens@2"
+ },
+ {
+ "command": "gitlens.copyShaToClipboard",
+ "when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.copy",
+ "group": "9_gitlens@1"
+ },
+ {
+ "command": "gitlens.copyMessageToClipboard",
+ "when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.copy",
+ "group": "9_gitlens@2"
+ }
+ ]
+ },
+ "keybindings": [
+ {
+ "command": "gitlens.key.left",
+ "key": "alt+left",
+ "when": "gitlens:key:left"
+ },
+ {
+ "command": "gitlens.key.right",
+ "key": "alt+right",
+ "when": "gitlens:key:right"
+ },
+ {
+ "command": "gitlens.key.,",
+ "key": "alt+,",
+ "when": "gitlens:key:,"
+ },
+ {
+ "command": "gitlens.key..",
+ "key": "alt+.",
+ "when": "gitlens:key:."
+ },
+ {
+ "command": "gitlens.toggleFileBlame",
+ "key": "alt+b",
+ "mac": "alt+b",
+ "when": "editorTextFocus && gitlens:isTracked"
+ },
+ {
+ "command": "gitlens.toggleCodeLens",
+ "key": "shift+alt+b",
+ "mac": "shift+alt+b",
+ "when": "editorTextFocus && gitlens:isTracked && gitlens:canToggleCodeLens"
+ },
+ {
+ "command": "gitlens.showLastQuickPick",
+ "key": "alt+-",
+ "mac": "alt+-",
+ "when": "gitlens:enabled"
+ },
+ {
+ "command": "gitlens.showCommitSearch",
+ "key": "alt+/",
+ "mac": "alt+/",
+ "when": "gitlens:enabled"
+ },
+ {
+ "command": "gitlens.showQuickFileHistory",
+ "key": "alt+h",
+ "mac": "alt+h",
+ "when": "gitlens:enabled"
+ },
+ {
+ "command": "gitlens.showQuickRepoHistory",
+ "key": "shift+alt+h",
+ "mac": "shift+alt+h",
+ "when": "gitlens:enabled"
+ },
+ {
+ "command": "gitlens.showQuickRepoStatus",
+ "key": "alt+s",
+ "mac": "alt+s",
+ "when": "gitlens:enabled"
+ },
+ {
+ "command": "gitlens.showQuickCommitFileDetails",
+ "key": "alt+c",
+ "mac": "alt+c",
+ "when": "editorTextFocus && gitlens:enabled"
+ },
+ {
+ "command": "gitlens.diffWithNext",
+ "key": "alt+.",
+ "mac": "alt+.",
+ "when": "editorTextFocus && gitlens:isTracked"
+ },
+ {
+ "command": "gitlens.diffLineWithPrevious",
+ "key": "shift+alt+,",
+ "mac": "shift+alt+,",
+ "when": "editorTextFocus && gitlens:isTracked"
+ },
+ {
+ "command": "gitlens.diffWithPrevious",
+ "key": "alt+,",
+ "mac": "alt+,",
+ "when": "editorTextFocus && gitlens:isTracked"
+ },
+ {
+ "command": "gitlens.diffLineWithWorking",
+ "key": "alt+w",
+ "mac": "alt+w",
+ "when": "editorTextFocus && gitlens:isTracked"
+ },
+ {
+ "command": "gitlens.diffWithWorking",
+ "key": "shift+alt+w",
+ "mac": "shift+alt+w",
+ "when": "editorTextFocus && gitlens:isTracked"
+ }
+ ]
+ },
+ "activationEvents": [
+ "*"
+ ],
+ "scripts": {
+ "clean": "git clean -xdf",
+ "compile": "tslint --project tslint.json && tsc -p ./",
+ "watch": "tsc -watch -p ./",
+ "lint": "tslint --project tslint.json",
+ "pack": "git clean -xdf && vsce package",
+ "postinstall": "node ./node_modules/vscode/bin/install",
+ "pub": "git clean -xdf && vsce publish",
+ "reset": "git clean -xdf && npm install",
+ "vscode:prepublish": "npm install --no-save && npm run compile"
+ },
+ "dependencies": {
+ "applicationinsights": "0.20.1",
+ "copy-paste": "1.3.0",
+ "iconv-lite": "0.4.17",
+ "ignore": "3.3.3",
+ "lodash.debounce": "4.0.8",
+ "lodash.escaperegexp": "4.1.2",
+ "lodash.isequal": "4.5.0",
+ "lodash.once": "4.1.1",
+ "moment": "2.18.1",
+ "spawn-rx": "2.0.11",
+ "tmp": "0.0.31"
+ },
+ "devDependencies": {
+ "@types/copy-paste": "1.1.30",
+ "@types/iconv-lite": "0.0.1",
+ "@types/mocha": "2.2.41",
+ "@types/node": "7.0.28",
+ "@types/tmp": "0.0.33",
+ "mocha": "3.4.2",
+ "tslint": "5.4.3",
+ "typescript": "2.3.4",
+ "vscode": "1.1.0"
+ }
+}
diff --git a/src/annotations/annotationController.ts b/src/annotations/annotationController.ts
new file mode 100644
index 0000000..995513e
--- /dev/null
+++ b/src/annotations/annotationController.ts
@@ -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();
+ get onDidToggleAnnotations(): Event {
+ return this._onDidToggleAnnotations.event;
+ }
+
+ private _annotationsDisposable: Disposable | undefined;
+ private _annotationProviders: Map = 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('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('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(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 {
+ 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 {
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/annotations/annotationProvider.ts b/src/annotations/annotationProvider.ts
new file mode 100644
index 0000000..e36168e
--- /dev/null
+++ b/src/annotations/annotationProvider.ts
@@ -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(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(ExtensionKey)!;
+
+ await this.provideAnnotation(this.editor === undefined ? undefined : this.editor.selection.active.line);
+ }
+
+ abstract async provideAnnotation(shaOrLine?: string | number): Promise;
+ abstract async selection(shaOrLine?: string | number): Promise;
+ abstract async validate(): Promise;
+ }
\ No newline at end of file
diff --git a/src/annotations/annotations.ts b/src/annotations/annotations.ts
new file mode 100644
index 0000000..6b71b38
--- /dev/null
+++ b/src/annotations/annotations.ts
@@ -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 {
+ 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 } };
+ }
+}
\ No newline at end of file
diff --git a/src/annotations/blameAnnotationProvider.ts b/src/annotations/blameAnnotationProvider.ts
new file mode 100644
index 0000000..8e02c47
--- /dev/null
+++ b/src/annotations/blameAnnotationProvider.ts
@@ -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;
+
+ 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 {
+ const blame = await this._blame;
+ return blame !== undefined && blame.lines.length !== 0;
+ }
+
+ protected async getBlame(requiresWhitespaceHack: boolean): Promise {
+ let whitespacePromise: Promise | 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;
+ }
+}
\ No newline at end of file
diff --git a/src/annotations/diffAnnotationProvider.ts b/src/annotations/diffAnnotationProvider.ts
new file mode 100644
index 0000000..23db986
--- /dev/null
+++ b/src/annotations/diffAnnotationProvider.ts
@@ -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 {
+ // 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 {
+ }
+
+ async validate(): Promise {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/annotations/gutterBlameAnnotationProvider.ts b/src/annotations/gutterBlameAnnotationProvider.ts
new file mode 100644
index 0000000..a125438
--- /dev/null
+++ b/src/annotations/gutterBlameAnnotationProvider.ts
@@ -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 {
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/src/annotations/hoverBlameAnnotationProvider.ts b/src/annotations/hoverBlameAnnotationProvider.ts
new file mode 100644
index 0000000..64694f0
--- /dev/null
+++ b/src/annotations/hoverBlameAnnotationProvider.ts
@@ -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 {
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/src/whitespaceController.ts b/src/annotations/whitespaceController.ts
similarity index 96%
rename from src/whitespaceController.ts
rename to src/annotations/whitespaceController.ts
index 67a54ef..2a02145 100644
--- a/src/whitespaceController.ts
+++ b/src/annotations/whitespaceController.ts
@@ -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));
}
}
diff --git a/src/blameActiveLineController.ts b/src/blameActiveLineController.ts
deleted file mode 100644
index a99da0b..0000000
--- a/src/blameActiveLineController.ts
+++ /dev/null
@@ -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;
- 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(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 {
- // 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);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/blameAnnotationController.ts b/src/blameAnnotationController.ts
deleted file mode 100644
index 5cc80b8..0000000
--- a/src/blameAnnotationController.ts
+++ /dev/null
@@ -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();
- get onDidToggleBlameAnnotations(): Event {
- return this._onDidToggleBlameAnnotations.event;
- }
-
- private _annotationProviders: Map = 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('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('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('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 {
- 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 {
- 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);
- }
- }
-}
\ No newline at end of file
diff --git a/src/blameAnnotationFormatter.ts b/src/blameAnnotationFormatter.ts
deleted file mode 100644
index 007383b..0000000
--- a/src/blameAnnotationFormatter.ts
+++ /dev/null
@@ -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;
- }
-}
\ No newline at end of file
diff --git a/src/blameAnnotationProvider.ts b/src/blameAnnotationProvider.ts
deleted file mode 100644
index 0e9bba1..0000000
--- a/src/blameAnnotationProvider.ts
+++ /dev/null
@@ -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;
- 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('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 {
- const blame = await this._blame;
- return !!(blame && blame.lines.length);
- }
-
- async provideBlameAnnotation(shaOrLine?: string | number): Promise {
- let whitespacePromise: Promise | 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;
- });
- }
-}
\ No newline at end of file
diff --git a/src/commands.ts b/src/commands.ts
index 2f33c49..a84e773 100644
--- a/src/commands.ts
+++ b/src/commands.ts
@@ -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';
\ No newline at end of file
+export * from './commands/toggleCodeLens';
+export * from './commands/toggleFileBlame';
+export * from './commands/toggleLineBlame';
\ No newline at end of file
diff --git a/src/commands/common.ts b/src/commands/common.ts
index e850379..ae6c56b 100644
--- a/src/commands/common.ts
+++ b/src/commands/common.ts
@@ -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
};
diff --git a/src/commands/showBlame.ts b/src/commands/showBlame.ts
deleted file mode 100644
index efb78d7..0000000
--- a/src/commands/showBlame.ts
+++ /dev/null
@@ -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 {
- 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`);
- }
- }
-}
\ No newline at end of file
diff --git a/src/commands/showFileBlame.ts b/src/commands/showFileBlame.ts
new file mode 100644
index 0000000..d18d24c
--- /dev/null
+++ b/src/commands/showFileBlame.ts
@@ -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 {
+ if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
+
+ try {
+ if (args.type === undefined) {
+ const cfg = workspace.getConfiguration().get(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`);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/commands/showLineBlame.ts b/src/commands/showLineBlame.ts
new file mode 100644
index 0000000..340a559
--- /dev/null
+++ b/src/commands/showLineBlame.ts
@@ -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 {
+ if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
+
+ try {
+ if (args.type === undefined) {
+ const cfg = workspace.getConfiguration().get(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`);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/commands/toggleBlame.ts b/src/commands/toggleBlame.ts
deleted file mode 100644
index b26d053..0000000
--- a/src/commands/toggleBlame.ts
+++ /dev/null
@@ -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 {
- 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`);
- }
- }
-}
\ No newline at end of file
diff --git a/src/commands/toggleFileBlame.ts b/src/commands/toggleFileBlame.ts
new file mode 100644
index 0000000..901d1d2
--- /dev/null
+++ b/src/commands/toggleFileBlame.ts
@@ -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 {
+ if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
+
+ try {
+ if (args.type === undefined) {
+ const cfg = workspace.getConfiguration().get(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`);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/commands/toggleLineBlame.ts b/src/commands/toggleLineBlame.ts
new file mode 100644
index 0000000..3742ddd
--- /dev/null
+++ b/src/commands/toggleLineBlame.ts
@@ -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 {
+ if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
+
+ try {
+ if (args.type === undefined) {
+ const cfg = workspace.getConfiguration().get(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`);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/configuration.ts b/src/configuration.ts
index bd686d2..5975bd5 100644
--- a/src/configuration.ts
+++ b/src/configuration.ts
@@ -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 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 BlameLineHighlightLocations = 'gutter' | 'line' | 'overviewRuler';
+export const BlameLineHighlightLocations = {
+ Gutter: 'gutter' as BlameLineHighlightLocations,
+ Line: 'line' as BlameLineHighlightLocations,
+ OverviewRuler: 'overviewRuler' as BlameLineHighlightLocations
+};
-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 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 LineAnnotationType = 'trailing' | 'hover';
+export const LineAnnotationType = {
+ Trailing: 'trailing' as LineAnnotationType,
+ Hover: 'hover' as LineAnnotationType
+};
-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;
+ insiders: boolean;
outputLevel: OutputLevel;
- blame: IBlameConfig;
- codeLens: ICodeLensesConfig;
- statusBar: IStatusBarConfig;
+
advanced: IAdvancedConfig;
- insiders: boolean;
}
\ No newline at end of file
diff --git a/src/currentLineController.ts b/src/currentLineController.ts
new file mode 100644
index 0000000..f19cd31
--- /dev/null
+++ b/src/currentLineController.ts
@@ -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;
+ 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(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 {
+ // 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();
+ }
+}
\ No newline at end of file
diff --git a/src/extension.ts b/src/extension.ts
index 9380c40..86fcfb9 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -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());
diff --git a/src/git/formatters/commit.ts b/src/git/formatters/commit.ts
new file mode 100644
index 0000000..aef578a
--- /dev/null
+++ b/src/git/formatters/commit.ts
@@ -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()}
+\`\`\``;
+ }
+}
\ No newline at end of file
diff --git a/src/git/models/diff.ts b/src/git/models/diff.ts
index a52cf5d..51c1d3e 100644
--- a/src/git/models/diff.ts
+++ b/src/git/models/diff.ts
@@ -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;
diff --git a/src/git/parsers/blameParser.ts b/src/git/parsers/blameParser.ts
index 1c20438..125b939 100644
--- a/src/git/parsers/blameParser.ts
+++ b/src/git/parsers/blameParser.ts
@@ -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;
diff --git a/src/git/parsers/diffParser.ts b/src/git/parsers/diffParser.ts
index 0d96cb7..569aac6 100644
--- a/src/git/parsers/diffParser.ts
+++ b/src/git/parsers/diffParser.ts
@@ -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;
}
}
diff --git a/src/git/parsers/logParser.ts b/src/git/parsers/logParser.ts
index 3f69f75..3bc13a0 100644
--- a/src/git/parsers/logParser.ts
+++ b/src/git/parsers/logParser.ts
@@ -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;
diff --git a/src/gitCodeLensProvider.ts b/src/gitCodeLensProvider.ts
index d56d253..f67a3a7 100644
--- a/src/gitCodeLensProvider.ts
+++ b/src/gitCodeLensProvider.ts
@@ -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 {
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(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;
diff --git a/src/gitService.ts b/src/gitService.ts
index 68989e3..7962968 100644
--- a/src/gitService.ts
+++ b/src/gitService.ts
@@ -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) {
diff --git a/src/quickPicks/common.ts b/src/quickPicks/common.ts
index 10dc8ef..29ca012 100644
--- a/src/quickPicks/common.ts
+++ b/src/quickPicks/common.ts
@@ -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';
diff --git a/src/system/function.ts b/src/system/function.ts
index 3b5f0e0..9ab6d82 100644
--- a/src/system/function.ts
+++ b/src/system/function.ts
@@ -15,4 +15,8 @@ export namespace Functions {
export function once(fn: T): T {
return _once(fn);
}
+
+ export async function wait(ms: number) {
+ await new Promise(resolve => setTimeout(resolve, ms));
+ }
}
\ No newline at end of file
diff --git a/src/system/object.ts b/src/system/object.ts
index cc1029f..01244be 100644
--- a/src/system/object.ts
+++ b/src/system/object.ts
@@ -55,4 +55,10 @@ export namespace Objects {
}
}
}
+
+ export function* values(o: any): IterableIterator<[any]> {
+ for (const key in o) {
+ yield [o[key]];
+ }
+ }
}
\ No newline at end of file
diff --git a/src/system/string.ts b/src/system/string.ts
index e9ef1b7..b0a1954 100644
--- a/src/system/string.ts
+++ b/src/system/string.ts
@@ -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`;
+ }
}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index 6a41444..e40fc16 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -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",
diff --git a/tslint.json b/tslint.json
index bf5beb6..f5c0c8b 100644
--- a/tslint.json
+++ b/tslint.json
@@ -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,