Configuring eglot for Rcpp and Its Friends
posted on 2023-11-29 · last updated on 2024-04-20This note introduces how one can quickly configure eglot (a built-in language server protocol client in Emacs 29) for Rcpp users (or more specifically R package developers using Rcpp) so that code diagnostics, auto-completion, and jump-to-definition, etc. work smoothly when writing Rcpp code in Emacs.
For Emacs 29, it is straightforward to enable eglot for C/C++ as follows:
(require 'eglot)
(add-hook 'c-mode-hook 'eglot-ensure)
(add-hook 'c++-mode-hook 'eglot-ensure)
I have a few more configurations specific to eglot given below, where I set clangd as the eglot-server-programs
for C/C++ and disabled the aggressive header insertion feature.
(add-to-list 'eglot-server-programs
'((c++-mode c-mode)
. ("clangd"
"-j=2"
"--header-insertion=never"
"--header-insertion-decorators=0")))
Next, we just need to inform clangd of where the R.h
and Rcpp.h
are placed through a plain text file named .clangd
in the project/package root directory (e.g., ~/foo-pkg/
).
An example .clangd
file is as follows:
CompileFlags:
Add: [
-xc++, -std=c++11, -Wall,
-I/usr/include/R/,
-I/home/wenjie/R/x86_64-pc-linux-gnu-library/4.3/Rcpp/include,
-I/home/wenjie/R/x86_64-pc-linux-gnu-library/4.3/RcppArmadillo/include]
The paths to the header files can vary and I use the following R script1 to automatically generate a .clangd
file for me (under Linux or Mac OS).
#!/usr/bin/env Rscript
### include R and Rcpp headers in .clangd for eglot
## R include
r_path <- Sys.getenv("R_INCLUDE_DIR")
## Rcpp
rcpp_family <- c("Rcpp", "RcppArmadillo")
rcpp_paths <- sapply(rcpp_family, function(a) {
file.path(find.package(a), "include")
})
## optional: add include path for mac
if (isTRUE(Sys.info()["sysname"] == "Darwin")) {
tmp <- tempfile("dummy", fileext = ".cpp")
tmp_cout <- sub("cpp$", "o", tmp)
writeLines("#include<iostream>\nint main() { return 0; }", tmp)
tmp_out <- system2("clang++", sprintf("-c %s -o %s -v", tmp, tmp_cout),
stdout = TRUE, stderr = TRUE)
start_idx <- which(grepl("^#include <\\.\\.\\.> search starts here:",
tmp_out))[1L]
end_idx <- which(grepl("^End of search list", tmp_out))[1L]
include_paths <- sapply(seq.int(start_idx + 1L, end_idx - 1L),
function(i) {
if (grepl("include", tmp_out[i]))
tmp_out[i]
else
NA_character_
})
include_paths <- trimws(include_paths[! is.na(include_paths)])
} else {
include_paths <- NULL
}
## create flags
flags0 <- "-xc++, -std=c++11, -Wall,\n"
flags <- paste0("-I", c(include_paths, r_path, rcpp_paths))
flags <- paste0(flags0, paste(flags, collapse = ",\n"))
## add project include path
proj_include <- "inst/include"
if (dir.exists(proj_include)) {
proj_include <- normalizePath(proj_include)
flags <- paste(flags, paste0("-I", proj_include), sep = ",\n")
}
## constants
prefix <- "CompileFlags:\n Add: [\n"
suffix <- "]\n"
## write .clang_complete
writeLines(c(prefix, flags, suffix), sep = "", con = ".clangd")
message("Generated '.clangd'")
I made this script executable and included the directory containing it in $PATH
so that I can run it under the project/package root directory in bash conveniently.
With the generated .clangd
, eglot should work out of the box when one edits Rcpp code under ~/foo-pkg/
.
Although the configuration looks straightforward, it took me a while to go through the documentation of eglot and clangd to figure things out. I hope this note can be of help to my future self or other Emacs + R + Rcpp users like me.
I likely took some of the code (probably the chunk locating the additional header files for Mac OS) from Stack Overflow. But I feel sorry that I can no longer recall the details. ↩︎