C and C++
UPDATE
switching to clangd + lsp-modeThis is my C++ "IDE" set-up. I wen't from CEDET to rtags to this. A simple LSP + clangd set-up. I also distribute a version of LLVM on my web server for quick bootstrapping.
Coding Style
(c-add-style "llvm" '("stroustrup" (c-basic-offset . 2) (c-offsets-alist . ((arglist-intro . ++) (member-init-intro . ++) (statement-cont . (c-lineup-assignments ++)) (innamespace . 0) (inextern-lang . 0))) (fill-column . 80) (indent-tabs-mode . nil))) (setq c-default-style "llvm")
Clang-format
Run clang-format before saving
(defvar do.cpp/clang-format-executable "clang-format" "Path to the clang-format executable") (use-package clang-format :ensure t :init (setq-default clang-format-style "{BasedOnStyle: llvm}") (defun do.cpp.clang-format/enable () (make-variable-buffer-local 'before-save-hook) (add-hook 'before-save-hook 'clang-format-buffer)) (add-hook 'c++-mode-hook 'do.cpp.clang-format/enable) (add-hook 'c-mode-hook 'do.cpp.clang-format/enable) (defun do.cpp.clang-format/disable () (interactive) (remove-hook 'before-save-hook 'clang-format-buffer)) (defalias 'disable-clang-format 'do.cpp.clang-format/disable) :config (setq clang-format-executable do.cpp/clang-format-executable))
GDB
This is pretty crufty stuff, I notice that gdb-mi improved a lot since I wrote all of this. Maybe need to go over this again.
The built-in GDB mode is fantastic. That being said, we want to customize it a little.
(use-package gdb-mi :demand :bind <<do.cpp.gdb/keys>> :init <<do.cpp.gdb/settings>> <<do.cpp.gdb/alt-layout>> <<do.cpp.gdb/disassembly-layout>> <<do.cpp.gdb/switch-layout>>)
General Settings
;; tell the mode-line that we are in a comint-derived mode (add-hook 'gdb-inferior-io-mode-hook 'no-trailing-whitespace) (add-hook 'gdb-mode-hook 'no-trailing-whitespace) (add-hook 'gdb-inferior-io-mode-hook '(lambda () (setq-local ml-interactive? t))) (add-hook 'gdb-mode-hook '(lambda () (setq-local ml-interactive? t))) (setq gdb-many-windows t) ;; always activate many-windows mode (setq gdb-display-io-nopopup t) (setq gdb-restore-window-configuration-after-quit t)
Alternate Layout
The default layout is nice on low-resolution displays, but for high resolutions, we can split the screen in thirds and have a more comfortable layout.
Note
this layout seems to not work anymore. Need to fix it eventually.(defun do.cpp.gdb/alt-layout () "Layout the window pattern for option `gdb-many-windows'." ;; create 3 of the four buffers (gdb-get-buffer-create 'gdb-locals-buffer) (gdb-get-buffer-create 'gdb-stack-buffer) (gdb-get-buffer-create 'gdb-breakpoints-buffer) ;; comint buffer (set-window-dedicated-p (selected-window) nil) (switch-to-buffer gud-comint-buffer) (delete-other-windows) (let ((gdb-comint-window (selected-window)) (gdb-src-window (split-window nil ( / (window-height) 4) 'above)) (gdb-breakpoints-window (split-window-horizontally (/ (window-width) 3)))) ;; source buffer (select-window gdb-src-window) (set-window-buffer gdb-src-window (if gud-last-last-frame (gud-find-file (car gud-last-last-frame)) (if gdb-main-file (gud-find-file gdb-main-file) ;; scratch if we can't find a src file (get-buffer-create "*scratch*")))) (setq gdb-source-window (selected-window)) ;; io buffer (let ((gdb-io-window (split-window-horizontally (/ (* 2 (window-width)) 3)))) (gdb-set-window-buffer (gdb-get-buffer-create 'gdb-inferior-io) nil gdb-io-window) ;; stack buffer (select-window gdb-io-window) (let ((gdb-stack-window (split-window nil (/ (* 2 (window-height)) 3) 'above))) (gdb-set-window-buffer (gdb-stack-buffer-name) nil gdb-stack-window))) ;; breakpoints buffer (select-window gdb-breakpoints-window) (gdb-set-window-buffer (if gdb-show-threads-by-default (gdb-threads-buffer-name) (gdb-breakpoints-buffer-name)) nil gdb-breakpoints-window) ;; locals buffer (let ((gdb-locals-window (split-window-right))) (gdb-set-window-buffer (gdb-locals-buffer-name) nil gdb-locals-window)) ;; select the main window and bounce (select-window gdb-comint-window)))
Here is another layout, one that displays the instructions buffer instead of I/O by default. This is relatively useful for embedded programming
(defun do.cpp.gdb/disassembly-layout () "Lay out the window pattern for option `gdb-many-windows'. This is replacing the I/O buffer with a disassembly buffer." (if gdb-default-window-configuration-file (gdb-load-window-configuration (if (file-name-absolute-p gdb-default-window-configuration-file) gdb-default-window-configuration-file (expand-file-name gdb-default-window-configuration-file gdb-window-configuration-directory))) ;; Create default layout as before. ;; Make sure that local values are updated before locals. (gdb-get-buffer-create 'gdb-locals-values-buffer) (gdb-get-buffer-create 'gdb-locals-buffer) (gdb-get-buffer-create 'gdb-stack-buffer) (gdb-get-buffer-create 'gdb-breakpoints-buffer) (set-window-dedicated-p (selected-window) nil) (switch-to-buffer gud-comint-buffer) (delete-other-windows) (let ((win0 (selected-window)) (win1 (split-window nil ( / ( * (window-height) 3) 4))) (win2 (split-window nil ( / (window-height) 3))) (win3 (split-window-right))) (gdb-set-window-buffer (gdb-locals-buffer-name) nil win3) (select-window win2) (set-window-buffer win2 (or (gdb-get-source-buffer) (list-buffers-noselect))) (setq gdb-source-window-list (list (selected-window))) (let ((win4 (split-window-right))) (gdb-set-window-buffer ;; (gdb-get-buffer-create 'gdb-inferior-io) nil win4 (gdb-get-buffer-create 'gdb-disassembly-buffer) nil win4)) (select-window win1) (gdb-set-window-buffer (gdb-stack-buffer-name)) (let ((win5 (split-window-right))) (gdb-set-window-buffer (if gdb-show-threads-by-default (gdb-threads-buffer-name) (gdb-breakpoints-buffer-name)) nil win5)) (select-window win0))))
We choose the alternate layout as default. If we wanted to change this during runtime, we can just change the variable below.
(defvar do.cpp.gdb/layout 'disassembly "Specifies the GDB layout to use. ALTERNATE is optimzed for higher-resolution displays, whereas DEFAULT is better for lower resolutions. DISASSEMBLY is like DEFAULT, but it replaces the I/O buffer with the disassembly buffer. Add more options as needed.") (defun do.cpp.gdb/setup-windows (original-gdb-setup-windows) "Call the appropriate function for `gdb-setup-windows'. Add more options as needed." (cond ((eq do.cpp.gdb/layout 'alternate) (funcall #'do.cpp.gdb/alt-layout)) ((eq do.cpp.gdb/layout 'disassembly) (funcall #'do.cpp.gdb/disassembly-layout)) (t (funcall original-gdb-setup-windows))))
TODO Advice
Debug: for some reason, the advice does not get activated inside of the use-package statement…
(add-function :around (symbol-function #'gdb-setup-windows) #'do.cpp.gdb/setup-windows)
Keybindings
(("<f5>" . gud-next) ("<f6>" . gud-step) ("<f7>" . gud-cont) ("<f8>" . gud-finish))
Simpler gdb
(add-hook 'gud-mode-hook 'no-trailing-whitespace)
(add-hook 'gdb-disassembly-mode-hook 'no-trailing-whitespace)
(add-hook 'gud-mode-hook '(lambda () (setq-local ml-interactive? t)))
C++ style comments in C
(defun do.cpp/c-hooks () (setq comment-start "//" comment-end "")) (add-hook 'c-mode-hook #'do.cpp/c-hooks)
Preprocessor macros
Those can always look a little sharper than the default…
(use-package preproc-font-lock :ensure t :init (preproc-font-lock-global-mode 1) :config (make-face 'do.cpp/preproc-macro-face) (cond ((eq do.theme/enabled-theme 'dark) (set-face-attribute 'do.cpp/preproc-macro-face nil :background (face-attribute 'default :background) :foreground (face-attribute 'font-lock-constant-face :foreground) :underline nil :slant (if (display-graphic-p) 'italic 'normal) :weight 'bold)) ((eq do.theme/enabled-theme 'light) (set-face-attribute 'do.cpp/preproc-macro-face nil :background (face-attribute 'default :background) :foreground (face-attribute 'font-lock-constant-face :foreground) :underline nil :slant (if (display-graphic-p) 'italic 'normal) :weight 'bold))) (setq preproc-font-lock-preprocessor-background-face 'do.cpp/preproc-macro-face))
Font-locking for modern c++
(use-package modern-cpp-font-lock :ensure t :diminish modern-c++-font-lock-mode :init (modern-c++-font-lock-global-mode t))
CMake
The packages cmake-mode and cmake-font-lock help when editing CMakeLists.txt source files.
(use-package cmake-font-lock :ensure t :commands cmake-font-lock-activate) (use-package cmake-mode :ensure t :mode ("CMakeLists\\.txt\\'" "\\.cmake\\'") :init (add-hook 'cmake-mode-hook 'cmake-font-lock-activate))
LSP
Over the years, I went from CEDET -> rtags + cmake-ide -> LSP. The configuration is still a little tricky, but much easier than before.
TODO: break up and document properly
(use-package lsp-clangd :after lsp-mode :demand t) (defvar do.cpp.lsp/clangd-executable nil "path to the clangd executable") (defun do.cpp.lsp/start-lsp () "Start LSP in C++ and C mode. Have to hack this a little bit." (when (or (equal major-mode 'c++-mode) (equal major-mode 'c-mode)) ;; only start LSP when clangd is installed. otherwise LSP will ask to ;; download clangd and we do not want that! (if (not do.cpp.lsp/clangd-executable) ;; only start LSP when the executable is set (display-warning :warning "`do.cpp.lsp/clangd-executable' is not set. not starting LSP.") ;; it is not clear to me why I have to do this here. this seems ;; to be the only place that this works. we make a few copies ;; of clangd so we can run multiple instances of it. to force ;; a particular project to use e.g. clangd-3, we do the ;; equivalent of the following in .dir-locals.el: ;; ;; (setq lsp-enabled-clients '(clangd-3)) (unless (gethash 'clangd-1 lsp-clients) (dolist (id '(clangd-1 clangd-2 clangd-3 clangd-4 clangd-5 clangd-6)) (let ((c (copy-lsp--client (gethash 'clangd lsp-clients)))) (setf (lsp--client-server-id c) id) (setf (lsp--client-priority c) -5) (lsp-register-client c)))) ;; point clangd to the compile commands file and the query driver (setq lsp-clients-clangd-executable do.cpp.lsp/clangd-executable) (setq lsp-clients-clangd-args `(;; compile-commands.json needs to be in `do-cpp-build-dir'. ,@(if (boundp 'do-cpp-build-dir) (list "--compile-commands-dir" do-cpp-build-dir) (message (concat "`do-cpp-build-dir' not defined." " " "calling clangd without --compile-commands-dir argument.")) nil) ;; query-driver is especially important when the compiler ;; is NOT from the same distribution as the clangd executable ,@(when (boundp 'do-cpp-clangd-query-driver) (list "--query-driver" do-cpp-clangd-query-driver)) ;; we allow to use more or less threads, default is 4. ,@(if (boundp 'do-cpp-clangd-threads) (list (format "-j=%d" do-cpp-clangd-threads)) '("-j=4")) "-background-index")) ;; start lsp-mode (lsp)))) (defun do.cpp/add-compile-bindings () (local-set-key (kbd "C-c c") #'do.cpp/try-compile) (local-set-key (kbd "C-c t") #'do.cpp/try-test)) ;; call this sometime directly from init.el (defun do.cpp.lsp/setup () ;; we need to hook in after the local variables are set. This affects every ;; buffer open, but should be ok (add-hook 'hack-local-variables-hook #'do.cpp.lsp/start-lsp) ;; bind the compile commands to the major mode (add-hook 'c++-mode-hook #'do.cpp/add-compile-bindings) (add-hook 'c-mode-hook #'do.cpp/add-compile-bindings)) (do.cpp.lsp/setup) ;; try-compile (defun do.cpp/is-cmake-build-dir (dir) (file-exists-p (expand-file-name "CMakeCache.txt" dir))) (defun do.cpp/is-makefile-build-dir (dir) (or (file-exists-p (expand-file-name "Makefile" dir)) (file-exists-p (expand-file-name "makefile" dir)) (file-exists-p (expand-file-name "GNUmakefile" dir)))) (defun do.cpp/is-ninja-build-dir (dir) (file-exists-p (expand-file-name "build.ninja" dir))) (defun do.cpp/get-cmake-compile-command (dir) (cond ((do.cpp/is-ninja-build-dir dir) (concat (executable-find "ninja") " -C " (shell-quote-argument dir))) ((do.cpp/is-makefile-build-dir dir) (let* ((nprocbin (executable-find "nproc")) (nprocs (if nprocbin (replace-regexp-in-string "\n$" "" (shell-command-to-string nprocbin)) "1"))) (concat (executable-find "make") " -j " nprocs " -C " (shell-quote-argument dir)))) (t nil))) ;; get-build-dir (defun do.cpp/get-build-dir () "Get the location of the build directory." (expand-file-name (if (boundp 'do-cpp-build-dir) do-cpp-build-dir ;; TODO: find a way to silently save this dir-local variable. ;; check the code of "add-dir-local-variable" (read-directory-name "Couldn't find build directory. Select: ")))) (defun do.cpp/try-compile-command (command) (let ((build-dir (do.cpp/get-build-dir)) (compilation-read-command nil) (compilation-scroll-output t) (cmd (or command ""))) (cond ;; custom compile command ((bound-and-true-p do-cpp-build-cmd) (compile do-cpp-build-cmd)) ;; CMake build ((do.cpp/is-cmake-build-dir build-dir) (let ((compile-cmd (do.cpp/get-cmake-compile-command build-dir))) (compile (concat compile-cmd " " cmd)))) ;; Makefile build ((do.cpp/is-makefile-build-dir build-dir) (let* ((nprocbin (executable-find "nproc")) (nprocs (if nprocbin (replace-regexp-in-string "\n$" "" (shell-command-to-string nprocbin)) "1"))) (compile (concat (executable-find "make") " -j " nprocs " -C " (shell-quote-argument build-dir) " " cmd)))) ;; we give up (t (error "Could not find compile command"))))) (defun do.cpp/try-compile () (interactive) (do.cpp/try-compile-command nil)) (defun do.cpp/try-test () () (interactive) (do.cpp/try-compile-command "test"))
.dir-locals.el
To make all of this work, we set a directory-local variable
do-cpp-build-dir
at the root of the project directory.
((nil . ((do-cpp-build-dir . "<PATH_TO_PROJECT_BUILD_DIRECTORY>"))))