Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

repl: Support for eager evaluation #22875

Closed
wants to merge 5 commits into from

Conversation

antsmartian
Copy link
Contributor

@antsmartian antsmartian commented Sep 15, 2018

Address the issue #20977

Well, I would say its "initial" checkins. May be work will be required for handling cursors, additional edge cases (may be 🤔 ) etc, but I guess this should be a good starting point.

Added code for handling cursors and also for line refresh, when necessary.

Thanks for your time on this.

  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

@nodejs-github-bot nodejs-github-bot added readline Issues and PRs related to the built-in readline module. repl Issues and PRs related to the REPL subsystem. labels Sep 15, 2018
@Trott
Copy link
Member

Trott commented Sep 15, 2018

@nodejs/repl

@devsnek
Copy link
Member

devsnek commented Sep 15, 2018

any reason this is limited to call expressions?

@antsmartian
Copy link
Contributor Author

antsmartian commented Sep 15, 2018

@devsnek I need to extend to other expressions as well, but just wanted to post my progress here so that I can get review from the community (mainly on whether or not I'm in right direction).

Edit: Or we can first go with call expressions and expand there-after. As always, its all depends on what community says :)

@antsmartian antsmartian changed the title repl: Initial support for eager evaluation repl: Support for eager evaluation Sep 16, 2018
@antsmartian
Copy link
Contributor Author

@devsnek Added support for other expressions and as well for Identifier too.

lib/repl.js Outdated
}

try {
if (isValidExpression(line)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's no need to check if its a valid expression, or even an expression at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just thought sending code for every key press for eval might be unnecessary.

Yes we can remove them, anyways the code block is in try catch block, so in worst case that should catch the exception if any.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v8 will detect invalid code anyway, so there's no need to manually check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devsnek Ok make sense, if I remove the check, then there would be preview results as necessary. Hence, few test cases will be failing (I could see test-repl-persistent-history.js failing already). Let me work on fixing those test cases and update the PR.

Copy link
Member

@Fishrock123 Fishrock123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pretty neat but certainly needs more work (as you seem to be aware)!

Some things I noticed:

  • Not all edits to the current line cause the eager eval to re-appear.
  • The eager eval should probably be colored with grey and the colors of inspect outputs should probably also go though a grey-ing filter.

lib/readline.js Outdated Show resolved Hide resolved
@antsmartian
Copy link
Contributor Author

@Fishrock123 Yes, you are correct. Not all edits (with valid code) trigger eager eval. I knew that it needs to be worked out. Regarding color will change them. Thanks for taking time to review this!

@danbev
Copy link
Contributor

danbev commented Sep 19, 2018

@antsmartian
Copy link
Contributor Author

Just to give an update, I will find some time later this week, afterwhich I will be updating the PR with comments addressed. Sorry for the delay :)

@antsmartian antsmartian force-pushed the eager-20977 branch 3 times, most recently from bcadcce to e005596 Compare October 14, 2018 04:35
@antsmartian
Copy link
Contributor Author

antsmartian commented Oct 14, 2018

@devsnek @Fishrock123 Addressed your comments. Now eager eval will work on all lines (when v8 says it has a preview). Also changed the color to grey. Updated the tests.

Let me know if anything else is needed. Thanks!

@antsmartian
Copy link
Contributor Author

antsmartian commented Oct 18, 2018

Friendly remainder : @devsnek @Fishrock123 🔉

lib/readline.js Outdated
@@ -456,6 +458,28 @@ Interface.prototype._insertString = function(c) {

// a hack to get the line refreshed if it's needed
this._moveCursor(0);
// emit current line for generating preview
this.emit('buffer', this.line);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is there in both the branches, at the end. Perhaps this can be moved out of the if..else.

lib/repl.js Outdated
}, (error, result) => {

if (error) {
// mostly possible side effect exception
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its better to expect the specific exceptions and ignore them. Is that possible?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also if there is an error, will the result object always have result property?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes result will always have result even in case of error. Will refactor the variable to previewResult, so that its better readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And about the exception, it can be syntax issues, side effects etc. Not sure if I can catch them specifically. @devsnek can give some thoughts on this.


assert.ok(actual[0].includes(wrapWithColorCode('\'test\'')));
// side effect scenario, preview is not available
assert.ok(!actual[2].includes('// '));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

appendPreview doesn't have a space character after //. Is this intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, it was a miss. Good catch. Will fix them.

@antsmartian
Copy link
Contributor Author

@thefourtheye I have addressed your comments.

@devsnek @jdalton @Fishrock123 Can you please have a look?

Copy link
Member

@Fishrock123 Fishrock123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting there... also needs some more tests. :)

I'd really like to see this make it into a release though!

lib/readline.js Outdated
};

Interface.prototype.appendPreview = function(result) {
this.previewResult = `\u001b[90m //${result}\u001b[39m`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.previewResult = `\u001b[90m //${result}\u001b[39m`;
this.previewResult = `\u001b[90m // ${result}\u001b[39m`;

lib/repl.js Outdated
@@ -844,10 +844,49 @@ REPLServer.prototype.createContext = function() {
});

addBuiltinLibsToObject(context);
previewResults.call(this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be necessary - can you instead add the function on the class but either prefix the function name with _ or, better, put it behind a Symbol? Something like kPreviewResults = new Symbol('Preview Results Fn'), and then you can do this[kPreviewResults]().

lib/repl.js Outdated

return context;
};

function previewResults() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need this wrapping function? Also, it's confusing, since there is a variable with the same name.

lib/repl.js Outdated
}

