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

General stability fixes and arglist support in completion hover #375

Conversation

cfehse
Copy link
Contributor

@cfehse cfehse commented Oct 4, 2019

Fixes #238, #366, #367, #368

Introduces the following change(s):

  • Added the argument list to HoverProvider and CalvaCompletionItemProvider.
  • Added a mechanism to create the namespace from a file in the REPL if that namespace does not exist in the REPL. This is necessary because if the namespace with which the nrepl command 'info' is called (the namespace of the file) does not exists no data is returned.
  • Added activation events workspaceContains:**/project.clj, workspaceContains:**/shadow-cljs.edn and workspaceContains:**/deps.edn to allow the extension to be activated faster.
  • Improved the state.initProjectDir() function to use vscode.workspace.workspaceFolders![0] as a second starting point in case no file is open in the workspace is not multi-root. This conforms to our wiki entry about the supported workspace layout. Made the function robust if no document is defined.
  • Made the code robust in case a jack-in task exists prematurely an no REPL client is connected.
  • Catch exception during fs.watch() in executeJackInTask(). These exception is according to the fs team by design if a given directory to watch does not exists or the underlying filesystem (network filesystems for example) does not support the operation. During the jack-in process this may occur if the user selects the wrong type of project - e.g. Leiningen + shadow-cljs if there is no shadow support in the project. In case the exception is detected, the jack-in task is canceled.
  • Added the file and untitled schemes to all document selectors. These are the vscode supported schemes. The addition prevents the vscode warning message Extension '<extension>' uses a document selector without scheme. Learn more about....

Tested on

  • Windows
  • Linux
  • macOS

I have:

  • Read How to Contribute.
  • Made sure I am directing this pull request at the dev branch. (Or have specific reasons to target some other branch.)
  • Made sure I am changed the default PR base branch, so that it is not master. (Sorry for the nagging.)
  • Referenced the issue I am fixing/addressing in a commit message for the pull request.
  • Updated the [Unreleased] entry in CHANGELOG.md, linking the issue(s) that the PR is addressing.

Ping: @PEZ, @kstehn

cf and others added 4 commits October 4, 2019 17:01
See PR description for details.
The link to About-Calva-Jack-in should be (I believe!) to Connect-Calva-to-Your-Project
@cfehse
Copy link
Contributor Author

cfehse commented Oct 5, 2019

@PEZ This is ready for testing/merging.

@PEZ
Copy link
Collaborator

PEZ commented Oct 5, 2019

Added a mechanism to load a file into the REPL if the namespace of the file does not exist in the REPL. This is necessary because if the namespace with which the nrepl command 'info' is called (the namespace of the file) no data is returned. So the file must be loaded to retrieve data. Another advantage of this approach is that if the file was loaded information of the elements contained in the file can be displayed as well.

This is a generally good idea. There are many first time experiences with Calva that get a bit strange because the namespaces are not loaded. However, I think this needs to be a setting. Much as I try to avoid settings, there are quite a few users who have opposed to make namespace loading the default behaviour. They have workflows where their namespaces contain code that can take very long to run, and will be hit badly if Calva runs the code just because a file is opened. I think this setting should default to loading the namespaces.

@PEZ
Copy link
Collaborator

PEZ commented Oct 5, 2019

Added activation events workspaceContains:**/project.clj and workspaceContains:**/shadow-cljs.edn to allow the extension to be activated faster.

Can I haz deps.edn added too?

