221 lines
8.2 KiB

'use strict';
import {
CancellationToken,
ConfigurationChangeEvent,
debug,
Disposable,
Hover,
HoverProvider,
languages,
Position,
Range,
TextDocument,
TextEditor,
window
} from 'vscode';
import { Annotations } from './annotations';
import { configuration } from './../configuration';
import { Container } from './../container';
import { LinesChangeEvent } from './../trackers/gitLineTracker';
export class LineHoverController extends Disposable {
private _debugSessionEndDisposable: Disposable | undefined;
private _disposable: Disposable;
private _hoverProviderDisposable: Disposable | undefined;
constructor() {
super(() => this.dispose());
this._disposable = Disposable.from(
configuration.onDidChange(this.onConfigurationChanged, this),
debug.onDidStartDebugSession(this.onDebugSessionStarted, this)
);
this.onConfigurationChanged(configuration.initializingChangeEvent);
}
dispose() {
this.unregister();
this._debugSessionEndDisposable && this._debugSessionEndDisposable.dispose();
Container.lineTracker.stop(this);
this._disposable && this._disposable.dispose();
}
private onConfigurationChanged(e: ConfigurationChangeEvent) {
const initializing = configuration.initializing(e);
if (
!initializing &&
!configuration.changed(e, configuration.name('hovers')('enabled').value) &&
!configuration.changed(e, configuration.name('hovers')('currentLine')('enabled').value)
) {
return;
}
if (Container.config.hovers.enabled && Container.config.hovers.currentLine.enabled) {
Container.lineTracker.start(
this,
Disposable.from(Container.lineTracker.onDidChangeActiveLines(this.onActiveLinesChanged, this))
);
this.register(window.activeTextEditor);
}
else {
Container.lineTracker.stop(this);
this.unregister();
}
}
private get debugging() {
return this._debugSessionEndDisposable !== undefined;
}
private onActiveLinesChanged(e: LinesChangeEvent) {
if (e.pending || e.reason !== 'editor') return;
if (e.editor === undefined || e.lines === undefined) {
this.unregister();
return;
}
this.register(e.editor);
}
private onDebugSessionStarted() {
if (this._debugSessionEndDisposable === undefined) {
this._debugSessionEndDisposable = debug.onDidTerminateDebugSession(this.onDebugSessionEnded, this);
}
}
private onDebugSessionEnded() {
if (this._debugSessionEndDisposable !== undefined) {
this._debugSessionEndDisposable.dispose();
this._debugSessionEndDisposable = undefined;
}
}
async provideDetailsHover(
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<Hover | undefined> {
if (!Container.lineTracker.includes(position.line)) return undefined;
const lineState = Container.lineTracker.getState(position.line);
const commit = lineState !== undefined ? lineState.commit : undefined;
if (commit === undefined) return undefined;
// Avoid double annotations if we are showing the whole-file hover blame annotations
const fileAnnotations = await Container.fileAnnotations.getAnnotationType(window.activeTextEditor);
if (fileAnnotations !== undefined && Container.config.hovers.annotations.details) return undefined;
const wholeLine = this.debugging ? false : Container.config.hovers.currentLine.over === 'line';
// If we aren't showing the hover over the whole line, make sure the annotation is on
if (!wholeLine && Container.lineAnnotations.suspended) return undefined;
const range = document.validateRange(
new Range(position.line, wholeLine ? 0 : Number.MAX_SAFE_INTEGER, position.line, Number.MAX_SAFE_INTEGER)
);
if (!wholeLine && range.start.character !== position.character) return undefined;
// Get the full commit message -- since blame only returns the summary
let logCommit = lineState !== undefined ? lineState.logCommit : undefined;
if (logCommit === undefined && !commit.isUncommitted) {
logCommit = await Container.git.getLogCommitForFile(commit.repoPath, commit.uri.fsPath, {
ref: commit.sha
});
if (logCommit !== undefined) {
// Preserve the previous commit from the blame commit
logCommit.previousSha = commit.previousSha;
logCommit.previousFileName = commit.previousFileName;
if (lineState !== undefined) {
lineState.logCommit = logCommit;
}
}
}
const trackedDocument = await Container.tracker.get(document);
if (trackedDocument === undefined) return undefined;
const message = Annotations.getHoverMessage(
logCommit || commit,
Container.config.defaultDateFormat,
await Container.git.getRemotes(commit.repoPath),
fileAnnotations,
position.line
);
return new Hover(message, range);
}
async provideChangesHover(
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<Hover | undefined> {
if (!Container.lineTracker.includes(position.line)) return undefined;
const lineState = Container.lineTracker.getState(position.line);
const commit = lineState !== undefined ? lineState.commit : undefined;
if (commit === undefined) return undefined;
// Avoid double annotations if we are showing the whole-file hover blame annotations
if (Container.config.hovers.annotations.changes) {
const fileAnnotations = await Container.fileAnnotations.getAnnotationType(window.activeTextEditor);
if (fileAnnotations !== undefined) return undefined;
}
const wholeLine = this.debugging ? false : Container.config.hovers.currentLine.over === 'line';
// If we aren't showing the hover over the whole line, make sure the annotation is on
if (!wholeLine && Container.lineAnnotations.suspended) return undefined;
const range = document.validateRange(
new Range(position.line, wholeLine ? 0 : Number.MAX_SAFE_INTEGER, position.line, Number.MAX_SAFE_INTEGER)
);
if (!wholeLine && range.start.character !== position.character) return undefined;
const trackedDocument = await Container.tracker.get(document);
if (trackedDocument === undefined) return undefined;
const hover = await Annotations.changesHover(commit, position.line, trackedDocument.uri);
if (hover.hoverMessage === undefined) return undefined;
return new Hover(hover.hoverMessage, range);
}
private register(editor: TextEditor | undefined) {
this.unregister();
if (editor === undefined /* || this.suspended */) return;
const cfg = Container.config.hovers;
if (!cfg.enabled || !cfg.currentLine.enabled || (!cfg.currentLine.details && !cfg.currentLine.changes)) return;
const subscriptions = [];
if (cfg.currentLine.changes) {
subscriptions.push(
languages.registerHoverProvider({ pattern: editor.document.uri.fsPath }, {
provideHover: this.provideChangesHover.bind(this)
} as HoverProvider)
);
}
if (cfg.currentLine.details) {
subscriptions.push(
languages.registerHoverProvider({ pattern: editor.document.uri.fsPath }, {
provideHover: this.provideDetailsHover.bind(this)
} as HoverProvider)
);
}
this._hoverProviderDisposable = Disposable.from(...subscriptions);
}
private unregister() {
if (this._hoverProviderDisposable !== undefined) {
this._hoverProviderDisposable.dispose();
this._hoverProviderDisposable = undefined;
}
}
}