You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
convergencelabs_monaco-coll.../src/ts/RemoteSelection.ts

255 lines
6.3 KiB

import * as monaco from "monaco-editor";
import {editor, IPosition} from "monaco-editor";
import {OnDisposed} from "./OnDisposed";
import {Validation} from "./Validation";
export class RemoteSelection {
/**
* A helper method to add a style tag to the head of the document that will
* style the color of the selection. The Monaco Editor only allows setting
* the class name of decorations, so we can not set a style property directly.
* This method will create, add, and return the style tag for this element.
*
* @param className
* The className to use as the css selector.
* @param color
* The color to set for the selection.
* @returns
* The style element that was added to the document head.
*
* @private
* @internal
*/
private static _addDynamicStyleElement(className: string, color: string): HTMLStyleElement {
Validation.assertString(className, "className");
Validation.assertString(color, "color");
const css =
`.${className} {
background-color: ${color};
}`.trim();
const styleElement = document.createElement("style");
styleElement.innerText = css;
document.head.appendChild(styleElement);
return styleElement;
}
/**
* A helper method to ensure the start position is before the end position.
*
* @param start
* The current start position.
* @param end
* The current end position.
* @return
* An object containing the correctly ordered start and end positions.
*
* @private
* @internal
*/
private static _swapIfNeeded(start: IPosition, end: IPosition): { start: IPosition, end: IPosition } {
if (start.lineNumber < end.lineNumber || (start.lineNumber === end.lineNumber && start.column <= end.column)) {
return {start, end};
} else {
return {start: end, end: start};
}
}
/**
* The userland id of the selection.
* @internal
*/
private readonly _id: string;
/**
* The css classname to apply to the Monaco decoration.
* @internal
*/
private readonly _className: string;
/**
* The HTML Style element added to the document to color the selection.
* @internal
*/
private readonly _styleElement: HTMLStyleElement;
/**
* The Monaco editor isntance to render selection into.
* @internal
*/
private readonly _editor: editor.ICodeEditor;
/**
* An internal callback used to dispose of the selection.
* @internal
*/
private readonly _onDisposed: OnDisposed;
/**
* The current start position of the selection.
* @internal
*/
private _startPosition: IPosition;
/**
* The current end position of the selection.
* @internal
*/
private _endPosition: IPosition;
/**
* The id's of the current Monaco decorations rendering the selection.
* @internal
*/
private _decorations: string[];
/**
* A flag determining if the selection has been disposed.
* @internal
*/
private _disposed: boolean;
/**
* Constructs a new remote selection.
*
* @internal
*/
constructor(
codeEditor: editor.ICodeEditor,
id: string,
classId: number,
color: string,
onDisposed: OnDisposed
) {
this._editor = codeEditor;
this._id = id;
const uniqueClassId = `monaco-remote-selection-${classId}`;
this._className = `monaco-remote-selection ${uniqueClassId}`;
this._styleElement = RemoteSelection._addDynamicStyleElement(uniqueClassId, color);
this._decorations = [];
this._onDisposed = onDisposed;
}
/**
* Gets the userland id of this selection.
*/
public getId(): string {
return this._id;
}
/**
* Gets the start position of the selection.
*
* @returns
* The start position of the selection.
*/
public getStartPosition(): IPosition {
return {...this._startPosition};
}
/**
* Gets the start position of the selection.
*
* @returns
* The start position of the selection.
*/
public getEndPosition(): IPosition {
return {...this._endPosition};
}
/**
* Sets the selection using zero-based text indices.
*
* @param start
* The start offset to set the selection to.
* @param end
* The end offset to set the selection to.
*/
public setOffsets(start: number, end: number): void {
const startPosition = this._editor.getModel().getPositionAt(start);
const endPosition = this._editor.getModel().getPositionAt(end);
this.setPositions(startPosition, endPosition);
}
/**
* Sets the selection using Monaco's line-number / column coordinate system.
*
* @param start
* The start position to set the selection to.
* @param end
* The end position to set the selection to.
*/
public setPositions(start: IPosition, end: IPosition): void {
// this._decorations = this._editor.deltaDecorations(this._decorations, []);
const ordered = RemoteSelection._swapIfNeeded(start, end);
this._startPosition = ordered.start;
this._endPosition = ordered.end;
this._render();
}
/**
* Makes the selection visible if it is hidden.
*/
public show(): void {
this._render();
}
/**
* Makes the selection hidden if it is visible.
*/
public hide(): void {
this._decorations = this._editor.deltaDecorations(this._decorations, []);
}
/**
* Determines if the selection has been permanently removed from the editor.
*
* @returns
* True if the selection has been disposed, false otherwise.
*/
public isDisposed(): boolean {
return this._disposed;
}
/**
* Permanently removes the selection from the editor.
*/
public dispose(): void {
if (!this._disposed) {
this._styleElement.parentElement.removeChild(this._styleElement);
this.hide();
this._onDisposed();
this._disposed = true;
}
}
/**
* A helper method that actually renders the selection as a decoration within
* the Monaco Editor.
*
* @private
* @internal
*/
private _render(): void {
this._decorations = this._editor.deltaDecorations(this._decorations,
[
{
range: new monaco.Range(
this._startPosition.lineNumber,
this._startPosition.column,
this._endPosition.lineNumber,
this._endPosition.column
),
options: {
className: this._className
}
}
]
);
}
}