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

OCamlformat RPC #386

Merged
merged 4 commits into from
Jul 8, 2021
Merged

OCamlformat RPC #386

merged 4 commits into from
Jul 8, 2021

Conversation

voodoos
Copy link
Collaborator

@voodoos voodoos commented Mar 16, 2021

This PR add support for formatting of types in popups. Types provided by Merlin are not always nicely formatted and module signatures often wrap because it exceeds the maximum number of columns of a VSCode popup (64 characters on my setup). The idea is to use OCamlformat to format these types, but starting a new process would be to heavy for that usage.

In a joint effort with the OCamlformat team this PR acheive type formatting by spawning an "OCamlformat RPC server" and then communicating with it using a simple protocol. The protocol is vendored for now (in the file ocamlformat_rpc_lib.ml but will be made available as a separate and common package.

In case of failure to start the server, negotiate a version or receive a response we log some information and fallback to the non-formatted type.

Todo

  • Wait for OCaml-RPC lib to be published as a separate package and remove the vendored copy.
  • Change the program name to the definitive name of the OCamllsp-RPC server when known. (for now it is a hard path to my copy of OCamlformat).

Related works

  • This OCamlformat PR tracks the progress on the RPC library and the server implementation.
  • This mdx PR tracks the progress on implementing a client similar to the one of this PR to format code blocks in mdx.

@voodoos voodoos added the enhancement New feature or request label Mar 16, 2021
@gpetiot
Copy link

gpetiot commented Apr 2, 2021

ocamlformat-rpc-lib is now available on opam!

@voodoos voodoos force-pushed the ocf-rpc branch 2 times, most recently from 0dd9f6d to 6738834 Compare June 22, 2021 15:14
@voodoos voodoos marked this pull request as ready for review June 22, 2021 15:14
@voodoos
Copy link
Collaborator Author

voodoos commented Jun 23, 2021

About error messages, I think most of them will stay as debug messages.
The only user-facing message should be an information diagnostic showing when OCaml-LSP cannot find the ocamlformat-rpc binary and the user should install it for full functionality.

OCamlformat crashes and errors should be kept in the debug log: there are useful for devs to debug issues but useless to the users and unrelated to their programs.

@voodoos
Copy link
Collaborator Author

voodoos commented Jun 29, 2021

As you suggested it in a comment I decided to stick more closely to how Dune RPC process is handled (in
42b0971):
I start the OCamllsp_rpc server and wait for it to finish in Ocaml_lsp_server.start:

Fiber.parallel_iter
          ~f:(fun f -> f ())
          [ (fun () -> Server.start server)
          ; (fun () ->
              let* _state = Ocamlformat_rpc.run ocamlformat_rpc in
              (* TODO ulysse Handle errors*)
              Fiber.return ())
          ; (fun () -> ...  Dune.run dune  ... )
          ]

However I have a problem to stop it: you appear to stop the DRPC client after this first fiber returns:

      match !dune with
      | None -> Fiber.return ()
      | Some dune -> Dune.stop dune)

But I don't understand why the first fiber would return before the process is stopped because it is waiting for the process to stop to complete ? In any case I had that problem with OCamlformat server and decided to stop it explicitely when the Server does:

Fiber.parallel_iter
          ~f:(fun f -> f ())
          [ (fun () ->
              let* () = Server.start server in
              (* When server exits we also stop ocamlformat rpc *)
              Ocamlformat_rpc.stop ocamlformat_rpc)
          ; (fun () ->
              let* _state = Ocamlformat_rpc.run ocamlformat_rpc in
              (* TODO ulysse Handle errors*)
              Fiber.return ())
          ; (fun () -> ...  Dune.run dune  ... )
          ]

This way the second item in the parallel iter does complete because it watches the process' exit and the process is exited after stopping the server in the first item of the parallel iter.

Is that the right thing to do or did I miss something essential ?

(Also when I use Dune master the ocamllsp process does not exits when asking a reload of the server in vscode, process are multiplying , which does not happen with a version of Dune without the rpc server)

@rgrinberg
Copy link
Member

Is that the right thing to do or did I miss something essential ?

You aren't missing anything. I already had this fixed in a branch that I merged yesterday.

@voodoos
Copy link
Collaborator Author

voodoos commented Jun 29, 2021

Is that the right thing to do or did I miss something essential ?

You aren't missing anything. I already had this fixed in a branch that I merged yesterday.

Great, I will have one last pass and it will be ready for a (hopefully) final review.

@voodoos
Copy link
Collaborator Author

voodoos commented Jun 29, 2021

I refactored the module a bit.

