convergencelabs_monaco-coll.../src/ts/RemoteCursorWidget.ts
Michael MacFadden c743f8df2a Initial commit.
2019-03-03 22:18:50 -06:00

222 lines
6.0 KiB
TypeScript

import {editor, IDisposable, IPosition} from "monaco-editor";
import {EditorContentManager} from "./EditorContentManager";
import {OnDisposed} from "./OnDisposed";
import {Validation} from "./Validation";
/**
* This class implements a Monaco Content Widget to render a remote user's
* cursor, and an optional tooltip.
*
* @internal
*/
export class RemoteCursorWidget implements editor.IContentWidget, IDisposable {
private readonly _id: string;
private readonly _editor: editor.ICodeEditor;
private readonly _domNode: HTMLDivElement;
private readonly _tooltipNode: HTMLDivElement | null;
private readonly _tooltipDuration: number;
private readonly _scrollListener: IDisposable | null;
private readonly _onDisposed: OnDisposed;
private readonly _contentManager: EditorContentManager;
private _position: editor.IContentWidgetPosition | null;
private _offset: number;
private _hideTimer: any;
private _disposed: boolean;
constructor(codeEditor: editor.ICodeEditor,
widgetId: string,
color: string,
label: string,
tooltipEnabled: boolean,
tooltipDuration: number,
onDisposed: OnDisposed) {
this._editor = codeEditor;
this._tooltipDuration = tooltipDuration;
this._id = `monaco-remote-cursor-${widgetId}`;
this._onDisposed = onDisposed;
// Create the main node for the cursor element.
const {lineHeight} = this._editor.getConfiguration();
this._domNode = document.createElement("div");
this._domNode.className = "monaco-remote-cursor";
this._domNode.style.background = color;
this._domNode.style.height = `${lineHeight}px`;
// Create the tooltip element if the tooltip is enabled.
if (tooltipEnabled) {
this._tooltipNode = document.createElement("div");
this._tooltipNode.className = "monaco-remote-cursor-tooltip";
this._tooltipNode.style.background = color;
this._tooltipNode.innerHTML = label;
this._domNode.appendChild(this._tooltipNode);
// we only need to listen to scroll positions to update the
// tooltip location on scrolling.
this._scrollListener = this._editor.onDidScrollChange(() => {
this._updateTooltipPosition();
});
} else {
this._tooltipNode = null;
this._scrollListener = null;
}
this._contentManager = new EditorContentManager({
editor: this._editor,
onInsert: this._onInsert,
onReplace: this._onReplace,
onDelete: this._onDelete
});
this._hideTimer = null;
this._editor.addContentWidget(this);
this._offset = -1;
this._disposed = false;
}
public hide(): void {
this._domNode.style.display = "none";
}
public show(): void {
this._domNode.style.display = "inherit";
}
public setOffset(offset: number): void {
Validation.assertNumber(offset, "offset");
const position = this._editor.getModel().getPositionAt(offset);
this.setPosition(position);
}
public setPosition(position: IPosition): void {
Validation.assertPosition(position, "position");
this._updatePosition(position);
if (this._tooltipNode !== null) {
setTimeout(() => this._showTooltip(), 0);
}
}
public isDisposed(): boolean {
return this._disposed;
}
public dispose(): void {
if (this._disposed) {
return;
}
this._editor.removeContentWidget(this);
if (this._scrollListener !== null) {
this._scrollListener.dispose();
}
this._contentManager.dispose();
this._disposed = true;
this._onDisposed();
}
public getId(): string {
return this._id;
}
public getDomNode(): HTMLElement {
return this._domNode;
}
public getPosition(): editor.IContentWidgetPosition | null {
return this._position;
}
private _updatePosition(position: IPosition): void {
this._position = {
position: {...position},
preference: [editor.ContentWidgetPositionPreference.EXACT]
};
this._offset = this._editor.getModel().getOffsetAt(position);
this._editor.layoutContentWidget(this);
}
private _showTooltip(): void {
this._updateTooltipPosition();
if (this._hideTimer !== null) {
clearTimeout(this._hideTimer);
} else {
this._setTooltipVisible(true);
}
this._hideTimer = setTimeout(() => {
this._setTooltipVisible(false);
this._hideTimer = null;
}, this._tooltipDuration);
}
private _updateTooltipPosition(): void {
const distanceFromTop = this._domNode.offsetTop - this._editor.getScrollTop();
if (distanceFromTop - this._tooltipNode.offsetHeight < 5) {
this._tooltipNode.style.top = `${this._tooltipNode.offsetHeight + 2}px`;
} else {
this._tooltipNode.style.top = `-${this._tooltipNode.offsetHeight}px`;
}
this._tooltipNode.style.left = "0";
}
private _setTooltipVisible(visible: boolean): void {
if (visible) {
this._tooltipNode.style.opacity = "1.0";
} else {
this._tooltipNode.style.opacity = "0";
}
}
private _onInsert = (index: number, text: string) => {
if (this._position === null) {
return;
}
const offset = this._offset;
if (index <= offset) {
const newOffset = offset + text.length;
const position = this._editor.getModel().getPositionAt(newOffset);
this._updatePosition(position);
}
}
private _onReplace = (index: number, length: number, text: string) => {
if (this._position === null) {
return;
}
const offset = this._offset;
if (index <= offset) {
const newOffset = (offset - Math.min(offset - index, length)) + text.length;
const position = this._editor.getModel().getPositionAt(newOffset);
this._updatePosition(position);
}
}
private _onDelete = (index: number, length: number) => {
if (this._position === null) {
return;
}
const offset = this._offset;
if (index <= offset) {
const newOffset = offset - Math.min(offset - index, length);
const position = this._editor.getModel().getPositionAt(newOffset);
this._updatePosition(position);
}
}
}