buffer.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  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. late int _marginTop;
  22. late 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. final maxLines = terminal.maxLines;
  190. if (maxLines != null && lines.length > maxLines) {
  191. lines.removeRange(0, lines.length - maxLines);
  192. }
  193. } else {
  194. moveCursorY(1);
  195. }
  196. }
  197. /// https://vt100.net/docs/vt100-ug/chapter3.html#RI
  198. void reverseIndex() {
  199. if (_cursorY == _marginTop) {
  200. areaScrollDown(1);
  201. } else if (_cursorY > 0) {
  202. moveCursorY(-1);
  203. }
  204. }
  205. Cell? getCell(int col, int row) {
  206. final rawRow = convertViewLineToRawLine(row);
  207. return getRawCell(col, rawRow);
  208. }
  209. Cell? getRawCell(int col, int rawRow) {
  210. if (col < 0 || rawRow < 0 || rawRow >= lines.length) {
  211. return null;
  212. }
  213. final line = lines[rawRow];
  214. if (col >= line.length) {
  215. return null;
  216. }
  217. return line.getCell(col);
  218. }
  219. Cell? getCellUnderCursor() {
  220. return getCell(cursorX, cursorY);
  221. }
  222. void cursorGoForward() {
  223. setCursorX(_cursorX + 1);
  224. terminal.refresh();
  225. }
  226. void setCursorX(int cursorX) {
  227. _cursorX = cursorX.clamp(0, terminal.viewWidth - 1);
  228. terminal.refresh();
  229. }
  230. void setCursorY(int cursorY) {
  231. _cursorY = cursorY.clamp(0, terminal.viewHeight - 1);
  232. terminal.refresh();
  233. }
  234. void moveCursorX(int offset) {
  235. setCursorX(_cursorX + offset);
  236. }
  237. void moveCursorY(int offset) {
  238. setCursorY(_cursorY + offset);
  239. }
  240. void setPosition(int cursorX, int cursorY) {
  241. var maxLine = terminal.viewHeight - 1;
  242. if (terminal.originMode) {
  243. cursorY += _marginTop;
  244. maxLine = _marginBottom;
  245. }
  246. _cursorX = cursorX.clamp(0, terminal.viewWidth - 1);
  247. _cursorY = cursorY.clamp(0, maxLine);
  248. }
  249. void movePosition(int offsetX, int offsetY) {
  250. final cursorX = _cursorX + offsetX;
  251. final cursorY = _cursorY + offsetY;
  252. setPosition(cursorX, cursorY);
  253. }
  254. int get scrollOffsetFromBottom {
  255. return _scrollLinesFromBottom;
  256. }
  257. int get scrollOffsetFromTop {
  258. return terminal.invisibleHeight - scrollOffsetFromBottom;
  259. }
  260. void setScrollOffsetFromBottom(int offset) {
  261. if (height < terminal.viewHeight) return;
  262. final maxOffset = height - terminal.viewHeight;
  263. _scrollLinesFromBottom = offset.clamp(0, maxOffset);
  264. terminal.refresh();
  265. }
  266. void setScrollOffsetFromTop(int offset) {
  267. final bottomOffset = terminal.invisibleHeight - offset;
  268. setScrollOffsetFromBottom(bottomOffset);
  269. }
  270. void screenScrollUp(int lines) {
  271. setScrollOffsetFromBottom(scrollOffsetFromBottom + lines);
  272. }
  273. void screenScrollDown(int lines) {
  274. setScrollOffsetFromBottom(scrollOffsetFromBottom - lines);
  275. }
  276. void saveCursor() {
  277. _savedCellAttr = terminal.cellAttr.value;
  278. _savedCursorX = _cursorX;
  279. _savedCursorY = _cursorY;
  280. charset.save();
  281. }
  282. void restoreCursor() {
  283. if (_savedCellAttr != null) {
  284. terminal.cellAttr.use(_savedCellAttr!);
  285. }
  286. if (_savedCursorX != null) {
  287. _cursorX = _savedCursorX!;
  288. }
  289. if (_savedCursorY != null) {
  290. _cursorY = _savedCursorY!;
  291. }
  292. charset.restore();
  293. }
  294. void setVerticalMargins(int top, int bottom) {
  295. _marginTop = top.clamp(0, terminal.viewHeight - 1);
  296. _marginBottom = bottom.clamp(0, terminal.viewHeight - 1);
  297. _marginTop = min(_marginTop, _marginBottom);
  298. _marginBottom = max(_marginTop, _marginBottom);
  299. }
  300. bool get hasScrollableRegion {
  301. return _marginTop > 0 || _marginBottom < (terminal.viewHeight - 1);
  302. }
  303. bool get isInScrollableRegion {
  304. return hasScrollableRegion &&
  305. _cursorY >= _marginTop &&
  306. _cursorY <= _marginBottom;
  307. }
  308. void resetVerticalMargins() {
  309. setVerticalMargins(0, terminal.viewHeight - 1);
  310. }
  311. void deleteChars(int count) {
  312. final start = _cursorX.clamp(0, currentLine.length);
  313. final end = min(_cursorX + count, currentLine.length);
  314. currentLine.removeRange(start, end);
  315. }
  316. void clearScrollback() {
  317. if (lines.length <= terminal.viewHeight) {
  318. return;
  319. }
  320. lines.removeRange(0, lines.length - terminal.viewHeight);
  321. }
  322. void clear() {
  323. lines.clear();
  324. }
  325. void insertBlankCharacters(int count) {
  326. for (var i = 0; i < count; i++) {
  327. final cell = Cell(attr: terminal.cellAttr.value);
  328. currentLine.insert(_cursorX + i, cell);
  329. }
  330. }
  331. void insertLines(int count) {
  332. if (hasScrollableRegion && !isInScrollableRegion) {
  333. return;
  334. }
  335. setCursorX(0);
  336. for (var i = 0; i < count; i++) {
  337. insertLine();
  338. }
  339. }
  340. void insertLine() {
  341. if (!isInScrollableRegion) {
  342. final index = convertViewLineToRawLine(_cursorX);
  343. final newLine = BufferLine();
  344. lines.insert(index, newLine);
  345. final maxLines = terminal.maxLines;
  346. if (maxLines != null && lines.length > maxLines) {
  347. lines.removeRange(0, lines.length - maxLines);
  348. }
  349. } else {
  350. final bottom = convertViewLineToRawLine(marginBottom);
  351. final movedLines = lines.getRange(_cursorY, bottom - 1);
  352. lines.setRange(_cursorY + 1, bottom, movedLines);
  353. final newLine = BufferLine();
  354. lines[_cursorY] = newLine;
  355. }
  356. }
  357. void deleteLines(int count) {
  358. if (hasScrollableRegion && !isInScrollableRegion) {
  359. return;
  360. }
  361. setCursorX(0);
  362. for (var i = 0; i < count; i++) {
  363. deleteLine();
  364. }
  365. }
  366. void deleteLine() {
  367. final index = convertViewLineToRawLine(_cursorX);
  368. if (index >= height) {
  369. return;
  370. }
  371. lines.removeAt(index);
  372. }
  373. }