Emacs is a free software text editor, extensible via the programming language Emacs Lisp
In this article, we will see how to add the following functionnalities to Emacs with custom Elisp (Emacs Lisp) configuration
- Advanced syntaxic coloration with tree-sitter
- Live code analysis: LSP Mode
- Integrated debugger: DAP Mode
Installing a first package
Each software cited previously in the functionnalities we will add is distributed in the form of Emacs Lisp packages. It is similar to Debian packages: there are package requirements, you download and install a tarball of Emacs Lisp code.
The first package we are going to install is dap-mode
Configuration of Melpa package repository
The dap-mode package is distributed on Melpa package repository. In order to add Melpa to Emacs known repositories, we need to add a bit of Elisp code to the file ~/.emacs.d/init.el
~/.emacs.d/init.el is the entrypoint (i.e main) configuration file of Emacs. It is executed by default when the Emacs text editor is launched. It contains Emacs Lisp code (.el extension).
;; `package` feature is part of Emacs builtin packages
;; This feature will be useful to install other packages like `dap-mode`
(require 'package)
;; Add Melpa to the list of Emacs package repositories
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
;; Load Emacs Lisp packages, and activate them
(package-initialize)
Listing available packages in Melpa
Adding a package repository to the list package-archives has not the side effect to fetch the list of packages of that repository
In order to fetch the list of packages of Melpa, we need to call the functionpackage-refresh-contents of package. Its like calling apt update on Emacs
We will automate the following logic on Emacs startup: if the list of known packages is empty, then we will fetch the packages available on all repos. That way we doesn’t bloat the init script with network call if we already a list of available packages
;; package-archive-contents variable
;; holds the list of known packages
(unless package-archive-contents (package-refresh-contents))
One of the cool functionnalities of Emacs is that the text Editor is its builtin documentaion. You can describe any Elisp function/variable available with describe-function (keyboard shortcut C-h f i.e press Control+h then press f) and describe-variable (keyboard shortcut C-h v)
To launch an interactive Emacs Lisp function, use keyboard shortcut Alt+x (i.e M-x) and then enter the Elisp command. For example, do M-x describe-function
Installing dap-mode
Now that we have an up-to-date packages list, we can install package dap-mode
;; Let's define the list of required package in a new variable: package-list
(setq package-list '(dap-mode))
;; If a package of package-list is not installed, install it
(dolist (package package-list)
(unless (package-installed-p package) (package-install package)))
You also can manually browse packages with M-x package-list-packages and install them with M-x package-install.
Typescript configuration
Typescript Major Mode
Emacs allow a single major mode for each buffer (~= open file), and many minor edition modes
Let’s install a major mode for Typescript:
;; Let's define the list of required package in a new variable: package-list
(setq package-list '(dap-mode typescript-mode))
All Typescript files (ending with .ts) will now be opened with
Typescript mode.
At this point, opened Typescript files will have appropriate syntaxic coloration, but we can improve it furthermore with tree-sitter package
Tree-Sitter advanced syntaxic coloration
Let’s install tree-sitter package. It allow us to color programming languages keywords with more precision, because it constructs an abstract syntax tree of the language, and them give each node an appropriated color. This give us more precise colors than Typescript major mode can offer natively
;; Let's define the list of required package in a new variable: package-list
(setq package-list '(dap-mode typescript-mode tree-sitter tree-sitter-langs))
;; Loading tree-sitter package
(require 'tree-sitter-langs)
(require 'tree-sitter)
;; Activate tree-sitter globally (minor mode registered on every buffer)
(global-tree-sitter-mode)
(add-hook 'tree-sitter-after-on-hook #'tree-sitter-hl-mode)
If you prefer Emacs theme on the right, you can enable it with (load-theme 'tango-dark)
LSP Mode
LSP mode provides code actions and hints like:
- Variable types
- Goto definition
- Rename variable/function everywhere in a project
- Detect and highlight errors
- …and most of the IDE provides
;; Let's define the list of required package in a new variable: package-list
(setq package-list '(dap-mode typescript-mode tree-sitter tree-sitter-langs lsp-mode lsp-ui))
(require 'lsp-mode)
(add-hook 'typescript-mode-hook 'lsp-deferred)
(add-hook 'javascript-mode-hook 'lsp-deferred)
DAP Mode
We will now provide our text editor a Debugger using DAP Mode, and use it on Typescript project
Installing DAP Mode
dap-node feature from dap-mode package contains code specific to NodeJS projects debugging. We can load these features (i.e set of functions/variables) using (require 'dap-node) when we are visiting a Typescript file
We can achieve that (load dap-node feature in Typescript buffer only) using Emacs hooks
Using Emacs NodeJS debugger with dap-mode requires downloading and install of a VSCode module with dap-node-setup. We will also automate that:
(defun my-setup-dap-node ()
"Require dap-node feature and run dap-node-setup if VSCode module isn't already installed"
(require 'dap-node)
(unless (file-exists-p dap-node-debug-path) (dap-node-setup)))
(add-hook 'typescript-mode-hook 'my-setup-dap-node)
(add-hook 'javascript-mode-hook 'my-setup-dap-node)
Verify that installation worked
A recuring bug during dap-node-setup is that Microsoft server hosting this tarball respond with 429 HTTP error code (request rate limit exceeded)
In order to check that the install succeedeed, you can launch this lisp script:
(require 'dap-node) (if (file-exists-p (nth 1 dap-node-debug-program)) (message "NodeJS debugger successfully installed") (message "NodeJS debugger install failed. Please download it manually"))
To launch this script, copy it in an Emacs buffer (*scratch* for example), highlight it and launch command eval-region
Manual install procedure for NodeJS debug adapter
To install manually the driver if the server refused the download on dap-node-setup:
- Go to the VSCode Node Debug extension page
- Download the extension
- Then unzip the tarball to the ~/.emacs.d/.extension/vscode/ directory
You can the Emacs shell Run M-x eshell, and an Emacs Shell will be open. Eshell allow running both Elisp code and shell commands That way, you can evaluate the destination of the archive with (eval dap-node-debug-path) and use unzip shell command at the same time
Using Typescript Debugger
Node debugger installed by dap-node-setup and Emacs client dap-mode now allow us to debug a Typescript project
On va utiliser le projet d’affichage de flux
rss et le déboguer avec Emacs
Create a directory and open an index.ts file inside it. Install tsc with npm i typescript
Then, LSP Mode will ask you to save this project in the list of known projects. Accept with i
You can now write Typescript code in your index.ts file with hints and advanced syntax highlighting
You can also debug this file. I advise you to run command dap-hydra which provides fast shortcuts for debugging
Once dap-hydra command launched, you can add a breakpoint on the first line of code with b a
Then, evaluate the following Elisp code:
(dap-register-debug-template "Launch index.ts" (list :type "node" :request "launch" :program "${workspaceFolder}/index.ts" :dap-compilation "npx tsc index.ts --outdir dist --sourceMap true" :outFiles (list "${workspaceFolder}/dist/**/*.js") :name "Launch index.ts"))
And run d d to create a debug session.
Here is what we obtain:
How to configure the debugger
Using launch.json
You can define your project debug configurations in
launch.json
.vscode/launch.json. Here is an example:
{ "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Launch index", "program": "${workspaceFolder}/index.ts", "preLaunchTask": "npm: build", "dap-compilation": "npm run build", "outFiles": ["${workspaceFolder}/dist/**/*.js"] } ] }
This file describe the debug configuration. It is named Launch index. This config will automatically be loaded by dap-mode if it is in the right directory relatively to the project
Here is an explanation of the config: Node can't directly interpret Typescript code. We have to compile Typescript to Javascript first. It is done before running the debugger, using dap-compilation Emacs. It is the equivalent of preLaunchTask in VSCode
In this config, the npm script build will launch tsc and output into dist
In order for the debugger to map Typescript files to Javascript ones, we set the outFiles directories according to the npm build script
Note that we use the variable workspaceFolder which is part of the launch.json specification
Emacs equivalent of launch.json
You can also configure the debugger using Emacs lisp function dap-register-debug-template. Here is the equivalent of the launch.json above:
(dap-register-debug-template "Launch index::(Emacs DAP template)" (list :type "node" :request "launch" :program "${workspaceFolder}/index.ts" :dap-compilation "npm run build" :outFiles (list "${workspaceFolder}/dist/**/*.js") :name "Launch index (Emacs DAP template)"))
Typescript compiler configuration
In order to use the debugger with Typescript files mapping, you have to set a few options in tsconfig.json:
{ "compilerOptions": { "target": "es5", "module": "commonjs", "outDir": "dist", "sourceMap": true, "esModuleInterop": true } }
Conclusion
I hope this tutorial allowed you to quickstart with Emacs text editor configured for Typescript code editing, as well as understanding the Emacs Lisp basics. In order to continue customizing Emacs, here is a list of awesome Emacs packages sorted by theme
Also, don’t skip the tutoriel
Elisp
, and use C-h f/C-h v commands to describe the features available in Emacs
Fill this form to get the init.el used in this tutorial:
Using Emacs for your Typescript/Node project