screen-manager.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. 'use strict';
  2. var _ = require('lodash');
  3. var util = require('./readline');
  4. var readline = require('readline');
  5. var cliWidth = require('cli-width');
  6. var stripAnsi = require('strip-ansi');
  7. var ScreenManager = module.exports = function (rl) {
  8. // These variables are keeping information to allow correct prompt re-rendering
  9. this.height = 0;
  10. this.extraLinesUnderPrompt = 0;
  11. this.rl = rl;
  12. };
  13. ScreenManager.prototype.render = function (content, opt) {
  14. opt = _.extend({ cursor: 0 }, opt || {});
  15. var cursorPos = this.rl._getCursorPos();
  16. this.rl.output.unmute();
  17. this.clean(this.extraLinesUnderPrompt);
  18. /**
  19. * Write message to screen and setPrompt to control backspace
  20. */
  21. var lines = content.split(/\n/);
  22. var promptLine = lines[lines.length - 1 - opt.cursor];
  23. var rawPromptLine = stripAnsi(promptLine);
  24. // Remove the rl.line from our prompt. We can't rely on the content of
  25. // rl.line (mainly because of the password prompt), so just rely on it's
  26. // length.
  27. var prompt = promptLine;
  28. if (this.rl.line.length) {
  29. prompt = prompt.slice(0, -this.rl.line.length);
  30. }
  31. this.rl.setPrompt(prompt);
  32. var rawPrompt = stripAnsi(prompt);
  33. // Manually insert an extra line if we're at the end of the line.
  34. // This prevent the cursor from appearing at the beginning of the
  35. // current line.
  36. if (rawPromptLine.length === cliWidth()) {
  37. lines.splice(lines.length, 0, ' ');
  38. }
  39. this.rl.output.write(lines.join('\n'));
  40. /**
  41. * Re-adjust the cursor at the correct position.
  42. */
  43. var breakedLines = breakLines(lines);
  44. var actualLines = _.flatten(breakedLines);
  45. var promptLineUpDiff = Math.floor(rawPromptLine.length / cliWidth()) - cursorPos.rows;
  46. if (opt.cursor + promptLineUpDiff > 0) {
  47. util.up(this.rl, opt.cursor + promptLineUpDiff);
  48. }
  49. // Reset cursor at the beginning of the line
  50. util.left(this.rl, stripAnsi(_.last(actualLines)).length);
  51. var rightPos = cursorPos.cols;
  52. if (cursorPos.rows === 0) {
  53. rightPos = Math.max(rightPos, rawPrompt.length);
  54. }
  55. // rightPos should never be further than the total line content size.
  56. // If we changed the prompt and reset the rl.line, we want to reset our
  57. // cursor at the beginning of the prompt.
  58. if (rightPos > rawPromptLine.length && !this.rl.line) {
  59. rightPos = rawPrompt.length;
  60. }
  61. util.right(this.rl, rightPos);
  62. /**
  63. * Set up state for next re-rendering
  64. */
  65. var bottomSection = breakedLines.slice(breakedLines.length - opt.cursor - promptLineUpDiff);
  66. this.extraLinesUnderPrompt = _.flatten(bottomSection).length;
  67. this.height = actualLines.length;
  68. this.rl.output.mute();
  69. };
  70. ScreenManager.prototype.clean = function (extraLines) {
  71. if (extraLines > 0) {
  72. util.down(this.rl, extraLines);
  73. }
  74. util.clearLine(this.rl, this.height);
  75. };
  76. ScreenManager.prototype.done = function () {
  77. this.rl.setPrompt('');
  78. this.rl.output.unmute();
  79. this.rl.output.write('\n');
  80. };
  81. function breakLines(lines) {
  82. // Break lines who're longuer than the cli width so we can gracefully handle line
  83. // returns.
  84. var regex = new RegExp('.{1,' + cliWidth() + '}', 'g');
  85. return lines.map(function (line) {
  86. return stripAnsi(line).match(regex);
  87. });
  88. }