buffer.dart 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. import 'dart:math' show max, min;
  2. import 'package:xterm/buffer/buffer_line.dart';
  3. import 'package:xterm/buffer/cell.dart';
  4. import 'package:xterm/buffer/cell_attr.dart';
  5. import 'package:xterm/terminal/charset.dart';
  6. import 'package:xterm/terminal/terminal.dart';
  7. import 'package:xterm/utli/scroll_range.dart';
  8. import 'package:xterm/utli/unicode_v11.dart';
  9. class Buffer {
  10. Buffer(this.terminal) {
  11. resetVerticalMargins();
  12. }
  13. final Terminal terminal;
  14. final lines = <BufferLine>[];
  15. final charset = Charset();
  16. int _cursorX = 0;
  17. int _cursorY = 0;
  18. int _savedCursorX;
  19. int _savedCursorY;
  20. int _scrollLinesFromBottom = 0;
  21. int _marginTop;
  22. int _marginBottom;
  23. CellAttr _savedCellAttr;
  24. int get cursorX => _cursorX.clamp(0, terminal.viewWidth - 1);
  25. int get cursorY => _cursorY;
  26. int get marginTop => _marginTop;
  27. int get marginBottom => _marginBottom;
  28. void write(String text) {
  29. for (var char in text.runes) {
  30. writeChar(char);
  31. }
  32. }
  33. void writeChar(int codePoint) {
  34. codePoint = charset.translate(codePoint);
  35. final cellWidth = unicodeV11.wcwidth(codePoint);
  36. if (_cursorX >= terminal.viewWidth) {
  37. newLine();
  38. setCursorX(0);
  39. }
  40. final line = currentLine;
  41. while (line.length <= _cursorX) {
  42. line.add(Cell());
  43. }
  44. final cell = line.getCell(_cursorX);
  45. cell.setCodePoint(codePoint);
  46. cell.setWidth(cellWidth);
  47. cell.setAttr(terminal.cellAttr.value);
  48. if (_cursorX < terminal.viewWidth) {
  49. _cursorX++;
  50. }
  51. if (cellWidth == 2) {
  52. writeChar(0);
  53. }
  54. }
  55. BufferLine getViewLine(int index) {
  56. if (index > terminal.viewHeight) {
  57. return lines.last;
  58. }
  59. while (index >= lines.length) {
  60. final newLine = BufferLine();
  61. lines.add(newLine);
  62. }
  63. return lines[convertViewLineToRawLine(index)];
  64. }
  65. BufferLine get currentLine {
  66. return getViewLine(_cursorY);
  67. }
  68. int get height {
  69. return lines.length;
  70. }
  71. int convertViewLineToRawLine(int viewLine) {
  72. if (terminal.viewHeight > height) {
  73. return viewLine;
  74. }
  75. return viewLine + (height - terminal.viewHeight);
  76. }
  77. int convertRawLineToViewLine(int rawLine) {
  78. if (terminal.viewHeight > height) {
  79. return rawLine;
  80. }
  81. return rawLine - (height - terminal.viewHeight);
  82. }
  83. void newLine() {
  84. if (terminal.lineFeed == false) {
  85. setCursorX(0);
  86. }
  87. index();
  88. }
  89. void carriageReturn() {
  90. setCursorX(0);
  91. }
  92. void backspace() {
  93. if (_cursorX == 0 && currentLine.isWrapped) {
  94. movePosition(terminal.viewWidth - 1, -1);
  95. } else if (_cursorX == terminal.viewWidth) {
  96. movePosition(-2, 0);
  97. } else {
  98. movePosition(-1, 0);
  99. }
  100. }
  101. List<BufferLine> getVisibleLines() {
  102. final result = <BufferLine>[];
  103. for (var i = height - terminal.viewHeight; i < height; i++) {
  104. final y = i - scrollOffsetFromBottom;
  105. if (y >= 0 && y < height) {
  106. result.add(lines[y]);
  107. }
  108. }
  109. return result;
  110. }
  111. void eraseDisplayFromCursor() {
  112. eraseLineFromCursor();
  113. for (var i = _cursorY + 1; i < terminal.viewHeight; i++) {
  114. getViewLine(i).erase(terminal.cellAttr.value, 0, terminal.viewWidth);
  115. }
  116. }
  117. void eraseDisplayToCursor() {
  118. eraseLineToCursor();
  119. for (var i = 0; i < _cursorY; i++) {
  120. getViewLine(i).erase(terminal.cellAttr.value, 0, terminal.viewWidth);
  121. }
  122. }
  123. void eraseDisplay() {
  124. for (var i = 0; i < terminal.viewHeight; i++) {
  125. final line = getViewLine(i);
  126. line.erase(terminal.cellAttr.value, 0, terminal.viewWidth);
  127. }
  128. }
  129. void eraseLineFromCursor() {
  130. currentLine.erase(terminal.cellAttr.value, _cursorX, terminal.viewWidth);
  131. }
  132. void eraseLineToCursor() {
  133. currentLine.erase(terminal.cellAttr.value, 0, _cursorX);
  134. }
  135. void eraseLine() {
  136. currentLine.erase(terminal.cellAttr.value, 0, terminal.viewWidth);
  137. }
  138. void eraseCharacters(int count) {
  139. final start = _cursorX;
  140. for (var i = start; i < start + count; i++) {
  141. if (i >= currentLine.length) {
  142. currentLine.add(Cell(attr: terminal.cellAttr.value));
  143. } else {
  144. currentLine.getCell(i).erase(terminal.cellAttr.value);
  145. }
  146. }
  147. }
  148. ScrollRange getAreaScrollRange() {
  149. var top = convertViewLineToRawLine(_marginTop);
  150. var bottom = convertViewLineToRawLine(_marginBottom) + 1;
  151. if (bottom > lines.length) {
  152. bottom = lines.length;
  153. }
  154. return ScrollRange(top, bottom);
  155. }
  156. void areaScrollDown(int lines) {
  157. final scrollRange = getAreaScrollRange();
  158. for (var i = scrollRange.bottom; i > scrollRange.top;) {
  159. i--;
  160. if (i >= scrollRange.top + lines) {
  161. this.lines[i] = this.lines[i - lines];
  162. } else {
  163. this.lines[i] = BufferLine();
  164. }
  165. }
  166. }
  167. void areaScrollUp(int lines) {
  168. final scrollRange = getAreaScrollRange();
  169. for (var i = scrollRange.top; i < scrollRange.bottom; i++) {
  170. if (i + lines < scrollRange.bottom) {
  171. this.lines[i] = this.lines[i + lines];
  172. } else {
  173. this.lines[i] = BufferLine();
  174. }
  175. }
  176. }
  177. /// https://vt100.net/docs/vt100-ug/chapter3.html#IND
  178. void index() {
  179. if (isInScrollableRegion) {
  180. if (_cursorY < _marginBottom) {
  181. moveCursorY(1);
  182. } else {
  183. areaScrollUp(1);
  184. }
  185. return;
  186. }
  187. if (_cursorY >= terminal.viewHeight - 1) {
  188. lines.add(BufferLine());
  189. if (terminal.maxLines != null && lines.length > terminal.maxLines) {
  190. lines.removeRange(0, lines.length - terminal.maxLines);
  191. }
  192. } else {
  193. moveCursorY(1);
  194. }
  195. }
  196. /// https://vt100.net/docs/vt100-ug/chapter3.html#RI
  197. void reverseIndex() {
  198. if (_cursorY == _marginTop) {
  199. areaScrollDown(1);
  200. } else if (_cursorY > 0) {
  201. moveCursorY(-1);
  202. }
  203. }
  204. Cell getCell(int col, int row) {
  205. final rawRow = convertViewLineToRawLine(row);
  206. return getRawCell(col, rawRow);
  207. }
  208. Cell getRawCell(int col, int rawRow) {
  209. if (col < 0 || rawRow < 0 || rawRow >= lines.length) {
  210. return null;
  211. }
  212. final line = lines[rawRow];
  213. if (col >= line.length) {
  214. return null;
  215. }
  216. return line.getCell(col);
  217. }
  218. Cell getCellUnderCursor() {
  219. return getCell(cursorX, cursorY);
  220. }
  221. void cursorGoForward() {
  222. setCursorX(_cursorX + 1);
  223. terminal.refresh();
  224. }
  225. void setCursorX(int cursorX) {
  226. _cursorX = cursorX.clamp(0, terminal.viewWidth - 1);
  227. terminal.refresh();
  228. }
  229. void setCursorY(int cursorY) {
  230. _cursorY = cursorY.clamp(0, terminal.viewHeight - 1);
  231. terminal.refresh();
  232. }
  233. void moveCursorX(int offset) {
  234. setCursorX(_cursorX + offset);
  235. }
  236. void moveCursorY(int offset) {
  237. setCursorY(_cursorY + offset);
  238. }
  239. void setPosition(int cursorX, int cursorY) {
  240. var maxLine = terminal.viewHeight - 1;
  241. if (terminal.originMode) {
  242. cursorY += _marginTop;
  243. maxLine = _marginBottom;
  244. }
  245. _cursorX = cursorX.clamp(0, terminal.viewWidth - 1);
  246. _cursorY = cursorY.clamp(0, maxLine);
  247. }
  248. void movePosition(int offsetX, int offsetY) {
  249. final cursorX = _cursorX + offsetX;
  250. final cursorY = _cursorY + offsetY;
  251. setPosition(cursorX, cursorY);
  252. }
  253. int get scrollOffsetFromBottom {
  254. return _scrollLinesFromBottom;
  255. }
  256. int get scrollOffsetFromTop {
  257. return terminal.invisibleHeight - scrollOffsetFromBottom;
  258. }
  259. void setScrollOffsetFromBottom(int offset) {
  260. if (height < terminal.viewHeight) return;
  261. final maxOffset = height - terminal.viewHeight;
  262. _scrollLinesFromBottom = offset.clamp(0, maxOffset);
  263. terminal.refresh();
  264. }
  265. void setScrollOffsetFromTop(int offset) {
  266. final bottomOffset = terminal.invisibleHeight - offset;
  267. setScrollOffsetFromBottom(bottomOffset);
  268. }
  269. void screenScrollUp(int lines) {
  270. setScrollOffsetFromBottom(scrollOffsetFromBottom + lines);
  271. }
  272. void screenScrollDown(int lines) {
  273. setScrollOffsetFromBottom(scrollOffsetFromBottom - lines);
  274. }
  275. void saveCursor() {
  276. _savedCellAttr = terminal.cellAttr.value;
  277. _savedCursorX = _cursorX;
  278. _savedCursorY = _cursorY;
  279. charset.save();
  280. }
  281. void restoreCursor() {
  282. if (_savedCellAttr != null) {
  283. terminal.cellAttr.use(_savedCellAttr);
  284. }
  285. if (_savedCursorX != null) {
  286. _cursorX = _savedCursorX;
  287. }
  288. if (_savedCursorY != null) {
  289. _cursorY = _savedCursorY;
  290. }
  291. charset.restore();
  292. }
  293. void setVerticalMargins(int top, int bottom) {
  294. _marginTop = top.clamp(0, terminal.viewHeight - 1);
  295. _marginBottom = bottom.clamp(0, terminal.viewHeight - 1);
  296. _marginTop = min(_marginTop, _marginBottom);
  297. _marginBottom = max(_marginTop, _marginBottom);
  298. }
  299. bool get hasScrollableRegion {
  300. return _marginTop > 0 || _marginBottom < (terminal.viewHeight - 1);
  301. }
  302. bool get isInScrollableRegion {
  303. return hasScrollableRegion &&
  304. _cursorY >= _marginTop &&
  305. _cursorY <= _marginBottom;
  306. }
  307. void resetVerticalMargins() {
  308. setVerticalMargins(0, terminal.viewHeight - 1);
  309. }
  310. void deleteChars(int count) {
  311. final start = _cursorX.clamp(0, currentLine.length);
  312. final end = min(_cursorX + count, currentLine.length);
  313. currentLine.removeRange(start, end);
  314. }
  315. void clearScrollback() {
  316. if (lines.length <= terminal.viewHeight) {
  317. return;
  318. }
  319. lines.removeRange(0, lines.length - terminal.viewHeight);
  320. }
  321. void clear() {
  322. lines.clear();
  323. }
  324. void insertBlankCharacters(int count) {
  325. for (var i = 0; i < count; i++) {
  326. final cell = Cell(attr: terminal.cellAttr.value);
  327. currentLine.insert(_cursorX + i, cell);
  328. }
  329. }
  330. void insertLines(int count) {
  331. if (hasScrollableRegion && !isInScrollableRegion) {
  332. return;
  333. }
  334. setCursorX(0);
  335. for (var i = 0; i < count; i++) {
  336. insertLine();
  337. }
  338. }
  339. void insertLine() {
  340. if (!isInScrollableRegion) {
  341. final index = convertViewLineToRawLine(_cursorX);
  342. final newLine = BufferLine();
  343. lines.insert(index, newLine);
  344. if (terminal.maxLines != null && lines.length > terminal.maxLines) {
  345. lines.removeRange(0, lines.length - terminal.maxLines);
  346. }
  347. } else {
  348. final bottom = convertViewLineToRawLine(marginBottom);
  349. final movedLines = lines.getRange(_cursorY, bottom - 1);
  350. lines.setRange(_cursorY + 1, bottom, movedLines);
  351. final newLine = BufferLine();
  352. lines[_cursorY] = newLine;
  353. }
  354. }
  355. void deleteLines(int count) {
  356. if (hasScrollableRegion && !isInScrollableRegion) {
  357. return;
  358. }
  359. setCursorX(0);
  360. for (var i = 0; i < count; i++) {
  361. deleteLine();
  362. }
  363. }
  364. void deleteLine() {
  365. final index = convertViewLineToRawLine(_cursorX);
  366. if (index >= height) {
  367. return;
  368. }
  369. lines.removeAt(index);
  370. }
  371. }