Refactored commands and blame annotationsmain
@ -0,0 +1,246 @@ | |||||
'use strict' | |||||
import {commands, DecorationOptions, Disposable, ExtensionContext, OverviewRulerLane, Range, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, Uri, window, workspace} from 'vscode'; | |||||
import {BuiltInCommands} from './constants'; | |||||
import {BlameAnnotationStyle, IBlameConfig} from './configuration'; | |||||
import GitProvider, {IGitBlame, IGitCommit} from './gitProvider'; | |||||
import * as moment from 'moment'; | |||||
const blameDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({ | |||||
before: { | |||||
margin: '0 1.75em 0 0' | |||||
} | |||||
}); | |||||
let highlightDecoration: TextEditorDecorationType; | |||||
export class BlameAnnotationProvider extends Disposable { | |||||
public uri: Uri; | |||||
private _blame: Promise<IGitBlame>; | |||||
private _config: IBlameConfig; | |||||
private _disposable: Disposable; | |||||
private _document: TextDocument; | |||||
private _toggleWhitespace: boolean; | |||||
constructor(private context: ExtensionContext, private git: GitProvider, public editor: TextEditor) { | |||||
super(() => this.dispose()); | |||||
if (!highlightDecoration) { | |||||
highlightDecoration = window.createTextEditorDecorationType({ | |||||
dark: { | |||||
backgroundColor: 'rgba(255, 255, 255, 0.15)', | |||||
gutterIconPath: context.asAbsolutePath('images/blame-dark.png'), | |||||
overviewRulerColor: 'rgba(255, 255, 255, 0.75)', | |||||
}, | |||||
light: { | |||||
backgroundColor: 'rgba(0, 0, 0, 0.15)', | |||||
gutterIconPath: context.asAbsolutePath('images/blame-light.png'), | |||||
overviewRulerColor: 'rgba(0, 0, 0, 0.75)', | |||||
}, | |||||
gutterIconSize: 'contain', | |||||
overviewRulerLane: OverviewRulerLane.Right, | |||||
isWholeLine: true | |||||
}); | |||||
} | |||||
this._document = this.editor.document; | |||||
this.uri = this._document.uri; | |||||
this._blame = this.git.getBlameForFile(this.uri.fsPath); | |||||
this._config = workspace.getConfiguration('gitlens').get<IBlameConfig>('blame'); | |||||
const subscriptions: Disposable[] = []; | |||||
subscriptions.push(window.onDidChangeTextEditorSelection(this._onActiveSelectionChanged, this)); | |||||
this._disposable = Disposable.from(...subscriptions); | |||||
} | |||||
dispose() { | |||||
if (this.editor) { | |||||
// HACK: This only works when switching to another editor - diffs handle whitespace toggle differently | |||||
if (this._toggleWhitespace) { | |||||
commands.executeCommand(BuiltInCommands.ToggleRenderWhitespace); | |||||
} | |||||
this.editor.setDecorations(blameDecoration, []); | |||||
this.editor.setDecorations(highlightDecoration, []); | |||||
} | |||||
this._disposable && this._disposable.dispose(); | |||||
} | |||||
private _onActiveSelectionChanged(e: TextEditorSelectionChangeEvent) { | |||||
this.git.getBlameForLine(e.textEditor.document.fileName, e.selections[0].active.line) | |||||
.then(blame => blame && this._applyCommitHighlight(blame.commit.sha)); | |||||
} | |||||
provideBlameAnnotation(sha?: string) { | |||||
return this._blame.then(blame => { | |||||
if (!blame || !blame.lines.length) return; | |||||
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- toggle whitespace off | |||||
const whitespace = workspace.getConfiguration('editor').get<string>('renderWhitespace'); | |||||
this._toggleWhitespace = whitespace !== 'false' && whitespace !== 'none'; | |||||
if (this._toggleWhitespace) { | |||||
commands.executeCommand(BuiltInCommands.ToggleRenderWhitespace); | |||||
} | |||||
let blameDecorationOptions: DecorationOptions[] | undefined; | |||||
switch (this._config.annotation.style) { | |||||
case BlameAnnotationStyle.Compact: | |||||
blameDecorationOptions = this._getCompactGutterDecorations(blame); | |||||
break; | |||||
case BlameAnnotationStyle.Expanded: | |||||
blameDecorationOptions = this._getExpandedGutterDecorations(blame); | |||||
break; | |||||
} | |||||
if (blameDecorationOptions) { | |||||
this.editor.setDecorations(blameDecoration, blameDecorationOptions); | |||||
} | |||||
sha = sha || blame.commits.values().next().value.sha; | |||||
return this._applyCommitHighlight(sha); | |||||
}); | |||||
} | |||||
private _applyCommitHighlight(sha: string) { | |||||
return this._blame.then(blame => { | |||||
if (!blame || !blame.lines.length) return; | |||||
const highlightDecorationRanges = blame.lines | |||||
.filter(l => l.sha === sha) | |||||
.map(l => this.editor.document.validateRange(new Range(l.line, 0, l.line, 1000000))); | |||||
this.editor.setDecorations(highlightDecoration, highlightDecorationRanges); | |||||
}); | |||||
} | |||||
private _getCompactGutterDecorations(blame: IGitBlame): DecorationOptions[] { | |||||
let count = 0; | |||||
let lastSha; | |||||
return blame.lines.map(l => { | |||||
let color = l.previousSha ? '#999999' : '#6b6b6b'; | |||||
let commit = blame.commits.get(l.sha); | |||||
let hoverMessage: string | Array<string> = [`_${l.sha}_ - ${commit.message}`, `${commit.author}, ${moment(commit.date).format('MMMM Do, YYYY h:MM a')}`]; | |||||
if (commit.isUncommitted) { | |||||
color = 'rgba(0, 188, 242, 0.6)'; | |||||
let previous = blame.commits.get(commit.previousSha); | |||||
if (previous) { | |||||
hoverMessage = ['Uncommitted changes', `_${previous.sha}_ - ${previous.message}`, `${previous.author}, ${moment(previous.date).format('MMMM Do, YYYY h:MM a')}`]; | |||||
} else { | |||||
hoverMessage = ['Uncommitted changes', `_${l.previousSha}_`]; | |||||
} | |||||
} | |||||
let gutter = ''; | |||||
if (lastSha !== l.sha) { | |||||
count = -1; | |||||
} | |||||
const isEmptyOrWhitespace = this._document.lineAt(l.line).isEmptyOrWhitespace; | |||||
if (!isEmptyOrWhitespace) { | |||||
switch (++count) { | |||||
case 0: | |||||
gutter = commit.sha.substring(0, 8); | |||||
break; | |||||
case 1: | |||||
gutter = `\\00a6\\00a0 ${this._getAuthor(commit, 17, true)}`; | |||||
break; | |||||
case 2: | |||||
gutter = `\\00a6\\00a0 ${this._getDate(commit, true)}`; | |||||
break; | |||||
default: | |||||
gutter = '\\00a6\\00a0'; | |||||
break; | |||||
} | |||||
} | |||||
lastSha = l.sha; | |||||
return <DecorationOptions>{ | |||||
range: this.editor.document.validateRange(new Range(l.line, 0, l.line, 0)), | |||||
hoverMessage: hoverMessage, | |||||
renderOptions: { before: { color: color, contentText: gutter, width: '11em' } } | |||||
}; | |||||
}); | |||||
} | |||||
private _getExpandedGutterDecorations(blame: IGitBlame): DecorationOptions[] { | |||||
let width = 0; | |||||
if (this._config.annotation.sha) { | |||||
width += 5; | |||||
} | |||||
if (this._config.annotation.date) { | |||||
if (width > 0) { | |||||
width += 7; | |||||
} else { | |||||
width += 6; | |||||
} | |||||
} | |||||
if (this._config.annotation.author) { | |||||
if (width > 5 + 6) { | |||||
width += 12; | |||||
} else if (width > 0) { | |||||
width += 11; | |||||
} else { | |||||
width += 10; | |||||
} | |||||
} | |||||
return blame.lines.map(l => { | |||||
let color = l.previousSha ? '#999999' : '#6b6b6b'; | |||||
let commit = blame.commits.get(l.sha); | |||||
let hoverMessage: string | Array<string> = [`_${l.sha}_ - ${commit.message}`, `${commit.author}, ${moment(commit.date).format('MMMM Do, YYYY h:MM a')}`]; | |||||
if (commit.isUncommitted) { | |||||
color = 'rgba(0, 188, 242, 0.6)'; | |||||
let previous = blame.commits.get(commit.previousSha); | |||||
if (previous) { | |||||
hoverMessage = ['Uncommitted changes', `_${previous.sha}_ - ${previous.message}`, `${previous.author}, ${moment(previous.date).format('MMMM Do, YYYY h:MM a')}`]; | |||||
} else { | |||||
hoverMessage = ['Uncommitted changes', `_${l.previousSha}_`]; | |||||
} | |||||
} | |||||
const gutter = this._getGutter(commit); | |||||
return <DecorationOptions>{ | |||||
range: this.editor.document.validateRange(new Range(l.line, 0, l.line, 0)), | |||||
hoverMessage: hoverMessage, | |||||
renderOptions: { before: { color: color, contentText: gutter, width: `${width}em` } } | |||||
}; | |||||
}); | |||||
} | |||||
private _getAuthor(commit: IGitCommit, max: number = 17, force: boolean = false) { | |||||
if (!force && !this._config.annotation.author) return ''; | |||||
let author = commit.isUncommitted ? 'Uncommitted': commit.author; | |||||
if (author.length > max) { | |||||
return `${author.substring(0, max - 1)}\\2026`; | |||||
} | |||||
return author; | |||||
} | |||||
private _getDate(commit: IGitCommit, force?: boolean) { | |||||
if (!force && !this._config.annotation.date) return ''; | |||||
return moment(commit.date).format('MM/DD/YYYY'); | |||||
} | |||||
private _getGutter(commit: IGitCommit) { | |||||
const author = this._getAuthor(commit); | |||||
const date = this._getDate(commit); | |||||
if (this._config.annotation.sha) { | |||||
return `${commit.sha.substring(0, 8)}${(date ? `\\00a0\\2022\\00a0 ${date}` : '')}${(author ? `\\00a0\\2022\\00a0 ${author}` : '')}`; | |||||
} else if (this._config.annotation.date) { | |||||
return `${date}${(author ? `\\00a0\\2022\\00a0 ${author}` : '')}`; | |||||
} else { | |||||
return author; | |||||
} | |||||
} | |||||
} |
@ -1,189 +0,0 @@ | |||||
'use strict' | |||||
import {commands, DecorationOptions, Disposable, OverviewRulerLane, Position, Range, TextEditor, TextEditorEdit, TextEditorDecorationType, Uri, window} from 'vscode'; | |||||
import {BuiltInCommands, Commands} from './constants'; | |||||
import GitProvider from './gitProvider'; | |||||
import BlameAnnotationController from './blameAnnotationController'; | |||||
import * as moment from 'moment'; | |||||
import * as path from 'path'; | |||||
abstract class Command extends Disposable { | |||||
private _subscriptions: Disposable; | |||||
constructor(command: Commands) { | |||||
super(() => this.dispose()); | |||||
this._subscriptions = commands.registerCommand(command, this.execute, this); | |||||
} | |||||
dispose() { | |||||
this._subscriptions && this._subscriptions.dispose(); | |||||
} | |||||
abstract execute(...args): any; | |||||
} | |||||
abstract class EditorCommand extends Disposable { | |||||
private _subscriptions: Disposable; | |||||
constructor(command: Commands) { | |||||
super(() => this.dispose()); | |||||
this._subscriptions = commands.registerTextEditorCommand(command, this.execute, this); | |||||
} | |||||
dispose() { | |||||
this._subscriptions && this._subscriptions.dispose(); | |||||
} | |||||
abstract execute(editor: TextEditor, edit: TextEditorEdit, ...args): any; | |||||
} | |||||
export class DiffWithPreviousCommand extends EditorCommand { | |||||
constructor(private git: GitProvider) { | |||||
super(Commands.DiffWithPrevious); | |||||
} | |||||
execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, repoPath?: string, sha?: string, shaUri?: Uri, compareWithSha?: string, compareWithUri?: Uri, line?: number) { | |||||
line = line || editor.selection.active.line + 1; | |||||
if (!sha || GitProvider.isUncommitted(sha)) { | |||||
if (!(uri instanceof Uri)) { | |||||
if (!editor.document) return; | |||||
uri = editor.document.uri; | |||||
} | |||||
return this.git.getBlameForLine(uri.fsPath, line) | |||||
.catch(ex => console.error('[GitLens.DiffWithPreviousCommand]', `getBlameForLine(${line})`, ex)) | |||||
.then(blame => { | |||||
if (!blame) return; | |||||
// If the line is uncommitted, find the previous commit | |||||
const commit = blame.commit; | |||||
if (commit.isUncommitted) { | |||||
return this.git.getBlameForLine(commit.previousUri.fsPath, blame.line.originalLine + 1, commit.previousSha, commit.repoPath) | |||||
.catch(ex => console.error('[GitLens.DiffWithPreviousCommand]', `getBlameForLine(${blame.line.originalLine}, ${commit.previousSha})`, ex)) | |||||
.then(prevBlame => { | |||||
if (!prevBlame) return; | |||||
const prevCommit = prevBlame.commit; | |||||
return commands.executeCommand(Commands.DiffWithPrevious, commit.previousUri, commit.repoPath, commit.previousSha, commit.previousUri, prevCommit.sha, prevCommit.uri, blame.line.originalLine); | |||||
}); | |||||
} | |||||
return commands.executeCommand(Commands.DiffWithPrevious, commit.uri, commit.repoPath, commit.sha, commit.uri, commit.previousSha, commit.previousUri, line); | |||||
}); | |||||
} | |||||
if (!compareWithSha) { | |||||
return window.showInformationMessage(`Commit ${sha} has no previous commit`); | |||||
} | |||||
return Promise.all([this.git.getVersionedFile(shaUri.fsPath, repoPath, sha), this.git.getVersionedFile(compareWithUri.fsPath, repoPath, compareWithSha)]) | |||||
.catch(ex => console.error('[GitLens.DiffWithPreviousCommand]', 'getVersionedFile', ex)) | |||||
.then(values => commands.executeCommand(BuiltInCommands.Diff, Uri.file(values[1]), Uri.file(values[0]), `${path.basename(compareWithUri.fsPath)} (${compareWithSha}) ↔ ${path.basename(shaUri.fsPath)} (${sha})`) | |||||
.then(() => commands.executeCommand(BuiltInCommands.RevealLine, {lineNumber: line, at: 'center'}))); | |||||
} | |||||
} | |||||
export class DiffWithWorkingCommand extends EditorCommand { | |||||
constructor(private git: GitProvider) { | |||||
super(Commands.DiffWithWorking); | |||||
} | |||||
execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, repoPath?: string, sha?: string, shaUri?: Uri, line?: number) { | |||||
line = line || editor.selection.active.line + 1; | |||||
if (!sha || GitProvider.isUncommitted(sha)) { | |||||
if (!(uri instanceof Uri)) { | |||||
if (!editor.document) return; | |||||
uri = editor.document.uri; | |||||
} | |||||
return this.git.getBlameForLine(uri.fsPath, line) | |||||
.catch(ex => console.error('[GitLens.DiffWithWorkingCommand]', `getBlameForLine(${line})`, ex)) | |||||
.then(blame => { | |||||
if (!blame) return; | |||||
const commit = blame.commit; | |||||
// If the line is uncommitted, find the previous commit | |||||
if (commit.isUncommitted) { | |||||
return commands.executeCommand(Commands.DiffWithWorking, commit.uri, commit.repoPath, commit.previousSha, commit.previousUri, blame.line.line + 1); | |||||
} | |||||
return commands.executeCommand(Commands.DiffWithWorking, commit.uri, commit.repoPath, commit.sha, commit.uri, line) | |||||
}); | |||||
}; | |||||
return this.git.getVersionedFile(shaUri.fsPath, repoPath, sha) | |||||
.catch(ex => console.error('[GitLens.DiffWithWorkingCommand]', 'getVersionedFile', ex)) | |||||
.then(compare => commands.executeCommand(BuiltInCommands.Diff, Uri.file(compare), uri, `${path.basename(shaUri.fsPath)} (${sha}) ↔ ${path.basename(uri.fsPath)}`) | |||||
.then(() => commands.executeCommand(BuiltInCommands.RevealLine, {lineNumber: line, at: 'center'}))); | |||||
} | |||||
} | |||||
export class ShowBlameCommand extends EditorCommand { | |||||
constructor(private git: GitProvider, private annotationController: BlameAnnotationController) { | |||||
super(Commands.ShowBlame); | |||||
} | |||||
execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, sha?: string) { | |||||
if (sha) { | |||||
return this.annotationController.toggleBlameAnnotation(editor, sha); | |||||
} | |||||
if (!(uri instanceof Uri)) { | |||||
if (!editor.document) return; | |||||
uri = editor.document.uri; | |||||
} | |||||
return this.git.getBlameForLine(uri.fsPath, editor.selection.active.line) | |||||
.catch(ex => console.error('[GitLens.ShowBlameCommand]', `getBlameForLine(${editor.selection.active.line})`, ex)) | |||||
.then(blame => this.annotationController.showBlameAnnotation(editor, blame && blame.commit.sha)); | |||||
} | |||||
} | |||||
export class ShowBlameHistoryCommand extends EditorCommand { | |||||
constructor(private git: GitProvider) { | |||||
super(Commands.ShowBlameHistory); | |||||
} | |||||
execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, range?: Range, position?: Position) { | |||||
if (!(uri instanceof Uri)) { | |||||
if (!editor.document) return; | |||||
uri = editor.document.uri; | |||||
// If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file) | |||||
range = editor.document.validateRange(new Range(0, 0, 1000000, 1000000)); | |||||
position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start; | |||||
} | |||||
return this.git.getBlameLocations(uri.fsPath, range) | |||||
.catch(ex => console.error('[GitLens.ShowBlameHistoryCommand]', 'getBlameLocations', ex)) | |||||
.then(locations => commands.executeCommand(BuiltInCommands.ShowReferences, uri, position, locations)); | |||||
} | |||||
} | |||||
export class ToggleBlameCommand extends EditorCommand { | |||||
constructor(private git: GitProvider, private blameController: BlameAnnotationController) { | |||||
super(Commands.ToggleBlame); | |||||
} | |||||
execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, sha?: string) { | |||||
if (sha) { | |||||
return this.blameController.toggleBlameAnnotation(editor, sha); | |||||
} | |||||
if (!(uri instanceof Uri)) { | |||||
if (!editor.document) return; | |||||
uri = editor.document.uri; | |||||
} | |||||
return this.git.getBlameForLine(uri.fsPath, editor.selection.active.line) | |||||
.catch(ex => console.error('[GitLens.ToggleBlameCommand]', `getBlameForLine(${editor.selection.active.line})`, ex)) | |||||
.then(blame => this.blameController.toggleBlameAnnotation(editor, blame && blame.commit.sha)); | |||||
} | |||||
} | |||||
export class ToggleCodeLensCommand extends EditorCommand { | |||||
constructor(private git: GitProvider) { | |||||
super(Commands.ToggleCodeLens); | |||||
} | |||||
execute(editor: TextEditor, edit: TextEditorEdit) { | |||||
return this.git.toggleCodeLens(editor); | |||||
} | |||||
} |
@ -0,0 +1,33 @@ | |||||
'use strict' | |||||
import {commands, Disposable, TextEditor, TextEditorEdit} from 'vscode'; | |||||
import {Commands} from '../constants'; | |||||
export abstract class Command extends Disposable { | |||||
private _subscriptions: Disposable; | |||||
constructor(command: Commands) { | |||||
super(() => this.dispose()); | |||||
this._subscriptions = commands.registerCommand(command, this.execute, this); | |||||
} | |||||
dispose() { | |||||
this._subscriptions && this._subscriptions.dispose(); | |||||
} | |||||
abstract execute(...args): any; | |||||
} | |||||
export abstract class EditorCommand extends Disposable { | |||||
private _subscriptions: Disposable; | |||||
constructor(command: Commands) { | |||||
super(() => this.dispose()); | |||||
this._subscriptions = commands.registerTextEditorCommand(command, this.execute, this); | |||||
} | |||||
dispose() { | |||||
this._subscriptions && this._subscriptions.dispose(); | |||||
} | |||||
abstract execute(editor: TextEditor, edit: TextEditorEdit, ...args): any; | |||||
} |
@ -0,0 +1,51 @@ | |||||
'use strict' | |||||
import {commands, TextEditor, TextEditorEdit, Uri, window} from 'vscode'; | |||||
import {EditorCommand} from './commands'; | |||||
import {BuiltInCommands, Commands} from '../constants'; | |||||
import GitProvider from '../gitProvider'; | |||||
import * as path from 'path'; | |||||
export default class DiffWithPreviousCommand extends EditorCommand { | |||||
constructor(private git: GitProvider) { | |||||
super(Commands.DiffWithPrevious); | |||||
} | |||||
execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, repoPath?: string, sha?: string, shaUri?: Uri, compareWithSha?: string, compareWithUri?: Uri, line?: number) { | |||||
line = line || editor.selection.active.line; | |||||
if (!sha || GitProvider.isUncommitted(sha)) { | |||||
if (!(uri instanceof Uri)) { | |||||
if (!editor.document) return; | |||||
uri = editor.document.uri; | |||||
} | |||||
return this.git.getBlameForLine(uri.fsPath, line) | |||||
.catch(ex => console.error('[GitLens.DiffWithPreviousCommand]', `getBlameForLine(${line})`, ex)) | |||||
.then(blame => { | |||||
if (!blame) return; | |||||
// If the line is uncommitted, find the previous commit | |||||
const commit = blame.commit; | |||||
if (commit.isUncommitted) { | |||||
return this.git.getBlameForLine(commit.previousUri.fsPath, blame.line.originalLine + 1, commit.previousSha, commit.repoPath) | |||||
.catch(ex => console.error('[GitLens.DiffWithPreviousCommand]', `getBlameForLine(${blame.line.originalLine}, ${commit.previousSha})`, ex)) | |||||
.then(prevBlame => { | |||||
if (!prevBlame) return; | |||||
const prevCommit = prevBlame.commit; | |||||
return commands.executeCommand(Commands.DiffWithPrevious, commit.previousUri, commit.repoPath, commit.previousSha, commit.previousUri, prevCommit.sha, prevCommit.uri, blame.line.originalLine); | |||||
}); | |||||
} | |||||
return commands.executeCommand(Commands.DiffWithPrevious, commit.uri, commit.repoPath, commit.sha, commit.uri, commit.previousSha, commit.previousUri, line); | |||||
}); | |||||
} | |||||
if (!compareWithSha) { | |||||
return window.showInformationMessage(`Commit ${sha} has no previous commit`); | |||||
} | |||||
return Promise.all([this.git.getVersionedFile(shaUri.fsPath, repoPath, sha), this.git.getVersionedFile(compareWithUri.fsPath, repoPath, compareWithSha)]) | |||||
.catch(ex => console.error('[GitLens.DiffWithPreviousCommand]', 'getVersionedFile', ex)) | |||||
.then(values => commands.executeCommand(BuiltInCommands.Diff, Uri.file(values[1]), Uri.file(values[0]), `${path.basename(compareWithUri.fsPath)} (${compareWithSha}) ↔ ${path.basename(shaUri.fsPath)} (${sha})`) | |||||
.then(() => commands.executeCommand(BuiltInCommands.RevealLine, {lineNumber: line, at: 'center'}))); | |||||
} | |||||
} |
@ -0,0 +1,40 @@ | |||||
'use strict' | |||||
import {commands, TextEditor, TextEditorEdit, Uri, window} from 'vscode'; | |||||
import {EditorCommand} from './commands'; | |||||
import {BuiltInCommands, Commands} from '../constants'; | |||||
import GitProvider from '../gitProvider'; | |||||
import * as path from 'path'; | |||||
export default class DiffWithWorkingCommand extends EditorCommand { | |||||
constructor(private git: GitProvider) { | |||||
super(Commands.DiffWithWorking); | |||||
} | |||||
execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, repoPath?: string, sha?: string, shaUri?: Uri, line?: number) { | |||||
line = line || editor.selection.active.line; | |||||
if (!sha || GitProvider.isUncommitted(sha)) { | |||||
if (!(uri instanceof Uri)) { | |||||
if (!editor.document) return; | |||||
uri = editor.document.uri; | |||||
} | |||||
return this.git.getBlameForLine(uri.fsPath, line) | |||||
.catch(ex => console.error('[GitLens.DiffWithWorkingCommand]', `getBlameForLine(${line})`, ex)) | |||||
.then(blame => { | |||||
if (!blame) return; | |||||
const commit = blame.commit; | |||||
// If the line is uncommitted, find the previous commit | |||||
if (commit.isUncommitted) { | |||||
return commands.executeCommand(Commands.DiffWithWorking, commit.uri, commit.repoPath, commit.previousSha, commit.previousUri, blame.line.line + 1); | |||||
} | |||||
return commands.executeCommand(Commands.DiffWithWorking, commit.uri, commit.repoPath, commit.sha, commit.uri, line) | |||||
}); | |||||
}; | |||||
return this.git.getVersionedFile(shaUri.fsPath, repoPath, sha) | |||||
.catch(ex => console.error('[GitLens.DiffWithWorkingCommand]', 'getVersionedFile', ex)) | |||||
.then(compare => commands.executeCommand(BuiltInCommands.Diff, Uri.file(compare), uri, `${path.basename(shaUri.fsPath)} (${sha}) ↔ ${path.basename(uri.fsPath)}`) | |||||
.then(() => commands.executeCommand(BuiltInCommands.RevealLine, {lineNumber: line, at: 'center'}))); | |||||
} | |||||
} |
@ -0,0 +1,27 @@ | |||||
'use strict' | |||||
import {TextEditor, TextEditorEdit, Uri} from 'vscode'; | |||||
import BlameAnnotationController from '../blameAnnotationController'; | |||||
import {EditorCommand} from './commands'; | |||||
import {Commands} from '../constants'; | |||||
import GitProvider from '../gitProvider'; | |||||
export default class ShowBlameCommand extends EditorCommand { | |||||
constructor(private git: GitProvider, private annotationController: BlameAnnotationController) { | |||||
super(Commands.ShowBlame); | |||||
} | |||||
execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, sha?: string) { | |||||
if (sha) { | |||||
return this.annotationController.toggleBlameAnnotation(editor, sha); | |||||
} | |||||
if (!(uri instanceof Uri)) { | |||||
if (!editor.document) return; | |||||
uri = editor.document.uri; | |||||
} | |||||
return this.git.getBlameForLine(uri.fsPath, editor.selection.active.line) | |||||
.catch(ex => console.error('[GitLens.ShowBlameCommand]', `getBlameForLine(${editor.selection.active.line})`, ex)) | |||||
.then(blame => this.annotationController.showBlameAnnotation(editor, blame && blame.commit.sha)); | |||||
} | |||||
} |
@ -0,0 +1,26 @@ | |||||
'use strict' | |||||
import {commands, Position, Range, TextEditor, TextEditorEdit, Uri} from 'vscode'; | |||||
import {EditorCommand} from './commands'; | |||||
import {BuiltInCommands, Commands} from '../constants'; | |||||
import GitProvider from '../gitProvider'; | |||||
export default class ShowBlameHistoryCommand extends EditorCommand { | |||||
constructor(private git: GitProvider) { | |||||
super(Commands.ShowBlameHistory); | |||||
} | |||||
execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, range?: Range, position?: Position) { | |||||
if (!(uri instanceof Uri)) { | |||||
if (!editor.document) return; | |||||
uri = editor.document.uri; | |||||
// If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file) | |||||
range = editor.document.validateRange(new Range(0, 0, 1000000, 1000000)); | |||||
position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start; | |||||
} | |||||
return this.git.getBlameLocations(uri.fsPath, range) | |||||
.catch(ex => console.error('[GitLens.ShowBlameHistoryCommand]', 'getBlameLocations', ex)) | |||||
.then(locations => commands.executeCommand(BuiltInCommands.ShowReferences, uri, position, locations)); | |||||
} | |||||
} |
@ -0,0 +1,37 @@ | |||||
'use strict' | |||||
import {TextEditor, TextEditorEdit, Uri} from 'vscode'; | |||||
import BlameAnnotationController from '../blameAnnotationController'; | |||||
import {EditorCommand} from './commands'; | |||||
import {Commands} from '../constants'; | |||||
import GitProvider from '../gitProvider'; | |||||
export default class ToggleBlameCommand extends EditorCommand { | |||||
constructor(private git: GitProvider, private blameController: BlameAnnotationController) { | |||||
super(Commands.ToggleBlame); | |||||
} | |||||
execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, sha?: string) { | |||||
if (sha) { | |||||
return this.blameController.toggleBlameAnnotation(editor, sha); | |||||
} | |||||
if (!(uri instanceof Uri)) { | |||||
if (!editor.document) return; | |||||
uri = editor.document.uri; | |||||
} | |||||
return this.git.getBlameForLine(uri.fsPath, editor.selection.active.line) | |||||
.catch(ex => console.error('[GitLens.ToggleBlameCommand]', `getBlameForLine(${editor.selection.active.line})`, ex)) | |||||
.then(blame => this.blameController.toggleBlameAnnotation(editor, blame && blame.commit.sha)); | |||||
} | |||||
} | |||||
export class ToggleCodeLensCommand extends EditorCommand { | |||||
constructor(private git: GitProvider) { | |||||
super(Commands.ToggleCodeLens); | |||||
} | |||||
execute(editor: TextEditor, edit: TextEditorEdit) { | |||||
return this.git.toggleCodeLens(editor); | |||||
} | |||||
} |
@ -0,0 +1,15 @@ | |||||
'use strict' | |||||
import {TextEditor, TextEditorEdit} from 'vscode'; | |||||
import {EditorCommand} from './commands'; | |||||
import {Commands} from '../constants'; | |||||
import GitProvider from '../gitProvider'; | |||||
export default class ToggleCodeLensCommand extends EditorCommand { | |||||
constructor(private git: GitProvider) { | |||||
super(Commands.ToggleCodeLens); | |||||
} | |||||
execute(editor: TextEditor, edit: TextEditorEdit) { | |||||
return this.git.toggleCodeLens(editor); | |||||
} | |||||
} |