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: add support for multiline history #57400

Merged
merged 13 commits into from
Apr 13, 2025

Conversation

puskin94
Copy link
Contributor

@puskin94 puskin94 commented Mar 10, 2025

Previously, when navigating the history in the Node.js REPL, multiline commands were displayed line by line, making it difficult to review and edit complete blocks of input.

This PR improves the REPL history behavior by treating multiline commands as a single entry. Now, when scrolling through history, entire blocks of multiline input are displayed together, improving usability and consistency.

Key changes:

  • Adjusted history handling to store and retrieve multiline commands as single entries.
  • Updated navigation logic to properly display multiline inputs.
  • Improved editing experience, allowing users to modify multiline history entries in place.
  • Changed the multiline indicator: from ... to |, to keep the indentation consistent
  • Added the multiline indicator also when editing the history, not only when adding new lines
  • If the last command caused some kind of error, you can now edit the multiline history, and if you remove the error, the history which caused the error will be replaced with the correct one

Important:

This change relies on the fact that I am now formatting new history entries and saving them in the .node_replhistory file in a specific format. While this does not break backward compatibility, it does mean that switching between older and newer versions (i.e., versions without and with this change) will result in multiline history entries not being preserved if they were created with the latest changes.

Impact:

This change significantly enhances the REPL experience for users who frequently enter complex or multiline expressions, making command recall and editing more intuitive.

Testing:

  • Manually tested with various multiline inputs, both on unix and windows systems
  • Verified history navigation behaves as expected with both single-line and multiline commands.

Demos

Before:

before

After:

after

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. readline Issues and PRs related to the built-in readline module. repl Issues and PRs related to the REPL subsystem. labels Mar 10, 2025
@puskin94 puskin94 force-pushed the repl-multiline-history branch 2 times, most recently from 9137def to 5f9b8f1 Compare March 12, 2025 10:27
@puskin94 puskin94 marked this pull request as ready for review March 12, 2025 10:27
Copy link

codecov bot commented Mar 12, 2025

Codecov Report

Attention: Patch coverage is 96.62921% with 6 lines in your changes missing coverage. Please review.

Project coverage is 90.24%. Comparing base (27f98c3) to head (cab0f16).
Report is 249 commits behind head on main.

Files with missing lines Patch % Lines
lib/internal/readline/interface.js 95.38% 4 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #57400      +/-   ##
==========================================
+ Coverage   90.20%   90.24%   +0.03%     
==========================================
  Files         630      630              
  Lines      185307   185613     +306     
  Branches    36269    36400     +131     
==========================================
+ Hits       167162   167507     +345     
+ Misses      11084    10994      -90     
- Partials     7061     7112      +51     
Files with missing lines Coverage Δ
lib/internal/repl/history.js 88.46% <100.00%> (+2.19%) ⬆️
lib/internal/repl/utils.js 96.79% <100.00%> (+0.52%) ⬆️
lib/repl.js 95.03% <100.00%> (+0.13%) ⬆️
lib/internal/readline/interface.js 96.76% <95.38%> (-0.19%) ⬇️

... and 145 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@puskin94 puskin94 force-pushed the repl-multiline-history branch from 5f9b8f1 to 8b261ae Compare March 12, 2025 12:26
@aduh95
Copy link
Contributor

aduh95 commented Mar 16, 2025

/cc @nodejs/repl

@puskin94
Copy link
Contributor Author

@BridgeAR you, being the requestor, maybe want to give this a shot 😄

Copy link
Member

@BridgeAR BridgeAR left a comment

Choose a reason for hiding this comment

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

This is a great start!

I did play around with it and I found the following issues:

  1. A history item is now not properly deduplicated anymore if it spans over multiple lines. That is the case with other entries when jumping through the history.
  2. When entering a multiline command that fails to evaluate, it is handled as individual lines. Especially those cases would be great to "correct". For comfort reasons, I would even consider deleting the broken one, if that one is edited and saved successfully right as following command.
  3. Moving up and down while working on a multiline entry feels somewhat off. It is opinionated, but I believe the former history entry should only be shown in case the cursor is at the top line while editing a multiline input. The reason is, that I likely want to finish the input and not change to the former history entry.

I would also change the ... to |. That way the indentation while entering something is identical. I guess we can still look into doing this potentially while going through the entries in the history but at least it is nicer during initial entry.

@puskin94 puskin94 force-pushed the repl-multiline-history branch 5 times, most recently from a434fe4 to 8e71cdf Compare March 18, 2025 15:13
@puskin94 puskin94 force-pushed the repl-multiline-history branch 2 times, most recently from 897b341 to 3418b3b Compare March 19, 2025 21:01
@puskin94 puskin94 force-pushed the repl-multiline-history branch from 3418b3b to b41273a Compare March 21, 2025 16:28
@puskin94 puskin94 requested a review from BridgeAR March 25, 2025 15:42
Copy link
Member

@BridgeAR BridgeAR left a comment

Choose a reason for hiding this comment

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

This is great work! I am looking forward to landing this change!

I left a few comments, mainly to understand some things better.

Copy link
Member

@ljharb ljharb left a comment

Choose a reason for hiding this comment

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

seems good

@nodejs-github-bot
Copy link
Collaborator

@nodejs-github-bot
Copy link
Collaborator

Copy link
Member

@BridgeAR BridgeAR left a comment

Choose a reason for hiding this comment

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

It is a bit difficult to tell if this change should be considered as minor or major. Any further opinion?

@ljharb
Copy link
Member

ljharb commented Apr 5, 2025

Since using an old version of node corrupts the history to split lines, i'd say it's major. If we can come up with a way to avoid that, then it'd be minor, and aggresively backportable, which would be ideal.

@puskin94 puskin94 force-pushed the repl-multiline-history branch from f2e778e to 37c339b Compare April 6, 2025 07:16
@puskin94
Copy link
Contributor Author

puskin94 commented Apr 6, 2025

@ljharb

corrupts the history to split lines: it is true, but you are not breaking anything that was existing before this PR, it is not breaking existing functionality, it is breaking new functionality.
This might be a valid point or not, but I would be curious to know how many people are constantly switching node versions on a daily basis, because this would be the only case where this issue would arise.
On the topic of "restoring" existing history:
I (I had other people thinking with me 😄 ) spent quite some time thinking about how / if we could understand and differentiate which "history version" we are reading from the history file, but it is a lot more complicated than what it seems like. These are the options:

Introducing a new history file format

something new, a .node_repl_history_v2, so to speak, which would be started to be written as soon as this PR lands.

🟢 Pros: there is no complicated logic to determine which history you want to load. If you have new node, read the new history file with the new format, otherwise the old history file with the old node.
🔴 Cons: we need to maintain 2 different history files, with 2 different formats, with 2 specific logics in the codebase to port/understand the history when loading and saving.

Introducing an additional "metadata" history file

something new, a .node_repl_history_metadata, so to speak, which would be started to be written as soon as this PR lands, which would only keep track where lines are to be treated as single lines or multilines.

🟢 Pros: there is no complicated logic to determine which history you want to load. If you have new node, read the new history "metadata" file and parse the history file accordingly, otherwise don't parse the "metadata" file and just read the history file.
🔴 Cons: we need to maintain 2 different files. Adding logic to handle the metadata which need to be updated when you load and save the file, it can become tricky really fast, and switching between old and new node would break as well.

Parsing the history file and trying to restore multiline structures from the old history file

when the old node lands and people start using it, it could try to parse the history file and understand, line by line, if a group of lines would be a single multiline structure.

🟢 Pros: you only handle a single history file.
🔴 Cons: it sounds easy on paper, it is not 😄 this is actually my first implementation, which I executed using acorn to parse the history file, and it worked great!
The only problem is that, reading line by line, the parsing in the lib was emitting syntax exceptions until it found the next line which was "completing the multiline structure", and then considered that a single multiline history. The issue tho is: what if there is a syntax exception in the history file because the user put it there? How does acorn know that the command is broken and it does not need to go to the next line to try to complete the multline command?
It might be possible, but it would be O(n^2) complex, which is not great. (thanks @BridgeAR for this insight)

@puskin94 puskin94 force-pushed the repl-multiline-history branch from 37c339b to 8f52797 Compare April 6, 2025 08:13
@ljharb
Copy link
Member

ljharb commented Apr 8, 2025

I was wondering if there's a way to avoid that parse by using unicode newlines instead of normal ones, such that older node handles it normally but newer node recognizes it as multiline input?

@BridgeAR BridgeAR added the notable-change PRs with changes that should be highlighted in changelogs. label Apr 9, 2025
Copy link
Contributor

github-actions bot commented Apr 9, 2025

The notable-change PRs with changes that should be highlighted in changelogs. label has been added by @BridgeAR.

Please suggest a text for the release notes if you'd like to include a more detailed summary, then proceed to update the PR description with the text or a link to the notable change suggested text comment. Otherwise, the commit will be placed in the Other Notable Changes section.

@BridgeAR BridgeAR added the semver-minor PRs that contain new features and should be released in the next minor version. label Apr 9, 2025
@BridgeAR
Copy link
Member

BridgeAR commented Apr 9, 2025

After looking into this again, I do not believe that it's possible to get the unicode line breaks to work in older Node.js versions. So the only way I believe is viable without renaming the file is to accept that switching to an older version removes the multilines for those history entries.
What we can do is to try to backport this to as many versions as possible.

@puskin94 puskin94 force-pushed the repl-multiline-history branch from fcf01f7 to 603b1d3 Compare April 9, 2025 13:42
@puskin94 puskin94 force-pushed the repl-multiline-history branch from 603b1d3 to 23f7e47 Compare April 10, 2025 15:20
This will make it consistent when loading the new history format with an old node binary
Copy link
Member

@BridgeAR BridgeAR left a comment

Choose a reason for hiding this comment

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

LGTM! Thank you for the great work! 🥳

@BridgeAR BridgeAR added the request-ci Add this label to start a Jenkins CI on a PR. label Apr 12, 2025
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Apr 12, 2025
@nodejs-github-bot
Copy link
Collaborator

nodejs-github-bot commented Apr 12, 2025

@nodejs-github-bot
Copy link
Collaborator

@BridgeAR BridgeAR added the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Apr 13, 2025
@nodejs-github-bot
Copy link
Collaborator

@BridgeAR BridgeAR added commit-queue Add this label to land a pull request using GitHub Actions. commit-queue-squash Add this label to instruct the Commit Queue to squash all the PR commits into the first one. labels Apr 13, 2025
@nodejs-github-bot nodejs-github-bot removed the commit-queue Add this label to land a pull request using GitHub Actions. label Apr 13, 2025
@nodejs-github-bot nodejs-github-bot merged commit 4a4aa58 into nodejs:main Apr 13, 2025
67 checks passed
@nodejs-github-bot
Copy link
Collaborator

Landed in 4a4aa58

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
author ready PRs that have at least one approval, no pending requests for changes, and a CI started. commit-queue-squash Add this label to instruct the Commit Queue to squash all the PR commits into the first one. needs-ci PRs that need a full CI run. notable-change PRs with changes that should be highlighted in changelogs. readline Issues and PRs related to the built-in readline module. repl Issues and PRs related to the REPL subsystem. semver-minor PRs that contain new features and should be released in the next minor version.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants