'use babel';
/**
* @license
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at
* http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at
* http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
///
import {CompositeDisposable, Disposable} from 'atom';
import {RemoteEditorService} from 'polymer-editor-service/lib/remote-editor-service';
import * as marked from 'marked';
class TooltipManager extends Disposable {
textEditor: AtomCore.IEditor;
tooltipMarker: AtomCore.IDisplayBufferMarker;
tooltipElement: HTMLElement = null;
documentationElement: HTMLElement = null;
editorService: RemoteEditorService;
editorElements: Array = [];
oldCursorsPosition: {x: number, y: number};
removeMouseMoveListener: Function;
subscriptions: CompositeDisposable;
constructor(editorService: RemoteEditorService) {
super(null);
this.editorService = editorService;
this.subscriptions = new CompositeDisposable();
this.subscriptions.add(
atom.workspace.observePanes((pane: AtomCore.IPane) => {
this.update();
this.subscriptions.add(pane.onDidChangeActiveItem(() => {
this.update();
}));
}));
};
private update() {
this.removeTooltip();
if (this.removeMouseMoveListener) {
this.removeMouseMoveListener();
}
this.textEditor = atom.workspace.getActiveTextEditor();
this.addMouseEventListener();
}
private addMouseEventListener() {
const textEditorElement = atom.views.getView(this.textEditor);
if (!textEditorElement) {
return;
}
if (this.editorElements.indexOf(textEditorElement) > -1) {
return;
}
this.editorElements.push(textEditorElement);
let timer: number;
if (this.removeMouseMoveListener) {
this.removeMouseMoveListener();
}
const mouseMoveListener = (event: MouseEvent) => {
window.clearTimeout(timer);
if (this.tooltipMarker
// You are going too far up
&& (this.oldCursorsPosition.y - event.y > 25
// You are going too far left
|| this.oldCursorsPosition.x - event.x > 25
// You are going too far right
|| (this.oldCursorsPosition.x - event.x < -40
// but you are not going down inside the tooltip.
&& this.oldCursorsPosition.y - event.y > -40))) {
this.removeTooltip();
}
if (!this.tooltipMarker) {
timer = window.setTimeout(
() => this.calculatePositions(event, textEditorElement), 100);
}
};
textEditorElement.addEventListener('mousemove', mouseMoveListener);
this.removeMouseMoveListener = () =>
textEditorElement.removeEventListener('mousemove', mouseMoveListener);
};
private calculatePositions(event: MouseEvent, textEditorElement: any) {
// The text editor could have been closed before the timeout was fired
if (!textEditorElement || !textEditorElement.component) {
return;
}
const reportedCursorPosition =
textEditorElement.component.screenPositionForMouseEvent(event);
const currentCursorPixelPosition =
textEditorElement.component.pixelPositionForMouseEvent(event);
const expectedPixelPosition =
textEditorElement.pixelPositionForScreenPosition(
reportedCursorPosition);
const nearColumn =
Math.abs(currentCursorPixelPosition.left - expectedPixelPosition.left) <
20;
const nearRow =
Math.abs(currentCursorPixelPosition.top - expectedPixelPosition.top) <
20;
if (nearColumn && nearRow) {
this.updateTooltip(this.textEditor.bufferPositionForScreenPosition(
reportedCursorPosition));
this.oldCursorsPosition = {x: event.x, y: event.y};
}
}
private async updateTooltip(point: {row: number, column: number}) {
this.removeTooltip();
if (!(this.editorService && this.textEditor)) {
return;
}
const relativePath: string =
atom.project.relativizePath(this.textEditor.getPath())[1];
let documentation: string;
try {
documentation = await this.editorService.getDocumentationAtPosition(
relativePath, {line: point.row, column: point.column});
if (!documentation) {
return;
}
} catch (e) {
// The analyzer was not able to parse the file
return;
}
const marker = this.textEditor.markBufferRange([point, point]);
const div = this.getOrCreateTooltipElement();
this.documentationElement.innerHTML =
marked.parse(documentation, {sanitize: true});
this.textEditor.decorateMarker(marker, {type: 'overlay', item: div});
this.tooltipMarker = marker;
};
private getOrCreateTooltipElement(): HTMLElement {
if (!this.tooltipElement) {
const div = document.createElement('div');
div.classList.add('tooltip');
div.classList.add('in');
div.classList.add('bottom');
div.id = 'polymer-tooltip-container';
const content = document.createElement('div');
content.id = 'polymer-tooltip-documentation';
content.classList.add('tooltip-inner');
div.appendChild(content);
div.addEventListener('mouseleave', () => {
this.removeTooltip();
});
this.tooltipElement = div;
this.documentationElement = content;
} else {
this.tooltipElement.style.display = 'block';
}
return this.tooltipElement;
};
private removeTooltip() {
if (this.tooltipMarker) {
this.tooltipMarker.destroy();
this.tooltipMarker = null;
this.tooltipElement.style.display = 'none';
}
};
dispose() {
this.removeTooltip();
this.subscriptions.dispose();
}
}
export default TooltipManager;