877 rader
27 KiB

import type {
CancellationToken,
CodeLensProvider,
Command,
DocumentSelector,
DocumentSymbol,
Event,
TextDocument,
Uri,
} from 'vscode';
import { CodeLens, EventEmitter, Location, Position, Range, SymbolInformation, SymbolKind } from 'vscode';
import type {
DiffWithPreviousCommandArgs,
OpenOnRemoteCommandArgs,
ShowCommitsInViewCommandArgs,
ShowQuickCommitCommandArgs,
ShowQuickCommitFileCommandArgs,
ShowQuickFileHistoryCommandArgs,
ToggleFileChangesAnnotationCommandArgs,
} from '../commands';
import type { CodeLensConfig, CodeLensLanguageScope } from '../configuration';
import { CodeLensCommand, CodeLensScopes, configuration, FileAnnotationType } from '../configuration';
import { Commands, CoreCommands, Schemes } from '../constants';
import type { Container } from '../container';
import type { GitUri } from '../git/gitUri';
import type { GitBlame, GitBlameLines } from '../git/models/blame';
import type { GitCommit } from '../git/models/commit';
import { RemoteResourceType } from '../git/remotes/provider';
import { Logger } from '../logger';
import { asCommand, executeCoreCommand } from '../system/command';
import { is, once } from '../system/function';
import { filterMap, find, first, join, map } from '../system/iterable';
import { isVirtualUri } from '../system/utils';
export class GitRecentChangeCodeLens extends CodeLens {
constructor(
public readonly languageId: string,
public readonly symbol: DocumentSymbol | SymbolInformation,
public readonly uri: GitUri | undefined,
public readonly dateFormat: string | null,
private readonly blame: (() => GitBlameLines | undefined) | undefined,
public readonly blameRange: Range,
public readonly isFullRange: boolean,
range: Range,
public readonly desiredCommand: CodeLensCommand | false,
command?: Command | undefined,
) {
super(range, command);
}
getBlame(): GitBlameLines | undefined {
return this.blame?.();
}
}
export class GitAuthorsCodeLens extends CodeLens {
constructor(
public readonly languageId: string,
public readonly symbol: DocumentSymbol | SymbolInformation,
public readonly uri: GitUri | undefined,
private readonly blame: () => GitBlameLines | undefined,
public readonly blameRange: Range,
public readonly isFullRange: boolean,
range: Range,
public readonly desiredCommand: CodeLensCommand | false,
) {
super(range);
}
getBlame(): GitBlameLines | undefined {
return this.blame();
}
}
export class GitCodeLensProvider implements CodeLensProvider {
static selector: DocumentSelector = [
{ scheme: Schemes.File },
{ scheme: Schemes.Git },
{ scheme: Schemes.GitLens },
{ scheme: Schemes.PRs },
{ scheme: Schemes.Vsls },
{ scheme: Schemes.VslsScc },
{ scheme: Schemes.Virtual },
{ scheme: Schemes.GitHub },
];
private _onDidChangeCodeLenses = new EventEmitter<void>();
get onDidChangeCodeLenses(): Event<void> {
return this._onDidChangeCodeLenses.event;
}
constructor(private readonly container: Container) {}
reset(_reason?: 'idle' | 'saved') {
this._onDidChangeCodeLenses.fire();
}
async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
// Since we can't currently blame edited virtual documents, don't even attempt anything if dirty
if (document.isDirty && isVirtualUri(document.uri)) return [];
const cfg = configuration.get('codeLens', document);
if (!cfg.enabled) return [];
const trackedDocument = await this.container.tracker.getOrAdd(document);
if (!trackedDocument.isBlameable) return [];
let dirty = false;
if (document.isDirty) {
// Only allow dirty blames if we are idle
if (trackedDocument.isDirtyIdle) {
const maxLines = configuration.get('advanced.blame.sizeThresholdAfterEdit');
if (maxLines > 0 && document.lineCount > maxLines) {
dirty = true;
}
} else {
dirty = true;
}
}
let languageScope = cfg.scopesByLanguage?.find(ll => ll.language?.toLowerCase() === document.languageId);
if (languageScope == null) {
languageScope = {
language: document.languageId,
};
}
if (languageScope.scopes == null) {
languageScope.scopes = cfg.scopes;
}
if (languageScope.symbolScopes == null) {
languageScope.symbolScopes = cfg.symbolScopes;
}
languageScope.symbolScopes =
languageScope.symbolScopes != null
? (languageScope.symbolScopes = languageScope.symbolScopes.map(s => s.toLowerCase()))
: [];
const lenses: CodeLens[] = [];
const gitUri = trackedDocument.uri;
let blame: GitBlame | undefined;
let symbols;
if (!dirty) {
if (token.isCancellationRequested) return lenses;
if (languageScope.scopes.length === 1 && languageScope.scopes.includes(CodeLensScopes.Document)) {
blame = await this.container.git.getBlame(gitUri, document);
} else {
[blame, symbols] = await Promise.all([
this.container.git.getBlame(gitUri, document),
executeCoreCommand<[Uri], SymbolInformation[]>(
CoreCommands.ExecuteDocumentSymbolProvider,
document.uri,
),
]);
}
if (blame == null || blame?.lines.length === 0) return lenses;
} else if (languageScope.scopes.length !== 1 || !languageScope.scopes.includes(CodeLensScopes.Document)) {
let tracked;
[tracked, symbols] = await Promise.all([
this.container.git.isTracked(gitUri),
executeCoreCommand<[Uri], SymbolInformation[]>(
CoreCommands.ExecuteDocumentSymbolProvider,
document.uri,
),
]);
if (!tracked) return lenses;
}
if (token.isCancellationRequested) return lenses;
const documentRangeFn = once(() => document.validateRange(new Range(0, 0, 1000000, 1000000)));
// Since blame information isn't valid when there are unsaved changes -- update the lenses appropriately
const dirtyCommand: Command | undefined = dirty
? { command: undefined!, title: this.getDirtyTitle(cfg) }
: undefined;
if (symbols !== undefined) {
Logger.log('GitCodeLensProvider.provideCodeLenses:', `${symbols.length} symbol(s) found`);
for (const sym of symbols) {
this.provideCodeLens(
lenses,
document,
sym,
languageScope as Required<CodeLensLanguageScope>,
documentRangeFn,
blame,
gitUri,
cfg,
dirty,
dirtyCommand,
);
}
}
if (
(languageScope.scopes.includes(CodeLensScopes.Document) || languageScope.symbolScopes.includes('file')) &&
!languageScope.symbolScopes.includes('!file')
) {
// Check if we have a lens for the whole document -- if not add one
if (lenses.find(l => l.range.start.line === 0 && l.range.end.line === 0) == null) {
const blameRange = documentRangeFn();
let blameForRangeFn: (() => GitBlameLines | undefined) | undefined = undefined;
if (dirty || cfg.recentChange.enabled) {
if (!dirty) {
blameForRangeFn = once(() => this.container.git.getBlameRange(blame!, gitUri, blameRange));
}
const fileSymbol = new SymbolInformation(
gitUri.fileName,
SymbolKind.File,
'',
new Location(gitUri.documentUri(), new Range(0, 0, 0, blameRange.start.character)),
);
lenses.push(
new GitRecentChangeCodeLens(
document.languageId,
fileSymbol,
gitUri,
cfg.dateFormat,
blameForRangeFn,
blameRange,
true,
getRangeFromSymbol(fileSymbol),
cfg.recentChange.command,
dirtyCommand,
),
);
}
if (!dirty && cfg.authors.enabled) {
if (blameForRangeFn === undefined) {
blameForRangeFn = once(() => this.container.git.getBlameRange(blame!, gitUri, blameRange));
}
const fileSymbol = new SymbolInformation(
gitUri.fileName,
SymbolKind.File,
'',
new Location(gitUri.documentUri(), new Range(0, 1, 0, blameRange.start.character)),
);
lenses.push(
new GitAuthorsCodeLens(
document.languageId,
fileSymbol,
gitUri,
blameForRangeFn,
blameRange,
true,
getRangeFromSymbol(fileSymbol),
cfg.authors.command,
),
);
}
}
}
return lenses;
}
private getValidateSymbolRange(
symbol: SymbolInformation | DocumentSymbol,
languageScope: Required<CodeLensLanguageScope>,
documentRangeFn: () => Range,
includeSingleLineSymbols: boolean,
): Range | undefined {
let valid = false;
let range: Range | undefined;
const symbolName = SymbolKind[symbol.kind].toLowerCase();
switch (symbol.kind) {
case SymbolKind.File:
if (
languageScope.scopes.includes(CodeLensScopes.Containers) ||
languageScope.symbolScopes.includes(symbolName)
) {
valid = !languageScope.symbolScopes.includes(`!${symbolName}`);
}
if (valid) {
// Adjust the range to be for the whole file
range = documentRangeFn();
}
break;
case SymbolKind.Package:
if (
languageScope.scopes.includes(CodeLensScopes.Containers) ||
languageScope.symbolScopes.includes(symbolName)
) {
valid = !languageScope.symbolScopes.includes(`!${symbolName}`);
}
if (valid) {
// Adjust the range to be for the whole file
range = getRangeFromSymbol(symbol);
if (range.start.line === 0 && range.end.line === 0) {
range = documentRangeFn();
}
}
break;
case SymbolKind.Class:
case SymbolKind.Interface:
case SymbolKind.Module:
case SymbolKind.Namespace:
case SymbolKind.Struct:
if (
languageScope.scopes.includes(CodeLensScopes.Containers) ||
languageScope.symbolScopes.includes(symbolName)
) {
range = getRangeFromSymbol(symbol);
valid =
!languageScope.symbolScopes.includes(`!${symbolName}`) &&
(includeSingleLineSymbols || !range.isSingleLine);
}
break;
case SymbolKind.Constructor:
case SymbolKind.Enum:
case SymbolKind.Function:
case SymbolKind.Method:
case SymbolKind.Property:
if (
languageScope.scopes.includes(CodeLensScopes.Blocks) ||
languageScope.symbolScopes.includes(symbolName)
) {
range = getRangeFromSymbol(symbol);
valid =
!languageScope.symbolScopes.includes(`!${symbolName}`) &&
(includeSingleLineSymbols || !range.isSingleLine);
}
break;
case SymbolKind.String:
if (
languageScope.symbolScopes.includes(symbolName) ||
// A special case for markdown files, SymbolKind.String seems to be returned for headers, so consider those containers
(languageScope.language === 'markdown' && languageScope.scopes.includes(CodeLensScopes.Containers))
) {
range = getRangeFromSymbol(symbol);
valid =
!languageScope.symbolScopes.includes(`!${symbolName}`) &&
(includeSingleLineSymbols || !range.isSingleLine);
}
break;
default:
if (languageScope.symbolScopes.includes(symbolName)) {
range = getRangeFromSymbol(symbol);
valid =
!languageScope.symbolScopes.includes(`!${symbolName}`) &&
(includeSingleLineSymbols || !range.isSingleLine);
}
break;
}
return valid ? range ?? getRangeFromSymbol(symbol) : undefined;
}
private provideCodeLens(
lenses: CodeLens[],
document: TextDocument,
symbol: SymbolInformation | DocumentSymbol,
languageScope: Required<CodeLensLanguageScope>,
documentRangeFn: () => Range,
blame: GitBlame | undefined,
gitUri: GitUri | undefined,
cfg: CodeLensConfig,
dirty: boolean,
dirtyCommand: Command | undefined,
): void {
try {
const blameRange = this.getValidateSymbolRange(
symbol,
languageScope,
documentRangeFn,
cfg.includeSingleLineSymbols,
);
if (blameRange === undefined) return;
const line = document.lineAt(getRangeFromSymbol(symbol).start);
// Make sure there is only 1 lens per line
if (lenses.length && lenses[lenses.length - 1].range.start.line === line.lineNumber) return;
// Anchor the CodeLens to the start of the line -- so that the range won't change with edits (otherwise the CodeLens will be removed and re-added)
let startChar = 0;
let blameForRangeFn: (() => GitBlameLines | undefined) | undefined;
if (dirty || cfg.recentChange.enabled) {
if (!dirty) {
blameForRangeFn = once(() => this.container.git.getBlameRange(blame!, gitUri!, blameRange));
}
lenses.push(
new GitRecentChangeCodeLens(
document.languageId,
symbol,
gitUri,
cfg.dateFormat,
blameForRangeFn,
blameRange,
false,
line.range.with(new Position(line.range.start.line, startChar)),
cfg.recentChange.command,
dirtyCommand,
),
);
startChar++;
}
if (cfg.authors.enabled) {
let multiline = !blameRange.isSingleLine;
// HACK for Omnisharp, since it doesn't return full ranges
if (!multiline && document.languageId === 'csharp') {
switch (symbol.kind) {
case SymbolKind.File:
break;
case SymbolKind.Package:
case SymbolKind.Module:
case SymbolKind.Namespace:
case SymbolKind.Class:
case SymbolKind.Interface:
case SymbolKind.Constructor:
case SymbolKind.Method:
case SymbolKind.Function:
case SymbolKind.Enum:
multiline = true;
break;
}
}
if (multiline && !dirty) {
if (blameForRangeFn === undefined) {
blameForRangeFn = once(() => this.container.git.getBlameRange(blame!, gitUri!, blameRange));
}
lenses.push(
new GitAuthorsCodeLens(
document.languageId,
symbol,
gitUri,
blameForRangeFn,
blameRange,
false,
line.range.with(new Position(line.range.start.line, startChar)),
cfg.authors.command,
),
);
}
}
} finally {
if (isDocumentSymbol(symbol)) {
for (const child of symbol.children) {
this.provideCodeLens(
lenses,
document,
child,
languageScope,
documentRangeFn,
blame,
gitUri,
cfg,
dirty,
dirtyCommand,
);
}
}
}
}
resolveCodeLens(lens: CodeLens, token: CancellationToken): CodeLens | Promise<CodeLens> {
if (lens instanceof GitRecentChangeCodeLens) return this.resolveGitRecentChangeCodeLens(lens, token);
if (lens instanceof GitAuthorsCodeLens) return this.resolveGitAuthorsCodeLens(lens, token);
return Promise.reject<CodeLens>(undefined);
}
private resolveGitRecentChangeCodeLens(lens: GitRecentChangeCodeLens, _token: CancellationToken): CodeLens {
const blame = lens.getBlame();
if (blame === undefined) return lens;
const recentCommit = first(blame.commits.values());
// TODO@eamodio This is FAR too expensive, but this accounts for commits that delete lines -- is there another way?
// if (lens.uri != null) {
// const commit = await this.container.git.getCommitForFile(lens.uri.repoPath, lens.uri.fsPath, {
// range: lens.blameRange,
// });
// if (
// commit != null &&
// commit.sha !== recentCommit.sha &&
// commit.date.getTime() > recentCommit.date.getTime()
// ) {
// recentCommit = commit;
// }
// }
let title = `${recentCommit.author.name}, ${
lens.dateFormat == null ? recentCommit.formattedDate : recentCommit.formatDate(lens.dateFormat)
}`;
if (configuration.get('debug')) {
title += ` [${lens.languageId}: ${SymbolKind[lens.symbol.kind]}(${lens.range.start.character}-${
lens.range.end.character
}${
(lens.symbol as SymbolInformation).containerName
? `|${(lens.symbol as SymbolInformation).containerName}`
: ''
}), Lines (${lens.blameRange.start.line + 1}-${lens.blameRange.end.line + 1}), Commit (${
recentCommit.shortSha
})]`;
}
if (lens.desiredCommand === false) {
return this.applyCommandWithNoClickAction(title, lens);
}
switch (lens.desiredCommand) {
case CodeLensCommand.CopyRemoteCommitUrl:
return this.applyCopyOrOpenCommitOnRemoteCommand<GitRecentChangeCodeLens>(
title,
lens,
recentCommit,
true,
);
case CodeLensCommand.CopyRemoteFileUrl:
return this.applyCopyOrOpenFileOnRemoteCommand<GitRecentChangeCodeLens>(
title,
lens,
recentCommit,
true,
);
case CodeLensCommand.DiffWithPrevious:
return this.applyDiffWithPreviousCommand<GitRecentChangeCodeLens>(title, lens, recentCommit);
case CodeLensCommand.OpenCommitOnRemote:
return this.applyCopyOrOpenCommitOnRemoteCommand<GitRecentChangeCodeLens>(title, lens, recentCommit);
case CodeLensCommand.OpenFileOnRemote:
return this.applyCopyOrOpenFileOnRemoteCommand<GitRecentChangeCodeLens>(title, lens, recentCommit);
case CodeLensCommand.RevealCommitInView:
return this.applyRevealCommitInViewCommand<GitRecentChangeCodeLens>(title, lens, recentCommit);
case CodeLensCommand.ShowCommitsInView:
return this.applyShowCommitsInViewCommand<GitRecentChangeCodeLens>(title, lens, blame, recentCommit);
case CodeLensCommand.ShowQuickCommitDetails:
return this.applyShowQuickCommitDetailsCommand<GitRecentChangeCodeLens>(title, lens, recentCommit);
case CodeLensCommand.ShowQuickCommitFileDetails:
return this.applyShowQuickCommitFileDetailsCommand<GitRecentChangeCodeLens>(title, lens, recentCommit);
case CodeLensCommand.ShowQuickCurrentBranchHistory:
return this.applyShowQuickCurrentBranchHistoryCommand<GitRecentChangeCodeLens>(title, lens);
case CodeLensCommand.ShowQuickFileHistory:
return this.applyShowQuickFileHistoryCommand<GitRecentChangeCodeLens>(title, lens);
case CodeLensCommand.ToggleFileBlame:
return this.applyToggleFileBlameCommand<GitRecentChangeCodeLens>(title, lens);
case CodeLensCommand.ToggleFileChanges:
return this.applyToggleFileChangesCommand<GitRecentChangeCodeLens>(title, lens, recentCommit);
case CodeLensCommand.ToggleFileChangesOnly:
return this.applyToggleFileChangesCommand<GitRecentChangeCodeLens>(title, lens, recentCommit, true);
case CodeLensCommand.ToggleFileHeatmap:
return this.applyToggleFileHeatmapCommand<GitRecentChangeCodeLens>(title, lens);
default:
return lens;
}
}
private resolveGitAuthorsCodeLens(lens: GitAuthorsCodeLens, _token: CancellationToken): CodeLens {
const blame = lens.getBlame();
if (blame === undefined) return lens;
const count = blame.authors.size;
const author = first(blame.authors.values()).name;
let title = `${count} ${count > 1 ? 'authors' : 'author'} (${author}${count > 1 ? ' and others' : ''})`;
if (configuration.get('debug')) {
title += ` [${lens.languageId}: ${SymbolKind[lens.symbol.kind]}(${lens.range.start.character}-${
lens.range.end.character
}${
(lens.symbol as SymbolInformation).containerName
? `|${(lens.symbol as SymbolInformation).containerName}`
: ''
}), Lines (${lens.blameRange.start.line + 1}-${lens.blameRange.end.line + 1}), Authors (${join(
map(blame.authors.values(), a => a.name),
', ',
)})]`;
}
if (lens.desiredCommand === false) {
return this.applyCommandWithNoClickAction(title, lens);
}
const commit = find(blame.commits.values(), c => c.author.name === author) ?? first(blame.commits.values());
switch (lens.desiredCommand) {
case CodeLensCommand.CopyRemoteCommitUrl:
return this.applyCopyOrOpenCommitOnRemoteCommand<GitAuthorsCodeLens>(title, lens, commit, true);
case CodeLensCommand.CopyRemoteFileUrl:
return this.applyCopyOrOpenFileOnRemoteCommand<GitAuthorsCodeLens>(title, lens, commit, true);
case CodeLensCommand.DiffWithPrevious:
return this.applyDiffWithPreviousCommand<GitAuthorsCodeLens>(title, lens, commit);
case CodeLensCommand.OpenCommitOnRemote:
return this.applyCopyOrOpenCommitOnRemoteCommand<GitAuthorsCodeLens>(title, lens, commit);
case CodeLensCommand.OpenFileOnRemote:
return this.applyCopyOrOpenFileOnRemoteCommand<GitAuthorsCodeLens>(title, lens, commit);
case CodeLensCommand.RevealCommitInView:
return this.applyRevealCommitInViewCommand<GitAuthorsCodeLens>(title, lens, commit);
case CodeLensCommand.ShowCommitsInView:
return this.applyShowCommitsInViewCommand<GitAuthorsCodeLens>(title, lens, blame);
case CodeLensCommand.ShowQuickCommitDetails:
return this.applyShowQuickCommitDetailsCommand<GitAuthorsCodeLens>(title, lens, commit);
case CodeLensCommand.ShowQuickCommitFileDetails:
return this.applyShowQuickCommitFileDetailsCommand<GitAuthorsCodeLens>(title, lens, commit);
case CodeLensCommand.ShowQuickCurrentBranchHistory:
return this.applyShowQuickCurrentBranchHistoryCommand<GitAuthorsCodeLens>(title, lens);
case CodeLensCommand.ShowQuickFileHistory:
return this.applyShowQuickFileHistoryCommand<GitAuthorsCodeLens>(title, lens);
case CodeLensCommand.ToggleFileBlame:
return this.applyToggleFileBlameCommand<GitAuthorsCodeLens>(title, lens);
case CodeLensCommand.ToggleFileChanges:
return this.applyToggleFileChangesCommand<GitAuthorsCodeLens>(title, lens, commit);
case CodeLensCommand.ToggleFileChangesOnly:
return this.applyToggleFileChangesCommand<GitAuthorsCodeLens>(title, lens, commit, true);
case CodeLensCommand.ToggleFileHeatmap:
return this.applyToggleFileHeatmapCommand<GitAuthorsCodeLens>(title, lens);
default:
return lens;
}
}
private applyDiffWithPreviousCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string,
lens: T,
commit: GitCommit | undefined,
): T {
lens.command = asCommand<[undefined, DiffWithPreviousCommandArgs]>({
title: title,
command: Commands.DiffWithPrevious,
arguments: [
undefined,
{
commit: commit,
uri: lens.uri!.toFileUri(),
},
],
});
return lens;
}
private applyCopyOrOpenCommitOnRemoteCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string,
lens: T,
commit: GitCommit,
clipboard: boolean = false,
): T {
lens.command = asCommand<[OpenOnRemoteCommandArgs]>({
title: title,
command: Commands.OpenOnRemote,
arguments: [
{
resource: {
type: RemoteResourceType.Commit,
sha: commit.sha,
},
repoPath: commit.repoPath,
clipboard: clipboard,
},
],
});
return lens;
}
private applyCopyOrOpenFileOnRemoteCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string,
lens: T,
commit: GitCommit,
clipboard: boolean = false,
): T {
lens.command = asCommand<[OpenOnRemoteCommandArgs]>({
title: title,
command: Commands.OpenOnRemote,
arguments: [
{
resource: {
type: RemoteResourceType.Revision,
fileName: commit.file?.path ?? '',
sha: commit.sha,
},
repoPath: commit.repoPath,
clipboard: clipboard,
},
],
});
return lens;
}
private applyRevealCommitInViewCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string,
lens: T,
commit: GitCommit | undefined,
): T {
lens.command = asCommand<[Uri, ShowQuickCommitCommandArgs]>({
title: title,
command: commit?.isUncommitted ? '' : CodeLensCommand.RevealCommitInView,
arguments: [
lens.uri!.toFileUri(),
{
commit: commit,
sha: commit === undefined ? undefined : commit.sha,
},
],
});
return lens;
}
private applyShowCommitsInViewCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string,
lens: T,
blame: GitBlameLines,
commit?: GitCommit,
): T {
let refs;
if (commit === undefined) {
refs = [...filterMap(blame.commits.values(), c => (c.isUncommitted ? undefined : c.ref))];
} else {
refs = [commit.ref];
}
lens.command = asCommand<[ShowCommitsInViewCommandArgs]>({
title: title,
command: refs.length === 0 ? '' : Commands.ShowCommitsInView,
arguments: [
{
repoPath: blame.repoPath,
refs: refs,
},
],
});
return lens;
}
private applyShowQuickCommitDetailsCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string,
lens: T,
commit: GitCommit | undefined,
): T {
lens.command = asCommand<[Uri, ShowQuickCommitCommandArgs]>({
title: title,
command: commit?.isUncommitted ? '' : CodeLensCommand.ShowQuickCommitDetails,
arguments: [
lens.uri!.toFileUri(),
{
commit: commit,
sha: commit === undefined ? undefined : commit.sha,
},
],
});
return lens;
}
private applyShowQuickCommitFileDetailsCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string,
lens: T,
commit: GitCommit | undefined,
): T {
lens.command = asCommand<[Uri, ShowQuickCommitFileCommandArgs]>({
title: title,
command: commit?.isUncommitted ? '' : CodeLensCommand.ShowQuickCommitFileDetails,
arguments: [
lens.uri!.toFileUri(),
{
commit: commit,
sha: commit === undefined ? undefined : commit.sha,
},
],
});
return lens;
}
private applyShowQuickCurrentBranchHistoryCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string,
lens: T,
): T {
lens.command = asCommand<[Uri]>({
title: title,
command: CodeLensCommand.ShowQuickCurrentBranchHistory,
arguments: [lens.uri!.toFileUri()],
});
return lens;
}
private applyShowQuickFileHistoryCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string,
lens: T,
): T {
lens.command = asCommand<[Uri, ShowQuickFileHistoryCommandArgs]>({
title: title,
command: CodeLensCommand.ShowQuickFileHistory,
arguments: [
lens.uri!.toFileUri(),
{
range: lens.isFullRange ? undefined : lens.blameRange,
},
],
});
return lens;
}
private applyToggleFileBlameCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string,
lens: T,
): T {
lens.command = asCommand<[Uri]>({
title: title,
command: Commands.ToggleFileBlame,
arguments: [lens.uri!.toFileUri()],
});
return lens;
}
private applyToggleFileChangesCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string,
lens: T,
commit: GitCommit,
only?: boolean,
): T {
lens.command = asCommand<[Uri, ToggleFileChangesAnnotationCommandArgs]>({
title: title,
command: Commands.ToggleFileChanges,
arguments: [
lens.uri!.toFileUri(),
{
type: FileAnnotationType.Changes,
context: { sha: commit.sha, only: only, selection: false },
},
],
});
return lens;
}
private applyToggleFileHeatmapCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string,
lens: T,
): T {
lens.command = asCommand<[Uri]>({
title: title,
command: Commands.ToggleFileHeatmap,
arguments: [lens.uri!.toFileUri()],
});
return lens;
}
private applyCommandWithNoClickAction<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string,
lens: T,
): T {
lens.command = {
title: title,
command: '',
};
return lens;
}
private getDirtyTitle(cfg: CodeLensConfig) {
if (cfg.recentChange.enabled && cfg.authors.enabled) {
return configuration.get('strings.codeLens.unsavedChanges.recentChangeAndAuthors');
}
if (cfg.recentChange.enabled) return configuration.get('strings.codeLens.unsavedChanges.recentChangeOnly');
return configuration.get('strings.codeLens.unsavedChanges.authorsOnly');
}
}
function getRangeFromSymbol(symbol: DocumentSymbol | SymbolInformation) {
return isDocumentSymbol(symbol) ? symbol.range : symbol.location.range;
}
function isDocumentSymbol(symbol: DocumentSymbol | SymbolInformation): symbol is DocumentSymbol {
return is<DocumentSymbol>(symbol, 'children');
}