clojure-emacs / clj-refactor.el

A CIDER extension that provides powerful commands for refactoring Clojure code.
GNU General Public License v3.0
771 stars 111 forks source link

Prompt user for new requires across language contexts using a single completing read #521

Closed dgtized closed 2 years ago

dgtized commented 2 years ago

Changes behavior for cljr--slash to prompt from a list of candidate requires, while annotating language contexts those requires come from. Previously in cljc files, typing short-name/ would prompt if completion should occur from cljs or clj context and then prompt a second time for an appropriate namespace to require and alias. This shortcuts the context prompt by showing the namespaces for that alias that are used in the project, and annotating whether the source is from a clj or cljs file.

image

I believe this solution is preferable to current behavior as often namespaces in cljc files overlap aliases used in both cljs and clj files, yet still defers to the user to determine if the require is appropriate for the current language context. I think this can still be improved when in a #(:cljs) context block as we might be able to prefer candidates with the matching context, but it's much nicer than bprompting for context on every / require in a cljc file. However I think some of the context block logic under the previous approach may have been better at detecting the only available require, so that may be a regression. I feel as if the overall workflow allows for more flexibility though if we treat it as:

  1. Check if / preconditions should attempt namespace completion
  2. Generate a list of candidate namespaces annotated with known aliases and language contexts (not filtered by context)
  3. Filter the candidate list based on the name before the / (and possibly context).
  4. Prompt the user to select a namespace from the remaining set a. Shortcutting if only one namespace matches regardless if it is for cljs, clj, or both
  5. Add missing namespace to require

I brought this up in the clojure Slack a couple months back but forgot to add a corresponding issue. I'm happy to add an issue now if that would be the preferable workflow. For now I'm opening this as a draft PR to solicit review. I'm also happy to cleanup the commit history a bit, but thought it would give some context on how I arrived where I did. Finally, I'm having a lot of trouble updating the ecukes/espud tests that interact with the completing-read as they are only asserting the final state, so it's unclear what's failing in between. Any assistance there would be appreciated.

Thanks for your consideration, hope others find this useful!

Before submitting a PR make sure the following things have been done (and denote this by checking the relevant checkboxes):

Thanks!

expez commented 2 years ago

Thanks for taking an interest! Most welcome to get some more eyes on the clj{c,s} features!

This definitely looks like an improvement to me 👍

I'm also happy to cleanup the commit history a bit, but thought it would give some context on how I arrived where I did.

Yeah, this will probably get merged as a single feature commit, but we can wait with that until the very end.

I'm not sure what policy we have on using cl in the clojure-emacs projects, maybe @bbatsov could chime in on that, but I believe that the use of cl-loop in particular makes it more difficult for others to quickly jump into the code base and contribute. I'd rather have a slightly less elegant / verbose solution and a lower barrier of entry 🙂

bbatsov commented 2 years ago

I'm fine with using cl-lib features in general, and I even liked loop - probably because Common Lisp was my first real exposure to Lisp. We can always replace this with something else, but I don't think it's a big deal.

These days there's also a built-in destructuring form as a potential alternative for cl-destructuring-bind - https://www.gnu.org/software/emacs/manual/html_node/elisp/Destructuring-with-pcase-Patterns.html

vemv commented 2 years ago

Will be reviewing 👍 the idea seems nice to me as the current UX is a bit redundant, yes.

As a quick note on commits, as hinted, they are now far too many. But sometimes just one squashed commit leaves a poor/conflated history.

If you have it at hand, as the last step after review you might want to leave it at a few commits, e.g. one for refactoring, another for the addition of the feature.

Personally I've never used cl-* code much over 10y of elisping. I'd much prefer if we used bare elisp, or perhaps simple libraries like seq which also happens to be very clojurey (I'd argue that clojure.core/map is more clojurey than clojure.core/for).

It matters to me because much of my maintenance around clj-refactor/refactor-nrepl is for cljr-slash. Probably many people using clj-refactor use it mostly for cljr-slash as well, and as @expez says it's good that anyone can understand what's going on.

dgtized commented 2 years ago

So it seems like the major concern is just that we want to support the existing behavior with a defcustom flag. It's kind of an invasive change as it's adjusting the whole workflow through cljr--slash, but I think I can see how we can split there. I'm going to think a little bit though.

I wonder if all the concerns with the refactors could be addressed by splitting this into two PRs. One that simply re-arranges and adds tests without changing the existing behavior but leaves an easier cut-point to insert the new behavior, and then a second after that supports the new behavior behind a defcustom? That might make it easier to review and a safer path?

dgtized commented 2 years ago

Rewritten cleanly in https://github.com/clojure-emacs/clj-refactor.el/pull/525 and https://github.com/clojure-emacs/clj-refactor.el/pull/522.