mirror of
https://github.com/ckaczor/vscode-gitlens.git
synced 2026-02-12 11:08:34 -05:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a17605017 | ||
|
|
2c9a26e47b | ||
|
|
1c7785fd52 | ||
|
|
079f7b7f36 | ||
|
|
bedc1a05f5 | ||
|
|
858d9ec578 | ||
|
|
2809991096 | ||
|
|
f6019454b6 | ||
|
|
f0bdf3e2c3 | ||
|
|
0fdf856c27 | ||
|
|
aacf7cc2b5 |
16
CHANGELOG.md
16
CHANGELOG.md
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [5.1.0] - 2017-09-15
|
||||
### Added
|
||||
- Adds full (multi-line) commit message to the `details` hover annotations -- closes [#116](https://github.com/eamodio/vscode-gitlens/issues/116)
|
||||
- Adds an external link icon to the `details` hover annotations to run the `Open Commit in Remote` command (`gitlens.openCommitInRemote`)
|
||||
|
||||
### Changed
|
||||
- Optimizes performance of the providing blame annotations, especially for large files (saw a ~78% improvement on some files)
|
||||
- Optimizes date handling (parsing and formatting) for better performance and reduced memory consumption
|
||||
|
||||
### Removed
|
||||
- Removes `gitlens.annotations.file.recentChanges.hover.wholeLine` setting as it didn't really make sense
|
||||
|
||||
### Fixed
|
||||
- Fixes an issue where stashes with only untracked files would not show in the `Stashes` node of the GitLens custom view
|
||||
- Fixes an issue where stashes with untracked files would not show its untracked files in the GitLens custom view
|
||||
|
||||
## [5.0.0] - 2017-09-12
|
||||
### Added
|
||||
- Adds an all-new `GitLens` custom view to the Explorer activity
|
||||
|
||||
@@ -25,6 +25,7 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
|
||||
- Adds a `changes` (diff) hover annotation to the current line annotation, which provides **instant** access to the line's previous version ([optional](#line-blame-annotation-settings), on by default)
|
||||
- Clicking on `Changes` will run the `Compare File Revisions` command (`gitlens.diffWith`)
|
||||
- Clicking the current and previous commit ids will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
|
||||
- Clicking on external link icon will run the the `Open Commit in Remote` command (`gitlens.openCommitInRemote`)
|
||||
|
||||

|
||||
|
||||
@@ -331,7 +332,6 @@ GitLens is highly customizable and provides many configuration settings to allow
|
||||
|`gitlens.recentChanges.file.lineHighlight.locations`|Specifies where the highlights of the recently changed lines will be shown<br />`gutter` - adds a gutter glyph<br />`line` - adds a full-line highlight background color<br />`overviewRuler` - adds a decoration to the overviewRuler (scroll bar)
|
||||
|`gitlens.annotations.file.recentChanges.hover.details`|Specifies whether or not to provide a commit details hover annotation
|
||||
|`gitlens.annotations.file.recentChanges.hover.changes`|Specifies whether or not to provide a changes (diff) hover annotation
|
||||
|`gitlens.annotations.file.recentChanges.hover.wholeLine`|Specifies whether or not to trigger hover annotations over the whole line
|
||||
|
||||
### Code Lens Settings
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect fill="#7F4E7E" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
|
||||
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";" fill="white">
|
||||
C
|
||||
!
|
||||
</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 431 B After Width: | Height: | Size: 431 B |
6
images/dark/icon-status-unknown.svg
Normal file
6
images/dark/icon-status-unknown.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect fill="#6C6C6C" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
|
||||
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";" fill="white">
|
||||
?
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 431 B |
@@ -1,6 +1,6 @@
|
||||
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect fill="#9B4F96" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
|
||||
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";" fill="white">
|
||||
C
|
||||
!
|
||||
</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 431 B After Width: | Height: | Size: 431 B |
6
images/light/icon-status-unknown.svg
Normal file
6
images/light/icon-status-unknown.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect fill="#6C6C6C" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
|
||||
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";" fill="white">
|
||||
?
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 431 B |
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gitlens",
|
||||
"version": "5.0.0",
|
||||
"version": "5.1.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gitlens",
|
||||
"version": "5.0.0",
|
||||
"version": "5.1.0",
|
||||
"author": {
|
||||
"name": "Eric Amodio",
|
||||
"email": "eamodio@gmail.com"
|
||||
@@ -132,11 +132,6 @@
|
||||
"default": true,
|
||||
"description": "Specifies whether or not to provide a changes (diff) hover annotation"
|
||||
},
|
||||
"gitlens.annotations.file.recentChanges.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,
|
||||
|
||||
@@ -222,7 +222,7 @@ export class AnnotationController extends Disposable {
|
||||
}
|
||||
|
||||
getProvider(editor: TextEditor | undefined): AnnotationProviderBase | undefined {
|
||||
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return undefined;
|
||||
if (editor === undefined || editor.document === undefined || !this.git.isEditorBlameable(editor)) return undefined;
|
||||
|
||||
return this._annotationProviders.get(editor.viewColumn || -1);
|
||||
}
|
||||
@@ -233,7 +233,7 @@ export class AnnotationController extends Disposable {
|
||||
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)) {
|
||||
if (currentProvider !== undefined && TextEditorComparer.equals(currentProvider.editor, editor)) {
|
||||
await currentProvider.selection(shaOrLine);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Strings } from '../system';
|
||||
import { Dates, Strings } from '../system';
|
||||
import { DecorationInstanceRenderOptions, DecorationOptions, MarkdownString, ThemableDecorationRenderOptions } from 'vscode';
|
||||
import { DiffWithCommand, ShowQuickCommitDetailsCommand } from '../commands';
|
||||
import { DiffWithCommand, OpenCommitInRemoteCommand, ShowQuickCommitDetailsCommand } from '../commands';
|
||||
import { IThemeConfig, themeDefaults } from '../configuration';
|
||||
import { GlyphChars } from '../constants';
|
||||
import { CommitFormatter, GitCommit, GitDiffChunkLine, GitService, GitUri, ICommitFormatOptions } from '../gitService';
|
||||
import * as moment from 'moment';
|
||||
|
||||
interface IHeatmapConfig {
|
||||
enabled: boolean;
|
||||
@@ -28,13 +27,13 @@ const escapeMarkdownRegEx = /[`\>\#\*\_\-\+\.]/g;
|
||||
|
||||
export class Annotations {
|
||||
|
||||
static applyHeatmap(decoration: DecorationOptions, date: Date, now: moment.Moment) {
|
||||
static applyHeatmap(decoration: DecorationOptions, date: Date, now: number) {
|
||||
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');
|
||||
private static _getHeatmapColor(now: number, date: Date) {
|
||||
const days = Dates.dateDaysFromNow(date, now);
|
||||
|
||||
if (days <= 2) return '#ffeca7';
|
||||
if (days <= 7) return '#ffdd8c';
|
||||
@@ -48,7 +47,7 @@ export class Annotations {
|
||||
return '#793738';
|
||||
}
|
||||
|
||||
static getHoverMessage(commit: GitCommit, dateFormat: string | null): MarkdownString {
|
||||
static getHoverMessage(commit: GitCommit, dateFormat: string | null, hasRemotes: boolean): MarkdownString {
|
||||
if (dateFormat === null) {
|
||||
dateFormat = 'MMMM Do, YYYY h:MMa';
|
||||
}
|
||||
@@ -65,7 +64,11 @@ export class Annotations {
|
||||
message = `\n\n> ${message}`;
|
||||
}
|
||||
|
||||
const markdown = new MarkdownString(`[\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)}) __${commit.author}__, ${moment(commit.date).fromNow()} _(${moment(commit.date).format(dateFormat)})_${message}`);
|
||||
const openInRemoteCommand = hasRemotes
|
||||
? `${' '.repeat(3)} [\`${GlyphChars.ArrowUpRight}\`](${OpenCommitInRemoteCommand.getMarkdownCommandArgs(commit.sha)} "Open in Remote")`
|
||||
: '';
|
||||
|
||||
const markdown = new MarkdownString(`[\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)} "Show Commit Details") __${commit.author}__, ${commit.fromNow()} _(${commit.formatDate(dateFormat)})_ ${openInRemoteCommand} ${message}`);
|
||||
markdown.isTrusted = true;
|
||||
return markdown;
|
||||
}
|
||||
@@ -75,8 +78,8 @@ export class Annotations {
|
||||
|
||||
const codeDiff = this._getCodeDiff(chunkLine);
|
||||
const markdown = new MarkdownString(commit.isUncommitted
|
||||
? `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)}) ${GlyphChars.Dash} _uncommitted_\n${codeDiff}`
|
||||
: `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)}) ${GlyphChars.Dash} [\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.previousSha!)}) ${GlyphChars.ArrowLeftRight} [\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)})\n${codeDiff}`);
|
||||
? `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") ${GlyphChars.Dash} _uncommitted_\n${codeDiff}`
|
||||
: `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") ${GlyphChars.Dash} [\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.previousSha!)} "Show Commit Details") ${GlyphChars.ArrowLeftRight} [\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)} "Show Commit Details")\n${codeDiff}`);
|
||||
markdown.isTrusted = true;
|
||||
return markdown;
|
||||
}
|
||||
@@ -98,8 +101,8 @@ export class Annotations {
|
||||
} as DecorationOptions;
|
||||
}
|
||||
|
||||
static detailsHover(commit: GitCommit, dateFormat: string | null): DecorationOptions {
|
||||
const message = this.getHoverMessage(commit, dateFormat);
|
||||
static detailsHover(commit: GitCommit, dateFormat: string | null, hasRemotes: boolean): DecorationOptions {
|
||||
const message = this.getHoverMessage(commit, dateFormat, hasRemotes);
|
||||
return {
|
||||
hoverMessage: message
|
||||
} as DecorationOptions;
|
||||
@@ -164,11 +167,12 @@ export class Annotations {
|
||||
} as IRenderOptions;
|
||||
}
|
||||
|
||||
static hover(commit: GitCommit, renderOptions: IRenderOptions, heatmap: boolean, dateFormat: string | null): DecorationOptions {
|
||||
return {
|
||||
hoverMessage: this.getHoverMessage(commit, dateFormat),
|
||||
renderOptions: heatmap ? { before: { ...renderOptions.before } } : undefined
|
||||
static hover(commit: GitCommit, renderOptions: IRenderOptions, now: number): DecorationOptions {
|
||||
const decoration = {
|
||||
renderOptions: { before: { ...renderOptions.before } }
|
||||
} as DecorationOptions;
|
||||
this.applyHeatmap(decoration, commit.date, now);
|
||||
return decoration;
|
||||
}
|
||||
|
||||
static hoverRenderOptions(cfgTheme: IThemeConfig, heatmap: IHeatmapConfig): IRenderOptions {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
'use strict';
|
||||
import { Iterables } from '../system';
|
||||
import { ExtensionContext, Range, TextEditor, TextEditorDecorationType } from 'vscode';
|
||||
import { CancellationToken, Disposable, ExtensionContext, Hover, HoverProvider, languages, Position, Range, TextDocument, TextEditor, TextEditorDecorationType } from 'vscode';
|
||||
import { AnnotationProviderBase } from './annotationProvider';
|
||||
import { GitBlame, GitService, GitUri } from '../gitService';
|
||||
import { Annotations, endOfLineIndex } from './annotations';
|
||||
import { GitBlame, GitCommit, GitService, GitUri } from '../gitService';
|
||||
import { WhitespaceController } from './whitespaceController';
|
||||
|
||||
export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase {
|
||||
export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase implements HoverProvider {
|
||||
|
||||
protected _blame: Promise<GitBlame | undefined>;
|
||||
protected _hoverProviderDisposable: Disposable;
|
||||
|
||||
constructor(context: ExtensionContext, editor: TextEditor, decoration: TextEditorDecorationType | undefined, highlightDecoration: TextEditorDecorationType | undefined, whitespaceController: WhitespaceController | undefined, protected git: GitService, protected uri: GitUri) {
|
||||
super(context, editor, decoration, highlightDecoration, whitespaceController);
|
||||
@@ -15,6 +17,11 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
|
||||
this._blame = this.git.getBlameForFile(this.uri);
|
||||
}
|
||||
|
||||
async clear() {
|
||||
this._hoverProviderDisposable && this._hoverProviderDisposable.dispose();
|
||||
super.clear();
|
||||
}
|
||||
|
||||
async selection(shaOrLine?: string | number, blame?: GitBlame) {
|
||||
if (!this.highlightDecoration) return;
|
||||
|
||||
@@ -56,6 +63,7 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
|
||||
const blame = await this._blame;
|
||||
return blame !== undefined && blame.lines.length !== 0;
|
||||
}
|
||||
|
||||
protected async getBlame(requiresWhitespaceHack: boolean): Promise<GitBlame | undefined> {
|
||||
let whitespacePromise: Promise<void> | undefined;
|
||||
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- override whitespace (turn off)
|
||||
@@ -64,18 +72,47 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
|
||||
}
|
||||
|
||||
let blame: GitBlame | undefined;
|
||||
if (whitespacePromise) {
|
||||
if (whitespacePromise !== undefined) {
|
||||
[blame] = await Promise.all([this._blame, whitespacePromise]);
|
||||
}
|
||||
else {
|
||||
blame = await this._blame;
|
||||
}
|
||||
|
||||
if (blame === undefined || !blame.lines.length) {
|
||||
if (blame === undefined || blame.lines.length === 0) {
|
||||
this.whitespaceController && await this.whitespaceController.restore();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return blame;
|
||||
}
|
||||
|
||||
registerHoverProvider() {
|
||||
this._hoverProviderDisposable = languages.registerHoverProvider({ pattern: this.uri.fsPath }, this);
|
||||
}
|
||||
|
||||
async provideHover(document: TextDocument, position: Position, token: CancellationToken): Promise<Hover | undefined> {
|
||||
// Avoid double annotations if we are showing the whole-file hover blame annotations
|
||||
if (this._config.blame.line.enabled && this.editor.selection.start.line === position.line) return undefined;
|
||||
|
||||
const cfg = this._config.annotations.file.gutter;
|
||||
if (!cfg.hover.wholeLine && position.character !== 0) return undefined;
|
||||
|
||||
const blame = await this.getBlame(true);
|
||||
if (blame === undefined) return undefined;
|
||||
|
||||
const line = blame.lines[position.line - this.uri.offset];
|
||||
|
||||
const commit = blame.commits.get(line.sha);
|
||||
if (commit === undefined) return undefined;
|
||||
|
||||
// Get the full commit message -- since blame only returns the summary
|
||||
let logCommit: GitCommit | undefined = undefined;
|
||||
if (!commit.isUncommitted) {
|
||||
logCommit = await this.git.getLogCommit(commit.repoPath, commit.uri.fsPath, commit.sha);
|
||||
}
|
||||
|
||||
const message = Annotations.getHoverMessage(logCommit || commit, this._config.defaultDateFormat, this.git.hasRemotes(commit.repoPath));
|
||||
return new Hover(message, document.validateRange(new Range(position.line, 0, position.line, endOfLineIndex)));
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,11 @@
|
||||
import { Strings } from '../system';
|
||||
import { DecorationOptions, Range } from 'vscode';
|
||||
import { FileAnnotationType } from './annotationController';
|
||||
import { Annotations, endOfLineIndex } from './annotations';
|
||||
import { Annotations } from './annotations';
|
||||
import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
|
||||
import { GlyphChars } from '../constants';
|
||||
import { GitBlameCommit, ICommitFormatOptions } from '../gitService';
|
||||
import * as moment from 'moment';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
|
||||
|
||||
@@ -16,7 +16,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
|
||||
const blame = await this.getBlame(true);
|
||||
if (blame === undefined) return false;
|
||||
|
||||
// console.time('Computing blame annotations...');
|
||||
const start = process.hrtime();
|
||||
|
||||
const cfg = this._config.annotations.file.gutter;
|
||||
|
||||
@@ -32,59 +32,53 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
|
||||
tokenOptions: tokenOptions
|
||||
};
|
||||
|
||||
const now = moment();
|
||||
const now = Date.now();
|
||||
const offset = this.uri.offset;
|
||||
const renderOptions = Annotations.gutterRenderOptions(this._config.theme, cfg.heatmap);
|
||||
const dateFormat = this._config.defaultDateFormat;
|
||||
const separateLines = this._config.theme.annotations.file.gutter.separateLines;
|
||||
|
||||
const decorations: DecorationOptions[] = [];
|
||||
const document = this.document;
|
||||
const decorationsMap: { [sha: string]: DecorationOptions | undefined } = Object.create(null);
|
||||
|
||||
let commit: GitBlameCommit | undefined;
|
||||
let compacted = false;
|
||||
let details: DecorationOptions | undefined;
|
||||
let gutter: DecorationOptions | undefined;
|
||||
let previousSha: string | undefined;
|
||||
|
||||
for (const l of blame.lines) {
|
||||
commit = blame.commits.get(l.sha);
|
||||
if (commit === undefined) continue;
|
||||
|
||||
const line = l.line + offset;
|
||||
|
||||
if (previousSha === l.sha) {
|
||||
// Use a shallow copy of the previous decoration options
|
||||
gutter = { ...gutter } as DecorationOptions;
|
||||
|
||||
if (cfg.compact && !compacted) {
|
||||
// Since we are wiping out the contextText make sure to copy the objects
|
||||
gutter.renderOptions = { ...gutter.renderOptions };
|
||||
gutter.renderOptions.before = {
|
||||
...gutter.renderOptions.before,
|
||||
...{ contentText: GlyphChars.Space.repeat(Strings.getWidth(gutter.renderOptions!.before!.contentText!)) }
|
||||
gutter.renderOptions = {
|
||||
...gutter.renderOptions,
|
||||
before: {
|
||||
...gutter.renderOptions!.before,
|
||||
contentText: GlyphChars.Space.repeat(Strings.getWidth(gutter.renderOptions!.before!.contentText!))
|
||||
}
|
||||
};
|
||||
|
||||
if (separateLines) {
|
||||
gutter.renderOptions.dark = { ...gutter.renderOptions.dark };
|
||||
gutter.renderOptions.dark.before = { ...gutter.renderOptions.dark.before, ...{ textDecoration: 'none' } };
|
||||
gutter.renderOptions.light = { ...gutter.renderOptions.light };
|
||||
gutter.renderOptions.light.before = { ...gutter.renderOptions.light.before, ...{ textDecoration: 'none' } };
|
||||
gutter.renderOptions.dark = {
|
||||
...gutter.renderOptions.dark,
|
||||
before: { ...gutter.renderOptions.dark!.before, textDecoration: 'none' }
|
||||
};
|
||||
gutter.renderOptions.light = {
|
||||
...gutter.renderOptions.light,
|
||||
before: { ...gutter.renderOptions.light!.before, textDecoration: 'none' }
|
||||
};
|
||||
}
|
||||
|
||||
compacted = true;
|
||||
}
|
||||
|
||||
const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
|
||||
gutter.range = new Range(line, 0, line, endIndex);
|
||||
decorations.push(gutter);
|
||||
gutter.range = new Range(line, 0, line, 0);
|
||||
|
||||
if (details !== undefined) {
|
||||
details = { ...details } as DecorationOptions;
|
||||
details.range = cfg.hover.wholeLine
|
||||
? document.validateRange(new Range(line, 0, line, endOfLineIndex))
|
||||
: gutter.range;
|
||||
decorations.push(details);
|
||||
}
|
||||
decorations.push(gutter);
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -92,30 +86,43 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
|
||||
compacted = false;
|
||||
previousSha = l.sha;
|
||||
|
||||
gutter = decorationsMap[l.sha];
|
||||
if (gutter !== undefined) {
|
||||
gutter = {
|
||||
...gutter,
|
||||
range: new Range(line, 0, line, 0)
|
||||
} as DecorationOptions;
|
||||
|
||||
decorations.push(gutter);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
commit = blame.commits.get(l.sha);
|
||||
if (commit === undefined) continue;
|
||||
|
||||
gutter = Annotations.gutter(commit, cfg.format, options, renderOptions);
|
||||
|
||||
if (cfg.heatmap.enabled) {
|
||||
Annotations.applyHeatmap(gutter, commit.date, now);
|
||||
}
|
||||
|
||||
const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
|
||||
gutter.range = new Range(line, 0, line, endIndex);
|
||||
decorations.push(gutter);
|
||||
gutter.range = new Range(line, 0, line, 0);
|
||||
|
||||
if (cfg.hover.details) {
|
||||
details = Annotations.detailsHover(commit, dateFormat);
|
||||
details.range = cfg.hover.wholeLine
|
||||
? document.validateRange(new Range(line, 0, line, endOfLineIndex))
|
||||
: gutter.range;
|
||||
decorations.push(details);
|
||||
}
|
||||
decorations.push(gutter);
|
||||
decorationsMap[l.sha] = gutter;
|
||||
}
|
||||
|
||||
if (decorations.length) {
|
||||
this.editor.setDecorations(this.decoration!, decorations);
|
||||
}
|
||||
|
||||
// console.timeEnd('Computing blame annotations...');
|
||||
const duration = process.hrtime(start);
|
||||
Logger.log(`${(duration[0] * 1000) + Math.floor(duration[1] / 1000000)} ms to compute gutter blame annotations`);
|
||||
|
||||
if (cfg.hover.details) {
|
||||
this.registerHoverProvider();
|
||||
}
|
||||
|
||||
this.selection(shaOrLine, blame);
|
||||
return true;
|
||||
|
||||
@@ -1,63 +1,70 @@
|
||||
'use strict';
|
||||
import { DecorationOptions, Range } from 'vscode';
|
||||
import { FileAnnotationType } from './annotationController';
|
||||
import { Annotations, endOfLineIndex } from './annotations';
|
||||
import { Annotations } from './annotations';
|
||||
import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
|
||||
import { GitBlameCommit } from '../gitService';
|
||||
import * as moment from 'moment';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase {
|
||||
|
||||
async provideAnnotation(shaOrLine?: string | number): Promise<boolean> {
|
||||
this.annotationType = FileAnnotationType.Hover;
|
||||
|
||||
const blame = await this.getBlame(this._config.annotations.file.hover.heatmap.enabled);
|
||||
if (blame === undefined) return false;
|
||||
|
||||
// console.time('Computing blame annotations...');
|
||||
|
||||
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 dateFormat = this._config.defaultDateFormat;
|
||||
const blame = await this.getBlame(cfg.heatmap.enabled);
|
||||
if (blame === undefined) return false;
|
||||
|
||||
const decorations: DecorationOptions[] = [];
|
||||
const document = this.document;
|
||||
if (cfg.heatmap.enabled) {
|
||||
const start = process.hrtime();
|
||||
|
||||
let commit: GitBlameCommit | undefined;
|
||||
let hover: DecorationOptions | undefined;
|
||||
const now = Date.now();
|
||||
const offset = this.uri.offset;
|
||||
const renderOptions = Annotations.hoverRenderOptions(this._config.theme, cfg.heatmap);
|
||||
|
||||
for (const l of blame.lines) {
|
||||
commit = blame.commits.get(l.sha);
|
||||
if (commit === undefined) continue;
|
||||
const decorations: DecorationOptions[] = [];
|
||||
const decorationsMap: { [sha: string]: DecorationOptions } = Object.create(null);
|
||||
|
||||
const line = l.line + offset;
|
||||
let commit: GitBlameCommit | undefined;
|
||||
let hover: DecorationOptions | undefined;
|
||||
|
||||
hover = Annotations.hover(commit, renderOptions, cfg.heatmap.enabled, dateFormat);
|
||||
for (const l of blame.lines) {
|
||||
const line = l.line + offset;
|
||||
|
||||
hover = decorationsMap[l.sha];
|
||||
|
||||
if (hover !== undefined) {
|
||||
hover = {
|
||||
...hover,
|
||||
range: new Range(line, 0, line, 0)
|
||||
} as DecorationOptions;
|
||||
|
||||
decorations.push(hover);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
commit = blame.commits.get(l.sha);
|
||||
if (commit === undefined) continue;
|
||||
|
||||
hover = Annotations.hover(commit, renderOptions, now);
|
||||
hover.range = new Range(line, 0, line, 0);
|
||||
|
||||
decorations.push(hover);
|
||||
decorationsMap[l.sha] = hover;
|
||||
|
||||
if (cfg.wholeLine) {
|
||||
hover.range = document.validateRange(new Range(line, 0, line, endOfLineIndex));
|
||||
}
|
||||
else {
|
||||
const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
|
||||
hover.range = new Range(line, 0, line, endIndex);
|
||||
}
|
||||
|
||||
if (cfg.heatmap.enabled) {
|
||||
Annotations.applyHeatmap(hover, commit.date, now);
|
||||
if (decorations.length) {
|
||||
this.editor.setDecorations(this.decoration!, decorations);
|
||||
}
|
||||
|
||||
decorations.push(hover);
|
||||
const duration = process.hrtime(start);
|
||||
Logger.log(`${(duration[0] * 1000) + Math.floor(duration[1] / 1000000)} ms to compute hover blame annotations`);
|
||||
}
|
||||
|
||||
if (decorations.length) {
|
||||
this.editor.setDecorations(this.decoration!, decorations);
|
||||
}
|
||||
|
||||
// console.timeEnd('Computing blame annotations...');
|
||||
|
||||
this.registerHoverProvider();
|
||||
this.selection(shaOrLine, blame);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Annotations, endOfLineIndex } from './annotations';
|
||||
import { FileAnnotationType } from './annotationController';
|
||||
import { AnnotationProviderBase } from './annotationProvider';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
|
||||
|
||||
@@ -20,6 +21,8 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
|
||||
const diff = await this.git.getDiffForFile(this.uri, commit.previousSha);
|
||||
if (diff === undefined) return false;
|
||||
|
||||
const start = process.hrtime();
|
||||
|
||||
const cfg = this._config.annotations.file.recentChanges;
|
||||
const dateFormat = this._config.defaultDateFormat;
|
||||
|
||||
@@ -34,16 +37,11 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
|
||||
|
||||
if (line.state === 'unchanged') continue;
|
||||
|
||||
let endingIndex = 0;
|
||||
if (cfg.hover.details || cfg.hover.changes) {
|
||||
endingIndex = cfg.hover.wholeLine ? endOfLineIndex : this.editor.document.lineAt(count).firstNonWhitespaceCharacterIndex;
|
||||
}
|
||||
|
||||
const range = this.editor.document.validateRange(new Range(new Position(count, 0), new Position(count, endingIndex)));
|
||||
const range = this.editor.document.validateRange(new Range(new Position(count, 0), new Position(count, endOfLineIndex)));
|
||||
|
||||
if (cfg.hover.details) {
|
||||
decorators.push({
|
||||
hoverMessage: Annotations.getHoverMessage(commit, dateFormat),
|
||||
hoverMessage: Annotations.getHoverMessage(commit, dateFormat, this.git.hasRemotes(commit.repoPath)),
|
||||
range: range
|
||||
} as DecorationOptions);
|
||||
}
|
||||
@@ -62,6 +60,9 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
|
||||
|
||||
this.editor.setDecorations(this.highlightDecoration!, decorators);
|
||||
|
||||
const duration = process.hrtime(start);
|
||||
Logger.log(`${(duration[0] * 1000) + Math.floor(duration[1] / 1000000)} ms to compute recent changes annotations`);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,15 @@ export interface OpenCommitInRemoteCommandArgs {
|
||||
|
||||
export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
|
||||
|
||||
static getMarkdownCommandArgs(sha: string): string;
|
||||
static getMarkdownCommandArgs(args: OpenCommitInRemoteCommandArgs): string;
|
||||
static getMarkdownCommandArgs(argsOrSha: OpenCommitInRemoteCommandArgs | string): string {
|
||||
const args = typeof argsOrSha === 'string'
|
||||
? { sha: argsOrSha }
|
||||
: argsOrSha;
|
||||
return super.getMarkdownCommandArgsCore<OpenCommitInRemoteCommandArgs>(Commands.OpenCommitInRemote, args);
|
||||
}
|
||||
|
||||
constructor(private git: GitService) {
|
||||
super(Commands.OpenCommitInRemote);
|
||||
}
|
||||
|
||||
@@ -251,7 +251,6 @@ export interface IConfig {
|
||||
hover: {
|
||||
details: boolean;
|
||||
changes: boolean;
|
||||
wholeLine: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -80,6 +80,7 @@ export type GlyphChars = '\u21a9' |
|
||||
'\u2192' |
|
||||
'\u21e8' |
|
||||
'\u2191' |
|
||||
'\u2197' |
|
||||
'\u2713' |
|
||||
'\u2014' |
|
||||
'\u2022' |
|
||||
@@ -95,6 +96,7 @@ export const GlyphChars = {
|
||||
ArrowRight: '\u2192' as GlyphChars,
|
||||
ArrowRightHollow: '\u21e8' as GlyphChars,
|
||||
ArrowUp: '\u2191' as GlyphChars,
|
||||
ArrowUpRight: '\u2197' as GlyphChars,
|
||||
Check: '\u2713' as GlyphChars,
|
||||
Dash: '\u2014' as GlyphChars,
|
||||
Dot: '\u2022' as GlyphChars,
|
||||
|
||||
@@ -295,12 +295,10 @@ export class CurrentLineController extends Disposable {
|
||||
const decorationOptions: DecorationOptions[] = [];
|
||||
|
||||
let showChanges = false;
|
||||
let showChangesStartIndex = 0;
|
||||
let showChangesInStartingWhitespace = false;
|
||||
|
||||
let showDetails = false;
|
||||
let showDetailsStartIndex = 0;
|
||||
let showDetailsInStartingWhitespace = false;
|
||||
|
||||
let showAtStart = false;
|
||||
let showStartIndex = 0;
|
||||
|
||||
switch (state.annotationType) {
|
||||
case LineAnnotationType.Trailing: {
|
||||
@@ -308,21 +306,7 @@ export class CurrentLineController extends Disposable {
|
||||
|
||||
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;
|
||||
}
|
||||
showStartIndex = cfgAnnotations.hover.wholeLine ? 0 : endOfLineIndex;
|
||||
|
||||
const decoration = Annotations.trailing(commit, cfgAnnotations.format, cfgAnnotations.dateFormat === null ? this._config.defaultDateFormat : cfgAnnotations.dateFormat, this._config.theme);
|
||||
decoration.range = editor.document.validateRange(new Range(line, endOfLineIndex, line, endOfLineIndex));
|
||||
@@ -334,12 +318,8 @@ export class CurrentLineController extends Disposable {
|
||||
const cfgAnnotations = this._config.annotations.line.hover;
|
||||
|
||||
showChanges = cfgAnnotations.changes;
|
||||
showChangesStartIndex = 0;
|
||||
showChangesInStartingWhitespace = false;
|
||||
|
||||
showDetails = cfgAnnotations.details;
|
||||
showDetailsStartIndex = 0;
|
||||
showDetailsInStartingWhitespace = false;
|
||||
showStartIndex = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -348,25 +328,15 @@ export class CurrentLineController extends Disposable {
|
||||
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;
|
||||
showStartIndex = 0;
|
||||
}
|
||||
else {
|
||||
if (showDetailsStartIndex === 0) {
|
||||
showDetailsStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
|
||||
}
|
||||
if (showChangesStartIndex === 0) {
|
||||
showChangesInStartingWhitespace = true;
|
||||
showChangesStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
|
||||
}
|
||||
else if (showStartIndex !== 0) {
|
||||
showAtStart = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,20 +344,11 @@ export class CurrentLineController extends Disposable {
|
||||
}
|
||||
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;
|
||||
showStartIndex = 0;
|
||||
}
|
||||
else {
|
||||
if (showDetailsStartIndex === 0) {
|
||||
showDetailsStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
|
||||
}
|
||||
if (showChangesStartIndex === 0) {
|
||||
showChangesInStartingWhitespace = true;
|
||||
showChangesStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
|
||||
}
|
||||
else if (showStartIndex !== 0) {
|
||||
showAtStart = true;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -395,29 +356,21 @@ export class CurrentLineController extends Disposable {
|
||||
case FileAnnotationType.RecentChanges: {
|
||||
const cfgChanges = this._config.annotations.file.recentChanges.hover;
|
||||
if (cfgChanges.details) {
|
||||
if (cfgChanges.wholeLine) {
|
||||
// Avoid double annotations if we are showing the whole-file hover blame annotations
|
||||
showDetails = false;
|
||||
}
|
||||
else {
|
||||
showDetailsInStartingWhitespace = false;
|
||||
}
|
||||
// Avoid double annotations if we are showing the whole-file hover blame annotations
|
||||
showDetails = false;
|
||||
}
|
||||
|
||||
if (cfgChanges.changes) {
|
||||
if (cfgChanges.wholeLine) {
|
||||
// Avoid double annotations if we are showing the whole-file hover blame annotations
|
||||
showChanges = false;
|
||||
}
|
||||
else {
|
||||
showChangesInStartingWhitespace = false;
|
||||
}
|
||||
// Avoid double annotations if we are showing the whole-file hover blame annotations
|
||||
showChanges = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const range = editor.document.validateRange(new Range(line, showStartIndex, line, endOfLineIndex));
|
||||
|
||||
if (showDetails) {
|
||||
// Get the full commit message -- since blame only returns the summary
|
||||
let logCommit: GitCommit | undefined = undefined;
|
||||
@@ -425,29 +378,22 @@ export class CurrentLineController extends Disposable {
|
||||
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, this._config.defaultDateFormat);
|
||||
decoration.range = editor.document.validateRange(new Range(line, showDetailsStartIndex, line, endOfLineIndex));
|
||||
const decoration = Annotations.detailsHover(logCommit || commit, this._config.defaultDateFormat, this.git.hasRemotes((logCommit || commit).repoPath));
|
||||
decoration.range = range;
|
||||
decorationOptions.push(decoration);
|
||||
|
||||
if (showDetailsInStartingWhitespace && showDetailsStartIndex !== 0 && decoration.range.end.character !== 0) {
|
||||
decorationOptions.push(Annotations.withRange(decoration, 0, firstNonWhitespace));
|
||||
if (showAtStart) {
|
||||
decorationOptions.push(Annotations.withRange(decoration, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
decoration.range = range;
|
||||
decorationOptions.push(decoration);
|
||||
|
||||
if (showChangesInStartingWhitespace && showChangesStartIndex !== 0 && decoration.range.end.character !== 0) {
|
||||
decorationOptions.push(Annotations.withRange(decoration, 0, firstNonWhitespace));
|
||||
if (showAtStart) {
|
||||
decorationOptions.push(Annotations.withRange(decoration, 0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { Strings } from '../../system';
|
||||
import { GitCommit } from '../models/commit';
|
||||
import { Formatter, IFormatOptions } from './formatter';
|
||||
import * as moment from 'moment';
|
||||
import { GlyphChars } from '../../constants';
|
||||
|
||||
export interface ICommitFormatOptions extends IFormatOptions {
|
||||
@@ -20,7 +19,7 @@ export interface ICommitFormatOptions extends IFormatOptions {
|
||||
export class CommitFormatter extends Formatter<GitCommit, ICommitFormatOptions> {
|
||||
|
||||
get ago() {
|
||||
const ago = moment(this._item.date).fromNow();
|
||||
const ago = this._item.fromNow();
|
||||
return this._padOrTruncate(ago, this._options.tokenOptions!.ago);
|
||||
}
|
||||
|
||||
@@ -30,12 +29,12 @@ export class CommitFormatter extends Formatter<GitCommit, ICommitFormatOptions>
|
||||
}
|
||||
|
||||
get authorAgo() {
|
||||
const authorAgo = `${this._item.author}, ${moment(this._item.date).fromNow()}`;
|
||||
const authorAgo = `${this._item.author}, ${this._item.fromNow()}`;
|
||||
return this._padOrTruncate(authorAgo, this._options.tokenOptions!.authorAgo);
|
||||
}
|
||||
|
||||
get date() {
|
||||
const date = moment(this._item.date).format(this._options.dateFormat!);
|
||||
const date = this._item.formatDate(this._options.dateFormat!);
|
||||
return this._padOrTruncate(date, this._options.tokenOptions!.date);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ export * from './remotes/provider';
|
||||
|
||||
let git: IGit;
|
||||
|
||||
// `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nparents %P%nsummary %B%nfilename ?`
|
||||
const defaultLogParams = [`log`, `--name-status`, `--full-history`, `-M`, `--date=iso8601`, `--format=%H -%nauthor %an%nauthor-date %ai%nparents %P%nsummary %B%nfilename ?`];
|
||||
const defaultStashParams = [`stash`, `list`, `--name-status`, `--full-history`, `-M`, `--format=%H -%nauthor-date %ai%nreflog-selector %gd%nsummary %B%nfilename ?`];
|
||||
const defaultBlameParams = [`blame`, `--root`, `--incremental`];
|
||||
const defaultLogParams = [`log`, `--name-status`, `--full-history`, `-M`, `--format=%H -%nauthor %an%nauthor-date %at%nparents %P%nsummary %B%nfilename ?`];
|
||||
const defaultStashParams = [`stash`, `list`, `--name-status`, `--full-history`, `-M`, `--format=%H -%nauthor-date %at%nreflog-selector %gd%nsummary %B%nfilename ?`];
|
||||
|
||||
let defaultEncoding = 'utf8';
|
||||
export function setDefaultEncoding(encoding: string) {
|
||||
@@ -180,7 +180,7 @@ export class Git {
|
||||
static blame(repoPath: string | undefined, fileName: string, sha?: string, options: { ignoreWhitespace?: boolean, startLine?: number, endLine?: number } = {}) {
|
||||
const [file, root] = Git.splitPath(fileName, repoPath);
|
||||
|
||||
const params = [`blame`, `--root`, `--incremental`];
|
||||
const params = [...defaultBlameParams];
|
||||
|
||||
if (options.ignoreWhitespace) {
|
||||
params.push('-w');
|
||||
|
||||
@@ -42,7 +42,7 @@ export class GitUri extends Uri {
|
||||
}
|
||||
else {
|
||||
const commit = commitOrRepoPath;
|
||||
base._fsPath = path.resolve(commit.repoPath, commit.originalFileName || commit.fileName);
|
||||
base._fsPath = path.resolve(commit.repoPath, commit.originalFileName || commit.fileName || '');
|
||||
|
||||
if (commit.repoPath !== undefined) {
|
||||
this.repoPath = commit.repoPath;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
import { Strings } from '../../system';
|
||||
import { Dates, Strings } from '../../system';
|
||||
import { Uri } from 'vscode';
|
||||
import { GlyphChars } from '../../constants';
|
||||
import { Git } from '../git';
|
||||
@@ -70,7 +70,23 @@ export class GitCommit {
|
||||
}
|
||||
|
||||
get uri(): Uri {
|
||||
return Uri.file(path.resolve(this.repoPath, this.originalFileName || this.fileName));
|
||||
return Uri.file(path.resolve(this.repoPath, this.originalFileName || this.fileName || ''));
|
||||
}
|
||||
|
||||
private _dateFormatter?: Dates.IDateFormatter;
|
||||
|
||||
formatDate(format: string) {
|
||||
if (this._dateFormatter === undefined) {
|
||||
this._dateFormatter = Dates.toFormatter(this.date);
|
||||
}
|
||||
return this._dateFormatter.format(format);
|
||||
}
|
||||
|
||||
fromNow() {
|
||||
if (this._dateFormatter === undefined) {
|
||||
this._dateFormatter = Dates.toFormatter(this.date);
|
||||
}
|
||||
return this._dateFormatter.fromNow();
|
||||
}
|
||||
|
||||
getFormattedPath(separator: string = Strings.pad(GlyphChars.Dot, 2, 2)): string {
|
||||
|
||||
@@ -40,7 +40,12 @@ export class GitLogCommit extends GitCommit {
|
||||
this.status = fileStatus.status;
|
||||
}
|
||||
else {
|
||||
this.fileStatuses = [{ status: status, fileName: fileName, originalFileName: originalFileName } as IGitStatusFile];
|
||||
if (fileName === undefined) {
|
||||
this.fileStatuses = [];
|
||||
}
|
||||
else {
|
||||
this.fileStatuses = [{ status: status, fileName: fileName, originalFileName: originalFileName } as IGitStatusFile];
|
||||
}
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface GitStatus {
|
||||
files: GitStatusFile[];
|
||||
}
|
||||
|
||||
export declare type GitStatusFileStatus = '!' | '?' | 'A' | 'C' | 'D' | 'M' | 'R' | 'U';
|
||||
export declare type GitStatusFileStatus = '!' | '?' | 'A' | 'C' | 'D' | 'M' | 'R' | 'T' | 'U' | 'X' | 'B';
|
||||
|
||||
export interface IGitStatusFile {
|
||||
status: GitStatusFileStatus;
|
||||
@@ -71,7 +71,10 @@ const statusOcticonsMap = {
|
||||
D: '$(diff-removed)',
|
||||
M: '$(diff-modified)',
|
||||
R: '$(diff-renamed)',
|
||||
U: '$(question)'
|
||||
T: '$(diff-modified)',
|
||||
U: '$(alert)',
|
||||
X: '$(question)',
|
||||
B: '$(question)'
|
||||
};
|
||||
|
||||
export function getGitStatusOcticon(status: GitStatusFileStatus, missing: string = GlyphChars.Space.repeat(4)): string {
|
||||
@@ -86,7 +89,10 @@ const statusIconsMap = {
|
||||
D: 'icon-status-deleted.svg',
|
||||
M: 'icon-status-modified.svg',
|
||||
R: 'icon-status-renamed.svg',
|
||||
U: 'icon-status-conflict.svg'
|
||||
T: 'icon-status-modified.svg',
|
||||
U: 'icon-status-conflict.svg',
|
||||
X: 'icon-status-unknown.svg',
|
||||
B: 'icon-status-unknown.svg'
|
||||
};
|
||||
|
||||
export function getGitStatusIcon(status: GitStatusFileStatus): string {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
import { Strings } from '../../system';
|
||||
import { Git, GitAuthor, GitBlame, GitBlameCommit, GitCommitLine } from './../git';
|
||||
import * as moment from 'moment';
|
||||
import * as path from 'path';
|
||||
|
||||
interface BlameEntry {
|
||||
@@ -134,7 +133,7 @@ export class GitBlameParser {
|
||||
}
|
||||
}
|
||||
|
||||
commit = new GitBlameCommit(repoPath!, entry.sha, fileName!, entry.author, moment(`${entry.authorDate} ${entry.authorTimeZone}`, 'X +-HHmm').toDate(), entry.summary!, []);
|
||||
commit = new GitBlameCommit(repoPath!, entry.sha, fileName!, entry.author, new Date(entry.authorDate as any * 1000), entry.summary!, []);
|
||||
|
||||
if (fileName !== entry.fileName) {
|
||||
commit.originalFileName = entry.fileName;
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Strings } from '../../system';
|
||||
import { Range } from 'vscode';
|
||||
import { Git, GitAuthor, GitCommitType, GitLog, GitLogCommit, GitStatusFileStatus, IGitStatusFile } from './../git';
|
||||
// import { Logger } from '../../logger';
|
||||
import * as moment from 'moment';
|
||||
import * as path from 'path';
|
||||
|
||||
interface LogEntry {
|
||||
@@ -87,7 +86,7 @@ export class GitLogParser {
|
||||
break;
|
||||
|
||||
case 'author-date':
|
||||
entry.authorDate = `${lineParts[1]}T${lineParts[2]}${lineParts[3]}`;
|
||||
entry.authorDate = lineParts[1];
|
||||
break;
|
||||
|
||||
case 'parents':
|
||||
@@ -231,7 +230,7 @@ export class GitLogParser {
|
||||
}
|
||||
}
|
||||
|
||||
commit = new GitLogCommit(type, repoPath!, entry.sha, relativeFileName, entry.author, moment(entry.authorDate).toDate(), entry.summary!, entry.status, entry.fileStatuses, undefined, entry.originalFileName);
|
||||
commit = new GitLogCommit(type, repoPath!, entry.sha, relativeFileName, entry.author, new Date(entry.authorDate! as any * 1000), entry.summary!, entry.status, entry.fileStatuses, undefined, entry.originalFileName);
|
||||
commit.parentShas = entry.parentShas!;
|
||||
|
||||
if (relativeFileName !== entry.fileName) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
import { Git, GitStash, GitStashCommit, GitStatusFileStatus, IGitStatusFile } from './../git';
|
||||
// import { Logger } from '../../logger';
|
||||
import * as moment from 'moment';
|
||||
|
||||
interface StashEntry {
|
||||
sha: string;
|
||||
@@ -14,11 +13,33 @@ interface StashEntry {
|
||||
|
||||
export class GitStashParser {
|
||||
|
||||
static parse(data: string, repoPath: string): GitStash | undefined {
|
||||
const entries = this._parseEntries(data);
|
||||
if (entries === undefined) return undefined;
|
||||
|
||||
const commits: Map<string, GitStashCommit> = new Map();
|
||||
|
||||
for (let i = 0, len = entries.length; i < len; i++) {
|
||||
const entry = entries[i];
|
||||
|
||||
let commit = commits.get(entry.sha);
|
||||
if (commit === undefined) {
|
||||
commit = new GitStashCommit(entry.stashName, repoPath, entry.sha, entry.fileNames, new Date(entry.date! as any * 1000), entry.summary, undefined, entry.fileStatuses) as GitStashCommit;
|
||||
commits.set(entry.sha, commit);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
repoPath: repoPath,
|
||||
commits: commits
|
||||
} as GitStash;
|
||||
}
|
||||
|
||||
private static _parseEntries(data: string): StashEntry[] | undefined {
|
||||
if (!data) return undefined;
|
||||
|
||||
const lines = data.split('\n');
|
||||
if (!lines.length) return undefined;
|
||||
if (lines.length === 0) return undefined;
|
||||
|
||||
const entries: StashEntry[] = [];
|
||||
|
||||
@@ -42,7 +63,7 @@ export class GitStashParser {
|
||||
|
||||
switch (lineParts[0]) {
|
||||
case 'author-date':
|
||||
entry.date = `${lineParts[1]}T${lineParts[2]}${lineParts[3]}`;
|
||||
entry.date = lineParts[1];
|
||||
break;
|
||||
|
||||
case 'summary':
|
||||
@@ -66,7 +87,12 @@ export class GitStashParser {
|
||||
case 'filename':
|
||||
const nextLine = lines[position + 1];
|
||||
// If the next line isn't blank, make sure it isn't starting a new commit
|
||||
if (nextLine && Git.shaRegex.test(nextLine)) continue;
|
||||
if (nextLine && Git.shaRegex.test(nextLine)) {
|
||||
entries.push(entry);
|
||||
entry = undefined;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
position++;
|
||||
|
||||
@@ -109,28 +135,6 @@ export class GitStashParser {
|
||||
return entries;
|
||||
}
|
||||
|
||||
static parse(data: string, repoPath: string): GitStash | undefined {
|
||||
const entries = this._parseEntries(data);
|
||||
if (entries === undefined) return undefined;
|
||||
|
||||
const commits: Map<string, GitStashCommit> = new Map();
|
||||
|
||||
for (let i = 0, len = entries.length; i < len; i++) {
|
||||
const entry = entries[i];
|
||||
|
||||
let commit = commits.get(entry.sha);
|
||||
if (commit === undefined) {
|
||||
commit = new GitStashCommit(entry.stashName, repoPath, entry.sha, entry.fileNames, moment(entry.date).toDate(), entry.summary, undefined, entry.fileStatuses) as GitStashCommit;
|
||||
commits.set(entry.sha, commit);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
repoPath: repoPath,
|
||||
commits: commits
|
||||
} as GitStash;
|
||||
}
|
||||
|
||||
private static _parseFileName(entry: { fileName?: string, originalFileName?: string }) {
|
||||
if (entry.fileName === undefined) return;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export class GitStatusParser {
|
||||
if (!data) return undefined;
|
||||
|
||||
const lines = data.split('\n').filter(_ => !!_);
|
||||
if (!lines.length) return undefined;
|
||||
if (lines.length === 0) return undefined;
|
||||
|
||||
const status = {
|
||||
branch: '',
|
||||
|
||||
@@ -6,7 +6,6 @@ import { BuiltInCommands, DocumentSchemes, ExtensionKey } from './constants';
|
||||
import { CodeLensCommand, CodeLensLocations, ICodeLensLanguageLocation, IConfig } from './configuration';
|
||||
import { GitBlame, GitBlameCommit, GitBlameLines, GitService, GitUri } from './gitService';
|
||||
import { Logger } from './logger';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export class GitRecentChangeCodeLens extends CodeLens {
|
||||
|
||||
@@ -254,7 +253,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
|
||||
if (blame === undefined) return lens;
|
||||
|
||||
const recentCommit = Iterables.first(blame.commits.values());
|
||||
title = `${recentCommit.author}, ${moment(recentCommit.date).fromNow()}`;
|
||||
title = `${recentCommit.author}, ${recentCommit.fromNow()}`;
|
||||
if (this._config.codeLens.debug) {
|
||||
title += ` [${SymbolKind[lens.symbolKind]}(${lens.range.start.character}-${lens.range.end.character}), Lines (${lens.blameRange.start.line + 1}-${lens.blameRange.end.line + 1}), Commit (${recentCommit.shortSha})]`;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import { GitUri, IGitCommitInfo, IGitUriData } from './git/gitUri';
|
||||
import { Logger } from './logger';
|
||||
import * as fs from 'fs';
|
||||
import * as ignore from 'ignore';
|
||||
import * as moment from 'moment';
|
||||
import * as path from 'path';
|
||||
|
||||
export { GitUri, IGitCommitInfo };
|
||||
@@ -167,7 +166,6 @@ export class GitService extends Disposable {
|
||||
this._repoWatcher = undefined;
|
||||
|
||||
this._gitCache.clear();
|
||||
this._remotesCache.clear();
|
||||
}
|
||||
|
||||
this._gitignore = new Promise<ignore.Ignore | undefined>((resolve, reject) => {
|
||||
@@ -568,7 +566,7 @@ export class GitService extends Disposable {
|
||||
Iterables.forEach(blame.commits.values(), (c, i) => {
|
||||
if (c.isUncommitted) return;
|
||||
|
||||
const decoration = `${GlyphChars.ArrowDropRight} ${c.author}, ${moment(c.date).format(dateFormat)}`;
|
||||
const decoration = `${GlyphChars.ArrowDropRight} ${c.author}, ${c.formatDate(dateFormat)}`;
|
||||
const uri = GitService.toReferenceGitContentUri(c, i + 1, commitCount, c.originalFileName, decoration, dateFormat);
|
||||
locations.push(new Location(uri, new Position(0, 0)));
|
||||
if (c.sha === selectedSha) {
|
||||
@@ -888,7 +886,7 @@ export class GitService extends Disposable {
|
||||
Iterables.forEach(log.commits.values(), (c, i) => {
|
||||
if (c.isUncommitted) return;
|
||||
|
||||
const decoration = `${GlyphChars.ArrowDropRight} ${c.author}, ${moment(c.date).format(dateFormat)}`;
|
||||
const decoration = `${GlyphChars.ArrowDropRight} ${c.author}, ${c.formatDate(dateFormat)}`;
|
||||
const uri = GitService.toReferenceGitContentUri(c, i + 1, commitCount, c.originalFileName, decoration, dateFormat);
|
||||
locations.push(new Location(uri, new Position(0, 0)));
|
||||
if (c.sha === selectedSha) {
|
||||
@@ -899,21 +897,26 @@ export class GitService extends Disposable {
|
||||
return locations;
|
||||
}
|
||||
|
||||
hasRemotes(repoPath: string): boolean {
|
||||
const remotes = this._remotesCache.get(repoPath);
|
||||
return remotes !== undefined && remotes.length > 0;
|
||||
}
|
||||
|
||||
async getRemotes(repoPath: string): Promise<GitRemote[]> {
|
||||
if (!repoPath) return [];
|
||||
|
||||
Logger.log(`getRemotes('${repoPath}')`);
|
||||
|
||||
if (this.UseCaching) {
|
||||
const remotes = this._remotesCache.get(repoPath);
|
||||
if (remotes !== undefined) return remotes;
|
||||
}
|
||||
let remotes = this._remotesCache.get(repoPath);
|
||||
if (remotes !== undefined) return remotes;
|
||||
|
||||
const data = await Git.remote(repoPath);
|
||||
const remotes = GitRemoteParser.parse(data, repoPath);
|
||||
if (this.UseCaching) {
|
||||
remotes = GitRemoteParser.parse(data, repoPath);
|
||||
|
||||
if (remotes !== undefined) {
|
||||
this._remotesCache.set(repoPath, remotes);
|
||||
}
|
||||
|
||||
return remotes;
|
||||
}
|
||||
|
||||
@@ -1138,7 +1141,7 @@ export class GitService extends Disposable {
|
||||
}
|
||||
|
||||
// NOTE: Need to specify an index here, since I can't control the sort order -- just alphabetic or by file location
|
||||
return Uri.parse(`${scheme}:${pad(data.index || 0)} ${GlyphChars.Dot} ${encodeURIComponent(message)} ${GlyphChars.Dot} ${moment(commit.date).format(dateFormat)} ${GlyphChars.Dot} ${encodeURIComponent(uriPath)}?${JSON.stringify(data)}`);
|
||||
return Uri.parse(`${scheme}:${pad(data.index || 0)} ${GlyphChars.Dot} ${encodeURIComponent(message)} ${GlyphChars.Dot} ${commit.formatDate(dateFormat)} ${GlyphChars.Dot} ${encodeURIComponent(uriPath)}?${JSON.stringify(data)}`);
|
||||
}
|
||||
|
||||
private static _toGitUriData<T extends IGitUriData>(commit: IGitUriData, index?: number, originalFileName?: string, decoration?: string): T {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { commands, ExtensionContext, Uri, window } from 'vscode';
|
||||
import { BuiltInCommands } from './constants';
|
||||
import { GitCommit } from './gitService';
|
||||
import { Logger } from './logger';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export type SuppressedKeys = 'suppressCommitHasNoPreviousCommitWarning' |
|
||||
'suppressCommitNotFoundWarning' |
|
||||
@@ -34,7 +33,7 @@ export class Messages {
|
||||
|
||||
static showCommitHasNoPreviousCommitWarningMessage(commit?: GitCommit): Promise<string | undefined> {
|
||||
if (commit === undefined) return Messages._showMessage('info', `Commit has no previous commit`, SuppressedKeys.CommitHasNoPreviousCommitWarning);
|
||||
return Messages._showMessage('info', `Commit ${commit.shortSha} (${commit.author}, ${moment(commit.date).fromNow()}) has no previous commit`, SuppressedKeys.CommitHasNoPreviousCommitWarning);
|
||||
return Messages._showMessage('info', `Commit ${commit.shortSha} (${commit.author}, ${commit.fromNow()}) has no previous commit`, SuppressedKeys.CommitHasNoPreviousCommitWarning);
|
||||
}
|
||||
|
||||
static showCommitNotFoundWarningMessage(message: string): Promise<string | undefined> {
|
||||
|
||||
@@ -7,7 +7,6 @@ import { GlyphChars } from '../constants';
|
||||
import { getGitStatusOcticon, GitCommit, GitLog, GitLogCommit, GitService, GitStashCommit, GitStatusFile, GitStatusFileStatus, GitUri, IGitCommitInfo, IGitStatusFile, RemoteResource } from '../gitService';
|
||||
import { Keyboard, KeyCommand, KeyNoopCommand, Keys } from '../keyboard';
|
||||
import { OpenRemotesCommandQuickPickItem } from './remotes';
|
||||
import * as moment from 'moment';
|
||||
import * as path from 'path';
|
||||
|
||||
export class CommitWithFileStatusQuickPickItem extends OpenFileCommandQuickPickItem {
|
||||
@@ -299,7 +298,7 @@ export class CommitDetailsQuickPick {
|
||||
const pick = await window.showQuickPick(items, {
|
||||
matchOnDescription: true,
|
||||
matchOnDetail: true,
|
||||
placeHolder: `${commit.shortSha} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.author ? `${commit.author}, ` : ''}${moment(commit.date).fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.message}`,
|
||||
placeHolder: `${commit.shortSha} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.author ? `${commit.author}, ` : ''}${commit.fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.message}`,
|
||||
ignoreFocusOut: getQuickPickIgnoreFocusOut(),
|
||||
onDidSelectItem: (item: QuickPickItem) => {
|
||||
scope.setKeyCommand('right', item);
|
||||
|
||||
@@ -7,7 +7,6 @@ import { GlyphChars } from '../constants';
|
||||
import { GitLog, GitLogCommit, GitService, GitUri, RemoteResource } from '../gitService';
|
||||
import { Keyboard, KeyCommand, KeyNoopCommand } from '../keyboard';
|
||||
import { OpenRemotesCommandQuickPickItem } from './remotes';
|
||||
import * as moment from 'moment';
|
||||
import * as path from 'path';
|
||||
|
||||
export class OpenCommitFileCommandQuickPickItem extends OpenFileCommandQuickPickItem {
|
||||
@@ -275,7 +274,7 @@ export class CommitFileDetailsQuickPick {
|
||||
|
||||
const pick = await window.showQuickPick(items, {
|
||||
matchOnDescription: true,
|
||||
placeHolder: `${commit.getFormattedPath()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${isUncommitted ? `Uncommitted ${GlyphChars.ArrowRightHollow} ` : '' }${commit.shortSha} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.author}, ${moment(commit.date).fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.message}`,
|
||||
placeHolder: `${commit.getFormattedPath()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${isUncommitted ? `Uncommitted ${GlyphChars.ArrowRightHollow} ` : '' }${commit.shortSha} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.author}, ${commit.fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.message}`,
|
||||
ignoreFocusOut: getQuickPickIgnoreFocusOut(),
|
||||
onDidSelectItem: (item: QuickPickItem) => {
|
||||
scope.setKeyCommand('right', item as KeyCommand);
|
||||
|
||||
@@ -7,7 +7,6 @@ import { GlyphChars } from '../constants';
|
||||
import { GitCommit, GitLogCommit, GitStashCommit } from '../gitService';
|
||||
import { Keyboard, KeyboardScope, KeyMapping, Keys } from '../keyboard';
|
||||
// import { Logger } from '../logger';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export function getQuickPickIgnoreFocusOut() {
|
||||
const cfg = workspace.getConfiguration(ExtensionKey).get<IAdvancedConfig>('advanced')!;
|
||||
@@ -174,12 +173,12 @@ export class CommitQuickPickItem implements QuickPickItem {
|
||||
if (commit instanceof GitStashCommit) {
|
||||
this.label = message;
|
||||
this.description = '';
|
||||
this.detail = `${GlyphChars.Space} ${commit.stashName} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${moment(commit.date).fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.getDiffStatus()}`;
|
||||
this.detail = `${GlyphChars.Space} ${commit.stashName} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.getDiffStatus()}`;
|
||||
}
|
||||
else {
|
||||
this.label = message;
|
||||
this.description = `${Strings.pad('$(git-commit)', 1, 1)} ${commit.shortSha}`;
|
||||
this.detail = `${GlyphChars.Space} ${commit.author}, ${moment(commit.date).fromNow()}${(commit.type === 'branch') ? ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${(commit as GitLogCommit).getDiffStatus()}` : ''}`;
|
||||
this.detail = `${GlyphChars.Space} ${commit.author}, ${commit.fromNow()}${(commit.type === 'branch') ? ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${(commit as GitLogCommit).getDiffStatus()}` : ''}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
export * from './system/array';
|
||||
export * from './system/date';
|
||||
// export * from './system/disposable';
|
||||
// export * from './system/element';
|
||||
// export * from './system/event';
|
||||
|
||||
33
src/system/date.ts
Normal file
33
src/system/date.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
import * as moment from 'moment';
|
||||
|
||||
const MillisecondsPerMinute = 60000; // 60 * 1000
|
||||
const MillisecondsPerDay = 86400000; // 24 * 60 * 60 * 1000
|
||||
|
||||
export namespace Dates {
|
||||
|
||||
export interface IDateFormatter {
|
||||
fromNow: () => string;
|
||||
format: (format: string) => string;
|
||||
}
|
||||
|
||||
export function dateDaysFromNow(date: Date, now: number = Date.now()) {
|
||||
const startOfDayLeft = startOfDay(now);
|
||||
const startOfDayRight = startOfDay(date);
|
||||
|
||||
const timestampLeft = startOfDayLeft.getTime() - startOfDayLeft.getTimezoneOffset() * MillisecondsPerMinute;
|
||||
const timestampRight = startOfDayRight.getTime() - startOfDayRight.getTimezoneOffset() * MillisecondsPerMinute;
|
||||
|
||||
return Math.round((timestampLeft - timestampRight) / MillisecondsPerDay);
|
||||
}
|
||||
|
||||
export function startOfDay(date: Date | number) {
|
||||
const newDate = new Date(typeof date === 'number' ? date : date.getTime());
|
||||
newDate.setHours(0, 0, 0, 0);
|
||||
return newDate;
|
||||
}
|
||||
|
||||
export function toFormatter(date: Date): IDateFormatter {
|
||||
return moment(date);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
'use strict';
|
||||
import { Iterables } from '../system';
|
||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||
import { CommitFormatter, GitService, GitStashCommit, GitUri, ICommitFormatOptions } from '../gitService';
|
||||
@@ -13,7 +14,20 @@ export class StashNode extends ExplorerNode {
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
return Promise.resolve((this.commit as GitStashCommit).fileStatuses.map(s => new StashFileNode(s, this.commit, this.context, this.git)));
|
||||
const statuses = (this.commit as GitStashCommit).fileStatuses;
|
||||
|
||||
// Check for any untracked files -- since git doesn't return them via `git stash list` :(
|
||||
const log = await this.git.getLogForRepo(this.commit.repoPath, `${(this.commit as GitStashCommit).stashName}^3`, 1);
|
||||
if (log !== undefined) {
|
||||
const commit = Iterables.first(log.commits.values());
|
||||
if (commit !== undefined && commit.fileStatuses.length !== 0) {
|
||||
// Since these files are untracked -- make them look that way
|
||||
commit.fileStatuses.forEach(s => s.status = '?');
|
||||
statuses.splice(statuses.length, 0, ...commit.fileStatuses);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(statuses.map(s => new StashFileNode(s, this.commit, this.context, this.git)));
|
||||
}
|
||||
|
||||
getTreeItem(): TreeItem {
|
||||
|
||||
Reference in New Issue
Block a user