diff --git a/images/dark/git-icon-orange.svg b/images/dark/git-icon-orange.svg new file mode 100644 index 0000000..9b16645 --- /dev/null +++ b/images/dark/git-icon-orange.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/images/dark/git-icon-progress.svg b/images/dark/git-icon-progress.svg new file mode 100644 index 0000000..c1e501d --- /dev/null +++ b/images/dark/git-icon-progress.svg @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/images/light/git-icon-orange.svg b/images/light/git-icon-orange.svg new file mode 100644 index 0000000..9b16645 --- /dev/null +++ b/images/light/git-icon-orange.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/images/light/git-icon-progress.svg b/images/light/git-icon-progress.svg new file mode 100644 index 0000000..d79bf7b --- /dev/null +++ b/images/light/git-icon-progress.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/package.json b/package.json index b038dc5..292fcdd 100644 --- a/package.json +++ b/package.json @@ -819,6 +819,24 @@ } }, { + "command": "gitlens.clearFileAnnotations", + "title": "Clear File Annotations", + "category": "GitLens", + "icon": { + "dark": "images/dark/git-icon-orange.svg", + "light": "images/light/git-icon-orange.svg" + } + }, + { + "command": "gitlens.computingFileAnnotations", + "title": "Computing File Annotations...", + "category": "GitLens", + "icon": { + "dark": "images/dark/git-icon-progress.svg", + "light": "images/light/git-icon-progress.svg" + } + }, + { "command": "gitlens.toggleFileRecentChanges", "title": "Toggle Recent File Changes Annotations", "category": "GitLens", @@ -1047,6 +1065,14 @@ "when": "gitlens:isBlameable" }, { + "command": "gitlens.clearFileAnnotations", + "when": "gitlens:annotationStatus == computed" + }, + { + "command": "gitlens.computingFileAnnotations", + "when": "false" + }, + { "command": "gitlens.toggleFileRecentChanges", "when": "gitlens:isTracked" }, @@ -1227,7 +1253,17 @@ { "command": "gitlens.toggleFileBlame", "alt": "gitlens.toggleFileRecentChanges", - "when": "gitlens:isBlameable && config.gitlens.advanced.menus.editorTitle.blame", + "when": "gitlens:isBlameable && !gitlens:annotationStatus && config.gitlens.advanced.menus.editorTitle.blame", + "group": "navigation@100" + }, + { + "command": "gitlens.computingFileAnnotations", + "when": "gitlens:annotationStatus == computing && config.gitlens.advanced.menus.editorTitle.blame", + "group": "navigation@100" + }, + { + "command": "gitlens.clearFileAnnotations", + "when": "gitlens:annotationStatus == computed && config.gitlens.advanced.menus.editorTitle.blame", "group": "navigation@100" }, { diff --git a/src/annotations/annotationController.ts b/src/annotations/annotationController.ts index b5d98b7..885a94d 100644 --- a/src/annotations/annotationController.ts +++ b/src/annotations/annotationController.ts @@ -5,6 +5,7 @@ import { AnnotationProviderBase } from './annotationProvider'; import { Keyboard, KeyboardScope, KeyCommand, Keys } from '../keyboard'; import { TextDocumentComparer, TextEditorComparer } from '../comparers'; import { ExtensionKey, IConfig, LineHighlightLocations, themeDefaults } from '../configuration'; +import { CommandContext, setCommandContext } from '../constants'; import { BlameabilityChangeEvent, GitContextTracker, GitService, GitUri } from '../gitService'; import { GutterBlameAnnotationProvider } from './gutterBlameAnnotationProvider'; import { HoverBlameAnnotationProvider } from './hoverBlameAnnotationProvider'; @@ -203,6 +204,8 @@ export class AnnotationController extends Disposable { if (this._annotationProviders.size === 0) { Logger.log(`Remove listener registrations for annotations`); + await setCommandContext(CommandContext.AnnotationStatus, undefined); + this._keyboardScope && this._keyboardScope.dispose(); this._keyboardScope = undefined; @@ -235,7 +238,16 @@ export class AnnotationController extends Disposable { return true; } - return window.withProgress({ location: ProgressLocation.Window }, async (progress: Progress<{message: string}>) => this._showAnnotationsCore(currentProvider, editor, type, shaOrLine, progress)); + return window.withProgress({ location: ProgressLocation.Window }, async (progress: Progress<{ message: string }>) => { + await setCommandContext(CommandContext.AnnotationStatus, 'computing'); + + const computingAnnotations = this._showAnnotationsCore(currentProvider, editor, type, shaOrLine, progress); + const result = await computingAnnotations; + + await setCommandContext(CommandContext.AnnotationStatus, result ? 'computed' : undefined); + + return computingAnnotations; + }); } private async _showAnnotationsCore(currentProvider: AnnotationProviderBase | undefined, editor: TextEditor, type: FileAnnotationType, shaOrLine?: string | number, progress?: Progress<{ message: string}>): Promise { @@ -311,11 +323,12 @@ export class AnnotationController extends Disposable { this._onDidToggleAnnotations.fire(); return true; } + return false; } async toggleAnnotations(editor: TextEditor, type: FileAnnotationType, shaOrLine?: string | number): Promise { - if (!editor || !editor.document || type === FileAnnotationType.RecentChanges ? !this.git.isTrackable(editor.document.uri) : !this.git.isEditorBlameable(editor)) return false; + if (!editor || !editor.document || (type === FileAnnotationType.RecentChanges ? !this.git.isTrackable(editor.document.uri) : !this.git.isEditorBlameable(editor))) return false; const provider = this._annotationProviders.get(editor.viewColumn || -1); if (provider === undefined) return this.showAnnotations(editor, type, shaOrLine); diff --git a/src/annotations/annotationProvider.ts b/src/annotations/annotationProvider.ts index 0a8be81..430e672 100644 --- a/src/annotations/annotationProvider.ts +++ b/src/annotations/annotationProvider.ts @@ -1,5 +1,5 @@ 'use strict'; -import { Functions } from '../system'; +// import { Functions } from '../system'; import { Disposable, ExtensionContext, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode'; import { FileAnnotationType } from '../annotations/annotationController'; import { TextDocumentComparer } from '../comparers'; @@ -43,19 +43,12 @@ import { WhitespaceController } from './whitespaceController'; async clear() { if (this.editor !== undefined) { try { - if (this.decoration !== undefined) { - this.editor.setDecorations(this.decoration, []); - } - if (this.highlightDecoration !== undefined) { this.editor.setDecorations(this.highlightDecoration, []); + } - // I have no idea why the decorators sometimes don't get removed, but if they don't try again with a tiny delay - await Functions.wait(1); - - if (this.highlightDecoration === undefined) return; - - this.editor.setDecorations(this.highlightDecoration, []); + if (this.decoration !== undefined) { + this.editor.setDecorations(this.decoration, []); } } catch (ex) { } diff --git a/src/commands.ts b/src/commands.ts index b46c171..6e2919f 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1,6 +1,7 @@ 'use strict'; export * from './commands/common'; +export * from './commands/clearFileAnnotations'; export * from './commands/closeUnchangedFiles'; export * from './commands/copyMessageToClipboard'; export * from './commands/copyShaToClipboard'; diff --git a/src/commands/clearFileAnnotations.ts b/src/commands/clearFileAnnotations.ts new file mode 100644 index 0000000..58b93f4 --- /dev/null +++ b/src/commands/clearFileAnnotations.ts @@ -0,0 +1,24 @@ +'use strict'; +import { TextEditor, TextEditorEdit, Uri, window } from 'vscode'; +import { AnnotationController } from '../annotations/annotationController'; +import { Commands, EditorCommand } from './common'; +import { Logger } from '../logger'; + +export class ClearFileAnnotationsCommand extends EditorCommand { + + constructor(private annotationController: AnnotationController) { + super(Commands.ClearFileAnnotations); + } + + async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri): Promise { + if (editor === undefined || editor.document === undefined || editor.document.isDirty) return undefined; + + try { + return this.annotationController.clear(editor.viewColumn || -1); + } + catch (ex) { + Logger.error(ex, 'ClearFileAnnotationsCommand'); + return window.showErrorMessage(`Unable to clear file annotations. See output channel for more details`); + } + } +} \ No newline at end of file diff --git a/src/commands/common.ts b/src/commands/common.ts index 3e497ab..5b0a95a 100644 --- a/src/commands/common.ts +++ b/src/commands/common.ts @@ -4,7 +4,9 @@ import { ExplorerNode } from '../views/explorerNodes'; import { Logger } from '../logger'; import { Telemetry } from '../telemetry'; -export type Commands = 'gitlens.closeUnchangedFiles' | +export type Commands = + 'gitlens.clearFileAnnotations' | + 'gitlens.closeUnchangedFiles' | 'gitlens.copyMessageToClipboard' | 'gitlens.copyShaToClipboard' | 'gitlens.diffDirectory' | @@ -43,6 +45,7 @@ export type Commands = 'gitlens.closeUnchangedFiles' | 'gitlens.toggleFileRecentChanges' | 'gitlens.toggleLineBlame'; export const Commands = { + ClearFileAnnotations: 'gitlens.clearFileAnnotations' as Commands, CloseUnchangedFiles: 'gitlens.closeUnchangedFiles' as Commands, CopyMessageToClipboard: 'gitlens.copyMessageToClipboard' as Commands, CopyShaToClipboard: 'gitlens.copyShaToClipboard' as Commands, diff --git a/src/commands/showFileBlame.ts b/src/commands/showFileBlame.ts index 7029afc..9231f98 100644 --- a/src/commands/showFileBlame.ts +++ b/src/commands/showFileBlame.ts @@ -17,7 +17,7 @@ export class ShowFileBlameCommand extends EditorCommand { } async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowFileBlameCommandArgs = {}): Promise { - if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined; + if (editor === undefined || editor.document === undefined || editor.document.isDirty) return undefined; try { if (args.type === undefined) { diff --git a/src/commands/showLineBlame.ts b/src/commands/showLineBlame.ts index 5adc408..8ef5402 100644 --- a/src/commands/showLineBlame.ts +++ b/src/commands/showLineBlame.ts @@ -16,7 +16,7 @@ export class ShowLineBlameCommand extends EditorCommand { } async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowLineBlameCommandArgs = {}): Promise { - if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined; + if (editor === undefined || editor.document === undefined || editor.document.isDirty) return undefined; try { if (args.type === undefined) { diff --git a/src/commands/toggleFileBlame.ts b/src/commands/toggleFileBlame.ts index f3898dd..e9e1d98 100644 --- a/src/commands/toggleFileBlame.ts +++ b/src/commands/toggleFileBlame.ts @@ -17,7 +17,7 @@ export class ToggleFileBlameCommand extends EditorCommand { } async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ToggleFileBlameCommandArgs = {}): Promise { - if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined; + if (editor === undefined || editor.document === undefined || editor.document.isDirty) return undefined; try { if (args.type === undefined) { diff --git a/src/commands/toggleFileRecentChanges.ts b/src/commands/toggleFileRecentChanges.ts index d744f55..8b901b3 100644 --- a/src/commands/toggleFileRecentChanges.ts +++ b/src/commands/toggleFileRecentChanges.ts @@ -11,7 +11,7 @@ export class ToggleFileRecentChangesCommand extends EditorCommand { } async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri): Promise { - if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined; + if (editor === undefined || editor.document === undefined || editor.document.isDirty) return undefined; try { return this.annotationController.toggleAnnotations(editor, FileAnnotationType.RecentChanges); diff --git a/src/commands/toggleLineBlame.ts b/src/commands/toggleLineBlame.ts index 049d7dc..6ca04bf 100644 --- a/src/commands/toggleLineBlame.ts +++ b/src/commands/toggleLineBlame.ts @@ -16,7 +16,7 @@ export class ToggleLineBlameCommand extends EditorCommand { } async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ToggleLineBlameCommandArgs = {}): Promise { - if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined; + if (editor === undefined || editor.document === undefined || editor.document.isDirty) return undefined; try { if (args.type === undefined) { diff --git a/src/constants.ts b/src/constants.ts index 8cbdb77..83690f5 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -39,13 +39,15 @@ export const BuiltInCommands = { ToggleRenderWhitespace: 'editor.action.toggleRenderWhitespace' as BuiltInCommands }; -export type CommandContext = 'gitlens:canToggleCodeLens' | +export type CommandContext = + 'gitlens:canToggleCodeLens' | 'gitlens:enabled' | 'gitlens:hasRemotes' | 'gitlens:isBlameable' | 'gitlens:isRepository' | 'gitlens:isTracked' | - 'gitlens:key'; + 'gitlens:key' | + 'gitlens:annotationStatus'; export const CommandContext = { CanToggleCodeLens: 'gitlens:canToggleCodeLens' as CommandContext, Enabled: 'gitlens:enabled' as CommandContext, @@ -53,7 +55,8 @@ export const CommandContext = { IsBlameable: 'gitlens:isBlameable' as CommandContext, IsRepository: 'gitlens:isRepository' as CommandContext, IsTracked: 'gitlens:isTracked' as CommandContext, - Key: 'gitlens:key' as CommandContext + Key: 'gitlens:key' as CommandContext, + AnnotationStatus: 'gitlens:annotationStatus' as CommandContext }; export function setCommandContext(key: CommandContext | string, value: any) { diff --git a/src/extension.ts b/src/extension.ts index e2c1a9e..33a963b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,13 +1,13 @@ 'use strict'; // import { Objects } from './system'; -import { ExtensionContext, extensions, languages, window, workspace } from 'vscode'; +import { commands, ExtensionContext, extensions, languages, window, workspace } from 'vscode'; import { AnnotationController } from './annotations/annotationController'; import { CloseUnchangedFilesCommand, OpenChangedFilesCommand } from './commands'; import { OpenBranchInRemoteCommand, OpenCommitInRemoteCommand, OpenFileInRemoteCommand, OpenInRemoteCommand, OpenRepoInRemoteCommand } from './commands'; import { CopyMessageToClipboardCommand, CopyShaToClipboardCommand } from './commands'; import { DiffDirectoryCommand, DiffLineWithPreviousCommand, DiffLineWithWorkingCommand, DiffWithBranchCommand, DiffWithNextCommand, DiffWithPreviousCommand, DiffWithRevisionCommand, DiffWithWorkingCommand } from './commands'; import { ResetSuppressedWarningsCommand } from './commands'; -import { ShowFileBlameCommand, ShowLineBlameCommand, ToggleFileBlameCommand, ToggleFileRecentChangesCommand, ToggleLineBlameCommand } from './commands'; +import { ClearFileAnnotationsCommand, ShowFileBlameCommand, ShowLineBlameCommand, ToggleFileBlameCommand, ToggleFileRecentChangesCommand, ToggleLineBlameCommand } from './commands'; import { ShowBlameHistoryCommand, ShowFileHistoryCommand } from './commands'; import { ShowLastQuickPickCommand } from './commands'; import { ShowQuickBranchHistoryCommand, ShowQuickCurrentBranchHistoryCommand, ShowQuickFileHistoryCommand } from './commands'; @@ -94,6 +94,8 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(window.registerTreeDataProvider('gitlens.stashExplorer', new StashExplorer(context, git))); + context.subscriptions.push(commands.registerTextEditorCommand('gitlens.computingFileAnnotations', () => { })); + context.subscriptions.push(new CloseUnchangedFilesCommand(git)); context.subscriptions.push(new OpenChangedFilesCommand(git)); context.subscriptions.push(new CopyMessageToClipboardCommand(git)); @@ -111,6 +113,7 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(new OpenFileInRemoteCommand(git)); context.subscriptions.push(new OpenInRemoteCommand()); context.subscriptions.push(new OpenRepoInRemoteCommand(git)); + context.subscriptions.push(new ClearFileAnnotationsCommand(annotationController)); context.subscriptions.push(new ShowFileBlameCommand(annotationController)); context.subscriptions.push(new ShowLineBlameCommand(currentLineController)); context.subscriptions.push(new ToggleFileBlameCommand(annotationController));