Maybe **/*.{clj,edn}* would work, even...

@cfehse
Copy link
Contributor Author

cfehse commented Oct 5, 2019

@PEZ
If not loading the file then perhaps just evaluate an "empty file" containing the correct namespace to create the namespace in the repl. Without the namespace known to the repl there will be no information even about the core clojure elements.

Another option is to make the info call in the user namespace if the namespace of the open file does not exist in the repl.

@PEZ
Copy link
Collaborator

PEZ commented Oct 5, 2019

Tested the autoload namespace thing now, and love it.

However, in a .cljc file, it only seems to do it for the clj repl. If I toggle the file to be backed by the cljs REPL, then I need to manually load the file. (We can add this as a separate issue if it looks tricky to solve now.)

@PEZ
Copy link
Collaborator

PEZ commented Oct 5, 2019

I agree that we might do better than just skip loading anything. I still suggest that for now we add a setting that defaults to true and if it is false then the user has to load the file, just as today. We can add an issue for refining it. But it is your call, since your PR. 😄

@cfehse
Copy link
Contributor Author

cfehse commented Oct 5, 2019

@PEZ

Take a good look at all the changes so I can take care of the PR on Monday.

@PEZ
Copy link
Collaborator

PEZ commented Oct 5, 2019

Tested:

  • Hover. Works like a charm.
  • Autoloading file. See above comments.
  • Activation events, see above comments. (Can be shipped as is, but a bit strange to leave tools.deps users hanging. 😄
  • Jack-in in single-root and multi-root namespaces. I look forward to updating the wiki on this!

@PEZ
Copy link
Collaborator

PEZ commented Oct 5, 2019

I've skimmed the code changes now. Looks good to me. What do you say, @kstehn ?

src/nrepl/jack-in.ts Outdated Show resolved Hide resolved
// REPL window open commands if needed.
commands["Disconnect from the REPL server"] = "calva.disconnect";
commands["Disonnect from the REPL server"] = "calva.disconnect";
Copy link
Contributor

Choose a reason for hiding this comment

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

That should be Disconnect or not?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kstehn Corrected that.

if(client) {
await util.loadFileIfNamespaceNotExist(document);
let res = await client.info(ns, text);
// I really don't not why result.arglists-str does
Copy link
Contributor

@kstehn kstehn Oct 5, 2019

Choose a reason for hiding this comment

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

The reason for that is the -
Otherwise it would not be possible to do something like this
res.a - res.b i think

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kstehn Okay. gg

// Format the actual docstring
if (doc) {
result += doc.replace(/\s\s+/g, ' ');
if (documentation && documentation != "") {
Copy link
Contributor

Choose a reason for hiding this comment

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

Jumping in here, so please disregard.
This is basically the same as https://github.com/BetterThanTomorrow/calva/pull/375/files#diff-9205a2ccff45e74e74a392042d83efe8R25 Could/should it be extracted into a docUtils (or some such, naming is hard)?

if (this.state.deref().get('connected')) {
let client = util.getSession(util.getFileType(document));

if (util.getConnectedState()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

My knowledge of typescript is limited, but I'd suggest something like:

// in-ns util
function with-connection(f, ...) {
  function (args) {
     if (util.getConnectedState() {
       return  f(args);
    } else {
     return "Please connect to a REPL to retrieve information."
  }
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, I'd recommend decoupling this from the hover functionality itself.
It seems as provideHover "bottoms out" in
return new vscode.Hover(someString)
So I'd suggest:

calculateHoverString(document, position) {
 const text = util.getWordAtPosition(document, position);
  const  ns = util.getNamespace(document);

  const client = util.getSession(util.getFileType(document));
  if(client) {
                 await util.loadFileIfNamespaceNotExist(document);
                 let res = await client.info(ns, text);
                 // I really don't not why result.arglists-str does 
                 // not work, but it leads to an compiler error.
                 if (res.ns && res.doc) {
                     return this.formatDocString(res.ns + "/" + res.name, res["arglists-str"] || [], res.doc)
                 }
                 if (res.ns) {
                     return this.formatDocString(res.ns + "/" + res.name, res["arglists-str"] || [], "No documentation available");
                 } else {
                     return this.formatDocString(text, "", "No information available");
                 }
             }
             return this.formatDocString(text, "", "No information available");
}

async provideHover(document, position, _) {
        const message = util.withConnection(calculateHoverString(document, position);
       return new score.Hover(message);
}

If you go in this direction, you could potentially unit-test calculateHoverString

@@ -190,6 +190,29 @@ function getWordAtPosition(document, position) {
return text;
}

async function loadFileIfNamespaceNotExist(doc) {

if (getConnectedState()) {
Copy link
Contributor

@slipset slipset Oct 6, 2019

Choose a reason for hiding this comment

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

I'd suggest writing this in a guard-clause fashion, and also to possibly throw, since I would assume that it's an error to try loading a ns that we cannot load. Also, by throwing exceptions, we can inform the caller of why we didn't succeed:

async function loadFileIfNamespaceDoesNotExist(doc) {
if (!getConnectedState()) {
   throw new Error("Not connected");
}
const document = getDocument(doc);
if (!document) {
  throw new Error("Couldn't find " + doc)
}
const client = getSession(getFileType(document));
if (!client) {
  throw new Error("No client available for " + document);
}
const ns = getNamespace(document);
const nsList = await client.listNamespaces([]);
if (nsList['ns-list'] && nsList['ns-list'].includes(ns)) {
        return;
}
const name = getFileName(document);
const dir = document.hasOwnProperty('fileName') ? path.dirname(document.fileName) : ''; 
await client.loadFile(document.getText(), name, dir);
}

src/utilities.ts Outdated
return state.deref().get('launching');
}

function setLaunchingState(value) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is

function setLaunchingState(value) {
        vscode.commands.executeCommand("setContext", "calva:launching", Boolean(value));
         state.cursor.set('launching', value);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@slipset Good point!

@PEZ
Copy link
Collaborator

PEZ commented Oct 6, 2019

Many thanks for chipping in, @slipset !

@cfehse
Copy link
Contributor Author

cfehse commented Oct 6, 2019

@PEZ @kstehn

I change the mechanism to enable the info data to be retrieved (autoload namespace thing): if the namespace does not exists in the REPL it is now simply created by evaluating a form like (ns <namespace>). That is enough to get info data from the REPL for everything outside the file. If the user wants to have all information for the file, the file must be loaded. This avoids an option and does not interfere with the workflow of the users.

However, in a .cljc file, it only seems to do it for the clj repl. If I toggle the file to be backed by the cljs REPL, then I need to manually load the file. (We can add this as a separate issue if it looks tricky to solve now.)

I choose the corresponding REPL for the document using util.getSession(util.getFileType(document)) so the info call goes to the right REPL. If there is an issue I think it is not related to this PR.

Can I haz deps.edn added too?

Yes you can. gg

Maybe **/.{clj,edn} would work, even...

I would not do this because I find it more robust to surely have any supported project file in the workspace to activate the extension.

@slipset

I incorporated some of your suggestions - thanks for that. Refactor out some code into a new helper unit and decouple some of the hover and completion code I would like to be done in another PR. This is large enough.

src/utilities.ts Outdated
@@ -190,24 +190,19 @@ function getWordAtPosition(document, position) {
return text;
}

async function loadFileIfNamespaceNotExist(doc) {
async function CreateNamespaceFromDocumentIfNotExists(doc) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We name functions using camelCase, in our undocumented code convention. 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point! Changed...

@PEZ PEZ merged commit 058dd2b into BetterThanTomorrow:dev Oct 10, 2019
@cfehse cfehse deleted the fix/general-stability-fixes-arglist-in-completion-hover branch October 12, 2019 11:55
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.

5 participants