Posts
-
nREPL 1.1.1: Improved Completion with compliment-lite
Today I’ve released nREPL 1.1.1 with a couple of small bug-fixes and one more notable, if mostly invisible change.
Historically nREPL’s
completionsop (introduced in nREPL 0.8) used internally a modified version of clojure-complete, that I eventually released as a library named incomplete.clojure-completewas pretty much abadonware at this point and I wanted a simple drop-in replacement that we could use in tools like REPLy (and by association - Leiningen).1Using the much better compliment library wasn’t an option, as we needed something that was quite literally a single file. (so it’d be easy to inline in nREPL and load in REPLy) Recently, however,
compliment’s author Oleksandr Yakushev released exactly what we needed - a single-file, stripped-down version ofcomplimentcalled compliment-lite. Here’s how it differs from the full-fledgedcompliment:- Context. Completion features that require context (e.g., filtering methods by the class of the receiver object) don’t work.
- Local bindings and resources. Those two sources of completions completely rely on context, so they are disabled in Compliment-lite.
- Documentation. The
documentationfunction is absent from Compliment-lite.
None of those were things supported by
incomplete, so nothing lost. Naturally, it made sense for nREPL to adopt it and offer improved out-of-the-box completion experience to Clojure programmers. As this doesn’t affect the public API of nREPL2 I’ve opted to ship this change in a “bug-fix” release. A version like 1.1.1 is just too cool to pass up!In light of the creation of
compliment-liteI now considerincompleteto be obsolete and I’d encourage everyone looking for a simple, but capable code-completion library to go forcompliment-liteinstead.That’s all I have for you today. Keep hacking!
-
Need for Speed: Using RuboCop with Prism
By now probably most Rubists have heard of Prism (formerly know as YARP), the modern and (very) fast Ruby parser that’s aiming to replace all the existing Ruby parsers out there. Including RuboCop’s “own” parser (a.k.a
whitequark/parser), which aimed to replaceripperandruby_parser, back when it was created.I’ve been keeping an eye on Prism for while, as RuboCop has long been criticized for its choice of parser (and more specifically - for that parser being somewhat slow). That being said - parser migrations are always a pain, especially in a project as big as RuboCop. Early on I had to essentially rewrite RuboCop when I switched the codebase from
rippertoparser, and back then RuboCop was a lot smaller. The good thing is that such rewrites can be done incrementally (I was migrating cops in batches), but it was still a lot of (boring, repetitive) work.That’s why I was super happy when I recently discovered parser-prism - a gem that provides a new backend for the
parsergem’s syntax tree that uses the Prism parser. To make things better - it seemed that this library actually worked with RuboCop already. Yeah, the users still had to do some manual work, but it turned out that the migration could be a lot simpler than what I had expected. Did I also mention some mighty impressive benchmarks?
As a whole, this parser should be significantly faster than the
parsergem. Thebin/benchscript in this repository compares the performance ofParser::CurrentRubyandParser::Prism. Running against a large file likelib/parser/prism/compiler.rbyields:Warming up -------------------------------------- Parser::CurrentRuby 1.000 i/100ms Parser::Prism 6.000 i/100ms Calculating ------------------------------------- Parser::CurrentRuby 16.642 (± 0.0%) i/s - 84.000 in 5.052021s Parser::Prism 64.951 (± 3.1%) i/s - 330.000 in 5.088147s Comparison: Parser::Prism: 65.0 i/s Parser::CurrentRuby: 16.6 i/s - 3.90x slowerWhen running with
--yjit, the comparison is even more stark:Warming up -------------------------------------- Parser::CurrentRuby 1.000 i/100ms Parser::Prism 9.000 i/100ms Calculating ------------------------------------- Parser::CurrentRuby 20.062 (± 0.0%) i/s - 101.000 in 5.034389s Parser::Prism 112.823 (± 9.7%) i/s - 558.000 in 5.009460s Comparison: Parser::Prism: 112.8 i/s Parser::CurrentRuby: 20.1 i/s - 5.62x slowerThese benchmarks were run on a single laptop without a lot of control for other processes, so take them with a grain of salt.
Note: The results above were taken straight from
parser-prism’s README. In a nutshell we’re looking into something like 4-6 times speedup!!! Now I was VERY excited!Immediately I created an issue to improve the support for Prism in RuboCop and we’ve started to collaborate with Prism’s author Kevin Newton, who has been very supportive and accommodating. I suggest to everyone interested in the topic to peruse the discussion in this issue (and issues references from it), as we’ve already managed to simplify the usage of RuboCop with Prism quite a bit. And we’re very close to addressing the main outstanding item - multi-versioning for the parser. Basically, the
parsergem allows you to select the version of Ruby to parse code as (e.g. 3.1), independently from the version of Ruby runtime. This powers theTargetRubyVersionconfiguration option in RuboCop and was one of the many reasons for pickingparseroverripperback in the day. In practical terms - this allowed us to keep supporting parsing Ruby 2.x code, long after one couldn’t run RuboCop on Ruby 2.x. To put it in different terms - the versions of Ruby that RuboCop understands (can parse) are completely orthogonal to the versions of Ruby on which RuboCop can be run.This and other items we need to address, are nicely summarized here. None of them seems like a particularly hard obstacle, so I’m quite optimistic about the future. A future in which one day you’ll be able to have this in your RuboCop config:
ParserEngine: prismWhen is this going to happen exactly? No promises yet, but at the current pace it will likely happen sooner rather than later. I’d encourage adventurous people to play with RuboCop on Prism and to contribute to getting all required pieces in place faster.
I recall that RuboCop’s adoption of
whitequar/parserwas instrumental in uncovering (many) weird bugs in it, and I have a feeling that things might be the same forparser-prismas well. (I assume a lot less projects useparser-prismcompared to Prism) Exciting times ahead!That’s all I have for you today! Keep hacking!
-
Configuring fixed/tonsky indentation in clojure-mode
A few years ago Nikita Tonsky made popular a certain style of formating Clojure code, that became known as “fixed” or “tonsky” indentation in the community.
clojure-modehas long had some support for this viaclojure-indent-style:(setq clojure-indent-style 'always-indent)However, it was kind of hard to get exactly the same indentation from Nikita’s article, as there was no way to suppress the usage of indent specs and forms starting with a keyword were indented by separate rules from what was set by
clojure-indent-style. A recent upstream change made the indentation configuration more granular and now you can get fixed indentation with the following snippet:(setq clojure-indent-style 'always-indent clojure-indent-keyword-style 'always-indent clojure-enable-indent-specs nil)clojure-indent-keyword-styleandclojure-enable-indent-specsare the new additions, that made this possible. Here’s howclojure-indent-keyword-stylecan be used:-
always-align(default) - All args are vertically aligned with the first arg in case (A), and vertically aligned with the function name in case (B).(:require [foo.bar] [bar.baz]) (:require [foo.bar] [bar.baz]) -
always-indent- All args are indented like a macro body.(:require [foo.bar] [bar.baz]) (:x location 0) -
align-arguments- Case (A) is indented likealways-align, and case (B) is indented like a macro body.(:require [foo.bar] [bar.baz]) (:x location 0)
By the way,
clojure-ts-modealso supports the fixed indentation style:(setq clojure-ts-indent-style 'fixed)The configuration here is a lot simpler, as this functionality existed since day 1.
For the record, I still don’t endorse/like fixed indentation1 (and funny enough - neither does Arne, who implemented those changes). Still, I acknowledge that this style is somewhat popular in the Clojure community and I’m all for making it easier for people who like/need it to be able to use it.
That’s all I have for you today. Keep hacking!
-
-
Flycheck Updates: A new Stable Release and Way More
I’ve been pretty busy with Flycheck ever since I became its maintainer recently. Today I wanted to share with you a few of the recent highlights.
Flycheck 34
I’ve cut Flycheck 34, which features quite a few new checkers and removes a bunch of legacy checkers. As I’m not familiar with all the lint tools that Flycheck supports1, I’d appreciate your input on whether some checkers need to be updated/replaced/removed down the road.
Random trivia: I noticed that Chef’s
foodcritichas been replaced by cookstyle, a project built on top of my very own RuboCop project (a linter & formatter for Ruby) and I wrote the checker for it myself.flycheck.org Ownership
I’ve managed to obtain the ownership of the
flycheck.orgdomain. Big thanks to Matthias Güdemann for paying for the domain after the departure of Sebastian from Flycheck.flycheck-eglot
I quickly realized that Eglot support was one of the main reasons why people switched from Flycheck to Flymake, so when I noticed that Sergey Firsov had created Flycheck backend for Eglot I immediately invited him to move his project to the official Flycheck organization on GitHub. You can find
flycheck-eglothere.Documentation Updates
I’ve been updating the Flycheck docs here and there. Most notably, I’ve updated the comparison with Flymake, as a few people complained it was out-of-date.
I haven’t used Flymake in ages, so I’m definitely not an expert there, but I did my best to make sure the comparsion is reasonably accurate. If someone notices any issues there - please, submit improvements!
Open Collective
I’ve created an Open Collective for Flycheck, so people and organizations can support the project financially via donations. I hope that in the long-run it can make the maintenance of the project more sustainable. In the short run - I’ll use some of the donations to cover my expenses for
flycheck.org.NonGNU ELPA
I’ve filed a request to submit Flycheck to NonGNU ELPA, one of Emacs’s official package repositories. Let’s see how this will go. I’m hoping to have there Flycheck and many of its extensions in the long run.
Update: You can track the
emacs-develdiscussion on the topic here.Help Welcome
If you want to hack on Flycheck or any of its extensions - now would be a great time! I love working with other contributors and Flycheck could definitely use all the help it could get. The main organization features some 20 projects and not all of them are actively maintained today.
Epilogue
And that’s a wrap! It has been a busy few weeks working on Flycheck, but now I’ve done pretty much everything that I wanted to tackle originally. Moving forward I’ll be working slowly on the backlog of issues and pull requests and pondering whether more ambitious changes will be needed down the road.
Thanks to everyone for their support for the project! Keep hacking!
-
See https://www.flycheck.org/en/latest/languages.html for the list of built-in checkers. ↩
-
-
CIDER: Preliminary Support for clojure-ts-mode
I’m glad to report that yesterday the long-awaited preliminary support for clojure-ts-mode in CIDER has landed!1
This pull request (or rather the original PR on which it was based) was in the works for a very long time and it feels good to see it finally merged. What does this mean in practice? Well, CIDER will now properly recognize
clojure-ts-modeand modes derived from it, meaning most of the functionality in CIDER will work reasonably well with them. There are a few caveats to keep in mind, though:- CIDER still has a hard dependency on
clojure-mode, as it relies on some APIs from it that have yet to be ported toclojure-ts-mode - Some functionality like dynamic indentation and dynamic font-locking (syntax highlighting) is still not support by
clojure-ts-mode - You need to use the latest version of
clojure-ts-mode, as it features some related changes - The new code hasn’t been test much, so probably we’ll encounter some bugs along the way
- You’ll need to use a snapshot release of CIDER (e.g. one installed from MELPA), as there’s no stable CIDER release with this functionality yet (CIDER 1.14 will be the first one)
- You need to be on Emacs 29 to be able to use
clojure-ts-modein the first place
At any rate - we’ve made one big step towards decoupling CIDER from
clojure-modeand gradually we’ll get there. Thanks to everyone who was involved in making this happen! Keep hacking!-
I’d suggest checking out https://metaredux.com/posts/2023/03/12/clojure-mode-meets-tree-sitter.html if you’re not familiar with
clojure-ts-mode. ↩
- CIDER still has a hard dependency on
Subscribe via RSS | View Older Posts