cquery and nix, part 2
Less is More

2018-01-09

Following up from the previous article on using cquery with nix, this time we will consider a bare bones C++ project that does not rely on cmake for building.

cquery for the Minimalist

We will again use my cquery packaging for nix, but instead show how you can get the benefits of cquery in emacs if you want to compile your code with your own compiler invocation, or perhaps a Makefile.

Our project setup this time is entirely in nix; here is our shell.nix,

with (import <nixpkgs> {});
stdenv.mkDerivation {
  name = "cquery-test";
  buildInputs = [ opencv3 cquery ];
}

Now when we enter our nix-shell, we have the following interaction to generate a .cquery file that cquery will use to find include paths in our nix store,

$ nix-shell

[nix-shell]$ ls
shell.nix  src

[nix-shell]$ nix-cquery
There is no compile_commands.json file to edit!
Create one with cmake using:
(cd build && cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 .. && mv compile_commands.json ..)
Do you want to create a .cquery file instead? [Y/n]:

Generated a new .cquery file

And you’re ready to go! With this setup, we can build our program by running the command,

[nix-shell]$ clang++ -std=c++1z src/main.cpp -lopencv_core -lopencv_videoio -lopencv_highgui

Emacs

Our source code looks just like it did in the previous article, as do our emacs interactions.

Here is an elisp function to prepare the right modes when editing a C++ source file,

(defun cquery-nix-shell ()
  "Find a cquery executable in a nix-shell associated with the
directory containig the current file if that file’s extension is
`cpp` or `hpp`. Use the location of that executable in the nix
store to load and configure the cquery lsp client."
  (when (let ((ext (file-name-extension (or (buffer-file-name) ""))))
          (and (not (null ext))
               (or (string-equal ext "cpp")
                   (string-equal ext "hpp"))))
    (let ((nix-shell
           (concat (locate-dominating-file (or load-file-name buffer-file-name)
                                           "shell.nix")
                   "shell.nix")))
      (when nix-shell
        (let* ((cquery-exe
                (string-trim
                 (shell-command-to-string
                  (concat "nix-shell " nix-shell
                          " --run 'which cquery'"))))
               (cquery-root (file-name-directory
                             (directory-file-name
                              (file-name-directory cquery-exe)))))
          (require 'cquery
                   (concat cquery-root
                           "share/emacs/site-lisp/cquery.el"))

          (setq-local cquery-executable cquery-exe)
          (require 'lsp-flycheck)
          (flycheck-mode)
          (lsp-cquery-enable)
          (helm-gtags-mode -1)
          (local-set-key (kbd "M-.") #'xref-find-definitions))))))

And we can finally look at some source code (the following is largely copied from the earlier article),

#include <iostream>
#include <opencv2/opencv.hpp>

int main() {
  using namespace std;
  cout << "Displaying webcam video" << endl;

  auto cap = cv::VideoCapture(0);
  if(!cap.isOpened()) {
    cerr << "Couldn't open camera" << endl;
    return 1;
  }
  cv::namedWindow("Cam Demo");
  while(cv::waitKey(30) < 0) {
    cv::Mat frame;
    cap >> frame;
    imshow("Cam Demo", frame);
  }

  return 0;
}

After we evaluate (cquery-nix-shell) in that buffer, we see the results of the analyses cquery exposes:

cquery-emacs.png

Sure we would get error and warning highlights if any applied, but here I’m showing what we see with some perfectly cromulent code: extra semantic information! The cursor (point in emacs lingo) is on the cap >> frame; line. Since it is on cap, we see other occurrences of that identifier highlighted. We see the type of that identifier (cv::VideoCapture) in the echo area at the bottom of the frame, and we see the type of every identifier on the current line as a margin note on the right hand side of the window. Awesome!