I also added a user notification (ShowNotification) showing when the ocamlformat-rpc binary is not found.
However I do not show other errors, they are logged only if debug is enabled.

Some types will fail to be formatted by ocamlformat: Merlin prints types which may be disambiguated with a stamp like in t/1 -> t/2 and this is not valid syntax.
In this case a non-formatted type will be showed to the user as before.

@gpetiot is working on supporting this syntax in ocamlformat.

@rgrinberg
Copy link
Member

Looks much better now! There are still some opaque error messages that need fixing (or removing)

@voodoos
Copy link
Collaborator Author

voodoos commented Jun 30, 2021

I reworked the logging in ead0825.

Edit: running the tests spawns a huge number of hard-to-kill ocamlformat-rpc instances, I will have to investigate that. Tests also fail on CI because ocamlformat-rpc is not installed.

@rgrinberg
Copy link
Member

running the tests spawns a huge number of hard-to-kill ocamlformat-rpc instances, I will have to investigate that. Tests also fail on CI because ocamlformat-rpc is not installed.

That's definitely problematic. I forgot to mention that we should delay launching the process until we actually request it to format something.

@voodoos
Copy link
Collaborator Author

voodoos commented Jul 1, 2021

I decided to use a Fiber.Mvar.t in 98d4ca7 to only start the process when the first query is done and then perform that query as soon as the process started. The lifecycle of this Mvar is described in a comment.

Along with some marginal tweaks this does solve the tests. No more zombies :goberserk:

voodoos and others added 3 commits July 7, 2021 21:31
This is used to format types returned by Merlin.
Signed-off-by: Rudi Grinberg <[email protected]>
make sure that we turn off dune after the server is done

Signed-off-by: Rudi Grinberg <[email protected]>
@rgrinberg rgrinberg force-pushed the ocf-rpc branch 2 times, most recently from 6f94e3e to c101f63 Compare July 8, 2021 05:11
It pulls too many transitive test deps. This now causes a conflict with
ocamlformat-rpc's test deps

Signed-off-by: Rudi Grinberg <[email protected]>
@rgrinberg
Copy link
Member

Looks good. I modified the code to us two ivars instead of the mvar. I'm more confident with an ivar that we won't double write when we shouldn't.

@rgrinberg rgrinberg merged commit 6287232 into ocaml:master Jul 8, 2021
rgrinberg added a commit to rgrinberg/opam-repository that referenced this pull request Aug 19, 2021
CHANGES:

## Features

- Add a new code action `Add missing rec keyword`, which is available when
  adding a `rec` keyword can fix `Unbound value ...` error, e.g.,

  ```ocaml
  let fact n = if n = 0 then 1 else n * fact (n - 1)
                                     (* ^^^^ Unbound value fact *)
  ```

  Adding `rec` to the definition of `fact` will fix the problem. The new code
  action offers adding `rec`.

