Reputation: 66590
I have REPL for my language in Node.js and I'm auto indenting lines when user type text by hand and press enter so he have text indented. The problem is that when he copy/paste text that have indents it have double indent.
The problem is that I'm adding indent before the newline happen. So my idea to fix this issue is to some how delete my spaces (rewrite the line) after user press enter and he already have spaces. But I don't know how to do that. Is it possible maybe using some ANSI codes.
my full REPL looks like this:
if (process.stdin.isTTY) {
console.log(banner);
}
var prompt = 'lips> ';
var continuePrompt = '... ';
var terminal = !!process.stdin.isTTY && !(process.env.EMACS || process.env.INSIDE_EMACS);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: prompt,
terminal
});
if (process.stdin.isTTY) {
rl.prompt();
}
var code = '';
var multiline = false;
var resolve;
var newline;
// we use promise loop to fix issue when copy paste list of S-Expression
var prev_eval = Promise.resolve();
boostrap(interp).then(function() {
rl.on('line', function(line) {
code += line.replace(/^\s+/, '') + '\n';
try {
if (balanced_parenthesis(code)) {
rl.pause();
prev_eval = prev_eval.then(function() {
var result = run(code, interp);
code = '';
return result;
}).then(function(result) {
if (process.stdin.isTTY) {
print(result);
if (newline) {
// readline don't work with not endend lines
// it ignore those so we end then ourselfs
process.stdout.write("\n");
newline = false;
}
if (multiline) {
rl.setPrompt(prompt);
multiline = false;
}
rl.prompt();
}
rl.resume();
}).catch(function() {
if (process.stdin.isTTY) {
if (multiline) {
rl.setPrompt(prompt);
multiline = false;
}
rl.prompt();
}
});
} else {
multiline = true;
var ind = indent(code, 2, prompt.length - continuePrompt.length);
rl.setPrompt(continuePrompt);
rl.prompt();
var spaces = new Array(ind + 1).join(' ');
if (terminal) {
rl.write(spaces);
} else {
process.stdout.write(spaces);
code += spaces;
}
}
} catch (e) {
console.error(e.message);
code = '';
rl.prompt();
}
});
});
I'm using rl.write(spaces); in multiline command after user type his line and I need to check if
I think that I need is to modify the line that have cursor in Node.js Readline.
I've tried:
rl.on('line', function(line) {
if (line.match(/^\s/)) {
rl.write(null, { ctrl: true, name: 'u' });
rl.write((multiline ? continuePrompt : prompt) + line);
}
line = line.replace(/^\s+/, '');
code += line + '\n';
and
rl.on('line', function(line) {
var have_spaces = line.match(/^\s/);
line = line.replace(/^\s+/, '');
if (have_spaces) {
rl.pause();
process.stdout.write('\x1b[1K\x1b[0K' + line);
rl.resume();
}
code += line + '\n';
but both just echo in next line after I press enter, not the line that have cursor in. How to modify line that have cursor? I happy with library that will do that, I was not able to find any that will be helpful.
Upvotes: 2
Views: 2756
Reputation: 66590
Here is what I've got, to exactly answer the question in title, you can modify the input using keypress event:
rl._writeToOutput = function _writeToOutput(string) {
rl.output.write(scheme(string));
};
process.stdin.on('keypress', (c, k) => {
setTimeout(function() {
rl._refreshLine(); // force refresh colors
}, 0);
});
this highlight the line and refresh the line on each keypress to force colors.
And for indent I've solved my issue using this code and ANSI escape codes.
var terminal = !!process.stdin.isTTY && !(process.env.EMACS || process.env.INSIDE_EMACS);
bootstrap(interp).then(function() {
rl.on('line', function(line) {
code += line;
const lines = code.split('\n');
const cols = process.stdout.columns;
// fix formatting for previous lines that was echo
// ReadLine will not handle those
if (terminal && lines.length > 2) {
var count = 0;
// correction when line wrapps in original line
// that will be overwritten
lines.map(line => {
if (line.length > cols) {
count += Math.ceil(line.length / cols) - 1;
}
});
var f = new Formatter(code);
code = f.format();
const stdout = scheme(code).split('\n').map((line, i) => {
var prefix;
if (i === 0) {
prefix = unify_prompt(prompt, continuePrompt);
} else {
prefix = unify_prompt(continuePrompt, prompt);
}
return '\x1b[K' + prefix + line;
}).join('\n');
let num = lines.length + count;
const format = `\x1b[${num}F${stdout}\n`;
process.stdout.write(format);
}
code += '\n';
...
There is only one issue with this code when lines are longer then the width of the terminal the updating of lines are broken, and when you copy paste text last line is not indented properly (it have double indent) until you press enter.
Upvotes: 1
Reputation: 3
In readline module, whenever you press enter, readline clears the current line then emits the line event: readline.js source code, meaning you can't modify the previous line's output when the line event fires. This sounds disappointing, but we have a workaround:
const readline = require('readline')
const input = process.stdin
const rl = readline.createInterface({
input: input,
output: process.stdout,
})
rl.prompt()
input.setEncoding('utf8')
input.on('data', (input) => {
const REGEXP = /^\s+/
let haveSpaces = null
if (input.length > 1) {
input = input.replace(REGEXP, (match) => {
if (match) {
haveSpaces = true
return ''
}
})
if (haveSpaces) {
rl.write(null, { ctrl: true, name: 'u' })
rl.write(input)
}
}
})
By listening to the input stream's data
event, you can determine whether the user pasted a string with indentation and then modify the input
const { replLive, onInput } = require('repll')
const repll = replLive(['repll› '], 'A meaningful placeholder')
onInput((input) => {
const REGEXP = /^\s+/
let haveSpaces = null
if (input.length > 1) {
input = input.replace(REGEXP, (match) => {
if (match) {
haveSpaces = true
return ''
}
})
if (haveSpaces) {
repll.write(null, { ctrl: true, name: 'u' })
repll.write(input)
repll.refresh('Your pasted text have indents! We already fixed that')
}
}
})
I have a module(repll) that handles these scenario with livly output and placeholder and many other interactive features.
repl.it
(cause i do not want this answer gets too long to read).Remember to run it inside shell
(not console
)Upvotes: 0