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

Migrate to ocamlmerlin-lsp#269

Open
rusty-key wants to merge 34 commits into
reasonml-editor:masterfrom
rusty-key:ocamlmerlin-lsp
Open

Migrate to ocamlmerlin-lsp#269
rusty-key wants to merge 34 commits into
reasonml-editor:masterfrom
rusty-key:ocamlmerlin-lsp

Conversation

@rusty-key
Copy link
Copy Markdown

@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
Copy Markdown

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

Copy link
Copy Markdown

@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.

Comment thread .vscode/settings.json Outdated
@@ -1,5 +1,5 @@
{
"editor.formatOnSave": true,
// "editor.formatOnSave": true,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unrelated change?

Comment thread package.json Outdated
"enum": ["merlin", "bsb"]
"enum": [
"merlin",
"bsb"
Copy link
Copy Markdown

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?

Comment thread src/formatters/reason.ts Outdated
{
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";
Copy link
Copy Markdown

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?

Comment thread src/formatters/reason.ts Outdated
fs.writeFileSync(tempFileName, textEditor.document.getText(), "utf8");
const formattedText = execSync(`${formatter} ${tempFileName}`).toString();
const textRange = getFullTextRange(textEditor);
fs.unlinkSync(tempFileName);
Copy link
Copy Markdown

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
Copy Markdown
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

Comment thread src/formatters/reason.ts Outdated
if (textEditor) {
const tempFileName = `/tmp/vscode-refmt-${uuidv4()}.re`;
fs.writeFileSync(tempFileName, textEditor.document.getText(), "utf8");
const formattedText = execSync(`${formatter} ${tempFileName}`).toString();
Copy link
Copy Markdown

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?

Comment thread src/formatters/ocaml.ts
@@ -0,0 +1,37 @@
import { execSync } from "child_process";
Copy link
Copy Markdown

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.

Comment thread src/client/index.ts Outdated
return serverOptions;
}

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

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
Copy Markdown
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?

Comment thread src/formatters/ocaml.ts Outdated
const textEditor = vscode.window.activeTextEditor;

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

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
Copy Markdown
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
Copy Markdown

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
Copy Markdown

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

Comment thread package.json
"reason.diagnostics.tools": {
"type": "array",
"items": {
"enum": ["merlin", "bsb"]
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown

@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
Copy Markdown
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
Copy Markdown
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 !!

Comment thread src/client/index.ts Outdated
if (options.useEsy) {
run = {
args: ["exec-command", "--include-current-env", merlinLsp],
command: process.platform === "win32" ? "esy.cmd" : "esy",
Copy link
Copy Markdown
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.

Comment thread package.json
"default": "./node_modules/bs-platform/lib/bsb.exe",
"description": "The path to the `bsb` binary."
},
"reason.path.ocamlfind": {
Copy link
Copy Markdown
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.

Comment thread package.json Outdated
"default": "ocamlfind",
"description": "The path to the `ocamlfind` binary."
},
"reason.path.esy": {
Copy link
Copy Markdown
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)

Comment thread src/client/index.ts Outdated
env: {
...process.env,
MERLIN_LOG: "-",
OCAMLFIND_CONF: "/dev/null",
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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:///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
Copy Markdown
Member

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

@rusty-key
Copy link
Copy Markdown
Author

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

Comment thread src/formatters/utils.ts Outdated

if (!formatter) {
vscode.window.showInformationMessage(
`${formatterPath} is not available. Please specify "vscode-reasonml.path.${formatterName}"`,
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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?

@Khady Khady force-pushed the ocamlmerlin-lsp branch from 50023a6 to 9ae3159 Compare April 8, 2019 01:39
@wokalski
Copy link
Copy Markdown

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
Copy Markdown
Author

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

@rusty-key
Copy link
Copy Markdown
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
Copy Markdown

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
Copy Markdown

@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.

7 participants