ソースを参照

Improves changes hover accuracy & diff rendering

Adds hovers.changesDiff setting for line vs hunk diff
main
Eric Amodio 5年前
コミット
f625d37b00
15個のファイルの変更179行の追加135行の削除
  1. +1
    -0
      README.md
  2. +14
    -0
      package.json
  3. +36
    -22
      src/annotations/annotations.ts
  4. +4
    -3
      src/annotations/blameAnnotationProvider.ts
  5. +5
    -4
      src/annotations/gutterBlameAnnotationProvider.ts
  6. +4
    -3
      src/annotations/heatmapBlameAnnotationProvider.ts
  7. +7
    -6
      src/annotations/recentChangesAnnotationProvider.ts
  8. +2
    -1
      src/commands/diffLineWithWorking.ts
  9. +1
    -0
      src/config.ts
  10. +5
    -1
      src/git/git.ts
  11. +47
    -34
      src/git/gitService.ts
  12. +11
    -13
      src/git/models/diff.ts
  13. +3
    -3
      src/git/parsers/blameParser.ts
  14. +38
    -44
      src/git/parsers/diffParser.ts
  15. +1
    -1
      src/trackers/gitLineTracker.ts

+ 1
- 0
README.md ファイルの表示

@ -695,6 +695,7 @@ GitLens is highly customizable and provides many configuration settings to allow
| `gitlens.hovers.annotations.enabled` | Specifies whether to provide any hovers when showing blame annotations | | `gitlens.hovers.annotations.enabled` | Specifies whether to provide any hovers when showing blame annotations |
| `gitlens.hovers.annotations.over` | Specifies when to trigger hovers when showing blame annotations<br /><br />`annotation` - only shown when hovering over the line annotation<br />`line` - shown when hovering anywhere over the line | | `gitlens.hovers.annotations.over` | Specifies when to trigger hovers when showing blame annotations<br /><br />`annotation` - only shown when hovering over the line annotation<br />`line` - shown when hovering anywhere over the line |
| `gitlens.hovers.avatars` | Specifies whether to show avatar images in hovers | | `gitlens.hovers.avatars` | Specifies whether to show avatar images in hovers |
| `gitlens.hovers.changesDiff` | Specifies whether to show just the changes to the line or the full set of related changes in the _changes (diff)_ hover<br /><br />`line` - Shows only the changes to the line<br /><br />`hunk` - Shows the full set of related changes |
| `gitlens.hovers.currentLine.changes` | Specifies whether to provide a _changes (diff)_ hover for the current line | | `gitlens.hovers.currentLine.changes` | Specifies whether to provide a _changes (diff)_ hover for the current line |
| `gitlens.hovers.currentLine.details` | Specifies whether to provide a _commit details_ hover for the current line | | `gitlens.hovers.currentLine.details` | Specifies whether to provide a _commit details_ hover for the current line |
| `gitlens.hovers.currentLine.enabled` | Specifies whether to provide any hovers for the current line | | `gitlens.hovers.currentLine.enabled` | Specifies whether to provide any hovers for the current line |

+ 14
- 0
package.json ファイルの表示

