mirror of
https://github.com/ckaczor/vscode-gitlens.git
synced 2026-02-13 11:38:40 -05:00
Compare commits
5 Commits
v5.1.1-bet
...
v5.2.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2245d82319 | ||
|
|
f7df845dfe | ||
|
|
712544fab8 | ||
|
|
a114e2de87 | ||
|
|
70071448d6 |
13
CHANGELOG.md
13
CHANGELOG.md
@@ -6,17 +6,22 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [5.1.1-beta]
|
## [5.2.0-beta] - 2017-09-20
|
||||||
### Added
|
### Added
|
||||||
- Adds new `Changed Files` node to the `Repository Status` node of the `GitLens` custom view's `Repository View` (enabled via `"gitlens.insiders": true`) -- closes [#139](https://github.com/eamodio/vscode-gitlens/issues/139)
|
- Adds working tree status (enabled via `"gitlens.insiders": true`) to the `Repository Status` node in the `GitLens` custom view
|
||||||
- Provides a file-based view of all the changed files in the working tree and/or files in commits that haven't yet been pushed upstream
|
- Adds new `Changed Files` node to the `Repository Status` node of the `GitLens` custom view's `Repository View` -- closes [#139](https://github.com/eamodio/vscode-gitlens/issues/139)
|
||||||
|
- Provides a at-a-glance view of all "working" changes
|
||||||
|
- Expands to a file-based view of all changed files in the working tree (enabled via `"gitlens.insiders": true`) and/or all files in all commits ahead of the upstream
|
||||||
- Adds `gitlens.gitExplorer.enabled` setting to specify whether or not to show the `GitLens` custom view - closes [#144](https://github.com/eamodio/vscode-gitlens/issues/144)
|
- Adds `gitlens.gitExplorer.enabled` setting to specify whether or not to show the `GitLens` custom view - closes [#144](https://github.com/eamodio/vscode-gitlens/issues/144)
|
||||||
|
- Adds `gitlens.gitExplorer.statusFileFormat` setting to the format of the status of a working or committed file in the `GitLens` custom view
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Chnages the default of the `gitlens.gitExplorer.commitFormat` setting to add parentheses around the commit id
|
- Changes the sorting (now alphabetical) of files shown in the `GitLens` custom view
|
||||||
|
- Changes the default of the `gitlens.gitExplorer.commitFormat` setting to add parentheses around the commit id
|
||||||
- Removes many menu items from `editor/title` & `editor/title/context` by default -- can be re-enabled via the `gitlens.advanced.menus` setting
|
- Removes many menu items from `editor/title` & `editor/title/context` by default -- can be re-enabled via the `gitlens.advanced.menus` setting
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
- Fixes [#146](https://github.com/eamodio/vscode-gitlens/issues/146) - Blame gutter annotation issue when commit contains emoji
|
||||||
- Fixes an issue when running `Open File in Remote` with a multi-line selection wasn't properly opening the selection in GitLab -- thanks to [PR #145](https://github.com/eamodio/vscode-gitlens/pull/145) by Amanda Cameron ([@AmandaCameron](https://github.com/AmandaCameron))!
|
- Fixes an issue when running `Open File in Remote` with a multi-line selection wasn't properly opening the selection in GitLab -- thanks to [PR #145](https://github.com/eamodio/vscode-gitlens/pull/145) by Amanda Cameron ([@AmandaCameron](https://github.com/AmandaCameron))!
|
||||||
- Fixes an issue where the `gitlens.advanced.menus` setting wasn't controlling all the menu items properly
|
- Fixes an issue where the `gitlens.advanced.menus` setting wasn't controlling all the menu items properly
|
||||||
|
|
||||||
|
|||||||
@@ -126,13 +126,17 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
|
|||||||

|

|
||||||
|
|
||||||
- `Repository Status` node — provides the status of the repository
|
- `Repository Status` node — provides the status of the repository
|
||||||
- Provides the name of the current branch, its upstream tracking branch (if available), and its upstream status (if available)
|
- Provides the name of the current branch, its working tree status (enabled via `"gitlens.insiders": true`), and its upstream tracking branch and status (if available)
|
||||||
- Provides indicator dots on the repository icon which denote the following:
|
- Provides indicator dots on the repository icon which denote the following:
|
||||||
- `None` - up-to-date with the upstream
|
- `None` - up-to-date with the upstream
|
||||||
- `Green` - ahead of the upstream
|
- `Green` - ahead of the upstream
|
||||||
- `Red` - behind the upstream
|
- `Red` - behind the upstream
|
||||||
- `Yellow` - both ahead of and behind the upstream
|
- `Yellow` - both ahead of and behind the upstream
|
||||||
- Provides additional nodes, if the current branch is not synchronized with the upstream, to quickly see and explore the specific commits ahead and/or behind the upstream
|
- Provides additional upstream status nodes, if the current branch is tracking a remote branch and
|
||||||
|
- is behind the upstream — quickly see and explore the specific commits behind the upstream (i.e. commits that haven't been pulled)
|
||||||
|
- is ahead of the upstream — quickly see and explore the specific commits ahead of the upstream (i.e. commits that haven't been pushed)
|
||||||
|
- `Changed Files` node — provides a at-a-glance view of all "working" changes
|
||||||
|
- Expands to a file-based view of all changed files in the working tree (enabled via `"gitlens.insiders": true`) and/or all files in all commits ahead of the upstream
|
||||||
- Provides a context menu with `Open Repository in Remote`, and `Refresh` commands
|
- Provides a context menu with `Open Repository in Remote`, and `Refresh` commands
|
||||||
|
|
||||||
- `Branches` node — provides a list of the local branches
|
- `Branches` node — provides a list of the local branches
|
||||||
@@ -357,6 +361,7 @@ GitLens is highly customizable and provides many configuration settings to allow
|
|||||||
|`gitlens.gitExplorer.commitFileFormat`|Specifies the format of a committed file in the `GitLens` custom view<br />Available tokens<br /> ${file} - file name<br /> ${filePath} - file name and path<br /> ${path} - file path
|
|`gitlens.gitExplorer.commitFileFormat`|Specifies the format of a committed file in the `GitLens` custom view<br />Available tokens<br /> ${file} - file name<br /> ${filePath} - file name and path<br /> ${path} - file path
|
||||||
|`gitlens.gitExplorer.stashFormat`|Specifies the format of stashed changes in the `GitLens` custom view<br />Available tokens<br /> ${id} - commit id<br /> ${author} - commit author<br /> ${message} - commit message<br /> ${ago} - relative commit date (e.g. 1 day ago)<br /> ${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)<br /> ${authorAgo} - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|
|`gitlens.gitExplorer.stashFormat`|Specifies the format of stashed changes in the `GitLens` custom view<br />Available tokens<br /> ${id} - commit id<br /> ${author} - commit author<br /> ${message} - commit message<br /> ${ago} - relative commit date (e.g. 1 day ago)<br /> ${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)<br /> ${authorAgo} - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|
||||||
|`gitlens.gitExplorer.stashFileFormat`|Specifies the format of a stashed file in the `GitLens` custom view<br />Available tokens<br /> ${file} - file name<br /> ${filePath} - file name and path<br /> ${path} - file path
|
|`gitlens.gitExplorer.stashFileFormat`|Specifies the format of a stashed file in the `GitLens` custom view<br />Available tokens<br /> ${file} - file name<br /> ${filePath} - file name and path<br /> ${path} - file path
|
||||||
|
|`gitlens.gitExplorer.statusFileFormat`|Specifies the format of the status of a working or committed file in the `GitLens` custom view<br />Available tokens<br /> ${file} - file name<br /> ${filePath} - file name and path<br /> ${path} - file path<br />${working} - optional indicator if the file is uncommitted
|
||||||
|
|
||||||
### Custom Remotes Settings
|
### Custom Remotes Settings
|
||||||
|
|
||||||
|
|||||||
4
images/dark/icon-diff.svg
Normal file
4
images/dark/icon-diff.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg width="16" height="22" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill="#C5C5C5" d="m7.5,10l2,0l0,1l-2,0l0,2l-1,0l0,-2l-2,0l0,-1l2,0l0,-2l1,0l0,2l0,0zm-3,6l5,0l0,-1l-5,0l0,1l0,0zm4.5,-11l3.5,3.5l0,9.5c0,0.55 -0.45,1 -1,1l-9,0c-0.55,0 -1,-0.45 -1,-1l0,-12c0,-0.55 0.45,-1 1,-1l6.5,0l0,0zm2.5,4l-3,-3l-6,0l0,12l9,0l0,-9l0,0zm-1.5,-6l-5.5,0l0,1l5,0l4,4l0,8l1,0l0,-8.5l-4.5,-4.5l0,0z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 437 B |
4
images/light/icon-diff.svg
Normal file
4
images/light/icon-diff.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg width="16" height="22" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill="#424242" d="m7.5,10l2,0l0,1l-2,0l0,2l-1,0l0,-2l-2,0l0,-1l2,0l0,-2l1,0l0,2l0,0zm-3,6l5,0l0,-1l-5,0l0,1l0,0zm4.5,-11l3.5,3.5l0,9.5c0,0.55 -0.45,1 -1,1l-9,0c-0.55,0 -1,-0.45 -1,-1l0,-12c0,-0.55 0.45,-1 1,-1l6.5,0l0,0zm2.5,4l-3,-3l-6,0l0,12l9,0l0,-9l0,0zm-1.5,-6l-5.5,0l0,1l5,0l4,4l0,8l1,0l0,-8.5l-4.5,-4.5l0,0z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 437 B |
29
package-lock.json
generated
29
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gitlens",
|
"name": "gitlens",
|
||||||
"version": "5.1.1-beta",
|
"version": "5.2.0-beta",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -47,11 +47,6 @@
|
|||||||
"json-stable-stringify": "1.0.1"
|
"json-stable-stringify": "1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ansi-regex": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
|
||||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
|
|
||||||
},
|
|
||||||
"ansi-styles": {
|
"ansi-styles": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
|
||||||
@@ -1336,11 +1331,6 @@
|
|||||||
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"is-fullwidth-code-point": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
|
||||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
|
|
||||||
},
|
|
||||||
"is-glob": {
|
"is-glob": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
|
||||||
@@ -2334,15 +2324,6 @@
|
|||||||
"integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=",
|
"integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"string-width": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
|
||||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
|
||||||
"requires": {
|
|
||||||
"is-fullwidth-code-point": "2.0.0",
|
|
||||||
"strip-ansi": "4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||||
@@ -2358,14 +2339,6 @@
|
|||||||
"integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
|
"integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"strip-ansi": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
|
||||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
|
||||||
"requires": {
|
|
||||||
"ansi-regex": "3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"strip-bom": {
|
"strip-bom": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gitlens",
|
"name": "gitlens",
|
||||||
"version": "5.1.1-beta",
|
"version": "5.2.0-beta",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Eric Amodio",
|
"name": "Eric Amodio",
|
||||||
"email": "eamodio@gmail.com"
|
"email": "eamodio@gmail.com"
|
||||||
@@ -443,6 +443,11 @@
|
|||||||
"default": "${filePath}",
|
"default": "${filePath}",
|
||||||
"description": "Specifies the format of a stashed file in the `GitLens` custom view\nAvailable tokens\n ${file} - file name\n ${filePath} - file name and path\n ${path} - file path"
|
"description": "Specifies the format of a stashed file in the `GitLens` custom view\nAvailable tokens\n ${file} - file name\n ${filePath} - file name and path\n ${path} - file path"
|
||||||
},
|
},
|
||||||
|
"gitlens.gitExplorer.statusFileFormat": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "${working}${filePath}",
|
||||||
|
"description": "Specifies the format of the status of a working or committed file in the `GitLens` custom view\nAvailable tokens\n ${file} - file name\n ${filePath} - file name and path\n ${path} - file path\n ${working} - optional indicator if the file is uncommitted"
|
||||||
|
},
|
||||||
"gitlens.gitExplorer.view": {
|
"gitlens.gitExplorer.view": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "repository",
|
"default": "repository",
|
||||||
@@ -1921,7 +1926,6 @@
|
|||||||
"lodash.once": "4.1.1",
|
"lodash.once": "4.1.1",
|
||||||
"moment": "2.18.1",
|
"moment": "2.18.1",
|
||||||
"spawn-rx": "2.0.11",
|
"spawn-rx": "2.0.11",
|
||||||
"string-width": "2.1.1",
|
|
||||||
"tmp": "0.0.33"
|
"tmp": "0.0.33"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Dates, Strings } from '../system';
|
import { Dates, Objects, Strings } from '../system';
|
||||||
import { DecorationInstanceRenderOptions, DecorationOptions, MarkdownString, ThemableDecorationRenderOptions } from 'vscode';
|
import { DecorationInstanceRenderOptions, DecorationOptions, MarkdownString, ThemableDecorationRenderOptions } from 'vscode';
|
||||||
import { DiffWithCommand, OpenCommitInRemoteCommand, ShowQuickCommitDetailsCommand } from '../commands';
|
import { DiffWithCommand, OpenCommitInRemoteCommand, ShowQuickCommitDetailsCommand } from '../commands';
|
||||||
import { IThemeConfig, themeDefaults } from '../configuration';
|
import { IThemeConfig, themeDefaults } from '../configuration';
|
||||||
@@ -133,9 +133,23 @@ export class Annotations {
|
|||||||
} as DecorationOptions;
|
} as DecorationOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
static gutterRenderOptions(cfgTheme: IThemeConfig, heatmap: IHeatmapConfig): IRenderOptions {
|
static gutterRenderOptions(cfgTheme: IThemeConfig, heatmap: IHeatmapConfig, options: ICommitFormatOptions): IRenderOptions {
|
||||||
const cfgFileTheme = cfgTheme.annotations.file.gutter;
|
const cfgFileTheme = cfgTheme.annotations.file.gutter;
|
||||||
|
|
||||||
|
// Try to get the width of the string, if there is a cap
|
||||||
|
let width = 4; // Start with a padding
|
||||||
|
for (const token of Objects.values<Strings.ITokenOptions | undefined>(options.tokenOptions)) {
|
||||||
|
if (token === undefined) continue;
|
||||||
|
|
||||||
|
// If any token is uncapped, kick out and set no max
|
||||||
|
if (token.truncateTo == null) {
|
||||||
|
width = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
width += token.truncateTo;
|
||||||
|
}
|
||||||
|
|
||||||
let borderStyle = undefined;
|
let borderStyle = undefined;
|
||||||
let borderWidth = undefined;
|
let borderWidth = undefined;
|
||||||
if (heatmap.enabled) {
|
if (heatmap.enabled) {
|
||||||
@@ -152,7 +166,8 @@ export class Annotations {
|
|||||||
borderStyle: borderStyle,
|
borderStyle: borderStyle,
|
||||||
borderWidth: borderWidth,
|
borderWidth: borderWidth,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
margin: '0 26px -1px 0'
|
margin: '0 26px -1px 0',
|
||||||
|
width: (width > 4) ? `${width}ch` : undefined
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
backgroundColor: cfgFileTheme.dark.backgroundColor || undefined,
|
backgroundColor: cfgFileTheme.dark.backgroundColor || undefined,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
|
|||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const offset = this.uri.offset;
|
const offset = this.uri.offset;
|
||||||
const renderOptions = Annotations.gutterRenderOptions(this._config.theme, cfg.heatmap);
|
const renderOptions = Annotations.gutterRenderOptions(this._config.theme, cfg.heatmap, options);
|
||||||
const separateLines = this._config.theme.annotations.file.gutter.separateLines;
|
const separateLines = this._config.theme.annotations.file.gutter.separateLines;
|
||||||
|
|
||||||
const decorations: DecorationOptions[] = [];
|
const decorations: DecorationOptions[] = [];
|
||||||
@@ -58,7 +58,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
|
|||||||
...gutter.renderOptions,
|
...gutter.renderOptions,
|
||||||
before: {
|
before: {
|
||||||
...gutter.renderOptions!.before,
|
...gutter.renderOptions!.before,
|
||||||
contentText: GlyphChars.Space.repeat(Strings.getWidth(gutter.renderOptions!.before!.contentText!))
|
contentText: GlyphChars.Space.repeat(Strings.width(gutter.renderOptions!.before!.contentText!))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -324,6 +324,7 @@ export interface IConfig {
|
|||||||
commitFileFormat: string;
|
commitFileFormat: string;
|
||||||
stashFormat: string;
|
stashFormat: string;
|
||||||
stashFileFormat: string;
|
stashFileFormat: string;
|
||||||
|
statusFileFormat: string;
|
||||||
// dateFormat: string | null;
|
// dateFormat: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -81,10 +81,12 @@ export type GlyphChars = '\u21a9' |
|
|||||||
'\u21e8' |
|
'\u21e8' |
|
||||||
'\u2191' |
|
'\u2191' |
|
||||||
'\u2197' |
|
'\u2197' |
|
||||||
|
'\u2217' |
|
||||||
'\u2713' |
|
'\u2713' |
|
||||||
'\u2014' |
|
'\u2014' |
|
||||||
'\u2022' |
|
'\u2022' |
|
||||||
'\u2026' |
|
'\u2026' |
|
||||||
|
'\u270E' |
|
||||||
'\u00a0' |
|
'\u00a0' |
|
||||||
'\u200b';
|
'\u200b';
|
||||||
export const GlyphChars = {
|
export const GlyphChars = {
|
||||||
@@ -97,10 +99,12 @@ export const GlyphChars = {
|
|||||||
ArrowRightHollow: '\u21e8' as GlyphChars,
|
ArrowRightHollow: '\u21e8' as GlyphChars,
|
||||||
ArrowUp: '\u2191' as GlyphChars,
|
ArrowUp: '\u2191' as GlyphChars,
|
||||||
ArrowUpRight: '\u2197' as GlyphChars,
|
ArrowUpRight: '\u2197' as GlyphChars,
|
||||||
|
Asterisk: '\u2217' as GlyphChars,
|
||||||
Check: '\u2713' as GlyphChars,
|
Check: '\u2713' as GlyphChars,
|
||||||
Dash: '\u2014' as GlyphChars,
|
Dash: '\u2014' as GlyphChars,
|
||||||
Dot: '\u2022' as GlyphChars,
|
Dot: '\u2022' as GlyphChars,
|
||||||
Ellipsis: '\u2026' as GlyphChars,
|
Ellipsis: '\u2026' as GlyphChars,
|
||||||
|
Pensil: '\u270E' as GlyphChars,
|
||||||
Space: '\u00a0' as GlyphChars,
|
Space: '\u00a0' as GlyphChars,
|
||||||
ZeroWidthSpace: '\u200b' as GlyphChars
|
ZeroWidthSpace: '\u200b' as GlyphChars
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export abstract class Formatter<TItem = any, TOptions extends IFormatOptions = I
|
|||||||
|
|
||||||
let max = options.truncateTo;
|
let max = options.truncateTo;
|
||||||
|
|
||||||
const width = Strings.getWidth(s);
|
const width = Strings.width(s);
|
||||||
if (max === undefined) {
|
if (max === undefined) {
|
||||||
if (this.collapsableWhitespace === 0) return s;
|
if (this.collapsableWhitespace === 0) return s;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { Strings } from '../../system';
|
import { Strings } from '../../system';
|
||||||
|
import { GlyphChars } from '../../constants';
|
||||||
import { Formatter, IFormatOptions } from './formatter';
|
import { Formatter, IFormatOptions } from './formatter';
|
||||||
import { GitStatusFile, IGitStatusFile } from '../models/status';
|
import { GitStatusFile, IGitStatusFile, IGitStatusFileWithCommit } from '../models/status';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
export interface IStatusFormatOptions extends IFormatOptions {
|
export interface IStatusFormatOptions extends IFormatOptions {
|
||||||
@@ -29,6 +30,11 @@ export class StatusFileFormatter extends Formatter<IGitStatusFile, IStatusFormat
|
|||||||
return this._padOrTruncate(directory, this._options.tokenOptions!.file);
|
return this._padOrTruncate(directory, this._options.tokenOptions!.file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get working() {
|
||||||
|
const commit = (this._item as IGitStatusFileWithCommit).commit;
|
||||||
|
return (commit !== undefined && commit.isUncommitted) ? `${GlyphChars.Pensil} ${GlyphChars.Space}` : '';
|
||||||
|
}
|
||||||
|
|
||||||
static fromTemplate(template: string, status: IGitStatusFile, dateFormat: string | null): string;
|
static fromTemplate(template: string, status: IGitStatusFile, dateFormat: string | null): string;
|
||||||
static fromTemplate(template: string, status: IGitStatusFile, options?: IStatusFormatOptions): string;
|
static fromTemplate(template: string, status: IGitStatusFile, options?: IStatusFormatOptions): string;
|
||||||
static fromTemplate(template: string, status: IGitStatusFile, dateFormatOrOptions?: string | null | IStatusFormatOptions): string;
|
static fromTemplate(template: string, status: IGitStatusFile, dateFormatOrOptions?: string | null | IStatusFormatOptions): string;
|
||||||
|
|||||||
@@ -259,6 +259,14 @@ export class Git {
|
|||||||
return gitCommand({ cwd: repoPath }, ...params);
|
return gitCommand({ cwd: repoPath }, ...params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static diff_shortstat(repoPath: string, sha?: string) {
|
||||||
|
const params = [`diff`, `--shortstat`, `--no-ext-diff`];
|
||||||
|
if (sha) {
|
||||||
|
params.push(sha);
|
||||||
|
}
|
||||||
|
return gitCommand({ cwd: repoPath }, ...params);
|
||||||
|
}
|
||||||
|
|
||||||
static difftool_dirDiff(repoPath: string, sha1: string, sha2?: string) {
|
static difftool_dirDiff(repoPath: string, sha1: string, sha2?: string) {
|
||||||
const params = [`difftool`, `--dir-diff`, sha1];
|
const params = [`difftool`, `--dir-diff`, sha1];
|
||||||
if (sha2) {
|
if (sha2) {
|
||||||
|
|||||||
@@ -34,3 +34,9 @@ export interface GitDiff {
|
|||||||
|
|
||||||
diff?: string;
|
diff?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GitDiffShortStat {
|
||||||
|
files: number;
|
||||||
|
insertions: number;
|
||||||
|
deletions: number;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { Strings } from '../../system';
|
|||||||
import { Uri } from 'vscode';
|
import { Uri } from 'vscode';
|
||||||
import { GlyphChars } from '../../constants';
|
import { GlyphChars } from '../../constants';
|
||||||
import { GitUri } from '../gitUri';
|
import { GitUri } from '../gitUri';
|
||||||
|
import { GitLogCommit } from './logCommit';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
export interface GitStatus {
|
export interface GitStatus {
|
||||||
@@ -27,6 +28,10 @@ export interface IGitStatusFile {
|
|||||||
originalFileName?: string;
|
originalFileName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IGitStatusFileWithCommit extends IGitStatusFile {
|
||||||
|
commit: GitLogCommit;
|
||||||
|
}
|
||||||
|
|
||||||
export class GitStatusFile implements IGitStatusFile {
|
export class GitStatusFile implements IGitStatusFile {
|
||||||
|
|
||||||
originalFileName?: string;
|
originalFileName?: string;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { Iterables, Strings } from '../../system';
|
import { Iterables, Strings } from '../../system';
|
||||||
import { GitDiff, GitDiffChunk, GitDiffChunkLine, GitDiffLine } from './../git';
|
import { GitDiff, GitDiffChunk, GitDiffChunkLine, GitDiffLine, GitDiffShortStat } from './../git';
|
||||||
|
|
||||||
const unifiedDiffRegex = /^@@ -([\d]+),([\d]+) [+]([\d]+),([\d]+) @@([\s\S]*?)(?=^@@)/gm;
|
const unifiedDiffRegex = /^@@ -([\d]+),([\d]+) [+]([\d]+),([\d]+) @@([\s\S]*?)(?=^@@)/gm;
|
||||||
|
const shortStatDiffRegex = /^\s*(\d+)\sfiles? changed(?:,\s+(\d+)\s+insertions?\(\+\))?(?:,\s+(\d+)\s+deletions?\(-\))?/;
|
||||||
|
|
||||||
export class GitDiffParser {
|
export class GitDiffParser {
|
||||||
|
|
||||||
@@ -116,4 +117,20 @@ export class GitDiffParser {
|
|||||||
|
|
||||||
return chunkLines;
|
return chunkLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static parseShortStat(data: string): GitDiffShortStat | undefined {
|
||||||
|
if (!data) return undefined;
|
||||||
|
|
||||||
|
const match = shortStatDiffRegex.exec(data);
|
||||||
|
if (match == null) return undefined;
|
||||||
|
|
||||||
|
const files = match[1];
|
||||||
|
const insertions = match[2];
|
||||||
|
const deletions = match[3];
|
||||||
|
return {
|
||||||
|
files: files == null ? 0 : parseInt(files, 10),
|
||||||
|
insertions: insertions == null ? 0 : parseInt(insertions, 10),
|
||||||
|
deletions: deletions == null ? 0 : parseInt(deletions, 10)
|
||||||
|
} as GitDiffShortStat;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import { Disposable, Event, EventEmitter, FileSystemWatcher, Location, Position,
|
|||||||
import { IConfig } from './configuration';
|
import { IConfig } from './configuration';
|
||||||
import { DocumentSchemes, ExtensionKey, GlyphChars } from './constants';
|
import { DocumentSchemes, ExtensionKey, GlyphChars } from './constants';
|
||||||
import { RemoteProviderFactory } from './git/remotes/factory';
|
import { RemoteProviderFactory } from './git/remotes/factory';
|
||||||
import { Git, GitAuthor, GitBlame, GitBlameCommit, GitBlameLine, GitBlameLines, GitBlameParser, GitBranch, GitBranchParser, GitCommit, GitDiff, GitDiffChunkLine, GitDiffParser, GitLog, GitLogCommit, GitLogParser, GitRemote, GitRemoteParser, GitStash, GitStashParser, GitStatus, GitStatusFile, GitStatusParser, IGit, setDefaultEncoding } from './git/git';
|
import { Git, GitAuthor, GitBlame, GitBlameCommit, GitBlameLine, GitBlameLines, GitBlameParser, GitBranch, GitBranchParser, GitCommit, GitDiff, GitDiffChunkLine, GitDiffParser, GitDiffShortStat, GitLog, GitLogCommit, GitLogParser, GitRemote, GitRemoteParser, GitStash, GitStashParser, GitStatus, GitStatusFile, GitStatusParser, IGit, setDefaultEncoding } from './git/git';
|
||||||
import { GitUri, IGitCommitInfo, IGitUriData } from './git/gitUri';
|
import { GitUri, IGitCommitInfo, IGitUriData } from './git/gitUri';
|
||||||
import { Logger } from './logger';
|
import { Logger } from './logger';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
@@ -599,6 +599,10 @@ export class GitService extends Disposable {
|
|||||||
return Git.normalizePath(typeof fileNameOrUri === 'string' ? fileNameOrUri : fileNameOrUri.fsPath).toLowerCase();
|
return Git.normalizePath(typeof fileNameOrUri === 'string' ? fileNameOrUri : fileNameOrUri.fsPath).toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getChangedFilesCount(repoPath: string, sha?: string): Promise<GitDiffShortStat | undefined> {
|
||||||
|
return GitDiffParser.parseShortStat(await Git.diff_shortstat(repoPath, sha));
|
||||||
|
}
|
||||||
|
|
||||||
async getConfig(key: string, repoPath?: string): Promise<string> {
|
async getConfig(key: string, repoPath?: string): Promise<string> {
|
||||||
Logger.log(`getConfig('${key}', '${repoPath}')`);
|
Logger.log(`getConfig('${key}', '${repoPath}')`);
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const _escapeRegExp = require('lodash.escaperegexp');
|
const _escapeRegExp = require('lodash.escaperegexp');
|
||||||
const stringWidth = require('string-width');
|
|
||||||
|
|
||||||
export namespace Strings {
|
export namespace Strings {
|
||||||
export function escapeRegExp(s: string): string {
|
export function escapeRegExp(s: string): string {
|
||||||
return _escapeRegExp(s);
|
return _escapeRegExp(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWidth(s: string): number {
|
|
||||||
return stringWidth(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
const TokenRegex = /\$\{([^|]*?)(?:\|(\d+)(\-|\?)?)?\}/g;
|
const TokenRegex = /\$\{([^|]*?)(?:\|(\d+)(\-|\?)?)?\}/g;
|
||||||
const TokenSanitizeRegex = /\$\{(\w*?)(?:\W|\d)*?\}/g;
|
const TokenSanitizeRegex = /\$\{(\w*?)(?:\W|\d)*?\}/g;
|
||||||
|
|
||||||
@@ -68,19 +63,19 @@ export namespace Strings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function padLeft(s: string, padTo: number, padding: string = '\u00a0') {
|
export function padLeft(s: string, padTo: number, padding: string = '\u00a0') {
|
||||||
const diff = padTo - getWidth(s);
|
const diff = padTo - width(s);
|
||||||
return (diff <= 0) ? s : '\u00a0'.repeat(diff) + s;
|
return (diff <= 0) ? s : '\u00a0'.repeat(diff) + s;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function padLeftOrTruncate(s: string, max: number, padding?: string) {
|
export function padLeftOrTruncate(s: string, max: number, padding?: string) {
|
||||||
const len = getWidth(s);
|
const len = width(s);
|
||||||
if (len < max) return padLeft(s, max, padding);
|
if (len < max) return padLeft(s, max, padding);
|
||||||
if (len > max) return truncate(s, max);
|
if (len > max) return truncate(s, max);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function padRight(s: string, padTo: number, padding: string = '\u00a0') {
|
export function padRight(s: string, padTo: number, padding: string = '\u00a0') {
|
||||||
const diff = padTo - getWidth(s);
|
const diff = padTo - width(s);
|
||||||
return (diff <= 0) ? s : s + '\u00a0'.repeat(diff);
|
return (diff <= 0) ? s : s + '\u00a0'.repeat(diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,14 +83,14 @@ export namespace Strings {
|
|||||||
const left = max < 0;
|
const left = max < 0;
|
||||||
max = Math.abs(max);
|
max = Math.abs(max);
|
||||||
|
|
||||||
const len = getWidth(s);
|
const len = width(s);
|
||||||
if (len < max) return left ? padLeft(s, max, padding) : padRight(s, max, padding);
|
if (len < max) return left ? padLeft(s, max, padding) : padRight(s, max, padding);
|
||||||
if (len > max) return truncate(s, max);
|
if (len > max) return truncate(s, max);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function padRightOrTruncate(s: string, max: number, padding?: string) {
|
export function padRightOrTruncate(s: string, max: number, padding?: string) {
|
||||||
const len = getWidth(s);
|
const len = width(s);
|
||||||
if (len < max) return padRight(s, max, padding);
|
if (len < max) return padRight(s, max, padding);
|
||||||
if (len > max) return truncate(s, max);
|
if (len > max) return truncate(s, max);
|
||||||
return s;
|
return s;
|
||||||
@@ -112,15 +107,15 @@ export namespace Strings {
|
|||||||
export function truncate(s: string, truncateTo: number, ellipsis: string = '\u2026') {
|
export function truncate(s: string, truncateTo: number, ellipsis: string = '\u2026') {
|
||||||
if (!s) return s;
|
if (!s) return s;
|
||||||
|
|
||||||
const len = getWidth(s);
|
const len = width(s);
|
||||||
if (len <= truncateTo) return s;
|
if (len <= truncateTo) return s;
|
||||||
if (len === s.length) return `${s.substring(0, truncateTo - 1)}${ellipsis}`;
|
if (len === s.length) return `${s.substring(0, truncateTo - 1)}${ellipsis}`;
|
||||||
|
|
||||||
// Skip ahead to start as far as we can by assuming all the double-width characters won't be truncated
|
// Skip ahead to start as far as we can by assuming all the double-width characters won't be truncated
|
||||||
let chars = Math.floor(truncateTo / (len / s.length));
|
let chars = Math.floor(truncateTo / (len / s.length));
|
||||||
let count = getWidth(s.substring(0, chars));
|
let count = width(s.substring(0, chars));
|
||||||
while (count < truncateTo) {
|
while (count < truncateTo) {
|
||||||
count += getWidth(s[chars++]);
|
count += width(s[chars++]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count >= truncateTo) {
|
if (count >= truncateTo) {
|
||||||
@@ -129,4 +124,107 @@ export namespace Strings {
|
|||||||
|
|
||||||
return `${s.substring(0, chars)}${ellipsis}`;
|
return `${s.substring(0, chars)}${ellipsis}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ansiRegex = /[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))/g;
|
||||||
|
|
||||||
|
export function width(s: string): number {
|
||||||
|
if (!s || s.length === 0) return 0;
|
||||||
|
|
||||||
|
s = s.replace(ansiRegex, '');
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
let emoji = 0;
|
||||||
|
let joiners = 0;
|
||||||
|
|
||||||
|
const graphemes = [...s];
|
||||||
|
for (let i = 0; i < graphemes.length; i++) {
|
||||||
|
const code = graphemes[i].codePointAt(0)!;
|
||||||
|
|
||||||
|
// Ignore control characters
|
||||||
|
if (code <= 0x1F || (code >= 0x7F && code <= 0x9F)) continue;
|
||||||
|
|
||||||
|
// Ignore combining characters
|
||||||
|
if (code >= 0x300 && code <= 0x36F) continue;
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/30757193/find-out-if-character-in-string-is-emoji
|
||||||
|
if (
|
||||||
|
(code >= 0x1F600 && code <= 0x1F64F) || // Emoticons
|
||||||
|
(code >= 0x1F300 && code <= 0x1F5FF) || // Misc Symbols and Pictographs
|
||||||
|
(code >= 0x1F680 && code <= 0x1F6FF) || // Transport and Map
|
||||||
|
(code >= 0x2600 && code <= 0x26FF) || // Misc symbols
|
||||||
|
(code >= 0x2700 && code <= 0x27BF) || // Dingbats
|
||||||
|
(code >= 0xFE00 && code <= 0xFE0F) || // Variation Selectors
|
||||||
|
(code >= 0x1F900 && code <= 0x1F9FF) || // Supplemental Symbols and Pictographs
|
||||||
|
(code >= 65024 && code <= 65039) || // Variation selector
|
||||||
|
(code >= 8400 && code <= 8447) // Combining Diacritical Marks for Symbols
|
||||||
|
) {
|
||||||
|
if (code >= 0x1F3FB && code <= 0x1F3FF) continue; // emoji modifier fitzpatrick type
|
||||||
|
|
||||||
|
emoji++;
|
||||||
|
count += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore zero-width joiners '\u200d'
|
||||||
|
if (code === 8205) {
|
||||||
|
joiners++;
|
||||||
|
count -= 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surrogates
|
||||||
|
if (code > 0xFFFF) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
count += isFullwidthCodePoint(code) ? 2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset = emoji - joiners;
|
||||||
|
if (offset > 1) {
|
||||||
|
count += offset - 1;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFullwidthCodePoint(cp: number) {
|
||||||
|
// code points are derived from:
|
||||||
|
// http://www.unix.org/Public/UNIDATA/EastAsianWidth.txt
|
||||||
|
if (
|
||||||
|
cp >= 0x1100 && (
|
||||||
|
cp <= 0x115f || // Hangul Jamo
|
||||||
|
cp === 0x2329 || // LEFT-POINTING ANGLE BRACKET
|
||||||
|
cp === 0x232a || // RIGHT-POINTING ANGLE BRACKET
|
||||||
|
// CJK Radicals Supplement .. Enclosed CJK Letters and Months
|
||||||
|
(0x2e80 <= cp && cp <= 0x3247 && cp !== 0x303f) ||
|
||||||
|
// Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A
|
||||||
|
(0x3250 <= cp && cp <= 0x4dbf) ||
|
||||||
|
// CJK Unified Ideographs .. Yi Radicals
|
||||||
|
(0x4e00 <= cp && cp <= 0xa4c6) ||
|
||||||
|
// Hangul Jamo Extended-A
|
||||||
|
(0xa960 <= cp && cp <= 0xa97c) ||
|
||||||
|
// Hangul Syllables
|
||||||
|
(0xac00 <= cp && cp <= 0xd7a3) ||
|
||||||
|
// CJK Compatibility Ideographs
|
||||||
|
(0xf900 <= cp && cp <= 0xfaff) ||
|
||||||
|
// Vertical Forms
|
||||||
|
(0xfe10 <= cp && cp <= 0xfe19) ||
|
||||||
|
// CJK Compatibility Forms .. Small Form Variants
|
||||||
|
(0xfe30 <= cp && cp <= 0xfe6b) ||
|
||||||
|
// Halfwidth and Fullwidth Forms
|
||||||
|
(0xff01 <= cp && cp <= 0xff60) ||
|
||||||
|
(0xffe0 <= cp && cp <= 0xffe6) ||
|
||||||
|
// Kana Supplement
|
||||||
|
(0x1b000 <= cp && cp <= 0x1b001) ||
|
||||||
|
// Enclosed Ideographic Supplement
|
||||||
|
(0x1f200 <= cp && cp <= 0x1f251) ||
|
||||||
|
// CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane
|
||||||
|
(0x20000 <= cp && cp <= 0x3fffd)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -42,14 +42,7 @@ export class CommitFileNode extends ExplorerNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const label = (this.displayAs & CommitFileNodeDisplayAs.CommitLabel)
|
const item = new TreeItem(this.label, TreeItemCollapsibleState.None);
|
||||||
? CommitFormatter.fromTemplate(this.getCommitTemplate(), this.commit, {
|
|
||||||
truncateMessageAtNewLine: true,
|
|
||||||
dataFormat: this.git.config.defaultDateFormat
|
|
||||||
} as ICommitFormatOptions)
|
|
||||||
: StatusFileFormatter.fromTemplate(this.getCommitFileTemplate(), this.status);
|
|
||||||
|
|
||||||
const item = new TreeItem(label, TreeItemCollapsibleState.None);
|
|
||||||
item.contextValue = this.resourceType;
|
item.contextValue = this.resourceType;
|
||||||
|
|
||||||
const icon = (this.displayAs & CommitFileNodeDisplayAs.CommitIcon)
|
const icon = (this.displayAs & CommitFileNodeDisplayAs.CommitIcon)
|
||||||
@@ -63,9 +56,25 @@ export class CommitFileNode extends ExplorerNode {
|
|||||||
|
|
||||||
item.command = this.getCommand();
|
item.command = this.getCommand();
|
||||||
|
|
||||||
|
// Only cache the label for a single refresh
|
||||||
|
this._label = undefined;
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _label: string | undefined;
|
||||||
|
get label() {
|
||||||
|
if (this._label === undefined) {
|
||||||
|
this._label = (this.displayAs & CommitFileNodeDisplayAs.CommitLabel)
|
||||||
|
? CommitFormatter.fromTemplate(this.getCommitTemplate(), this.commit, {
|
||||||
|
truncateMessageAtNewLine: true,
|
||||||
|
dataFormat: this.git.config.defaultDateFormat
|
||||||
|
} as ICommitFormatOptions)
|
||||||
|
: StatusFileFormatter.fromTemplate(this.getCommitFileTemplate(), this.status);
|
||||||
|
}
|
||||||
|
return this._label;
|
||||||
|
}
|
||||||
|
|
||||||
protected getCommitTemplate() {
|
protected getCommitTemplate() {
|
||||||
return this.git.config.gitExplorer.commitFormat;
|
return this.git.config.gitExplorer.commitFormat;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ export class CommitNode extends ExplorerNode {
|
|||||||
const commit = Iterables.first(log.commits.values());
|
const commit = Iterables.first(log.commits.values());
|
||||||
if (commit === undefined) return [];
|
if (commit === undefined) return [];
|
||||||
|
|
||||||
return [...Iterables.map(commit.fileStatuses, s => new CommitFileNode(s, commit, this.context, this.git, CommitFileNodeDisplayAs.File, this.branch))];
|
const children = [...Iterables.map(commit.fileStatuses, s => new CommitFileNode(s, commit, this.context, this.git, CommitFileNodeDisplayAs.File, this.branch))];
|
||||||
|
children.sort((a, b) => a.label!.localeCompare(b.label!));
|
||||||
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTreeItem(): TreeItem {
|
getTreeItem(): TreeItem {
|
||||||
|
|||||||
@@ -116,7 +116,8 @@ export class GitExplorer implements TreeDataProvider<ExplorerNode> {
|
|||||||
private onConfigurationChanged() {
|
private onConfigurationChanged() {
|
||||||
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
|
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
|
||||||
|
|
||||||
if (!Objects.areEquivalent(cfg.gitExplorer, this._config && this._config.gitExplorer)) {
|
if (!Objects.areEquivalent(cfg.gitExplorer, this._config && this._config.gitExplorer) ||
|
||||||
|
!Objects.areEquivalent(cfg.insiders, this._config && this._config.insiders)) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this._root = this.getRootNode(window.activeTextEditor);
|
this._root = this.getRootNode(window.activeTextEditor);
|
||||||
this.refresh();
|
this.refresh();
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ export class StashNode extends ExplorerNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return statuses.map(s => new StashFileNode(s, this.commit, this.context, this.git));
|
const children = statuses.map(s => new StashFileNode(s, this.commit, this.context, this.git));
|
||||||
|
children.sort((a, b) => a.label!.localeCompare(b.label!));
|
||||||
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTreeItem(): TreeItem {
|
getTreeItem(): TreeItem {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } fr
|
|||||||
import { Commands, DiffWithPreviousCommandArgs } from '../commands';
|
import { Commands, DiffWithPreviousCommandArgs } from '../commands';
|
||||||
import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode';
|
import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode';
|
||||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||||
import { getGitStatusIcon, GitBranch, GitLogCommit, GitService, GitUri, IGitStatusFile, StatusFileFormatter } from '../gitService';
|
import { getGitStatusIcon, GitBranch, GitLogCommit, GitService, GitUri, IGitStatusFile, IGitStatusFileWithCommit, StatusFileFormatter } from '../gitService';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
export class StatusFileCommitsNode extends ExplorerNode {
|
export class StatusFileCommitsNode extends ExplorerNode {
|
||||||
@@ -26,7 +26,7 @@ export class StatusFileCommitsNode extends ExplorerNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getTreeItem(): Promise<TreeItem> {
|
async getTreeItem(): Promise<TreeItem> {
|
||||||
const item = new TreeItem(StatusFileFormatter.fromTemplate(this.git.config.gitExplorer.commitFileFormat, this.status), TreeItemCollapsibleState.Collapsed);
|
const item = new TreeItem(this.label, TreeItemCollapsibleState.Collapsed);
|
||||||
item.contextValue = this.resourceType;
|
item.contextValue = this.resourceType;
|
||||||
|
|
||||||
const icon = getGitStatusIcon(this.status.status);
|
const icon = getGitStatusIcon(this.status.status);
|
||||||
@@ -41,9 +41,20 @@ export class StatusFileCommitsNode extends ExplorerNode {
|
|||||||
item.command = this.getCommand();
|
item.command = this.getCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only cache the label for a single refresh
|
||||||
|
this._label = undefined;
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _label: string | undefined;
|
||||||
|
get label() {
|
||||||
|
if (this._label === undefined) {
|
||||||
|
this._label = StatusFileFormatter.fromTemplate(this.git.config.gitExplorer.statusFileFormat, { ...this.status, commit: this.commit } as IGitStatusFileWithCommit);
|
||||||
|
}
|
||||||
|
return this._label;
|
||||||
|
}
|
||||||
|
|
||||||
get commit() {
|
get commit() {
|
||||||
return this.commits[0];
|
return this.commits[0];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,9 @@
|
|||||||
import { Arrays, Iterables, Objects } from '../system';
|
import { Arrays, Iterables, Objects } from '../system';
|
||||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
|
import { ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
|
||||||
import { ExplorerNode, ResourceType, ShowAllNode } from './explorerNode';
|
import { ExplorerNode, ResourceType, ShowAllNode } from './explorerNode';
|
||||||
import { GitBranch, GitLog, GitLogCommit, GitService, GitStatus, GitUri, IGitStatusFile } from '../gitService';
|
import { GitBranch, GitLog, GitLogCommit, GitService, GitStatus, GitUri, IGitStatusFileWithCommit } from '../gitService';
|
||||||
import { StatusFileCommitsNode } from './statusFileCommitsNode';
|
import { StatusFileCommitsNode } from './statusFileCommitsNode';
|
||||||
|
|
||||||
interface IGitStatusFileWithCommit extends IGitStatusFile {
|
|
||||||
commit: GitLogCommit;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class StatusFilesNode extends ExplorerNode {
|
export class StatusFilesNode extends ExplorerNode {
|
||||||
|
|
||||||
readonly resourceType: ResourceType = 'gitlens:status-files';
|
readonly resourceType: ResourceType = 'gitlens:status-files';
|
||||||
@@ -42,7 +38,7 @@ export class StatusFilesNode extends ExplorerNode {
|
|||||||
statuses = [];
|
statuses = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.status.files.length !== 0) {
|
if (this.status.files.length !== 0 && this.git.config.insiders) {
|
||||||
statuses.splice(0, 0, ...this.status.files.map(s => {
|
statuses.splice(0, 0, ...this.status.files.map(s => {
|
||||||
return { ...s, commit: new GitLogCommit('file', this.status.repoPath, GitService.uncommittedSha, s.fileName, 'You', new Date(), '', s.status, [s], s.originalFileName, 'HEAD', s.fileName) } as IGitStatusFileWithCommit;
|
return { ...s, commit: new GitLogCommit('file', this.status.repoPath, GitService.uncommittedSha, s.fileName, 'You', new Date(), '', s.status, [s], s.originalFileName, 'HEAD', s.fileName) } as IGitStatusFileWithCommit;
|
||||||
}));
|
}));
|
||||||
@@ -56,6 +52,8 @@ export class StatusFilesNode extends ExplorerNode {
|
|||||||
statuses => new StatusFileCommitsNode(this.uri.repoPath!, statuses[statuses.length - 1], statuses.map(s => s.commit), this.context, this.git, this.branch))
|
statuses => new StatusFileCommitsNode(this.uri.repoPath!, statuses[statuses.length - 1], statuses.map(s => s.commit), this.context, this.git, this.branch))
|
||||||
];
|
];
|
||||||
|
|
||||||
|
children.sort((a: StatusFileCommitsNode, b: StatusFileCommitsNode) => (a.commit.isUncommitted ? -1 : 1) - (b.commit.isUncommitted ? -1 : 1) || a.label!.localeCompare(b.label!));
|
||||||
|
|
||||||
if (log !== undefined && log.truncated) {
|
if (log !== undefined && log.truncated) {
|
||||||
children.push(new ShowAllNode('Show All Changes', this, this.context));
|
children.push(new ShowAllNode('Show All Changes', this, this.context));
|
||||||
}
|
}
|
||||||
@@ -63,8 +61,17 @@ export class StatusFilesNode extends ExplorerNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getTreeItem(): Promise<TreeItem> {
|
async getTreeItem(): Promise<TreeItem> {
|
||||||
const item = new TreeItem(`Changed Files`, TreeItemCollapsibleState.Collapsed);
|
const stats = await this.git.getChangedFilesCount(this.status.repoPath, this.git.config.insiders ? this.status.upstream : this.range);
|
||||||
|
const files = (stats === undefined) ? 0 : stats.files;
|
||||||
|
|
||||||
|
const label = `${files} file${files > 1 ? 's' : ''} changed`; // ${this.status.upstream === undefined ? '' : ` (ahead of ${this.status.upstream})`}`;
|
||||||
|
const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed);
|
||||||
item.contextValue = this.resourceType;
|
item.contextValue = this.resourceType;
|
||||||
|
item.iconPath = {
|
||||||
|
dark: this.context.asAbsolutePath(`images/dark/icon-diff.svg`),
|
||||||
|
light: this.context.asAbsolutePath(`images/light/icon-diff.svg`)
|
||||||
|
};
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,11 +30,11 @@ export class StatusNode extends ExplorerNode {
|
|||||||
children.push(new StatusUpstreamNode(status, 'ahead', this.context, this.git));
|
children.push(new StatusUpstreamNode(status, 'ahead', this.context, this.git));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.files.length !== 0 || status.state.ahead && this.git.config.insiders) {
|
if (status.state.ahead || (status.files.length !== 0 && this.git.config.insiders)) {
|
||||||
const range = status.state.ahead
|
const range = status.state.ahead
|
||||||
? `${status.upstream}..${status.branch}`
|
? `${status.upstream}..${status.branch}`
|
||||||
: undefined;
|
: undefined;
|
||||||
children.splice(0, 0, new StatusFilesNode(status, range, this.context, this.git));
|
children.push(new StatusFilesNode(status, range, this.context, this.git));
|
||||||
}
|
}
|
||||||
|
|
||||||
return children;
|
return children;
|
||||||
@@ -45,14 +45,16 @@ export class StatusNode extends ExplorerNode {
|
|||||||
if (status === undefined) return new TreeItem('No repo status');
|
if (status === undefined) return new TreeItem('No repo status');
|
||||||
|
|
||||||
let hasChildren = false;
|
let hasChildren = false;
|
||||||
|
const hasWorkingChanges = status.files.length !== 0 && this.git.config.insiders;
|
||||||
let label = '';
|
let label = '';
|
||||||
let iconSuffix = '';
|
let iconSuffix = '';
|
||||||
if (status.upstream) {
|
if (status.upstream) {
|
||||||
if (!status.state.ahead && !status.state.behind) {
|
if (!status.state.ahead && !status.state.behind) {
|
||||||
label = `${status.branch} is up-to-date with ${status.upstream}`;
|
label = `${status.branch}${hasWorkingChanges ? ' has uncommitted changes and' : ''} is up-to-date with ${status.upstream}`;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
label = `${status.branch} is not up-to-date with ${status.upstream}`;
|
label = `${status.branch}${hasWorkingChanges ? ' has uncommitted changes and' : ''} is not up-to-date with ${status.upstream}`;
|
||||||
|
|
||||||
hasChildren = true;
|
hasChildren = true;
|
||||||
if (status.state.ahead && status.state.behind) {
|
if (status.state.ahead && status.state.behind) {
|
||||||
iconSuffix = '-yellow';
|
iconSuffix = '-yellow';
|
||||||
@@ -66,14 +68,10 @@ export class StatusNode extends ExplorerNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
label = `${status.branch} is up-to-date`;
|
label = `${status.branch} ${hasWorkingChanges ? 'has uncommitted changes' : 'is clean'}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.git.config.insiders) {
|
const item = new TreeItem(label, (hasChildren || hasWorkingChanges) ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None);
|
||||||
hasChildren = hasChildren || status.files.length !== 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const item = new TreeItem(label, hasChildren ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None);
|
|
||||||
item.contextValue = this.resourceType;
|
item.contextValue = this.resourceType;
|
||||||
|
|
||||||
item.iconPath = {
|
item.iconPath = {
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ export class StatusUpstreamNode extends ExplorerNode {
|
|||||||
|
|
||||||
async getTreeItem(): Promise<TreeItem> {
|
async getTreeItem(): Promise<TreeItem> {
|
||||||
const label = this.direction === 'ahead'
|
const label = this.direction === 'ahead'
|
||||||
? `${this.status.state.ahead} commit${this.status.state.ahead > 1 ? 's' : ''} ahead (local changes)` // of ${this.status.upstream}`
|
? `${this.status.state.ahead} commit${this.status.state.ahead > 1 ? 's' : ''} (ahead of ${this.status.upstream})`
|
||||||
: `${this.status.state.behind} commit${this.status.state.behind > 1 ? 's' : ''} behind (remote changes)`; // ${this.status.upstream}`;
|
: `${this.status.state.behind} commit${this.status.state.behind > 1 ? 's' : ''} (behind ${this.status.upstream})`;
|
||||||
|
|
||||||
const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed);
|
const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed);
|
||||||
item.contextValue = this.resourceType;
|
item.contextValue = this.resourceType;
|
||||||
|
|||||||
Reference in New Issue
Block a user