- Jump to the first hole on calling `Destruct` code action (only with client
  VSCode OCaml Platform) (ocaml/ocaml-lsp#468)

  Example: when a user invokes `Destruct` code action on `Some 1`, this code is
  replaced by `match Some 1 with None -> _ | Some _ -> _`, where the 1st and
  3rd underscores are "typed holes", a concept created by Merlin to be able to
  put "holes" in OCaml code.

  With this change, now for VSCode OCaml Platform users, on such invocation of
  `Destruct`, the cursor will jump to the first typed hole and select it, so
  that user can start editing right away.

- Use ocamlformat to properly format type snippets. This feature requires the
  `ocamlformat-rpc` opam package to be installed. (ocaml/ocaml-lsp#386)

- Add completion support for polymorphic variants, when it is possible to pin
  down the precise type. Examples (`<|>` stands for the cursor) when completion
  will work (ocaml/ocaml-lsp#473)

  Function application:

  ```
  let foo (a: [`Alpha | `Beta]) = ()

  foo `A<|>
  ```

  Type explicitly shown:

  ```
  let a : [`Alpha | `Beta] = `B<|>
  ```

  Note: this is actually a bug fix, since we were ignoring the backtick when
  constructing the prefix for completion.

- Parse merlin errors (best effort) into a more structured form. This allows
  reporting all locations as "related information" (ocaml/ocaml-lsp#475)

- Add support for Merlin `Construct` command as completion suggestions, i.e.,
  show complex expressions that could complete the typed hole. (ocaml/ocaml-lsp#472)

- Add a code action `Construct an expression` that is shown when the cursor is
  at the end of the typed hole, i.e., `_|`, where `|` is the cursor. The code
  action simply triggers the client (currently only VS Code is supported) to
  show completion suggestions. (ocaml/ocaml-lsp#472)

- Change the formatting-on-save error notification to a warning notification
  (ocaml/ocaml-lsp#472)

- Code action to qualify ("put module name in identifiers") and unqualify
  ("remove module name from identifiers") module names in identifiers (ocaml/ocaml-lsp#399)

  Starting from:

  ```ocaml
  open Unix

  let times = Unix.times ()
  let f x = x.Unix.tms_stime, x.Unix.tms_utime
  ```

  Calling "remove module name from identifiers" with the cursor on the open
  statement will produce:

  ```ocaml
  open Unix

  let times = times ()
  let f x = x.tms_stime, x.tms_utime
  ```

  Calling "put module name in identifiers" will restore:

  ```ocaml
  open Unix

  let times = Unix.times ()
  let f x = x.Unix.tms_stime, x.Unix.tms_utime
  ```

## Fixes

- Do not show "random" documentation on hover

  - fixed by [merlin#1364](ocaml/merlin#1364)
  - fixes duplicate:
    - [ocaml-lsp#344](ocaml/ocaml-lsp#344)
    - [vscode-ocaml-platform#111](ocamllabs/vscode-ocaml-platform#111)

- Correctly rename a variable used as a named/optional argument (ocaml/ocaml-lsp#478)

- When reporting an error at the beginning of the file, use the first line not
  the second (ocaml/ocaml-lsp#489)
rgrinberg added a commit to rgrinberg/opam-repository that referenced this pull request Aug 25, 2021
CHANGES:

## Features

- Add a new code action `Add missing rec keyword`, which is available when
  adding a `rec` keyword can fix `Unbound value ...` error, e.g.,

  ```ocaml
  let fact n = if n = 0 then 1 else n * fact (n - 1)
                                     (* ^^^^ Unbound value fact *)
  ```

  Adding `rec` to the definition of `fact` will fix the problem. The new code
  action offers adding `rec`.

- Use ocamlformat to properly format type snippets. This feature requires the
  `ocamlformat-rpc` opam package to be installed. (ocaml/ocaml-lsp#386)

- Add completion support for polymorphic variants, when it is possible to pin
  down the precise type. Examples (`<|>` stands for the cursor) when completion
  will work (ocaml/ocaml-lsp#473)

  Function application:

  ```
  let foo (a: [`Alpha | `Beta]) = ()

  foo `A<|>
  ```

  Type explicitly shown:

  ```
  let a : [`Alpha | `Beta] = `B<|>
  ```

  Note: this is actually a bug fix, since we were ignoring the backtick when
  constructing the prefix for completion.

- Parse merlin errors (best effort) into a more structured form. This allows
  reporting all locations as "related information" (ocaml/ocaml-lsp#475)

- Add support for Merlin `Construct` command as completion suggestions, i.e.,
  show complex expressions that could complete the typed hole. (ocaml/ocaml-lsp#472)

- Add a code action `Construct an expression` that is shown when the cursor is
  at the end of the typed hole, i.e., `_|`, where `|` is the cursor. The code
  action simply triggers the client (currently only VS Code is supported) to
  show completion suggestions. (ocaml/ocaml-lsp#472)

- Change the formatting-on-save error notification to a warning notification
  (ocaml/ocaml-lsp#472)

- Code action to qualify ("put module name in identifiers") and unqualify
  ("remove module name from identifiers") module names in identifiers (ocaml/ocaml-lsp#399)

  Starting from:

  ```ocaml
  open Unix

  let times = Unix.times ()
  let f x = x.Unix.tms_stime, x.Unix.tms_utime
  ```

  Calling "remove module name from identifiers" with the cursor on the open
  statement will produce:

  ```ocaml
  open Unix

  let times = times ()
  let f x = x.tms_stime, x.tms_utime
  ```

  Calling "put module name in identifiers" will restore:

  ```ocaml
  open Unix

  let times = Unix.times ()
  let f x = x.Unix.tms_stime, x.Unix.tms_utime
  ```

## Fixes

- Do not show "random" documentation on hover

  - fixed by [merlin#1364](ocaml/merlin#1364)
  - fixes duplicate:
    - [ocaml-lsp#344](ocaml/ocaml-lsp#344)
    - [vscode-ocaml-platform#111](ocamllabs/vscode-ocaml-platform#111)

- Correctly rename a variable used as a named/optional argument (ocaml/ocaml-lsp#478)

- When reporting an error at the beginning of the file, use the first line not
  the second (ocaml/ocaml-lsp#489)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants