2 * Copyright (c) 2018 The xterm.js authors. All rights reserved.
6 import { C0 } from 'common/data/EscapeSequences';
7 import { IBufferService } from 'common/services/Services';
17 * Concatenates all the arrow sequences together.
18 * Resets the starting row to an unwrapped row, moves to the requested row,
19 * then moves to requested col.
21 export function moveToCellSequence(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
22 const startX = bufferService.buffer.x;
23 const startY = bufferService.buffer.y;
25 // The alt buffer should try to navigate between rows
26 if (!bufferService.buffer.hasScrollback) {
27 return resetStartingRow(startX, startY, targetX, targetY, bufferService, applicationCursor) +
28 moveToRequestedRow(startY, targetY, bufferService, applicationCursor) +
29 moveToRequestedCol(startX, startY, targetX, targetY, bufferService, applicationCursor);
32 // Only move horizontally for the normal buffer
33 return moveHorizontallyOnly(startX, startY, targetX, targetY, bufferService, applicationCursor);
37 * If the initial position of the cursor is on a row that is wrapped, move the
38 * cursor up to the first row that is not wrapped to have accurate vertical
41 function resetStartingRow(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
42 if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length === 0) {
45 return repeat(bufferLine(
46 startX, startY, startX,
47 startY - wrappedRowsForRow(bufferService, startY), false, bufferService
48 ).length, sequence(Direction.LEFT, applicationCursor));
52 * Using the reset starting and ending row, move to the requested row,
53 * ignoring wrapped rows
55 function moveToRequestedRow(startY: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
56 const startRow = startY - wrappedRowsForRow(bufferService, startY);
57 const endRow = targetY - wrappedRowsForRow(bufferService, targetY);
59 const rowsToMove = Math.abs(startRow - endRow) - wrappedRowsCount(startY, targetY, bufferService);
61 return repeat(rowsToMove, sequence(verticalDirection(startY, targetY), applicationCursor));
65 * Move to the requested col on the ending row
67 function moveToRequestedCol(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
69 if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length > 0) {
70 startRow = targetY - wrappedRowsForRow(bufferService, targetY);
75 const endRow = targetY;
76 const direction = horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor);
78 return repeat(bufferLine(
79 startX, startRow, targetX, endRow,
80 direction === Direction.RIGHT, bufferService
81 ).length, sequence(direction, applicationCursor));
84 function moveHorizontallyOnly(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
85 const direction = horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor);
86 return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor));
94 * Calculates the number of wrapped rows between the unwrapped starting and
95 * ending rows. These rows need to ignored since the cursor skips over them.
97 function wrappedRowsCount(startY: number, targetY: number, bufferService: IBufferService): number {
99 const startRow = startY - wrappedRowsForRow(bufferService, startY);
100 const endRow = targetY - wrappedRowsForRow(bufferService, targetY);
102 for (let i = 0; i < Math.abs(startRow - endRow); i++) {
103 const direction = verticalDirection(startY, targetY) === Direction.UP ? -1 : 1;
104 const line = bufferService.buffer.lines.get(startRow + (direction * i));
105 if (line && line.isWrapped) {
114 * Calculates the number of wrapped rows that make up a given row.
115 * @param currentRow The row to determine how many wrapped rows make it up
117 function wrappedRowsForRow(bufferService: IBufferService, currentRow: number): number {
119 let line = bufferService.buffer.lines.get(currentRow);
120 let lineWraps = line && line.isWrapped;
122 while (lineWraps && currentRow >= 0 && currentRow < bufferService.rows) {
124 line = bufferService.buffer.lines.get(--currentRow);
125 lineWraps = line && line.isWrapped;
132 * Direction determiners
136 * Determines if the right or left arrow is needed
138 function horizontalDirection(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): Direction {
140 if (moveToRequestedRow(targetX, targetY, bufferService, applicationCursor).length > 0) {
141 startRow = targetY - wrappedRowsForRow(bufferService, targetY);
146 if ((startX < targetX &&
147 startRow <= targetY) || // down/right or same y/right
148 (startX >= targetX &&
149 startRow < targetY)) { // down/left or same y/left
150 return Direction.RIGHT;
152 return Direction.LEFT;
156 * Determines if the up or down arrow is needed
158 function verticalDirection(startY: number, targetY: number): Direction {
159 return startY > targetY ? Direction.UP : Direction.DOWN;
163 * Constructs the string of chars in the buffer from a starting row and col
164 * to an ending row and col
165 * @param startCol The starting column position
166 * @param startRow The starting row position
167 * @param endCol The ending column position
168 * @param endRow The ending row position
169 * @param forward Direction to move
177 bufferService: IBufferService
179 let currentCol = startCol;
180 let currentRow = startRow;
183 while (currentCol !== endCol || currentRow !== endRow) {
184 currentCol += forward ? 1 : -1;
186 if (forward && currentCol > bufferService.cols - 1) {
187 bufferStr += bufferService.buffer.translateBufferLineToString(
188 currentRow, false, startCol, currentCol
193 } else if (!forward && currentCol < 0) {
194 bufferStr += bufferService.buffer.translateBufferLineToString(
195 currentRow, false, 0, startCol + 1
197 currentCol = bufferService.cols - 1;
198 startCol = currentCol;
203 return bufferStr + bufferService.buffer.translateBufferLineToString(
204 currentRow, false, startCol, currentCol
209 * Constructs the escape sequence for clicking an arrow
210 * @param direction The direction to move
212 function sequence(direction: Direction, applicationCursor: boolean): string {
213 const mod = applicationCursor ? 'O' : '[';
214 return C0.ESC + mod + direction;
218 * Returns a string repeated a given number of times
219 * Polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
220 * @param count The number of times to repeat the string
221 * @param string The string that is to be repeated
223 function repeat(count: number, str: string): string {
224 count = Math.floor(count);
226 for (let i = 0; i < count; i++) {