xterm
[VSoRC/.git] / node_modules / xterm / src / browser / input / MoveToCell.ts
1 /**
2  * Copyright (c) 2018 The xterm.js authors. All rights reserved.
3  * @license MIT
4  */
5
6 import { C0 } from 'common/data/EscapeSequences';
7 import { IBufferService } from 'common/services/Services';
8
9 const enum Direction {
10   UP = 'A',
11   DOWN = 'B',
12   RIGHT = 'C',
13   LEFT = 'D'
14 }
15
16 /**
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.
20  */
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;
24
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);
30   }
31
32   // Only move horizontally for the normal buffer
33   return moveHorizontallyOnly(startX, startY, targetX, targetY, bufferService, applicationCursor);
34 }
35
36 /**
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
39  * positioning.
40  */
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) {
43     return '';
44   }
45   return repeat(bufferLine(
46     startX, startY, startX,
47     startY - wrappedRowsForRow(bufferService, startY), false, bufferService
48   ).length, sequence(Direction.LEFT, applicationCursor));
49 }
50
51 /**
52  * Using the reset starting and ending row, move to the requested row,
53  * ignoring wrapped rows
54  */
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);
58
59   const rowsToMove = Math.abs(startRow - endRow) - wrappedRowsCount(startY, targetY, bufferService);
60
61   return repeat(rowsToMove, sequence(verticalDirection(startY, targetY), applicationCursor));
62 }
63
64 /**
65  * Move to the requested col on the ending row
66  */
67 function moveToRequestedCol(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
68   let startRow;
69   if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length > 0) {
70     startRow = targetY - wrappedRowsForRow(bufferService, targetY);
71   } else {
72     startRow = startY;
73   }
74
75   const endRow = targetY;
76   const direction = horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor);
77
78   return repeat(bufferLine(
79     startX, startRow, targetX, endRow,
80     direction === Direction.RIGHT, bufferService
81   ).length, sequence(direction, applicationCursor));
82 }
83
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));
87 }
88
89 /**
90  * Utility functions
91  */
92
93 /**
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.
96  */
97 function wrappedRowsCount(startY: number, targetY: number, bufferService: IBufferService): number {
98   let wrappedRows = 0;
99   const startRow = startY - wrappedRowsForRow(bufferService, startY);
100   const endRow = targetY - wrappedRowsForRow(bufferService, targetY);
101
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) {
106       wrappedRows++;
107     }
108   }
109
110   return wrappedRows;
111 }
112
113 /**
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
116  */
117 function wrappedRowsForRow(bufferService: IBufferService, currentRow: number): number {
118   let rowCount = 0;
119   let line = bufferService.buffer.lines.get(currentRow);
120   let lineWraps = line && line.isWrapped;
121
122   while (lineWraps && currentRow >= 0 && currentRow < bufferService.rows) {
123     rowCount++;
124     line = bufferService.buffer.lines.get(--currentRow);
125     lineWraps = line && line.isWrapped;
126   }
127
128   return rowCount;
129 }
130
131 /**
132  * Direction determiners
133  */
134
135 /**
136  * Determines if the right or left arrow is needed
137  */
138 function horizontalDirection(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): Direction {
139   let startRow;
140   if (moveToRequestedRow(targetX, targetY, bufferService, applicationCursor).length > 0) {
141     startRow = targetY - wrappedRowsForRow(bufferService, targetY);
142   } else {
143     startRow = startY;
144   }
145
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;
151   }
152   return Direction.LEFT;
153 }
154
155 /**
156  * Determines if the up or down arrow is needed
157  */
158 function verticalDirection(startY: number, targetY: number): Direction {
159   return startY > targetY ? Direction.UP : Direction.DOWN;
160 }
161
162 /**
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
170  */
171 function bufferLine(
172   startCol: number,
173   startRow: number,
174   endCol: number,
175   endRow: number,
176   forward: boolean,
177   bufferService: IBufferService
178 ): string {
179   let currentCol = startCol;
180   let currentRow = startRow;
181   let bufferStr = '';
182
183   while (currentCol !== endCol || currentRow !== endRow) {
184     currentCol += forward ? 1 : -1;
185
186     if (forward && currentCol > bufferService.cols - 1) {
187       bufferStr += bufferService.buffer.translateBufferLineToString(
188         currentRow, false, startCol, currentCol
189       );
190       currentCol = 0;
191       startCol = 0;
192       currentRow++;
193     } else if (!forward && currentCol < 0) {
194       bufferStr += bufferService.buffer.translateBufferLineToString(
195         currentRow, false, 0, startCol + 1
196       );
197       currentCol = bufferService.cols - 1;
198       startCol = currentCol;
199       currentRow--;
200     }
201   }
202
203   return bufferStr + bufferService.buffer.translateBufferLineToString(
204     currentRow, false, startCol, currentCol
205   );
206 }
207
208 /**
209  * Constructs the escape sequence for clicking an arrow
210  * @param direction The direction to move
211  */
212 function sequence(direction: Direction, applicationCursor: boolean): string {
213   const mod =  applicationCursor ? 'O' : '[';
214   return C0.ESC + mod + direction;
215 }
216
217 /**
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
222  */
223 function repeat(count: number, str: string): string {
224   count = Math.floor(count);
225   let rpt = '';
226   for (let i = 0; i < count; i++) {
227     rpt += str;
228   }
229   return rpt;
230 }