export interface ITerminal {
element: HTMLElement;
rowContainer: HTMLElement;
+ selectionContainer: HTMLElement;
+ charMeasure: ICharMeasure;
textarea: HTMLTextAreaElement;
ybase: number;
ydisp: number;
public refreshSelection(start: [number, number], end: [number, number]) {
console.log('renderer, refresh:', start, end);
+
+ // Remove all selections
+ while (this._terminal.selectionContainer.children.length) {
+ this._terminal.selectionContainer.removeChild(this._terminal.selectionContainer.children[0]);
+ }
+
+ // Selection does not exist
+ if (!start || !end) {
+ return;
+ }
+
+ // Swap the start and end if necessary
+ if (start[1] > end[1] || (start[1] === end[1] && start[0] > end[0])) {
+ const temp = start;
+ start = end;
+ end = temp;
+ }
+
+ // Translate from buffer position to viewport position
+ const viewportStartRow = start[1] - this._terminal.ydisp;
+ const viewportEndRow = end[1] - this._terminal.ydisp;
+ const viewportCappedStartRow = Math.max(viewportStartRow, 0);
+ const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1);
+
+ // No need to draw the selection
+ if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) {
+ return;
+ }
+
+ console.log('viewportStartRow', viewportCappedStartRow);
+ console.log('viewportEndRow', viewportCappedEndRow);
+
+ // TODO: Only redraw selections when necessary
+
+ // Create the selections
+ const documentFragment = document.createDocumentFragment();
+ // Draw first row
+ const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
+ const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols;
+ documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));
+ // Draw middle rows
+ for (let i = viewportCappedStartRow + 1; i < viewportCappedEndRow; i++) {
+ documentFragment.appendChild(this._createSelectionElement(i, 0, this._terminal.cols));
+ }
+ // Draw final row
+ if (viewportCappedStartRow !== viewportCappedEndRow) {
+ // Only draw viewportEndRow if it's not the same as viewporttartRow
+ const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols;
+ documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol));
+ }
+ this._terminal.selectionContainer.appendChild(documentFragment);
+ }
+
+ private _createSelectionElement(row: number, colStart: number, colEnd: number): HTMLElement {
+ const element = document.createElement('div');
+ // TODO: Move into a generated <style> element
+ element.style.height = `${this._terminal.charMeasure.height}px`;
+
+ element.style.top = `${row * this._terminal.charMeasure.height}px`;
+ element.style.left = `${colStart * this._terminal.charMeasure.width}px`;
+ element.style.width = `${this._terminal.charMeasure.width * (colEnd - colStart)}px`;
+ return element;
}
}
import { CircularList } from './utils/CircularList';
import { EventEmitter } from './EventEmitter';
import * as Mouse from './utils/Mouse';
+import { ITerminal } from './Interfaces';
export class SelectionManager extends EventEmitter {
// TODO: Create a SelectionModel
private _selectionStart: [number, number];
private _selectionEnd: [number, number];
- private _buffer: CircularList<any>;
- private _rowContainer: HTMLElement;
- private _selectionContainer: HTMLElement;
- private _charMeasure: CharMeasure;
-
private _mouseMoveListener: EventListener;
- constructor(buffer: CircularList<any>, rowContainer: HTMLElement, selectionContainer: HTMLElement, charMeasure: CharMeasure) {
+ constructor(
+ private _terminal: ITerminal,
+ private _buffer: CircularList<any>,
+ private _rowContainer: HTMLElement,
+ private _selectionContainer: HTMLElement,
+ private _charMeasure: CharMeasure
+ ) {
super();
- this._rowContainer = rowContainer;
- this._selectionContainer = selectionContainer;
- this._buffer = buffer;
- this._charMeasure = charMeasure;
this._attachListeners();
}
public refresh(): void {
// TODO: Figure out when to refresh the selection vs when to refresh the viewport
this.emit('refresh', { start: this._selectionStart, end: this._selectionEnd });
- console.log(`Selection: Start: (${this._selectionStart[0]}, ${this._selectionStart[1]}), End: (${this._selectionEnd[0]}, ${this._selectionEnd[1]})`);
- this._selectionContainer.innerHTML = `<div><br><br></div>`;
}
/**
}
private _getMouseBufferCoords(event: MouseEvent) {
- // TODO: Take into account the current terminal viewport when fetching coordinates
- return Mouse.getCoords(event, this._rowContainer, this._charMeasure);
+ const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure);
+ // Convert to 0-based
+ coords[0]--;
+ coords[1]--;
+ // Convert viewport coords to buffer coords
+ coords[1] += this._terminal.ydisp;
+ return coords;
}
private _onMouseDown(event: MouseEvent) {
this._selectionStart = this._getMouseBufferCoords(event);
if (this._selectionStart) {
+ this._selectionEnd = null;
this._rowContainer.addEventListener('mousemove', this._mouseMoveListener);
+ this.refresh();
}
}
}
.terminal .xterm-selection div {
+ position: absolute;
background-color: #777;
}
this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);
this.renderer = new Renderer(this);
- this.selectionManager = new SelectionManager(this.lines, this.rowContainer, this.selectionContainer, this.charMeasure);
+ this.selectionManager = new SelectionManager(this, this.lines, this.rowContainer, this.selectionContainer, this.charMeasure);
this.selectionManager.on('refresh', data => this.renderer.refreshSelection(data.start, data.end));
+ this.on('scroll', () => {
+ console.log('scroll');
+ this.selectionManager.refresh();
+ });
// Setup loop that draws to screen
this.refresh(0, this.rows - 1);