Skip to content
This repository has been archived by the owner on Mar 21, 2024. It is now read-only.

Migrate to ocamlmerlin-lsp #269

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

Conversation

rusty-key
Copy link

@rusty-key rusty-key commented Feb 28, 2019

Original PR is here: #264

Native LSP implementation was merged recently into merlin repository. This PR switches extension to use it instead of OLS. This goes with some features removed (temporarily, I hope):
— find occurances;
— symbol rename;
— case splitting;
— code lens.

Also, it adds support for ocamlformat/refmt, because it was handled by OLS.

What do you think? What else should be done here?

@andreypopp
Copy link

Could you write a summary of what's removed and what's added here?

Copy link

@andreypopp andreypopp left a comment

Choose a reason for hiding this comment

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

Great! I've left some comments inline.

@@ -1,5 +1,5 @@
{
"editor.formatOnSave": true,
// "editor.formatOnSave": true,

Choose a reason for hiding this comment

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

Unrelated change?

package.json Outdated
"enum": ["merlin", "bsb"]
"enum": [
"merlin",
"bsb"

Choose a reason for hiding this comment

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

We keep support for bsb diagnostics here?

{
provideDocumentFormattingEdits(_document: vscode.TextDocument): vscode.TextEdit[] {
const formatterPath = configuration.get<string | undefined>("path.refmt");
const formatter = formatterPath ? path.resolve(rootPath, formatterPath) : "/usr/local/bin/refmt";

Choose a reason for hiding this comment

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

Should this be just refmt so it is being resolved from PATH instead of hard coding to /usr/local/bin/refmt?

fs.writeFileSync(tempFileName, textEditor.document.getText(), "utf8");
const formattedText = execSync(`${formatter} ${tempFileName}`).toString();
const textRange = getFullTextRange(textEditor);
fs.unlinkSync(tempFileName);

Choose a reason for hiding this comment

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

If we use temporary file we'd want to remove it incase refmt produces an error. But we probably can just pipe input on stdin instead?

Copy link
Author

Choose a reason for hiding this comment

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

I tried it, but it doesn't work with large files

if (textEditor) {
const tempFileName = `/tmp/vscode-refmt-${uuidv4()}.re`;
fs.writeFileSync(tempFileName, textEditor.document.getText(), "utf8");
const formattedText = execSync(`${formatter} ${tempFileName}`).toString();

Choose a reason for hiding this comment

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

I think this lacks error handling — what happens if we try to reformat invalid code? This will throw?

@@ -0,0 +1,37 @@
import { execSync } from "child_process";

Choose a reason for hiding this comment

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

Same comments I left for reason formatter are applicable to this too, I think.

return serverOptions;
}

export async function launchMerlinLsp(context: vscode.ExtensionContext, useEsy: boolean): Promise<void> {

Choose a reason for hiding this comment

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

Let change useEsy arg to be options: {useEsy: bool} instead, this will make things more clear on call sites.

@andreypopp andreypopp mentioned this pull request Mar 1, 2019
2 tasks
@rusty-key
Copy link
Author

@andreypopp, I edited the description to mention removed features and addressed your comments

I guess I also need to provide a guide on how to build ocamlmerlin-lsp?

const textEditor = vscode.window.activeTextEditor;

if (textEditor) {
const tempFileName = `/tmp/vscode-reasonml-${uuidv4()}.ml`;
Copy link

Choose a reason for hiding this comment

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

This should be using os.tmpdir() instead of /tmp - otherwise it won't work correctly on Windows.

Copy link
Author

Choose a reason for hiding this comment

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

I didn't think about windows at all. Will try to setup vm and test it there this week

Copy link

Choose a reason for hiding this comment

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

Thanks @rusty-key !

@andreypopp
Copy link

from this list:

— find occurances;
— symbol rename;
— case splitting;
— code lens.

only "case splitting" is still unsupported by merlin-lsp, others were implemented in ocamlmerlin-lsp

"reason.diagnostics.tools": {
"type": "array",
"items": {
"enum": ["merlin", "bsb"]
Copy link
Member

Choose a reason for hiding this comment

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

@rusty-key @andreypopp One of the main advantages of this bsb integration was that bsb has full project visibility. So if one file changes and that change breaks other files, bsb is able to show those errors in the Diagnostics panel, even if the file is not opened. One has just to click on them to open the broken file, which is useful for large refactors.

Is something like that possible with merlin? I always believed that merlin only has a "local view" of the world. 🤔

@jchavarri
Copy link
Member

Another question: what's the best way to keep compatibility with ocamlmerlin and BuckleScript projects? ocamlmerlin is a crucial part of this extension to jump to definition etc, but the historical way to do so (using reason-cli) hasn't been updated since last August.

Ideally, this extension could be used to run both BuckleScript and native projects, but I wonder what's the best way to use merlin with BS projects, considering it doesn't play well with esy yet? cc @andreypopp

@andreypopp
Copy link

@jchavarri I've been managing native dependencies with esy in bs projects so far — esy.json for esy and package.json for yarn/bs. It worked fine.

@rusty-key
Copy link
Author

I included precompiled (with ocaml-4.02.3+buckle) versions of merlin-lsp and merlin-reason. Now extension should work with Bucklescript projects on mac and linux out-of-the-box.

Next steps are:
— do the same for windows (if it is possible)
— provide setup instructions for non-bs projects
— automate setup for non-bs projects.

Copy link
Member

@jchavarri jchavarri left a comment

Choose a reason for hiding this comment

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

Thanks for this @rusty-key !!

if (options.useEsy) {
run = {
args: ["exec-command", "--include-current-env", merlinLsp],
command: process.platform === "win32" ? "esy.cmd" : "esy",
Copy link
Member

Choose a reason for hiding this comment

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

The way this is implemented has some unexpected behavior. If I'm understanding correctly, if user sets in settings.json:

{
  "reason.path.ocamlmerlin-lsp": "/some/folder/ocamlmerlin-lsp"
}

then the command will be

esy /some/folder/ocamlmerlin-lsp

but I would expect it to be just

/some/folder/ocamlmerlin-lsp

If the path is set manually, then it should be used "as is", imo.

"default": "./node_modules/bs-platform/lib/bsb.exe",
"description": "The path to the `bsb` binary."
},
"reason.path.ocamlfind": {
Copy link
Member

Choose a reason for hiding this comment

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

@jordwalke Heads up: this will remove the APIs for introspecting environments that were added in https://github.com/freebroccolo/ocaml-language-server/pull/68.

package.json Outdated
"type": "string",
"default": "ocamlfind",
"description": "The path to the `ocamlfind` binary."
},
"reason.path.esy": {
Copy link
Member

Choose a reason for hiding this comment

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

@rusty-key I think this can be removed too, it was used for the env introspection (like ocamlfind, env etc)

env: {
...process.env,
MERLIN_LOG: "-",
OCAMLFIND_CONF: "/dev/null",
Copy link
Member

Choose a reason for hiding this comment

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

Does this have any implications? Why does merlin fail if this is not set? cc @andreypopp

Copy link
Author

Choose a reason for hiding this comment

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

afaiu, merlin relies on ocamlfind by default and when it is not there, it fails. This flag can be removed as soon as findless branch will be merged and released:
https://github.com/ocaml/merlin/tree/findless

@jchavarri
Copy link
Member

One thing I'm noticing when testing this branch is that when opening .re files with comments like /* */, merlin seems to choke:

# 0.02 lsp - debug
send: {
  "jsonrpc": "2.0",
  "method": "textDocument/publishDiagnostics",
  "params": {
    "uri":
      "file:https:///Users/javi/Development/github/Foo.re",
    "diagnostics": [
      {
        "range": {
          "start": { "line": 0, "character": 0 },
          "end": { "line": 0, "character": 0 }
        },
        "severity": 1,
        "message": "invalidCharacter.orComment.orString",
        "relatedInformation": [],
        "relatedLocations": []
      }
    ]
  }
}

I guess it has to do with the version of the Reason parser that is included with ocamlmerlin-reason?

@jchavarri
Copy link
Member

Hm. Can't repro with /* */ comments now. But // definitely make merlin unhappy.

@rusty-key
Copy link
Author

That's probably because I included ocamlmerlin-reason from reason-cli. I'll fix that


if (!formatter) {
vscode.window.showInformationMessage(
`${formatterPath} is not available. Please specify "vscode-reasonml.path.${formatterName}"`,
Copy link
Member

Choose a reason for hiding this comment

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

Please specify "vscode-reasonml.path.${formatterName}"

This message is not correct. The setting would be reason.path.refmt.

Copy link
Member

Choose a reason for hiding this comment

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

Unrelated, but I can't get refmt to work even when using the right config.

@rusty-key
Copy link
Author

rusty-key commented Apr 5, 2019

So, I talked to @jchavarri and we agreed that the best workflow would be one with esy because it installs all required dependencies locally and prevents global switches mess.

But I still really want to provide a seamless experience for BS users who don't want to deal with esy/opam. So, I decided to leave two options:

  1. If extension detects esy configuration, it asks you to specify all required binaries such as merlin-lsp and ocamlformat as devDependencies.
  2. If extension detects bsconfig without esy.json, it tries to use precompiled binaries, but still allow you to override formatters.

If we can't detect either, we just stop and ask user to provide of these configurations, because we have no idea what to do.

Next, we can provide a creation or population of esy.json and probably run esy to install dependencies. Also, I wonder, if we can hide all of this into extension internals and detect compiler version based on project configuration.

@andreypopp @jchavarri @jordwalke what do you think?

@wokalski
Copy link

Can we perhaps make the scope of this PR more limited and merge it? Like, if there's no ocamlmerlin-lsp in path, just add an error and say that it's not in the path? It would be a great improvement itself and any follow ups would be just icing on the cake!

@rusty-key
Copy link
Author

@wokalski, makes sense to me. I'll prepare changes over the week

@rusty-key
Copy link
Author

rusty-key commented Jul 21, 2019

@wokalski, it was quite a long week :)

I dumbed down the extension. Now it requires ocamlmerlin-lsp and ocamlmerlin-reason in configuration and doesn't work otherwise. Also, instead of trying to configure the environment automatically, I just added instructions to README.

I'll return to esy/opam and easier setup after this PR is merged.

cc @andreypopp @jchavarri @jordwalke

@jfrolich
Copy link

Is this abandoned? People were recommending vscode-reasonml as reason-language-server is pretty unreliable at the moment. This PR looks like a great improvement.

@wokalski
Copy link

@jfrolich take a look at ocamllabs/vscode-ocaml-platform.

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

Successfully merging this pull request may close these issues.

None yet

7 participants