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

More functionnalities for repl #951

Open
wants to merge 23 commits into
base: master
Choose a base branch
from

Conversation

OscarLahaie
Copy link
Contributor

@OscarLahaie OscarLahaie commented May 11, 2022

This pr is intended to be a part of the work of @ulugbekna in #677.
It would be interesting to have an evaluation system similar to Tuareg in Emacs. For this it is not only necessary to evaluate the user's selection but also when there is no selection the expression corresponding to the cursor position.

The objectives :

  • Evaluate a file
  • Preserve focus on editor after evaluations
  • Evaluate expressions
  • Option to jump to the next expression

@OscarLahaie
Copy link
Contributor Author

This is the cleaner version of pull request #943.

@rgrinberg rgrinberg requested review from ulugbekna and mnxn May 17, 2022 20:18
src/repl.ml Outdated
Comment on lines 149 to 152
if is_repl_ready code then
(* newline is for nicer look in REPL *)
"\n" ^ trimmed_code
else Printf.sprintf "\n%s\n;;" trimmed_code
Copy link
Collaborator

Choose a reason for hiding this comment

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

This formatting sometimes looks worse like in this example with a single expression:

# 
  x        
  ;;       
- : int = 1

Maybe it's worth checking if the code is a single line first? If it is, then there's no need to add any extra formatting.

I would prefer the following in simple cases:

# x ;;       
- : int = 1

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!
I have done some tests and it seems to work well with the new version of the function.
I am not sure about the List.mem efficiency, what do you think about this solution?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The new behavior is great.

String.mem is fine but I'm not sure what the difference is from String.contains that we use in other parts of the extension.

Copy link
Collaborator

@mnxn mnxn left a comment

Choose a reason for hiding this comment

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

A few changes need to be made for Windows and the official OCaml REPL.

I haven't had a chance to try macOS or utop yet.

src/repl.ml Outdated Show resolved Hide resolved
src/repl.ml Outdated Show resolved Hide resolved
@OscarLahaie OscarLahaie marked this pull request as draft May 26, 2022 12:39
@OscarLahaie OscarLahaie marked this pull request as ready for review May 26, 2022 16:14
@OscarLahaie OscarLahaie requested a review from mnxn May 26, 2022 16:14
@OscarLahaie
Copy link
Contributor Author

I made several tests to see if the jump is working but I am sure some cases unknown to me.
So if you find any bugs tell me so I can fix them.

@rgrinberg
Copy link
Contributor

@mnxn is this good to merge?

@ulugbekna
Copy link
Collaborator

I will have a look at this today

@mnxn
Copy link
Collaborator

mnxn commented Jun 11, 2022

is this good to merge?

It looked good to me when I tried it.

Ready to merge unless @ulugbekna finds something.

Copy link
Collaborator

@ulugbekna ulugbekna left a comment

Choose a reason for hiding this comment

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

This needs a changelog entry.

Didn't look too closely at the 180-line function, but functionality added by the PR seems to work well

@@ -165,7 +183,188 @@ module Command = struct
in
Extension_commands.register ~id:Extension_consts.Commands.open_repl handler

let _evaluate_selection =
let _evaluate_expression =
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm a little skeptical about having a 180-line function

@ulugbekna
Copy link
Collaborator

ulugbekna commented Jun 12, 2022

I think this can me squashed and merged, but jumping to next expression implementation needs to be made on the ocamllsp side as a new custom request so that we avoid hacks that we have now.

Also, one should be careful using registerTextEditorCommand because it's supposed to be run synchronously, meaning starting off a promise as we do it now is unsafe. See microsoft/vscode#16814

@OscarLahaie
Copy link
Contributor Author

I think this can me squashed and merged, but jumping to next expression implementation needs to be made on the ocamllsp side as a new custom request so that we avoid hacks that we have now.

Also, one should be careful using registerTextEditorCommand because it's supposed to be run synchronously, meaning starting off a promise as we do it now is unsafe. See microsoft/vscode#16814

I don't have much time this week. Nevertheless I will look at and improve the code in the coming weeks if necessary. Because there are some behaviours to be fixed at the ocaml-lsp level.

Vscode.TextLine.text line
let get_selected_code text_editor =
let selection = TextEditor.selection text_editor in
let document = TextEditor.document text_editor in
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you obtain document right away but only use it in one branch of the conditional?

let is_repl_ready s = String.is_suffix s ~suffix:";;" in
let trimmed_code = String.strip code in
if is_repl_ready trimmed_code then
if String.mem trimmed_code '\n' then
Copy link
Contributor

Choose a reason for hiding this comment

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

As far as I can tell String.mem trimmed_code '\n' is always evaluated. Therefore it should be assigned a binding and moved before the conditionals.

| `ocaml.evaluate-file` | Evaluate Selection | `Shift+Alt+Enter` |

Note: `ocaml.evaluate-expression` was previously named
`ocaml.evaluate-selection`. You can also disabe the new cursor jumps after this
Copy link
Contributor

Choose a reason for hiding this comment

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

Why don't we preserve ocaml.evaluate-selection? I don't see anything wrong with having both commands.

Copy link
Collaborator

Choose a reason for hiding this comment

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

the new command evaluates the selection if there's one; otherwise, it evaluates a (module-level) let or module binding depending which comes first

Copy link
Contributor

Choose a reason for hiding this comment

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

I understood. I just don't see why we should remove a command with a clear name and semantics and replace it with a command that has neither of those things. Seems like we're just irritating users who are used to the old command this way.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Good point, but then I would show a deprecation message saying to use the new command because

  1. The new command has almost the same semantics - select and evaluate (no selection resulted in evaluating the whole line but I'm not sure that's very useful and the new command does the same thing if it's a single-line let binding)
  2. There's then contention for the shortcut: keep the old and miss out on a better command or replace and still irritate

@rgrinberg
Copy link
Contributor

Before merging this, I think we need to get on the same page of what good REPL functionality looks like. I have no interest in copying tuareg and its limitations. We can do a lot better if we think about things a bit more.

@ulugbekna
Copy link
Collaborator

Before merging this, I think we need to get on the same page of what good REPL functionality looks like. I have no interest in copying tuareg and its limitations. We can do a lot better if we think about things a bit more.

Do you have precise limitations you'd like gone before merging?

Things that are not addressed in this PR but can be done in other, IMO:

  • configuration for how module is evaluated (#use or #mod_use) as a dialog
  • configurable REPL instance -- the best decision is to have two commands for picking a terminal to send commands to:
    • one command to pick an integrated terminal that already exists from a list (this allows users to jump into existing repl session and send commands to it, for example)
    • one command to launch a terminal from settings-defined repl binary and args OR default utop - this already exists but it uses a timeout before sending commands to a new REPL -- this needs to be fixed

@rgrinberg
Copy link
Contributor

First of all, as a general comment, there's just too many knobs, settings, commands that all make the experience way too complicated. I've already went through this stuff with tuareg mode. In the end, their toplevel integration is hardly usable.

99% of the time when I use the toplevel, I do the following:

  1. Open a project
  2. Navigate to a module I want to experiment with
  3. Figure out how to load into the toplevel.

We should focus on this core feature first before we worry about directives, selecting repl instances. Finally, we should absolutely avoid any code that tries to parse OCaml. I see this PR is adding quite a bit of that and it's really wasted effort.

Here's a simple idea for how to make the toplevel more useful.

  • We add a command $ dune ocaml top-module path/to/module.ml --source=/tmp/selection/file. This command will either load the toplevel binary or print the directives required to load module.ml as if its source was --source.

  • We add a request to lsp that given a position in the document, will return a completed & pretty printed parse tree. Luckily for us, merlin already knows how to do this.

  • On the vscode side, we add a command to launch a toplevel where the cursor is. It will obtain the source using lsp, then run the dune command mentioned above. There's no need to select toplevel instances. We associate every module with a toplevel instance. If there's already a toplevel for a module, then we kill it and launch it again.

Let's forget about all the other stuff for now.

~position:correct_position
in
match range with
| None -> ()
Copy link
Contributor

Choose a reason for hiding this comment

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

When can this request return None?

@rgrinberg
Copy link
Contributor

A few more thoughts:

  • When there's no up to date lsp server running, the user should only be able to send selections.
  • Requests such as the wrapping ast node should just select the range they're returning. The user can send it manually.
  • Moving to the next thing to evaluate should be a separate command. If someone wants to combine evaluating and moving, they can create their own keyboard shortcut for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants