From f66ba92e3cd5f512699f9f57a6a80e8cc253709d Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Sun, 12 Feb 2017 23:30:30 -0500 Subject: [PATCH] Renames statusBar to more generic activeLine --- src/blameActiveLineController.ts | 301 +++++++++++++++++++++++++++++++++++++++ src/blameStatusBarController.ts | 301 --------------------------------------- src/extension.ts | 2 +- 3 files changed, 302 insertions(+), 302 deletions(-) create mode 100644 src/blameActiveLineController.ts delete mode 100644 src/blameStatusBarController.ts diff --git a/src/blameActiveLineController.ts b/src/blameActiveLineController.ts new file mode 100644 index 0000000..025dfed --- /dev/null +++ b/src/blameActiveLineController.ts @@ -0,0 +1,301 @@ +'use strict'; +import { Functions, Objects } from './system'; +import { DecorationOptions, DecorationInstanceRenderOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextDocumentChangeEvent, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode'; +import BlameAnnotationController from './blameAnnotationController'; +import BlameAnnotationFormatter, { BlameAnnotationFormat } from './blameAnnotationFormatter'; +import { TextDocumentComparer, TextEditorComparer } from './comparers'; +import { IBlameConfig, IConfig, StatusBarCommand } from './configuration'; +import { DocumentSchemes } from './constants'; +import GitProvider, { GitCommit, GitUri, IGitBlame, IGitCommitLine } from './gitProvider'; +import * as moment from 'moment'; + +const activeLineDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({ + after: { + margin: '0 0 0 4em' + } +} as DecorationRenderOptions); + +export default class BlameActiveLineController extends Disposable { + + private _activeEditorLineDisposable: Disposable | undefined; + private _blame: Promise | undefined; + private _config: IConfig; + private _currentLine: number = -1; + private _disposable: Disposable; + private _editor: TextEditor | undefined; + private _statusBarItem: StatusBarItem | undefined; + private _updateBlameDebounced: (line: number, editor: TextEditor) => Promise; + private _uri: GitUri; + private _useCaching: boolean; + + constructor(context: ExtensionContext, private git: GitProvider, private annotationController: BlameAnnotationController) { + super(() => this.dispose()); + + this._updateBlameDebounced = Functions.debounce(this._updateBlame, 50); + + this._onConfigure(); + + const subscriptions: Disposable[] = []; + + subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigure, this)); + subscriptions.push(git.onDidRemoveCacheEntry(this._onRemoveCacheEntry, this)); + + this._disposable = Disposable.from(...subscriptions); + } + + dispose() { + this._editor && this._editor.setDecorations(activeLineDecoration, []); + + this._activeEditorLineDisposable && this._activeEditorLineDisposable.dispose(); + this._statusBarItem && this._statusBarItem.dispose(); + this._disposable && this._disposable.dispose(); + } + + private _onConfigure() { + const config = workspace.getConfiguration('').get('gitlens'); + + let changed: boolean = false; + + if (!Objects.areEquivalent(config.statusBar, this._config && this._config.statusBar)) { + changed = true; + if (config.statusBar.enabled) { + this._statusBarItem = this._statusBarItem || window.createStatusBarItem(StatusBarAlignment.Right, 1000); + switch (config.statusBar.command) { + case StatusBarCommand.ToggleCodeLens: + if (config.codeLens.visibility !== 'ondemand') { + config.statusBar.command = StatusBarCommand.BlameAnnotate; + } + break; + } + this._statusBarItem.command = config.statusBar.command; + } + else if (!config.statusBar.enabled && this._statusBarItem) { + this._statusBarItem.dispose(); + this._statusBarItem = undefined; + } + } + + if (!Objects.areEquivalent(config.blame.annotation.activeLine, this._config && this._config.blame.annotation.activeLine)) { + changed = true; + if (config.blame.annotation.activeLine !== 'off' && this._editor) { + this._editor.setDecorations(activeLineDecoration, []); + } + } + + this._config = config; + + if (!changed) return; + + let trackActiveLine = config.statusBar.enabled || config.blame.annotation.activeLine !== 'off'; + if (trackActiveLine && !this._activeEditorLineDisposable) { + const subscriptions: Disposable[] = []; + + subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this)); + subscriptions.push(window.onDidChangeTextEditorSelection(this._onEditorSelectionChanged, this)); + subscriptions.push(workspace.onDidChangeTextDocument(this._onDocumentChanged, this)); + + this._activeEditorLineDisposable = Disposable.from(...subscriptions); + } + else if (!trackActiveLine && this._activeEditorLineDisposable) { + this._activeEditorLineDisposable.dispose(); + this._activeEditorLineDisposable = undefined; + } + + this._onActiveTextEditorChanged(window.activeTextEditor); + } + + private _onRemoveCacheEntry() { + this._blame = undefined; + this._onActiveTextEditorChanged(window.activeTextEditor); + } + + private _onActiveTextEditorChanged(e: TextEditor) { + this._currentLine = -1; + + const previousEditor = this._editor; + previousEditor && previousEditor.setDecorations(activeLineDecoration, []); + + if (!e || !e.document || e.document.isUntitled || + (e.document.uri.scheme !== DocumentSchemes.File && e.document.uri.scheme !== DocumentSchemes.Git) || + (e.viewColumn === undefined && !this.git.hasGitUriForFile(e))) { + this.clear(e); + + this._editor = undefined; + + return; + } + + this._editor = e; + this._uri = GitUri.fromUri(e.document.uri, this.git); + const maxLines = this._config.advanced.caching.statusBar.maxLines; + this._useCaching = this._config.advanced.caching.enabled && (maxLines <= 0 || e.document.lineCount <= maxLines); + if (this._useCaching) { + this._blame = this.git.getBlameForFile(this._uri.fsPath, this._uri.sha, this._uri.repoPath); + } + else { + this._blame = undefined; + } + + this._updateBlame(e.selection.active.line, e); + } + + private _onEditorSelectionChanged(e: TextEditorSelectionChangeEvent): void { + // Make sure this is for the editor we are tracking + if (!TextEditorComparer.equals(e.textEditor, this._editor)) return; + + const line = e.selections[0].active.line; + if (line === this._currentLine) return; + this._currentLine = line; + + this._updateBlameDebounced(line, e.textEditor); + } + + private _onDocumentChanged(e: TextDocumentChangeEvent) { + // Make sure this is for the editor we are tracking + if (!this._editor || !TextDocumentComparer.equals(e.document, this._editor.document)) return; + this._currentLine = -1; + + this._updateBlame(this._editor.selections[0].active.line, this._editor); + } + + private async _updateBlame(line: number, editor: TextEditor) { + line = line - this._uri.offset; + + let commitLine: IGitCommitLine; + let commit: GitCommit; + if (line >= 0) { + if (this._useCaching) { + const blame = this._blame && await this._blame; + if (!blame || !blame.lines.length) { + this.clear(editor); + return; + } + + commitLine = blame.lines[line]; + const sha = commitLine && commitLine.sha; + commit = sha && blame.commits.get(sha); + } + else { + const blameLine = await this.git.getBlameForLine(this._uri.fsPath, line, this._uri.sha, this._uri.repoPath); + commitLine = blameLine && blameLine.line; + commit = blameLine && blameLine.commit; + } + } + + if (commit) { + this.show(commit, commitLine, editor); + } + else { + this.clear(editor); + } + } + + clear(editor: TextEditor, previousEditor?: TextEditor) { + editor && editor.setDecorations(activeLineDecoration, []); + + this._statusBarItem && this._statusBarItem.hide(); + } + + async show(commit: GitCommit, blameLine: IGitCommitLine, editor: TextEditor) { + if (this._config.statusBar.enabled) { + this._statusBarItem.text = `$(git-commit) ${commit.author}, ${moment(commit.date).fromNow()}`; + + switch (this._config.statusBar.command) { + case StatusBarCommand.BlameAnnotate: + this._statusBarItem.tooltip = 'Toggle Blame Annotations'; + break; + case StatusBarCommand.ShowBlameHistory: + this._statusBarItem.tooltip = 'Open Blame History'; + break; + case StatusBarCommand.ShowFileHistory: + this._statusBarItem.tooltip = 'Open File History'; + break; + case StatusBarCommand.DiffWithPrevious: + this._statusBarItem.tooltip = 'Compare to Previous Commit'; + break; + case StatusBarCommand.ToggleCodeLens: + this._statusBarItem.tooltip = 'Toggle Blame CodeLens'; + break; + case StatusBarCommand.ShowQuickFileHistory: + this._statusBarItem.tooltip = 'View Git File History'; + break; + } + + this._statusBarItem.show(); + } + + if (this._config.blame.annotation.activeLine !== 'off') { + let activeLine = this._config.blame.annotation.activeLine; + + // Because the inline annotations can be noisy -- only show them if the document isn't dirty + if (editor && editor.document && editor.document.isDirty) { + editor.setDecorations(activeLineDecoration, []); + switch (activeLine) { + case 'both': + activeLine = 'hover'; + break; + case 'inline': + return; + } + } + + const offset = this._uri.offset; + + const config = { + annotation: { + sha: true, + author: this._config.statusBar.enabled ? false : this._config.blame.annotation.author, + date: this._config.statusBar.enabled ? 'off' : this._config.blame.annotation.date, + message: true + } + } as IBlameConfig; + + // Escape single quotes because for some reason that breaks the ::before or ::after element + // https://github.com/Microsoft/vscode/issues/19922 remove once this is released + const annotation = BlameAnnotationFormatter.getAnnotation(config, commit, BlameAnnotationFormat.Unconstrained).replace(/\'/g, '\\\''); + + // Get the full commit message -- since blame only returns the summary + let logCommit: GitCommit; + if (!commit.isUncommitted) { + const log = await this.git.getLogForFile(this._uri.fsPath, commit.sha, this._uri.repoPath, undefined, 1); + logCommit = log && log.commits.get(commit.sha); + } + + let hoverMessage: string | string[]; + if (activeLine !== 'inline') { + // If the messages match (or we couldn't find the log), then this is a possible duplicate annotation + const possibleDuplicate = !logCommit || logCommit.message === commit.message; + // If we don't have a possible dupe or we aren't showing annotations get the hover message + if (!possibleDuplicate || !this.annotationController.isAnnotating(editor)) { + hoverMessage = BlameAnnotationFormatter.getAnnotationHover(config, blameLine, logCommit || commit); + } + } + + let decorationOptions: DecorationOptions; + switch (activeLine) { + case 'both': + case 'inline': + decorationOptions = { + range: editor.document.validateRange(new Range(blameLine.line + offset, 0, blameLine.line + offset, 1000000)), + hoverMessage: hoverMessage, + renderOptions: { + after: { + color: 'rgba(153, 153, 153, 0.3)', + contentText: annotation + } + } as DecorationInstanceRenderOptions + } as DecorationOptions; + break; + + case 'hover': + decorationOptions = { + range: editor.document.validateRange(new Range(blameLine.line + offset, 0, blameLine.line + offset, 1000000)), + hoverMessage: hoverMessage + } as DecorationOptions; + break; + } + + decorationOptions && editor.setDecorations(activeLineDecoration, [decorationOptions]); + } + } +} \ No newline at end of file diff --git a/src/blameStatusBarController.ts b/src/blameStatusBarController.ts deleted file mode 100644 index 80ec7d9..0000000 --- a/src/blameStatusBarController.ts +++ /dev/null @@ -1,301 +0,0 @@ -'use strict'; -import { Functions, Objects } from './system'; -import { DecorationOptions, DecorationInstanceRenderOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextDocumentChangeEvent, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode'; -import BlameAnnotationController from './blameAnnotationController'; -import BlameAnnotationFormatter, { BlameAnnotationFormat } from './blameAnnotationFormatter'; -import { TextDocumentComparer, TextEditorComparer } from './comparers'; -import { IBlameConfig, IConfig, StatusBarCommand } from './configuration'; -import { DocumentSchemes } from './constants'; -import GitProvider, { GitCommit, GitUri, IGitBlame, IGitCommitLine } from './gitProvider'; -import * as moment from 'moment'; - -const activeLineDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({ - after: { - margin: '0 0 0 4em' - } -} as DecorationRenderOptions); - -export default class BlameStatusBarController extends Disposable { - - private _activeEditorLineDisposable: Disposable | undefined; - private _blame: Promise | undefined; - private _config: IConfig; - private _currentLine: number = -1; - private _disposable: Disposable; - private _editor: TextEditor | undefined; - private _showBlameDebounced: (line: number, editor: TextEditor) => Promise; - private _statusBarItem: StatusBarItem | undefined; - private _uri: GitUri; - private _useCaching: boolean; - - constructor(context: ExtensionContext, private git: GitProvider, private annotationController: BlameAnnotationController) { - super(() => this.dispose()); - - this._showBlameDebounced = Functions.debounce(this._showBlame, 50); - - this._onConfigure(); - - const subscriptions: Disposable[] = []; - - subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigure, this)); - subscriptions.push(git.onDidRemoveCacheEntry(this._onRemoveCacheEntry, this)); - - this._disposable = Disposable.from(...subscriptions); - } - - dispose() { - this._editor && this._editor.setDecorations(activeLineDecoration, []); - - this._activeEditorLineDisposable && this._activeEditorLineDisposable.dispose(); - this._statusBarItem && this._statusBarItem.dispose(); - this._disposable && this._disposable.dispose(); - } - - private _onConfigure() { - const config = workspace.getConfiguration('').get('gitlens'); - - let changed: boolean = false; - - if (!Objects.areEquivalent(config.statusBar, this._config && this._config.statusBar)) { - changed = true; - if (config.statusBar.enabled) { - this._statusBarItem = this._statusBarItem || window.createStatusBarItem(StatusBarAlignment.Right, 1000); - switch (config.statusBar.command) { - case StatusBarCommand.ToggleCodeLens: - if (config.codeLens.visibility !== 'ondemand') { - config.statusBar.command = StatusBarCommand.BlameAnnotate; - } - break; - } - this._statusBarItem.command = config.statusBar.command; - } - else if (!config.statusBar.enabled && this._statusBarItem) { - this._statusBarItem.dispose(); - this._statusBarItem = undefined; - } - } - - if (!Objects.areEquivalent(config.blame.annotation.activeLine, this._config && this._config.blame.annotation.activeLine)) { - changed = true; - if (config.blame.annotation.activeLine !== 'off' && this._editor) { - this._editor.setDecorations(activeLineDecoration, []); - } - } - - this._config = config; - - if (!changed) return; - - let trackActiveLine = config.statusBar.enabled || config.blame.annotation.activeLine !== 'off'; - if (trackActiveLine && !this._activeEditorLineDisposable) { - const subscriptions: Disposable[] = []; - - subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this)); - subscriptions.push(window.onDidChangeTextEditorSelection(this._onEditorSelectionChanged, this)); - subscriptions.push(workspace.onDidChangeTextDocument(this._onDocumentChanged, this)); - - this._activeEditorLineDisposable = Disposable.from(...subscriptions); - } - else if (!trackActiveLine && this._activeEditorLineDisposable) { - this._activeEditorLineDisposable.dispose(); - this._activeEditorLineDisposable = undefined; - } - - this._onActiveTextEditorChanged(window.activeTextEditor); - } - - private _onRemoveCacheEntry() { - this._blame = undefined; - this._onActiveTextEditorChanged(window.activeTextEditor); - } - - private _onActiveTextEditorChanged(e: TextEditor) { - this._currentLine = -1; - - const previousEditor = this._editor; - previousEditor && previousEditor.setDecorations(activeLineDecoration, []); - - if (!e || !e.document || e.document.isUntitled || - (e.document.uri.scheme !== DocumentSchemes.File && e.document.uri.scheme !== DocumentSchemes.Git) || - (e.viewColumn === undefined && !this.git.hasGitUriForFile(e))) { - this.clear(e); - - this._editor = undefined; - - return; - } - - this._editor = e; - this._uri = GitUri.fromUri(e.document.uri, this.git); - const maxLines = this._config.advanced.caching.statusBar.maxLines; - this._useCaching = this._config.advanced.caching.enabled && (maxLines <= 0 || e.document.lineCount <= maxLines); - if (this._useCaching) { - this._blame = this.git.getBlameForFile(this._uri.fsPath, this._uri.sha, this._uri.repoPath); - } - else { - this._blame = undefined; - } - - this._showBlame(e.selection.active.line, e); - } - - private _onEditorSelectionChanged(e: TextEditorSelectionChangeEvent): void { - // Make sure this is for the editor we are tracking - if (!TextEditorComparer.equals(e.textEditor, this._editor)) return; - - const line = e.selections[0].active.line; - if (line === this._currentLine) return; - this._currentLine = line; - - this._showBlameDebounced(line, e.textEditor); - } - - private _onDocumentChanged(e: TextDocumentChangeEvent) { - // Make sure this is for the editor we are tracking - if (!this._editor || !TextDocumentComparer.equals(e.document, this._editor.document)) return; - this._currentLine = -1; - - this._showBlame(this._editor.selections[0].active.line, this._editor); - } - - private async _showBlame(line: number, editor: TextEditor) { - line = line - this._uri.offset; - - let commitLine: IGitCommitLine; - let commit: GitCommit; - if (line >= 0) { - if (this._useCaching) { - const blame = this._blame && await this._blame; - if (!blame || !blame.lines.length) { - this.clear(editor); - return; - } - - commitLine = blame.lines[line]; - const sha = commitLine && commitLine.sha; - commit = sha && blame.commits.get(sha); - } - else { - const blameLine = await this.git.getBlameForLine(this._uri.fsPath, line, this._uri.sha, this._uri.repoPath); - commitLine = blameLine && blameLine.line; - commit = blameLine && blameLine.commit; - } - } - - if (commit) { - this.show(commit, commitLine, editor); - } - else { - this.clear(editor); - } - } - - clear(editor: TextEditor, previousEditor?: TextEditor) { - editor && editor.setDecorations(activeLineDecoration, []); - - this._statusBarItem && this._statusBarItem.hide(); - } - - async show(commit: GitCommit, blameLine: IGitCommitLine, editor: TextEditor) { - if (this._config.statusBar.enabled) { - this._statusBarItem.text = `$(git-commit) ${commit.author}, ${moment(commit.date).fromNow()}`; - - switch (this._config.statusBar.command) { - case StatusBarCommand.BlameAnnotate: - this._statusBarItem.tooltip = 'Toggle Blame Annotations'; - break; - case StatusBarCommand.ShowBlameHistory: - this._statusBarItem.tooltip = 'Open Blame History'; - break; - case StatusBarCommand.ShowFileHistory: - this._statusBarItem.tooltip = 'Open File History'; - break; - case StatusBarCommand.DiffWithPrevious: - this._statusBarItem.tooltip = 'Compare to Previous Commit'; - break; - case StatusBarCommand.ToggleCodeLens: - this._statusBarItem.tooltip = 'Toggle Blame CodeLens'; - break; - case StatusBarCommand.ShowQuickFileHistory: - this._statusBarItem.tooltip = 'View Git File History'; - break; - } - - this._statusBarItem.show(); - } - - if (this._config.blame.annotation.activeLine !== 'off') { - let activeLine = this._config.blame.annotation.activeLine; - - // Because the inline annotations can be noisy -- only show them if the document isn't dirty - if (editor && editor.document && editor.document.isDirty) { - editor.setDecorations(activeLineDecoration, []); - switch (activeLine) { - case 'both': - activeLine = 'hover'; - break; - case 'inline': - return; - } - } - - const offset = this._uri.offset; - - const config = { - annotation: { - sha: true, - author: this._config.statusBar.enabled ? false : this._config.blame.annotation.author, - date: this._config.statusBar.enabled ? 'off' : this._config.blame.annotation.date, - message: true - } - } as IBlameConfig; - - // Escape single quotes because for some reason that breaks the ::before or ::after element - // https://github.com/Microsoft/vscode/issues/19922 remove once this is released - const annotation = BlameAnnotationFormatter.getAnnotation(config, commit, BlameAnnotationFormat.Unconstrained).replace(/\'/g, '\\\''); - - // Get the full commit message -- since blame only returns the summary - let logCommit: GitCommit; - if (!commit.isUncommitted) { - const log = await this.git.getLogForFile(this._uri.fsPath, commit.sha, this._uri.repoPath, undefined, 1); - logCommit = log && log.commits.get(commit.sha); - } - - let hoverMessage: string | string[]; - if (activeLine !== 'inline') { - // If the messages match (or we couldn't find the log), then this is a possible duplicate annotation - const possibleDuplicate = !logCommit || logCommit.message === commit.message; - // If we don't have a possible dupe or we aren't showing annotations get the hover message - if (!possibleDuplicate || !this.annotationController.isAnnotating(editor)) { - hoverMessage = BlameAnnotationFormatter.getAnnotationHover(config, blameLine, logCommit || commit); - } - } - - let decorationOptions: DecorationOptions; - switch (activeLine) { - case 'both': - case 'inline': - decorationOptions = { - range: editor.document.validateRange(new Range(blameLine.line + offset, 0, blameLine.line + offset, 1000000)), - hoverMessage: hoverMessage, - renderOptions: { - after: { - color: 'rgba(153, 153, 153, 0.3)', - contentText: annotation - } - } as DecorationInstanceRenderOptions - } as DecorationOptions; - break; - - case 'hover': - decorationOptions = { - range: editor.document.validateRange(new Range(blameLine.line + offset, 0, blameLine.line + offset, 1000000)), - hoverMessage: hoverMessage - } as DecorationOptions; - break; - } - - decorationOptions && editor.setDecorations(activeLineDecoration, [decorationOptions]); - } - } -} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 09250a4..75e4759 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,7 +1,7 @@ 'use strict'; import { ExtensionContext, languages, window, workspace } from 'vscode'; +import BlameActiveLineController from './blameActiveLineController'; import BlameAnnotationController from './blameAnnotationController'; -import BlameStatusBarController from './blameStatusBarController'; import { configureCssCharacters } from './blameAnnotationFormatter'; import DiffLineWithPreviousCommand from './commands/diffLineWithPrevious'; import DiffLineWithWorkingCommand from './commands/diffLineWithWorking';