@ -563,6 +563,20 @@
"markdownDescription": "Specifies whether to show avatar images in hovers", "markdownDescription": "Specifies whether to show avatar images in hovers",
"scope": "window" "scope": "window"
}, },
"gitlens.hovers.changesDiff": {
"type": "string",
"default": "line",
"enum": [
"line",
"hunk"
],
"enumDescriptions": [
"Shows only the changes to the line",
"Shows the full set of related changes"
],
"markdownDescription": "Specifies whether to show just the changes to the line or the full set of related changes in the _changes (diff)_ hover",
"scope": "window"
},
"gitlens.hovers.detailsMarkdownFormat": { "gitlens.hovers.detailsMarkdownFormat": {
"type": "string", "type": "string",
"default": "[${avatar} &nbsp;__${author}__](mailto:${email}), ${ago} &nbsp; _(${date})_ \n\n${message}\n\n${commands}", "default": "[${avatar} &nbsp;__${author}__](mailto:${email}), ${ago} &nbsp; _(${date})_ \n\n${message}\n\n${commands}",

+ 36
- 22
src/annotations/annotations.ts ファイルの表示

@ -14,8 +14,9 @@ import { Container } from '../container';
import { import {
CommitFormatOptions, CommitFormatOptions,
CommitFormatter, CommitFormatter,
GitBlameCommit,
GitCommit, GitCommit,
GitDiffChunkLine,
GitDiffHunkLine,
GitRemote, GitRemote,
GitService, GitService,
GitUri GitUri
@ -108,11 +109,11 @@ export class Annotations {
static getHoverDiffMessage( static getHoverDiffMessage(
commit: GitCommit, commit: GitCommit,
uri: GitUri, uri: GitUri,
chunkLine: GitDiffChunkLine | undefined
hunkLine: GitDiffHunkLine | undefined
): MarkdownString | undefined { ): MarkdownString | undefined {
if (chunkLine === undefined || commit.previousSha === undefined) return undefined;
if (hunkLine === undefined || commit.previousSha === undefined) return undefined;
const codeDiff = this.getCodeDiff(chunkLine);
const diff = this.getDiffFromHunkLine(hunkLine);
let message: string; let message: string;
if (commit.isUncommitted) { if (commit.isUncommitted) {
@ -121,12 +122,12 @@ export class Annotations {
GlyphChars.Dash GlyphChars.Dash
} &nbsp; [\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs( } &nbsp; [\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(
commit.previousSha! commit.previousSha!
)} "Show Commit Details") ${GlyphChars.ArrowLeftRightLong} _${uri.shortSha}_\n${codeDiff}`;
)} "Show Commit Details") ${GlyphChars.ArrowLeftRightLong} _${uri.shortSha}_\n${diff}`;
} }
else { else {
message = `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") &nbsp; ${ message = `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") &nbsp; ${
GlyphChars.Dash GlyphChars.Dash
} &nbsp; _uncommitted changes_\n${codeDiff}`;
} &nbsp; _uncommitted changes_\n${diff}`;
} }
} }
else { else {
@ -136,9 +137,7 @@ export class Annotations {
commit.previousSha! commit.previousSha!
)} "Show Commit Details") ${GlyphChars.ArrowLeftRightLong} [\`${ )} "Show Commit Details") ${GlyphChars.ArrowLeftRightLong} [\`${
commit.shortSha commit.shortSha
}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(
commit.sha
)} "Show Commit Details")\n${codeDiff}`;
}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)} "Show Commit Details")\n${diff}`;
} }
const markdown = new MarkdownString(message); const markdown = new MarkdownString(message);
@ -146,21 +145,36 @@ export class Annotations {
return markdown; return markdown;
} }
private static getCodeDiff(chunkLine: GitDiffChunkLine): string {
const previous = chunkLine.previous === undefined ? undefined : chunkLine.previous[0];
return `\`\`\`
- ${previous === undefined || previous.line === undefined ? '' : previous.line.trim()}
+ ${chunkLine.line === undefined ? '' : chunkLine.line.trim()}
\`\`\``;
private static getDiffFromHunkLine(hunkLine: GitDiffHunkLine): string {
if (Container.config.hovers.changesDiff === 'hunk') {
return `\`\`\`diff\n${hunkLine.hunk.diff}\n\`\`\``;
}
return `\`\`\`diff${hunkLine.previous === undefined ? '' : `\n-${hunkLine.previous.line}`}${
hunkLine.current === undefined ? '' : `\n+${hunkLine.current.line}`
}\n\`\`\``;
} }
static async changesHover(commit: GitCommit, line: number, uri: GitUri): Promise<Partial<DecorationOptions>> {
const sha =
!commit.isUncommitted || (uri.sha !== undefined && GitService.isStagedUncommitted(uri.sha))
? commit.previousSha
: undefined;
const chunkLine = await Container.git.getDiffForLine(uri, line, sha);
const message = this.getHoverDiffMessage(commit, uri, chunkLine);
static async changesHover(
commit: GitBlameCommit,
editorLine: number,
uri: GitUri
): Promise<Partial<DecorationOptions>> {
let ref;
if (commit.isUncommitted) {
if (uri.sha !== undefined && GitService.isStagedUncommitted(uri.sha)) {
ref = uri.sha;
}
}
else {
ref = commit.sha;
}
const line = editorLine + 1;
const commitLine = commit.lines.find(l => l.line === line) || commit.lines[0];
const hunkLine = await Container.git.getDiffForLine(uri, commitLine.originalLine - 1, ref);
const message = this.getHoverDiffMessage(commit, uri, hunkLine);
return { return {
hoverMessage: message hoverMessage: message

+ 4
- 3
src/annotations/blameAnnotationProvider.ts ファイルの表示

@ -11,7 +11,7 @@ import {
TextEditorDecorationType TextEditorDecorationType
} from 'vscode'; } from 'vscode';
import { Container } from '../container'; import { Container } from '../container';
import { GitBlame, GitCommit, GitUri } from '../git/gitService';
import { GitBlame, GitBlameCommit, GitCommit, GitUri } from '../git/gitService';
import { Arrays, Iterables, log } from '../system'; import { Arrays, Iterables, log } from '../system';
import { GitDocumentState, TrackedDocument } from '../trackers/gitDocumentTracker'; import { GitDocumentState, TrackedDocument } from '../trackers/gitDocumentTracker';
import { AnnotationProviderBase } from './annotationProvider'; import { AnnotationProviderBase } from './annotationProvider';
@ -88,7 +88,8 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
const highlightDecorationRanges = Arrays.filterMap(blame.lines, l => const highlightDecorationRanges = Arrays.filterMap(blame.lines, l =>
l.sha === sha l.sha === sha
? this.editor.document.validateRange(new Range(l.line, 0, l.line, Number.MAX_SAFE_INTEGER))
? // editor lines are 0-based
this.editor.document.validateRange(new Range(l.line - 1, 0, l.line - 1, Number.MAX_SAFE_INTEGER))
: undefined : undefined
); );
@ -255,7 +256,7 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
); );
} }
private async getCommitForHover(position: Position): Promise<GitCommit | undefined> {
private async getCommitForHover(position: Position): Promise<GitBlameCommit | undefined> {
if (Container.config.hovers.annotations.over !== 'line' && position.character !== 0) return undefined; if (Container.config.hovers.annotations.over !== 'line' && position.character !== 0) return undefined;
const blame = await this.getBlame(); const blame = await this.getBlame();

+ 5
- 4
src/annotations/gutterBlameAnnotationProvider.ts ファイルの表示

@ -58,7 +58,8 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
} }
for (const l of blame.lines) { for (const l of blame.lines) {
const line = l.line;
// editor lines are 0-based
const editorLine = l.line - 1;
if (previousSha === l.sha) { if (previousSha === l.sha) {
if (gutter === undefined) continue; if (gutter === undefined) continue;
@ -84,7 +85,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
compacted = true; compacted = true;
} }
gutter.range = new Range(line, 0, line, 0);
gutter.range = new Range(editorLine, 0, editorLine, 0);
this.decorations.push(gutter); this.decorations.push(gutter);
@ -105,7 +106,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
if (gutter !== undefined) { if (gutter !== undefined) {
gutter = { gutter = {
...gutter, ...gutter,
range: new Range(line, 0, line, 0)
range: new Range(editorLine, 0, editorLine, 0)
}; };
this.decorations.push(gutter); this.decorations.push(gutter);
@ -123,7 +124,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
Annotations.applyHeatmap(gutter, commit.date, computedHeatmap); Annotations.applyHeatmap(gutter, commit.date, computedHeatmap);
} }
gutter.range = new Range(line, 0, line, 0);
gutter.range = new Range(editorLine, 0, editorLine, 0);
this.decorations.push(gutter); this.decorations.push(gutter);

+ 4
- 3
src/annotations/heatmapBlameAnnotationProvider.ts ファイルの表示

@ -31,13 +31,14 @@ export class HeatmapBlameAnnotationProvider extends BlameAnnotationProviderBase
const computedHeatmap = this.getComputedHeatmap(blame); const computedHeatmap = this.getComputedHeatmap(blame);
for (const l of blame.lines) { for (const l of blame.lines) {
const line = l.line;
// editor lines are 0-based
const editorLine = l.line - 1;
heatmap = decorationsMap[l.sha]; heatmap = decorationsMap[l.sha];
if (heatmap !== undefined) { if (heatmap !== undefined) {
heatmap = { heatmap = {
...heatmap, ...heatmap,
range: new Range(line, 0, line, 0)
range: new Range(editorLine, 0, editorLine, 0)
}; };
this.decorations.push(heatmap); this.decorations.push(heatmap);
@ -49,7 +50,7 @@ export class HeatmapBlameAnnotationProvider extends BlameAnnotationProviderBase
if (commit === undefined) continue; if (commit === undefined) continue;
heatmap = Annotations.heatmap(commit, computedHeatmap, renderOptions) as DecorationOptions; heatmap = Annotations.heatmap(commit, computedHeatmap, renderOptions) as DecorationOptions;
heatmap.range = new Range(line, 0, line, 0);
heatmap.range = new Range(editorLine, 0, editorLine, 0);
this.decorations.push(heatmap); this.decorations.push(heatmap);
decorationsMap[l.sha] = heatmap; decorationsMap[l.sha] = heatmap;

+ 7
- 6
src/annotations/recentChangesAnnotationProvider.ts ファイルの表示

@ -32,7 +32,7 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
const commit = await Container.git.getRecentLogCommitForFile(this._uri.repoPath, this._uri.fsPath); const commit = await Container.git.getRecentLogCommitForFile(this._uri.repoPath, this._uri.fsPath);
if (commit === undefined) return false; if (commit === undefined) return false;
const diff = await Container.git.getDiffForFile(this._uri, commit.previousSha);
const diff = await Container.git.getDiffForFile(this._uri, commit.sha);
if (diff === undefined) return false; if (diff === undefined) return false;
let start = process.hrtime(); let start = process.hrtime();
@ -42,14 +42,15 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
this.decorations = []; this.decorations = [];
for (const chunk of diff.chunks) {
let count = chunk.currentPosition.start - 2;
for (const line of chunk.lines) {
if (line.line === undefined) continue;
for (const hunk of diff.hunks) {
// Subtract 2 because editor lines are 0-based and we will be adding 1 in the first iteration of the loop
let count = hunk.currentPosition.start - 2;
for (const line of hunk.lines) {
if (line.current === undefined) continue;
count++; count++;
if (line.state === 'unchanged') continue;
if (line.current.state === 'unchanged') continue;
const range = this.editor.document.validateRange( const range = this.editor.document.validateRange(
new Range(new Position(count, 0), new Position(count, Number.MAX_SAFE_INTEGER)) new Range(new Position(count, 0), new Position(count, Number.MAX_SAFE_INTEGER))

+ 2
- 1
src/commands/diffLineWithWorking.ts ファイルの表示

@ -59,7 +59,8 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
previousSha: null, previousSha: null,
previousFileName: null previousFileName: null
}); });
args.line = blame.line.line + 1;
// editor lines are 0-based
args.line = blame.line.line - 1;
} }
} }
catch (ex) { catch (ex) {

+ 1
- 0
src/config.ts ファイルの表示

@ -51,6 +51,7 @@ export interface Config {
over: 'line' | 'annotation'; over: 'line' | 'annotation';
}; };
avatars: boolean; avatars: boolean;
changesDiff: 'line' | 'hunk';
detailsMarkdownFormat: string; detailsMarkdownFormat: string;
enabled: boolean; enabled: boolean;
}; };

+ 5
- 1
src/git/git.ts ファイルの表示

@ -518,7 +518,7 @@ export class Git {
ref2?: string, ref2?: string,
options: { encoding?: string; filter?: string } = {} options: { encoding?: string; filter?: string } = {}
): Promise<string> { ): Promise<string> {
const params = ['diff', '-M', '--no-ext-diff', '--minimal'];
const params = ['diff', '-M', '--no-ext-diff', '-U0', '--minimal'];
if (options.filter) { if (options.filter) {
params.push(`--diff-filter=${options.filter}`); params.push(`--diff-filter=${options.filter}`);
} }
@ -866,6 +866,10 @@ export class Git {
} }
} }
static show_diff(repoPath: string, fileName: string, ref: string) {
return git<string>({ cwd: repoPath }, 'show', '--format=', '--minimal', '-U0', ref, '--', fileName);
}
static show_status(repoPath: string, fileName: string, ref: string) { static show_status(repoPath: string, fileName: string, ref: string) {
return git<string>({ cwd: repoPath }, 'show', '--name-status', '--format=', ref, '--', fileName); return git<string>({ cwd: repoPath }, 'show', '--name-status', '--format=', ref, '--', fileName);
} }

+ 47
- 34
src/git/gitService.ts ファイルの表示

@ -43,7 +43,7 @@ import {
GitCommitType, GitCommitType,
GitContributor, GitContributor,
GitDiff, GitDiff,
GitDiffChunkLine,
GitDiffHunkLine,
GitDiffParser, GitDiffParser,
GitDiffShortStat, GitDiffShortStat,
GitFile, GitFile,
@ -139,7 +139,7 @@ export class GitService implements Disposable {
this._disposable && this._disposable.dispose(); this._disposable && this._disposable.dispose();
} }
get UseCaching() {
get useCaching() {
return Container.config.advanced.caching.enabled; return Container.config.advanced.caching.enabled;
} }
@ -749,7 +749,7 @@ export class GitService implements Disposable {
} }
const doc = await Container.tracker.getOrAdd(uri); const doc = await Container.tracker.getOrAdd(uri);
if (this.UseCaching) {
if (this.useCaching) {
if (doc.state !== undefined) { if (doc.state !== undefined) {
const cachedBlame = doc.state.get<CachedBlame>(key); const cachedBlame = doc.state.get<CachedBlame>(key);
if (cachedBlame !== undefined) { if (cachedBlame !== undefined) {
@ -832,7 +832,7 @@ export class GitService implements Disposable {
const key = `blame:${Strings.sha1(contents)}`; const key = `blame:${Strings.sha1(contents)}`;
const doc = await Container.tracker.getOrAdd(uri); const doc = await Container.tracker.getOrAdd(uri);
if (this.UseCaching) {
if (this.useCaching) {
if (doc.state !== undefined) { if (doc.state !== undefined) {
const cachedBlame = doc.state.get<CachedBlame>(key); const cachedBlame = doc.state.get<CachedBlame>(key);
if (cachedBlame !== undefined) { if (cachedBlame !== undefined) {
@ -908,17 +908,17 @@ export class GitService implements Disposable {
@log() @log()
async getBlameForLine( async getBlameForLine(
uri: GitUri, uri: GitUri,
line: number,
editorLine: number, // editor lines are 0-based
options: { skipCache?: boolean } = {} options: { skipCache?: boolean } = {}
): Promise<GitBlameLine | undefined> { ): Promise<GitBlameLine | undefined> {
if (!options.skipCache && this.UseCaching) {
if (!options.skipCache && this.useCaching) {
const blame = await this.getBlameForFile(uri); const blame = await this.getBlameForFile(uri);
if (blame === undefined) return undefined; if (blame === undefined) return undefined;
let blameLine = blame.lines[line];
let blameLine = blame.lines[editorLine];
if (blameLine === undefined) { if (blameLine === undefined) {
if (blame.lines.length !== line) return undefined;
blameLine = blame.lines[line - 1];
if (blame.lines.length !== editorLine) return undefined;
blameLine = blame.lines[editorLine - 1];
} }
const commit = blame.commits.get(blameLine.sha); const commit = blame.commits.get(blameLine.sha);
@ -932,7 +932,7 @@ export class GitService implements Disposable {
}; };
} }
const lineToBlame = line + 1;
const lineToBlame = editorLine + 1;
const fileName = uri.fsPath; const fileName = uri.fsPath;
try { try {
@ -948,7 +948,7 @@ export class GitService implements Disposable {
return { return {
author: Iterables.first(blame.authors.values()), author: Iterables.first(blame.authors.values()),
commit: Iterables.first(blame.commits.values()), commit: Iterables.first(blame.commits.values()),
line: blame.lines[line]
line: blame.lines[editorLine]
}; };
} }
catch { catch {
@ -963,18 +963,18 @@ export class GitService implements Disposable {
}) })
async getBlameForLineContents( async getBlameForLineContents(
uri: GitUri, uri: GitUri,
line: number,
editorLine: number, // editor lines are 0-based
contents: string, contents: string,
options: { skipCache?: boolean } = {} options: { skipCache?: boolean } = {}
): Promise<GitBlameLine | undefined> { ): Promise<GitBlameLine | undefined> {
if (!options.skipCache && this.UseCaching) {
if (!options.skipCache && this.useCaching) {
const blame = await this.getBlameForFileContents(uri, contents); const blame = await this.getBlameForFileContents(uri, contents);
if (blame === undefined) return undefined; if (blame === undefined) return undefined;
let blameLine = blame.lines[line];
let blameLine = blame.lines[editorLine];
if (blameLine === undefined) { if (blameLine === undefined) {
if (blame.lines.length !== line) return undefined;
blameLine = blame.lines[line - 1];
if (blame.lines.length !== editorLine) return undefined;
blameLine = blame.lines[editorLine - 1];
} }
const commit = blame.commits.get(blameLine.sha); const commit = blame.commits.get(blameLine.sha);
@ -988,7 +988,7 @@ export class GitService implements Disposable {
}; };
} }
const lineToBlame = line + 1;
const lineToBlame = editorLine + 1;
const fileName = uri.fsPath; const fileName = uri.fsPath;
try { try {
@ -1005,7 +1005,7 @@ export class GitService implements Disposable {
return { return {
author: Iterables.first(blame.authors.values()), author: Iterables.first(blame.authors.values()),
commit: Iterables.first(blame.commits.values()), commit: Iterables.first(blame.commits.values()),
line: blame.lines[line]
line: blame.lines[editorLine]
}; };
} }
catch { catch {
@ -1034,13 +1034,17 @@ export class GitService implements Disposable {
const lines = blame.lines.slice(range.start.line, range.end.line + 1); const lines = blame.lines.slice(range.start.line, range.end.line + 1);
const shas = new Set(lines.map(l => l.sha)); const shas = new Set(lines.map(l => l.sha));
// ranges are 0-based
const startLine = range.start.line + 1;
const endLine = range.end.line + 1;
const authors: Map<string, GitAuthor> = new Map(); const authors: Map<string, GitAuthor> = new Map();
const commits: Map<string, GitBlameCommit> = new Map(); const commits: Map<string, GitBlameCommit> = new Map();
for (const c of blame.commits.values()) { for (const c of blame.commits.values()) {
if (!shas.has(c.sha)) continue; if (!shas.has(c.sha)) continue;
const commit = c.with({ const commit = c.with({
lines: c.lines.filter(l => l.line >= range.start.line && l.line <= range.end.line)
lines: c.lines.filter(l => l.line >= startLine && l.line <= endLine)
}); });
commits.set(c.sha, commit); commits.set(c.sha, commit);
@ -1165,13 +1169,9 @@ export class GitService implements Disposable {
} }
@log() @log()
async getDiffForFile(uri: GitUri, ref1?: string, ref2?: string): Promise<GitDiff | undefined> {
async getDiffForFile(uri: GitUri, ref1: string | undefined, ref2?: string): Promise<GitDiff | undefined> {
const cc = Logger.getCorrelationContext(); const cc = Logger.getCorrelationContext();
if (ref1 !== undefined && ref2 === undefined && uri.sha !== undefined) {
ref2 = uri.sha;
}
let key = 'diff'; let key = 'diff';
if (ref1 !== undefined) { if (ref1 !== undefined) {
key += `:${ref1}`; key += `:${ref1}`;
@ -1181,7 +1181,7 @@ export class GitService implements Disposable {
} }
const doc = await Container.tracker.getOrAdd(uri); const doc = await Container.tracker.getOrAdd(uri);
if (this.UseCaching) {
if (this.useCaching) {
if (doc.state !== undefined) { if (doc.state !== undefined) {
const cachedDiff = doc.state.get<CachedDiff>(key); const cachedDiff = doc.state.get<CachedDiff>(key);
if (cachedDiff !== undefined) { if (cachedDiff !== undefined) {
@ -1233,7 +1233,14 @@ export class GitService implements Disposable {
const [file, root] = Git.splitPath(fileName, repoPath, false); const [file, root] = Git.splitPath(fileName, repoPath, false);
try { try {
const data = await Git.diff(root, file, ref1, ref2, { ...options, filter: 'M' });
let data;
if (ref1 !== undefined && ref2 === undefined && !GitService.isStagedUncommitted(ref1)) {
data = await Git.show_diff(root, file, ref1);
}
else {
data = await Git.diff(root, file, ref1, ref2, { ...options, filter: 'M' });
}
const diff = GitDiffParser.parse(data); const diff = GitDiffParser.parse(data);
return diff; return diff;
} }
@ -1259,18 +1266,24 @@ export class GitService implements Disposable {
@log() @log()
async getDiffForLine( async getDiffForLine(
uri: GitUri, uri: GitUri,
line: number,
ref1?: string,
editorLine: number, // editor lines are 0-based
ref1: string | undefined,
ref2?: string ref2?: string
): Promise<GitDiffChunkLine | undefined> {
): Promise<GitDiffHunkLine | undefined> {
try { try {
const diff = await this.getDiffForFile(uri, ref1, ref2);
let diff = await this.getDiffForFile(uri, ref1, ref2);
// If we didn't find a diff & ref1 is undefined (meaning uncommitted), check for a staged diff
if (diff === undefined && ref1 === undefined) {
diff = await this.getDiffForFile(uri, Git.stagedUncommittedSha, ref2);
}
if (diff === undefined) return undefined; if (diff === undefined) return undefined;
const chunk = diff.chunks.find(c => c.currentPosition.start <= line && c.currentPosition.end >= line);
if (chunk === undefined) return undefined;
const line = editorLine + 1;
const hunk = diff.hunks.find(c => c.currentPosition.start <= line && c.currentPosition.end >= line);
if (hunk === undefined) return undefined;
return chunk.lines[line - chunk.currentPosition.start + 1];
return hunk.lines[line - hunk.currentPosition.start];
} }
catch (ex) { catch (ex) {
return undefined; return undefined;
@ -1480,7 +1493,7 @@ export class GitService implements Disposable {
} }
const doc = await Container.tracker.getOrAdd(GitUri.fromFile(fileName, repoPath!, options.ref)); const doc = await Container.tracker.getOrAdd(GitUri.fromFile(fileName, repoPath!, options.ref));
if (this.UseCaching && options.range === undefined) {
if (this.useCaching && options.range === undefined) {
if (doc.state !== undefined) { if (doc.state !== undefined) {
const cachedLog = doc.state.get<CachedLog>(key); const cachedLog = doc.state.get<CachedLog>(key);
if (cachedLog !== undefined) { if (cachedLog !== undefined) {

+ 11
- 13
src/git/models/diff.ts ファイルの表示

@ -6,26 +6,24 @@ export interface GitDiffLine {
state: 'added' | 'removed' | 'unchanged'; state: 'added' | 'removed' | 'unchanged';
} }
export interface GitDiffChunkLine extends GitDiffLine {
previous?: (GitDiffLine | undefined)[];
export interface GitDiffHunkLine {
hunk: GitDiffHunk;
current: GitDiffLine | undefined;
previous: GitDiffLine | undefined;
} }
export class GitDiffChunk {
private _chunk: string | undefined;
private _lines: GitDiffChunkLine[] | undefined;
export class GitDiffHunk {
private _lines: GitDiffHunkLine[] | undefined;
constructor( constructor(
chunk: string,
public readonly diff: string,
public currentPosition: { start: number; end: number }, public currentPosition: { start: number; end: number },
public previousPosition: { start: number; end: number } public previousPosition: { start: number; end: number }
) {
this._chunk = chunk;
}
) {}
get lines(): GitDiffChunkLine[] {
get lines(): GitDiffHunkLine[] {
if (this._lines === undefined) { if (this._lines === undefined) {
this._lines = GitDiffParser.parseChunk(this._chunk!);
this._chunk = undefined;
this._lines = GitDiffParser.parseHunk(this);
} }
return this._lines; return this._lines;
@ -33,7 +31,7 @@ export class GitDiffChunk {
} }
export interface GitDiff { export interface GitDiff {
readonly chunks: GitDiffChunk[];
readonly hunks: GitDiffHunk[];
readonly diff?: string; readonly diff?: string;
} }

+ 3
- 3
src/git/parsers/blameParser.ts ファイルの表示

@ -55,8 +55,8 @@ export class GitBlameParser {
entry = { entry = {
author: undefined!, author: undefined!,
sha: lineParts[0], sha: lineParts[0],
originalLine: parseInt(lineParts[1], 10) - 1,
line: parseInt(lineParts[2], 10) - 1,
originalLine: parseInt(lineParts[1], 10),
line: parseInt(lineParts[2], 10),
lineCount: parseInt(lineParts[3], 10) lineCount: parseInt(lineParts[3], 10)
}; };
@ -230,7 +230,7 @@ export class GitBlameParser {
} }
commit.lines.push(line); commit.lines.push(line);
lines[line.line] = line;
lines[line.line - 1] = line;
} }
} }
} }

+ 38
- 44
src/git/parsers/diffParser.ts ファイルの表示

@ -1,63 +1,70 @@
'use strict'; 'use strict';
import { Iterables, Strings } from '../../system';
import { GitDiff, GitDiffChunk, GitDiffChunkLine, GitDiffLine, GitDiffShortStat, GitFile, GitFileStatus } from '../git';
import { Strings } from '../../system';
import { GitDiff, GitDiffHunk, GitDiffHunkLine, GitDiffLine, GitDiffShortStat, GitFile, GitFileStatus } from '../git';
const nameStatusDiffRegex = /^(.*?)\t(.*?)(?:\t(.*?))?$/gm; const nameStatusDiffRegex = /^(.*?)\t(.*?)(?:\t(.*?))?$/gm;
const shortStatDiffRegex = /^\s*(\d+)\sfiles? changed(?:,\s+(\d+)\s+insertions?\(\+\))?(?:,\s+(\d+)\s+deletions?\(-\))?/; const shortStatDiffRegex = /^\s*(\d+)\sfiles? changed(?:,\s+(\d+)\s+insertions?\(\+\))?(?:,\s+(\d+)\s+deletions?\(-\))?/;
const unifiedDiffRegex = /^@@ -([\d]+),([\d]+) [+]([\d]+),([\d]+) @@([\s\S]*?)(?=^@@)/gm;
const unifiedDiffRegex = /^@@ -([\d]+)(?:,([\d]+))? \+([\d]+)(?:,([\d]+))? @@(?:.*?)\n([\s\S]*?)(?=^@@)/gm;
export class GitDiffParser { export class GitDiffParser {
static parse(data: string, debug: boolean = false): GitDiff | undefined { static parse(data: string, debug: boolean = false): GitDiff | undefined {
if (!data) return undefined; if (!data) return undefined;
const chunks: GitDiffChunk[] = [];
const hunks: GitDiffHunk[] = [];
let match: RegExpExecArray | null; let match: RegExpExecArray | null;
let chunk;
let hunk;
let currentStartStr;
let currentStart; let currentStart;
let currentCountStr;
let currentCount;
let previousStartStr;
let previousStart; let previousStart;
let previousCountStr;
let previousCount;
do { do {
match = unifiedDiffRegex.exec(`${data}\n@@`); match = unifiedDiffRegex.exec(`${data}\n@@`);
if (match == null) break; if (match == null) break;
// Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869 // Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
chunk = ` ${match[5]}`.substr(1);
currentStart = parseInt(match[3], 10);
previousStart = parseInt(match[1], 10);
hunk = ` ${match[5]}`.substr(1);
[, previousStartStr, previousCountStr, currentStartStr, currentCountStr] = match;
previousStart = parseInt(previousStartStr, 10);
previousCount = previousCountStr ? parseInt(previousCountStr, 10) : 0;
currentStart = parseInt(currentStartStr, 10);
currentCount = currentCountStr ? parseInt(currentCountStr, 10) : 0;
chunks.push(
new GitDiffChunk(
chunk,
hunks.push(
new GitDiffHunk(
hunk,
{ {
start: currentStart, start: currentStart,
end: currentStart + parseInt(match[4], 10)
end: currentStart + currentCount
}, },
{ {
start: previousStart, start: previousStart,
end: previousStart + parseInt(match[2], 10)
end: previousStart + previousCount
} }
) )
); );
} while (match != null); } while (match != null);
if (!chunks.length) return undefined;
if (!hunks.length) return undefined;
const diff: GitDiff = { const diff: GitDiff = {
diff: debug ? data : undefined, diff: debug ? data : undefined,
chunks: chunks
hunks: hunks
}; };
return diff; return diff;
} }
static parseChunk(chunk: string): GitDiffChunkLine[] {
const lines = Iterables.skip(Strings.lines(chunk), 1);
static parseHunk(hunk: GitDiffHunk): GitDiffHunkLine[] {
const currentLines: (GitDiffLine | undefined)[] = []; const currentLines: (GitDiffLine | undefined)[] = [];
const previousLines: (GitDiffLine | undefined)[] = []; const previousLines: (GitDiffLine | undefined)[] = [];
let removed = 0; let removed = 0;
for (const l of lines) {
for (const l of Strings.lines(hunk.diff)) {
switch (l[0]) { switch (l[0]) {
case '+': case '+':
currentLines.push({ currentLines.push({
@ -97,35 +104,22 @@ export class GitDiffParser {
} }
} }
const chunkLines: GitDiffChunkLine[] = [];
while (removed > 0) {
removed--;
currentLines.push(undefined);
}
let chunkLine: GitDiffChunkLine | undefined = undefined;
let current: GitDiffLine | undefined = undefined;
const hunkLines: GitDiffHunkLine[] = [];
for (let i = 0; i < currentLines.length; i++) { for (let i = 0; i < currentLines.length; i++) {
current = currentLines[i];
if (current === undefined) {
// Don't think we need to worry about this case because the diff will always have "padding" (i.e. unchanged lines) around each chunk
if (chunkLine === undefined) continue;
if (chunkLine.previous === undefined) {
chunkLine.previous = [previousLines[i]];
continue;
}
chunkLine.previous.push(previousLines[i]);
continue;
}
chunkLine = {
line: current.line,
state: current.state,
previous: [previousLines[i]]
};
chunkLines.push(chunkLine);
hunkLines.push({
hunk: hunk,
current: currentLines[i],
previous: previousLines[i]
});
} }
return chunkLines;
return hunkLines;
} }
static parseNameStatus(data: string, repoPath: string): GitFile[] | undefined { static parseNameStatus(data: string, repoPath: string): GitFile[] | undefined {

+ 1
- 1
src/trackers/gitLineTracker.ts ファイルの表示

@ -123,7 +123,7 @@ export class GitLineTracker extends LineTracker {
: await Container.git.getBlameForLine(trackedDocument.uri, lines[0]); : await Container.git.getBlameForLine(trackedDocument.uri, lines[0]);
if (blameLine === undefined) return false; if (blameLine === undefined) return false;
this.setState(blameLine.line.line, new GitLineState(blameLine.commit));
this.setState(blameLine.line.line - 1, new GitLineState(blameLine.commit));
} }
else { else {
const blame = editor.document.isDirty const blame = editor.document.isDirty

読み込み中…
キャンセル
保存