Building Emacs Major Modes with Tree-sitter: Lessons Learned
Over the past year I’ve been spending a lot of time building Tree-sitter-powered major modes for Emacs – clojure-ts-mode (as co-maintainer), neocaml (from scratch), and asciidoc-mode (also from scratch). Between the three projects I’ve accumulated enough knowledge (and battle scars) to write about the experience. This post distills the key lessons for anyone thinking about writing a Tree-sitter-based major mode, or curious about what it’s actually like.

Over the past year, I’ve been immersed in the process of building Tree-sitter-powered major modes for Emacs. This journey has involved contributions to clojure-ts-mode as a co-maintainer, as well as creating neocaml and asciidoc-mode from scratch. Through these projects, I’ve gained a wealth of knowledge and encountered various challenges that have shaped my understanding of developing Tree-sitter-based major modes. This article aims to distill the key lessons learned, offering insights for anyone considering embarking on a similar endeavor or simply curious about the process.
My first major mode, clojure-ts-mode, was a collaborative effort with the existing maintainers. This project provided an excellent opportunity to learn the ropes of integrating Tree-sitter with Emacs. Tree-sitter is a language server that offers robust parsing capabilities, enabling advanced syntax highlighting and code navigation. Emacs, with its powerful extensibility, is an ideal host for such tools. The initial challenge was understanding how to properly configure Tree-sitter within Emacs, ensuring that the language server communicates effectively with the editor.
One of the most important lessons I learned was the importance of a solid Tree-sitter grammar. The grammar forms the foundation of the language server’s understanding of the syntax, which directly impacts the quality of features like auto-completion, error highlighting, and code navigation. For clojure-ts-mode, the existing grammar was a great starting point, but I soon discovered that it required fine-tuning to match Clojure’s nuanced syntax. This involved working closely with the Tree-sitter maintainers to ensure that the grammar accurately captured the language’s intricacies.
When I moved on to developing neocaml from scratch, I faced the daunting task of creating a Tree-sitter grammar for a language I was not entirely familiar with. Neocaml, a dialect of Caml, posed unique challenges due to its specific syntax and features. I spent considerable time studying the language’s specification and existing parsers to build an accurate grammar. This process was slow and meticulous, but it was essential to ensure that the major mode provided accurate parsing and useful features.
Another critical aspect of developing major modes is integrating with Emacs’ existing infrastructure. Emacs has a rich set of conventions and APIs that must be respected to create a seamless user experience. For instance, the major mode must properly define syntax tables, faces, and hooks to integrate with features like flycheck for linting or company-mode for auto-completion. Additionally, ensuring compatibility with other popular packages, such as magit for Git integration, is crucial for a well-rounded experience.
The process of building asciidoc-mode further solidified my understanding of these challenges. AsciiDoc is a lightweight markup language, and its grammar is somewhat simpler than Clojure or Neocaml. However, the project still required careful attention to detail. One particularly tricky aspect was handling AsciiDoc’s extensive set of options and attributes, which needed to be reflected in the syntax highlighting and code navigation features.
Throughout these projects, I also encountered issues related to performance and resource management. Tree-sitter can be resource-intensive, especially for large files or complex grammars. Ensuring that the major mode did not negatively impact Emacs’ performance was a priority. This involved optimizing the grammar, leveraging Tree-sitter’s caching mechanisms, and carefully managing the language server’s lifecycle within Emacs.
In addition to technical challenges, I learned the importance of community engagement and documentation. Maintaining a major mode requires ongoing support and updates, particularly as the underlying language or Tree-sitter evolves. Engaging with the community—whether through forums, mailing lists, or issue trackers—proved invaluable for addressing user feedback and identifying potential improvements.
Documentation is another critical component. Clear and concise documentation helps users understand how to use the major mode effectively and encourages contributions from the community. For each project, I made a point to provide comprehensive README files, examples, and tutorials to guide new users and contributors.
Reflecting on these experiences, I realize that developing Tree-sitter-powered major modes is both rewarding and challenging. The process demands a deep understanding of both the target language and the intricacies of Emacs’ extensibility model. However, the end result—a powerful, feature-rich editing experience—is well worth the effort. For anyone considering embarking on a similar journey, I encourage you to start with a well-defined grammar, engage with the community, and prioritize performance and compatibility. By addressing these key aspects, you can create a major mode that not only meets the needs of users but also contributes to the broader ecosystem.










