terminal_isolate.dart 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. import 'dart:async';
  2. import 'dart:isolate';
  3. import 'package:xterm/buffer/buffer_line.dart';
  4. import 'package:xterm/input/keys.dart';
  5. import 'package:xterm/mouse/position.dart';
  6. import 'package:xterm/mouse/selection.dart';
  7. import 'package:xterm/terminal/platform.dart';
  8. import 'package:xterm/terminal/terminal.dart';
  9. import 'package:xterm/terminal/terminal_backend.dart';
  10. import 'package:xterm/terminal/terminal_ui_interaction.dart';
  11. import 'package:xterm/theme/terminal_theme.dart';
  12. import 'package:xterm/theme/terminal_themes.dart';
  13. import 'package:xterm/util/observable.dart';
  14. void terminalMain(SendPort port) async {
  15. final rp = ReceivePort();
  16. port.send(rp.sendPort);
  17. Terminal? _terminal;
  18. await for (var msg in rp) {
  19. final String action = msg[0];
  20. switch (action) {
  21. case 'sendPort':
  22. port = msg[1];
  23. break;
  24. case 'init':
  25. final TerminalInitData initData = msg[1];
  26. _terminal = Terminal(
  27. backend: initData.backend,
  28. onTitleChange: (String title) {
  29. port.send(['onTitleChange', title]);
  30. },
  31. onIconChange: (String icon) {
  32. port.send(['onIconChange', icon]);
  33. },
  34. onBell: () {
  35. port.send(['onBell']);
  36. },
  37. platform: initData.platform,
  38. theme: initData.theme,
  39. maxLines: initData.maxLines);
  40. _terminal.addListener(() {
  41. port.send(['notify']);
  42. });
  43. initData.backend?.exitCode.then((value) => port.send(['exit', value]));
  44. port.send(['notify']);
  45. break;
  46. case 'write':
  47. _terminal?.write(msg[1]);
  48. break;
  49. case 'refresh':
  50. _terminal?.refresh();
  51. break;
  52. case 'selection.clear':
  53. _terminal?.selection.clear();
  54. break;
  55. case 'mouseMode.onTap':
  56. _terminal?.mouseMode.onTap(_terminal, msg[1]);
  57. break;
  58. case 'mouseMode.onPanStart':
  59. _terminal?.mouseMode.onPanStart(_terminal, msg[1]);
  60. break;
  61. case 'mouseMode.onPanUpdate':
  62. _terminal?.mouseMode.onPanUpdate(_terminal, msg[1]);
  63. break;
  64. case 'setScrollOffsetFromBottom':
  65. _terminal?.setScrollOffsetFromBottom(msg[1]);
  66. break;
  67. case 'resize':
  68. _terminal?.resize(msg[1], msg[2]);
  69. break;
  70. case 'onInput':
  71. _terminal?.backend?.write(msg[1]);
  72. break;
  73. case 'keyInput':
  74. if (_terminal == null) {
  75. break;
  76. }
  77. _terminal.keyInput(msg[1],
  78. ctrl: msg[2], alt: msg[3], shift: msg[4], mac: msg[5]);
  79. break;
  80. case 'requestNewStateWhenDirty':
  81. if (_terminal == null) {
  82. break;
  83. }
  84. if (_terminal.dirty) {
  85. final newState = TerminalState(
  86. _terminal.buffer.scrollOffsetFromBottom,
  87. _terminal.buffer.scrollOffsetFromTop,
  88. _terminal.buffer.height,
  89. _terminal.invisibleHeight,
  90. _terminal.viewHeight,
  91. _terminal.viewWidth,
  92. _terminal.selection,
  93. _terminal.getSelectedText(),
  94. _terminal.theme.background,
  95. _terminal.cursorX,
  96. _terminal.cursorY,
  97. _terminal.showCursor,
  98. _terminal.theme.cursor,
  99. _terminal.getVisibleLines(),
  100. _terminal.scrollOffset);
  101. port.send(['newState', newState]);
  102. }
  103. break;
  104. case 'paste':
  105. if (_terminal == null) {
  106. break;
  107. }
  108. _terminal.paste(msg[1]);
  109. break;
  110. }
  111. }
  112. }
  113. class TerminalInitData {
  114. PlatformBehavior platform;
  115. TerminalTheme theme;
  116. int maxLines;
  117. TerminalBackend? backend;
  118. TerminalInitData(this.backend, this.platform, this.theme, this.maxLines);
  119. }
  120. class TerminalState {
  121. int scrollOffsetFromTop;
  122. int scrollOffsetFromBottom;
  123. int bufferHeight;
  124. int invisibleHeight;
  125. int viewHeight;
  126. int viewWidth;
  127. Selection selection;
  128. String? selectedText;
  129. int backgroundColor;
  130. int cursorX;
  131. int cursorY;
  132. bool showCursor;
  133. int cursorColor;
  134. List<BufferLine> visibleLines;
  135. int scrollOffset;
  136. bool consumed = false;
  137. TerminalState(
  138. this.scrollOffsetFromBottom,
  139. this.scrollOffsetFromTop,
  140. this.bufferHeight,
  141. this.invisibleHeight,
  142. this.viewHeight,
  143. this.viewWidth,
  144. this.selection,
  145. this.selectedText,
  146. this.backgroundColor,
  147. this.cursorX,
  148. this.cursorY,
  149. this.showCursor,
  150. this.cursorColor,
  151. this.visibleLines,
  152. this.scrollOffset);
  153. }
  154. void _defaultInputHandler(String _) {}
  155. void _defaultBellHandler() {}
  156. void _defaultTitleHandler(String _) {}
  157. void _defaultIconHandler(String _) {}
  158. class TerminalIsolate with Observable implements TerminalUiInteraction {
  159. final _receivePort = ReceivePort();
  160. SendPort? _sendPort;
  161. late Isolate _isolate;
  162. final TerminalBackend? backend;
  163. final BellHandler onBell;
  164. final TitleChangeHandler onTitleChange;
  165. final IconChangeHandler onIconChange;
  166. final PlatformBehavior _platform;
  167. final TerminalTheme theme;
  168. final int maxLines;
  169. TerminalState? _lastState;
  170. final _backendExited = Completer<int>();
  171. Future<int> get backendExited => _backendExited.future;
  172. TerminalState? get lastState {
  173. return _lastState;
  174. }
  175. TerminalIsolate(
  176. {this.backend,
  177. this.onBell = _defaultBellHandler,
  178. this.onTitleChange = _defaultTitleHandler,
  179. this.onIconChange = _defaultIconHandler,
  180. PlatformBehavior platform = PlatformBehaviors.unix,
  181. this.theme = TerminalThemes.defaultTheme,
  182. required this.maxLines})
  183. : _platform = platform;
  184. @override
  185. int get scrollOffsetFromBottom => _lastState!.scrollOffsetFromBottom;
  186. @override
  187. int get scrollOffsetFromTop => _lastState!.scrollOffsetFromTop;
  188. @override
  189. int get scrollOffset => _lastState!.scrollOffset;
  190. @override
  191. int get bufferHeight => _lastState!.bufferHeight;
  192. @override
  193. int get terminalHeight => _lastState!.viewHeight;
  194. @override
  195. int get terminalWidth => _lastState!.viewWidth;
  196. @override
  197. int get invisibleHeight => _lastState!.invisibleHeight;
  198. @override
  199. Selection? get selection => _lastState?.selection;
  200. @override
  201. bool get showCursor => _lastState?.showCursor ?? true;
  202. @override
  203. List<BufferLine> getVisibleLines() {
  204. if (_lastState == null) {
  205. return List<BufferLine>.empty();
  206. }
  207. return _lastState!.visibleLines;
  208. }
  209. @override
  210. int get cursorY => _lastState?.cursorY ?? 0;
  211. @override
  212. int get cursorX => _lastState?.cursorX ?? 0;
  213. @override
  214. BufferLine? get currentLine {
  215. if (_lastState == null) {
  216. return null;
  217. }
  218. int visibleLineIndex =
  219. _lastState!.cursorY - _lastState!.scrollOffsetFromTop;
  220. if (visibleLineIndex < 0) {
  221. visibleLineIndex = _lastState!.cursorY;
  222. }
  223. return _lastState!.visibleLines[visibleLineIndex];
  224. }
  225. @override
  226. int get cursorColor => _lastState?.cursorColor ?? 0;
  227. @override
  228. int get backgroundColor => _lastState?.backgroundColor ?? 0;
  229. @override
  230. bool get dirty {
  231. if (_lastState == null) {
  232. return false;
  233. }
  234. if (_lastState!.consumed) {
  235. return false;
  236. }
  237. _lastState!.consumed = true;
  238. return true;
  239. }
  240. @override
  241. PlatformBehavior get platform => _platform;
  242. @override
  243. bool get isReady => _lastState != null;
  244. void start() async {
  245. final initialRefreshCompleted = Completer<bool>();
  246. var firstReceivePort = ReceivePort();
  247. _isolate = await Isolate.spawn(terminalMain, firstReceivePort.sendPort);
  248. _sendPort = await firstReceivePort.first;
  249. _sendPort!.send(['sendPort', _receivePort.sendPort]);
  250. _receivePort.listen((message) {
  251. String action = message[0];
  252. switch (action) {
  253. case 'onBell':
  254. this.onBell();
  255. break;
  256. case 'onTitleChange':
  257. this.onTitleChange(message[1]);
  258. break;
  259. case 'onIconChange':
  260. this.onIconChange(message[1]);
  261. break;
  262. case 'notify':
  263. poll();
  264. break;
  265. case 'newState':
  266. _lastState = message[1];
  267. if (!initialRefreshCompleted.isCompleted) {
  268. initialRefreshCompleted.complete(true);
  269. }
  270. this.notifyListeners();
  271. break;
  272. case 'exit':
  273. _backendExited.complete(message[1]);
  274. break;
  275. }
  276. });
  277. _sendPort!.send([
  278. 'init',
  279. TerminalInitData(this.backend, this.platform, this.theme, this.maxLines)
  280. ]);
  281. await initialRefreshCompleted.future;
  282. }
  283. void stop() {
  284. _isolate.kill();
  285. }
  286. void poll() {
  287. if (_sendPort == null) {
  288. return;
  289. }
  290. _sendPort!.send(['requestNewStateWhenDirty']);
  291. }
  292. void refresh() {
  293. if (_sendPort == null) {
  294. return;
  295. }
  296. _sendPort!.send(['refresh']);
  297. }
  298. void clearSelection() {
  299. if (_sendPort == null) {
  300. return;
  301. }
  302. _sendPort!.send(['clearSelection']);
  303. }
  304. void onMouseTap(Position position) {
  305. if (_sendPort == null) {
  306. return;
  307. }
  308. _sendPort!.send(['mouseMode.onTap', position]);
  309. }
  310. void onPanStart(Position position) {
  311. if (_sendPort == null) {
  312. return;
  313. }
  314. _sendPort!.send(['mouseMode.onPanStart', position]);
  315. }
  316. void onPanUpdate(Position position) {
  317. if (_sendPort == null) {
  318. return;
  319. }
  320. _sendPort!.send(['mouseMode.onPanUpdate', position]);
  321. }
  322. void setScrollOffsetFromBottom(int offset) {
  323. if (_sendPort == null) {
  324. return;
  325. }
  326. _sendPort!.send(['setScrollOffsetFromBottom', offset]);
  327. }
  328. int convertViewLineToRawLine(int viewLine) {
  329. if (_lastState == null) {
  330. return 0;
  331. }
  332. if (_lastState!.viewHeight > _lastState!.bufferHeight) {
  333. return viewLine;
  334. }
  335. return viewLine + (_lastState!.bufferHeight - _lastState!.viewHeight);
  336. }
  337. void write(String text) {
  338. if (_sendPort == null) {
  339. return;
  340. }
  341. _sendPort!.send(['write', text]);
  342. }
  343. void paste(String data) {
  344. if (_sendPort == null) {
  345. return;
  346. }
  347. _sendPort!.send(['paste', data]);
  348. }
  349. void resize(int newWidth, int newHeight) {
  350. if (_sendPort == null) {
  351. return;
  352. }
  353. _sendPort!.send(['resize', newWidth, newHeight]);
  354. }
  355. void raiseOnInput(String text) {
  356. _sendPort!.send(['onInput', text]);
  357. }
  358. void keyInput(
  359. TerminalKey key, {
  360. bool ctrl = false,
  361. bool alt = false,
  362. bool shift = false,
  363. bool mac = false,
  364. // bool meta,
  365. }) {
  366. if (_sendPort == null) {
  367. return;
  368. }
  369. _sendPort!.send(['keyInput', key, ctrl, alt, shift, mac]);
  370. }
  371. }