xterm
[VSoRC/.git] / node_modules / xterm / src / browser / input / MoveToCell.ts
diff --git a/node_modules/xterm/src/browser/input/MoveToCell.ts b/node_modules/xterm/src/browser/input/MoveToCell.ts
new file mode 100644 (file)
index 0000000..406ec80
--- /dev/null
@@ -0,0 +1,230 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { C0 } from 'common/data/EscapeSequences';
+import { IBufferService } from 'common/services/Services';
+
+const enum Direction {
+  UP = 'A',
+  DOWN = 'B',
+  RIGHT = 'C',
+  LEFT = 'D'
+}
+
+/**
+ * Concatenates all the arrow sequences together.
+ * Resets the starting row to an unwrapped row, moves to the requested row,
+ * then moves to requested col.
+ */
+export function moveToCellSequence(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
+  const startX = bufferService.buffer.x;
+  const startY = bufferService.buffer.y;
+
+  // The alt buffer should try to navigate between rows
+  if (!bufferService.buffer.hasScrollback) {
+    return resetStartingRow(startX, startY, targetX, targetY, bufferService, applicationCursor) +
+      moveToRequestedRow(startY, targetY, bufferService, applicationCursor) +
+      moveToRequestedCol(startX, startY, targetX, targetY, bufferService, applicationCursor);
+  }
+
+  // Only move horizontally for the normal buffer
+  return moveHorizontallyOnly(startX, startY, targetX, targetY, bufferService, applicationCursor);
+}
+
+/**
+ * If the initial position of the cursor is on a row that is wrapped, move the
+ * cursor up to the first row that is not wrapped to have accurate vertical
+ * positioning.
+ */
+function resetStartingRow(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
+  if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length === 0) {
+    return '';
+  }
+  return repeat(bufferLine(
+    startX, startY, startX,
+    startY - wrappedRowsForRow(bufferService, startY), false, bufferService
+  ).length, sequence(Direction.LEFT, applicationCursor));
+}
+
+/**
+ * Using the reset starting and ending row, move to the requested row,
+ * ignoring wrapped rows
+ */
+function moveToRequestedRow(startY: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
+  const startRow = startY - wrappedRowsForRow(bufferService, startY);
+  const endRow = targetY - wrappedRowsForRow(bufferService, targetY);
+
+  const rowsToMove = Math.abs(startRow - endRow) - wrappedRowsCount(startY, targetY, bufferService);
+
+  return repeat(rowsToMove, sequence(verticalDirection(startY, targetY), applicationCursor));
+}
+
+/**
+ * Move to the requested col on the ending row
+ */
+function moveToRequestedCol(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
+  let startRow;
+  if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length > 0) {
+    startRow = targetY - wrappedRowsForRow(bufferService, targetY);
+  } else {
+    startRow = startY;
+  }
+
+  const endRow = targetY;
+  const direction = horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor);
+
+  return repeat(bufferLine(
+    startX, startRow, targetX, endRow,
+    direction === Direction.RIGHT, bufferService
+  ).length, sequence(direction, applicationCursor));
+}
+
+function moveHorizontallyOnly(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
+  const direction = horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor);
+  return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor));
+}
+
+/**
+ * Utility functions
+ */
+
+/**
+ * Calculates the number of wrapped rows between the unwrapped starting and
+ * ending rows. These rows need to ignored since the cursor skips over them.
+ */
+function wrappedRowsCount(startY: number, targetY: number, bufferService: IBufferService): number {
+  let wrappedRows = 0;
+  const startRow = startY - wrappedRowsForRow(bufferService, startY);
+  const endRow = targetY - wrappedRowsForRow(bufferService, targetY);
+
+  for (let i = 0; i < Math.abs(startRow - endRow); i++) {
+    const direction = verticalDirection(startY, targetY) === Direction.UP ? -1 : 1;
+    const line = bufferService.buffer.lines.get(startRow + (direction * i));
+    if (line && line.isWrapped) {
+      wrappedRows++;
+    }
+  }
+
+  return wrappedRows;
+}
+
+/**
+ * Calculates the number of wrapped rows that make up a given row.
+ * @param currentRow The row to determine how many wrapped rows make it up
+ */
+function wrappedRowsForRow(bufferService: IBufferService, currentRow: number): number {
+  let rowCount = 0;
+  let line = bufferService.buffer.lines.get(currentRow);
+  let lineWraps = line && line.isWrapped;
+
+  while (lineWraps && currentRow >= 0 && currentRow < bufferService.rows) {
+    rowCount++;
+    line = bufferService.buffer.lines.get(--currentRow);
+    lineWraps = line && line.isWrapped;
+  }
+
+  return rowCount;
+}
+
+/**
+ * Direction determiners
+ */
+
+/**
+ * Determines if the right or left arrow is needed
+ */
+function horizontalDirection(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): Direction {
+  let startRow;
+  if (moveToRequestedRow(targetX, targetY, bufferService, applicationCursor).length > 0) {
+    startRow = targetY - wrappedRowsForRow(bufferService, targetY);
+  } else {
+    startRow = startY;
+  }
+
+  if ((startX < targetX &&
+    startRow <= targetY) || // down/right or same y/right
+    (startX >= targetX &&
+    startRow < targetY)) {  // down/left or same y/left
+    return Direction.RIGHT;
+  }
+  return Direction.LEFT;
+}
+
+/**
+ * Determines if the up or down arrow is needed
+ */
+function verticalDirection(startY: number, targetY: number): Direction {
+  return startY > targetY ? Direction.UP : Direction.DOWN;
+}
+
+/**
+ * Constructs the string of chars in the buffer from a starting row and col
+ * to an ending row and col
+ * @param startCol The starting column position
+ * @param startRow The starting row position
+ * @param endCol The ending column position
+ * @param endRow The ending row position
+ * @param forward Direction to move
+ */
+function bufferLine(
+  startCol: number,
+  startRow: number,
+  endCol: number,
+  endRow: number,
+  forward: boolean,
+  bufferService: IBufferService
+): string {
+  let currentCol = startCol;
+  let currentRow = startRow;
+  let bufferStr = '';
+
+  while (currentCol !== endCol || currentRow !== endRow) {
+    currentCol += forward ? 1 : -1;
+
+    if (forward && currentCol > bufferService.cols - 1) {
+      bufferStr += bufferService.buffer.translateBufferLineToString(
+        currentRow, false, startCol, currentCol
+      );
+      currentCol = 0;
+      startCol = 0;
+      currentRow++;
+    } else if (!forward && currentCol < 0) {
+      bufferStr += bufferService.buffer.translateBufferLineToString(
+        currentRow, false, 0, startCol + 1
+      );
+      currentCol = bufferService.cols - 1;
+      startCol = currentCol;
+      currentRow--;
+    }
+  }
+
+  return bufferStr + bufferService.buffer.translateBufferLineToString(
+    currentRow, false, startCol, currentCol
+  );
+}
+
+/**
+ * Constructs the escape sequence for clicking an arrow
+ * @param direction The direction to move
+ */
+function sequence(direction: Direction, applicationCursor: boolean): string {
+  const mod =  applicationCursor ? 'O' : '[';
+  return C0.ESC + mod + direction;
+}
+
+/**
+ * Returns a string repeated a given number of times
+ * Polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
+ * @param count The number of times to repeat the string
+ * @param string The string that is to be repeated
+ */
+function repeat(count: number, str: string): string {
+  count = Math.floor(count);
+  let rpt = '';
+  for (let i = 0; i < count; i++) {
+    rpt += str;
+  }
+  return rpt;
+}