if (undefined !== previewResult.result.value) {
const value = util.inspect(previewResult.result.value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure this is what you think it is?

It currently seems to be printing Objects as Arrays of values:

> process.versions
{ http_parser: '2.8.0',
  node: '12.0.0-pre',
  v8: '7.0.276.38-node.11',
  uv: '1.24.0',
  zlib: '1.2.11',
  ares: '1.15.0',
  modules: '67',
  nghttp2: '1.34.0',
  napi: '3',
  openssl: '1.1.0i',
  icu: '63.1',
  unicode: '11.0',
  cldr: '34.0',
  tz: '2018e' }
> process.versions //[ '2.8.0', '12.0.0-pre', '7.0.276.38-node.11', '1.24.0', '1.2.11' ]

lib/repl.js Outdated

if (previewResult.result.preview) {
const previewResults = previewResult.result.preview.properties;
const previewData = previewResults.map((preview) => preview.value); // eslint-disable-line max-len
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should avoid the eslint message:

Suggested change
const previewData = previewResults.map((preview) => preview.value); // eslint-disable-line max-len
const previewData = previewResults.map(
(preview) => preview.value
);

lib/repl.js Outdated
Interface.prototype.appendPreview.call(this, value);
}
});
}, () => []);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean this?

Suggested change
}, () => []);
}, () => {});

