'use strict'; var _ = require('lodash'); var util = require('./readline'); var readline = require('readline'); var cliWidth = require('cli-width'); var stripAnsi = require('strip-ansi'); var ScreenManager = module.exports = function (rl) { // These variables are keeping information to allow correct prompt re-rendering this.height = 0; this.extraLinesUnderPrompt = 0; this.rl = rl; }; ScreenManager.prototype.render = function (content, opt) { opt = _.extend({ cursor: 0 }, opt || {}); var cursorPos = this.rl._getCursorPos(); this.rl.output.unmute(); this.clean(this.extraLinesUnderPrompt); /** * Write message to screen and setPrompt to control backspace */ var lines = content.split(/\n/); var promptLine = lines[lines.length - 1 - opt.cursor]; var rawPromptLine = stripAnsi(promptLine); // Remove the rl.line from our prompt. We can't rely on the content of // rl.line (mainly because of the password prompt), so just rely on it's // length. var prompt = promptLine; if (this.rl.line.length) { prompt = prompt.slice(0, -this.rl.line.length); } this.rl.setPrompt(prompt); var rawPrompt = stripAnsi(prompt); // Manually insert an extra line if we're at the end of the line. // This prevent the cursor from appearing at the beginning of the // current line. if (rawPromptLine.length === cliWidth()) { lines.splice(lines.length, 0, ' '); } this.rl.output.write(lines.join('\n')); /** * Re-adjust the cursor at the correct position. */ var breakedLines = breakLines(lines); var actualLines = _.flatten(breakedLines); var promptLineUpDiff = Math.floor(rawPromptLine.length / cliWidth()) - cursorPos.rows; if (opt.cursor + promptLineUpDiff > 0) { util.up(this.rl, opt.cursor + promptLineUpDiff); } // Reset cursor at the beginning of the line util.left(this.rl, stripAnsi(_.last(actualLines)).length); var rightPos = cursorPos.cols; if (cursorPos.rows === 0) { rightPos = Math.max(rightPos, rawPrompt.length); } // rightPos should never be further than the total line content size. // If we changed the prompt and reset the rl.line, we want to reset our // cursor at the beginning of the prompt. if (rightPos > rawPromptLine.length && !this.rl.line) { rightPos = rawPrompt.length; } util.right(this.rl, rightPos); /** * Set up state for next re-rendering */ var bottomSection = breakedLines.slice(breakedLines.length - opt.cursor - promptLineUpDiff); this.extraLinesUnderPrompt = _.flatten(bottomSection).length; this.height = actualLines.length; this.rl.output.mute(); }; ScreenManager.prototype.clean = function (extraLines) { if (extraLines > 0) { util.down(this.rl, extraLines); } util.clearLine(this.rl, this.height); }; ScreenManager.prototype.done = function () { this.rl.setPrompt(''); this.rl.output.unmute(); this.rl.output.write('\n'); }; function breakLines(lines) { // Break lines who're longuer than the cli width so we can gracefully handle line // returns. var regex = new RegExp('.{1,' + cliWidth() + '}', 'g'); return lines.map(function (line) { return stripAnsi(line).match(regex); }); }