r.on('exit', common.mustCall(() => {
const actual = output.split('\n');

const wrapWithColorCode = (str) => `\u001b[90m //${str}\u001b[39m`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const wrapWithColorCode = (str) => `\u001b[90m //${str}\u001b[39m`;
const wrapWithColorCode = (str) => `\u001b[90m // ${str}\u001b[39m`;

@antsmartian
Copy link
Contributor Author

@Fishrock123 Thanks for your review, will update the thread once I'm done with your review comments.

@antsmartian
Copy link
Contributor Author

@Fishrock123 Addressed review comments. I guess few things I need do to:

  1. Add more test. I thought of covering the below:
    a. For a simple fn expression
    b. Nested objects.
    c. Nested objects along with methods.
  2. When a huge object is inspected, we do get cursor move out of the current position, I need to fix that as well.

@antsmartian antsmartian force-pushed the eager-20977 branch 3 times, most recently from 319ea89 to 87b14fc Compare November 28, 2018 10:23
@antsmartian
Copy link
Contributor Author

antsmartian commented Nov 28, 2018

@nodejs/repl PTAL.. Have added test cases and made changes requested by @Fishrock123

lib/readline.js Outdated Show resolved Hide resolved
lib/readline.js Outdated Show resolved Hide resolved
@antsmartian
Copy link
Contributor Author

@BridgeAR will address your comments.
@nodejs/repl @Fishrock123 Friendly ping. Can we get some reviews on this? I want to get this to a closure.. Its open for sometime

@antsmartian
Copy link
Contributor Author

@nodejs/repl @Fishrock123 @BridgeAR PTAL..

@BridgeAR
Copy link
Member

BridgeAR commented Apr 1, 2019

@antsmartian have you seen my comments? They still seem to apply?

@antsmartian
Copy link
Contributor Author

@BridgeAR Yes, I have addressed the comments and also added check for color part in the prev commit, may be you can have a look now. Thanks!

lib/readline.js Outdated

if (columns) {
const colorDepth = this.output.getColorDepth();
const s = colorDepth > 256 ?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The maximum color depth is 24. You can also use hasColors() instead of getColorDepth() as it's more intuitive to use.

lib/readline.js Outdated
const colorDepth = this.output.getColorDepth();
const s = colorDepth > 256 ?
`${this._prompt}${this.line}${this.previewResult}` :
`${this._prompt}${this.line}${result}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not identical to the color part (it does not only strip the colors).

lib/readline.js Outdated
const s = colorDepth > 256 ?
`${this._prompt}${this.line}${this.previewResult}` :
`${this._prompt}${this.line}${result}`;
this.output.write(s.length < columns ?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The length check includes the colors but that should ideally not be the case. Colors are not visible characters and should as such not be calculated towards the total length.

lib/readline.js Outdated
`${this._prompt}${this.line}${result}`;
this.output.write(s.length < columns ?
s : `${s.slice(0, columns - 3)
.replace(/\r?\n|\r/g, '')}...\u001b[39m`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds the color unconditionally. Can you also please elaborate what exactly this does? I am not sure I understand why this part is sliced and replaced.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The slice and replace part is mainly for splitting the new lines. Since in output terminal has only limited columns, we need to split them and replace with empty space so that we can show them the content in single line.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already use util.inspect() on the preview. We should use options that deactivate the colors for the preview and make sure the output is printed on a single line. That can be done with the compact options set to true in combination with breakLength set to Infinity and colors set to false.

I would then limit the preview only instead of limiting the input as well. Right now we unconditionally slice the input value and that doesn't seem right.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BridgeAR: (sorry for a very late reply)

What you are saying is actually a good idea. But I was just trying to play with the options provided by you but still that doesn't seems to be atleast fixing the problem what we have now. For example, if the user types process, we get a long string, so if we apply the options like below:

util.inspect(process, {breakLength: Infinity, compact: true})

still we are getting results like below:

Screen Shot 2019-11-27 at 11 36 47 AM

This actually breaks the cursor position as the output is huge. I feel, the only way for us to slice the string to the length of the tty column. May be is there any other way we could achieve it? 🤔

lib/readline.js Outdated

// Append eager eval result to the
// Current line
Interface.prototype._appendPreview = function(result) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be possible to add internal functionality in a separate file that would only be loaded internally. That way we do not have to pollute the prototype further. An underscore in a property name would not hinder anyone from using or monkey patching this function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I will move them to internal/readline/util.

lib/repl.js Outdated
if (!previewResult.exceptionDetails && previewResult.result.objectId) {
eagerSession.post('Runtime.callFunctionOn', {
functionDeclaration:
'function(arg) { return util.inspect(arg, {depth: Infinity}) }',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

depth: Infinity does not sound like a good default to me. The preview should ideally be short and on the same line. This could cause the terminal to add lots of output.

lib/readline.js Outdated
s : `${s.slice(0, columns - 3)
.replace(/\r?\n|\r/g, '')}...\u001b[39m`);
} else {
this.output.write(this._prompt + this.line + this.previewResult);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds colors unconditionally.

lib/readline.js Outdated
const columns = this.output.columns;
const hasColors = this.output.hasColors();
const s = hasColors ?
`${this._prompt}${this.line}${this.previewResult}` : line;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally would keep the preview, even if no colors are supported. The comment should be indication enough.

If we do not want to have a preview in case the colors are deactivated, we should not start the eagerSession in the REPL. That way no code is run unnecessary.

And is it correct that this._prompt is necessary in one case but not in the other?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, I'm planning to send the output to this function and then use util.inspect within the function to customize our output. Do you think that would be a better approach? Because anyways I need to call util.inspect in my REPL session.

lib/readline.js Outdated
`${this._prompt}${this.line}${result}`;
this.output.write(s.length < columns ?
s : `${s.slice(0, columns - 3)
.replace(/\r?\n|\r/g, '')}...\u001b[39m`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already use util.inspect() on the preview. We should use options that deactivate the colors for the preview and make sure the output is printed on a single line. That can be done with the compact options set to true in combination with breakLength set to Infinity and colors set to false.

I would then limit the preview only instead of limiting the input as well. Right now we unconditionally slice the input value and that doesn't seem right.

@lundibundi
Copy link
Member

ping @antsmartian, will you be working on this further?

@antsmartian
Copy link
Contributor Author

@lundibundi Yes. This is open for long time ( I know :( ), I wanted it to get closed. Will look at it this week. Will update the PR soon.

Copy link
Member

@lundibundi lundibundi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 just a few nits and code refactoring suggestions.

lib/repl.js Outdated Show resolved Hide resolved
lib/repl.js Outdated Show resolved Hide resolved
lib/internal/readline/utils.js Outdated Show resolved Hide resolved
lib/repl.js Outdated Show resolved Hide resolved
lib/repl.js Outdated Show resolved Hide resolved
lib/repl.js Outdated
Comment on lines 300 to 302
// If no exception and we have objectId
// Run the expression via callFunctionOn
// And return it from util inspect.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

// If there was no exception and we've got an objectId
// stringify it with `util.inspect` via Runtime.callFunctionOn.

@antsmartian
Copy link
Contributor Author

@lundibundi @nodejs/repl PTAL.. 🔉

Copy link
Member

@lundibundi lundibundi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly LGTM from me, but I'm not that familiar with repl code to put a ✔️. This will have to get approval from @BridgeAR or someone else from @nodejs/repl.
Great work, would love to see preview in the repl.

return readline;
};

const { cursorTo, clearScreenDown } = lazyReadline();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 can we just const { cursorTo, clearScreenDown } = require('readline') at the top now?

repl.output.write(s);
}

// Move back the cursor to the original position
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
// Move back the cursor to the original position
// Move back the cursor to the original position.

@@ -87,7 +89,98 @@ function isRecoverableError(e, code) {
}
}

// Appends the preview of the result
// to the tty.
function appendPreview(repl, result, cursorTo, clearScreenDown) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function appendPreview(repl, result, cursorTo, clearScreenDown) {
function appendPreview(repl, preview, cursorTo, clearScreenDown) {

maybe? To make it a little clearer.

Comment on lines +92 to +93
// Appends the preview of the result
// to the tty.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think this will fit on one line.

@@ -490,6 +491,8 @@ Interface.prototype._insertString = function(c) {
// A hack to get the line refreshed if it's needed
this._moveCursor(0);
}
// Emit current line for generating preview
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
// Emit current line for generating preview
// Emit current line for generating preview.

return;
}

if (undefined !== previewResult.result.value) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
if (undefined !== previewResult.result.value) {
if (previewResult.result.value !== undefined) {

Comment on lines +230 to +231
eagerSession.once('Runtime.executionContextCreated',
({ params: { context } }) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps to avoid big indentation:

eagerSession.once(
  'Runtime.executionContextCreated',
  ({ params: { context } }) => {
  
  })

Comment on lines +107 to +109
repl.output.write(line.length < columns ?
s : `${s.slice(0, columns - 3)
.replace(/\r?\n|\r/g, '')}...\u001b[39m`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

    repl.output.write(line.length < columns ?
      s : s.slice(0, columns - 3).replace(/\r?\n|\r/g, '') + '...\u001b[39m');

@nodejs-github-bot
Copy link
Collaborator

@antsmartian antsmartian added the review wanted PRs that need reviews. label Dec 3, 2019
@BridgeAR
Copy link
Member

BridgeAR commented Dec 9, 2019

This got superseded by #30811
@antsmartian thank you very much for the awesome work!

@BridgeAR BridgeAR closed this Dec 9, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
readline Issues and PRs related to the built-in readline module. repl Issues and PRs related to the REPL subsystem. review wanted PRs that need reviews.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants