;;; ravi-init-cpp.el --- C/C++ handling ;; Copyright (C) 2013 ;; Author: ;; Keywords: ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; C/C++ initialization ;;; Code: ; Generate a random string (of letters and numbers) of a given length (defun ravi-random-ucn-string (len) (coerce (loop for i below len for x = (random 36) collect (+ x (cond ((< x 10) 48) ((< x 36) (- 65 10)) ))) 'string)) (use-package cmake-mode :mode (("CMakeLists\\.txt\\'" . cmake-mode) ("\\.cmake\\'" . cmake-mode))) (defvar get-buffer-compile-command (lambda (file) (cons file 1))) (make-variable-buffer-local 'get-buffer-compile-command) (setq-default compile-command "") (defun compile-dwim (&optional arg) "Compile Do What I Mean. Compile using `compile-command'. When `compile-command' is empty prompt for its default value. With prefix C-u always prompt for the default value of `compile-command'. With prefix C-u C-u prompt for buffer local compile command with suggestion from `get-buffer-compile-command'. An empty input removes the local compile command for the current buffer." (interactive "P") (cond ((and arg (> (car arg) 4)) (let ((cmd (read-from-minibuffer "Buffer local compile command: " (funcall get-buffer-compile-command (or (file-relative-name (buffer-file-name)) "")) nil nil 'compile-history))) (cond ((equal cmd "") (kill-local-variable 'compile-command) (kill-local-variable 'compilation-directory)) (t (set (make-local-variable 'compile-command) cmd) (set (make-local-variable 'compilation-directory) default-directory)))) (when (not (equal compile-command "")) ;; `compile' changes the default value of ;; compilation-directory but this is a buffer local ;; compilation (let ((dirbak (default-value 'compilation-directory))) (compile compile-command) (setq-default compilation-directory dirbak)))) ((or (and arg (<= (car arg) 4)) (equal compile-command "")) (setq-default compile-command (read-shell-command "Compile command: " (if (equal compile-command "") "make " compile-command) 'compile-history)) (setq-default compilation-directory default-directory) (when (not (equal (default-value 'compile-command) "")) (compile (default-value 'compile-command)))) (t (recompile)))) (use-package dummy-h-mode :mode (("\\.h\\'" . dummy-h-mode)) :config (progn ;; Hack to avoid ObjC files since we do not use it (defun ravi/do-not-allow-objc (mode-val) (if (eq mode-val 'objc-mode) nil mode-val)) (mapc (lambda (x) (advice-add x :filter-return #'ravi/do-not-allow-objc)) '(dummy-h-mode-get-major-mode-by-source-file ;dummy-h-mode-get-major-mode-by-keywords dummy-h-mode-get-major-mode-by-files-directory)) (setq dummy-h-mode-default-major-mode 'c++-mode))) (use-package cc-mode :mode (("\\.h\\(h\\|xx\\|pp\\)\\'" . c++-mode) ("\\.ccfg\\'" . c++-mode) ("\\.m\\'" . c-mode) ("\\.mm\\'" . c++-mode)) :init (progn (defvar printf-index 0) (defun insert-counting-printf (arg) (interactive "P") (if arg (setq printf-index 0)) (if t (insert (format "std::cerr << \"step %d..\" << std::endl;\n" (setq printf-index (1+ printf-index)))) (insert (format "printf(\"step %d..\\n\");\n" (setq printf-index (1+ printf-index))))) (forward-line -1) (indent-according-to-mode) (forward-line)) ) :config (progn (use-package highlight-doxygen :diminish highlight-doxygen-mode) (defun my-c-mode-common-hook () (hs-minor-mode 1) (hs-hide-initial-comment-block) (hide-ifdef-mode 1) (highlight-doxygen-mode) (diminish 'hs-minor-mode) (diminish 'hide-ifdef-mode) (bind-key "C-c P" 'insert-counting-printf c-mode-base-map) ;; (define-key c-mode-base-map [return] 'c-context-line-break) (bind-key "" 'c-context-line-break c-mode-base-map) (bind-key "RET" 'c-context-line-break c-mode-base-map) ; needed on non-X (bind-key "" 'compile-dwim c-mode-base-map) (unbind-key "M-j" c-mode-base-map) (bind-key "C-c C-i" 'c-includes-current-file c-mode-base-map) (when (functionp 'helm-dash) (setq-local dash-docs-docsets '("C"))) (when (functionp 'consult-dash) (setq-local consult-dash-docsets '("C"))) (set (make-local-variable 'parens-require-spaces) t) (setq fill-column 88) (bind-key "M-q" 'c-fill-paragraph c-mode-base-map) (c-toggle-electric-state 1) (c-toggle-auto-newline 1) (c-toggle-hungry-state 1) (setq-local lsp-enable-on-type-formatting nil) (c-set-style "stroustrup") (setq c-basic-offset 2) (setq c-recognize-knr-p nil) (modify-syntax-entry ?_ "w" c-mode-syntax-table) (add-to-list 'c-cleanup-list 'empty-defun-braces) (add-to-list 'c-cleanup-list 'defun-close-semi) (add-to-list 'c-cleanup-list 'list-close-comma) (add-to-list 'c-cleanup-list 'scope-operator) ;; one-liner-defun and comment-close-slash found to be annoying (when (bound-and-true-p ravi/use-cquery-mode) (setq-local company-transformers nil)) (font-lock-add-keywords 'c++-mode '(("\\<\\(assert\\|DEBUG\\)(" 1 font-lock-warning-face t)))) (add-hook 'c-mode-common-hook 'my-c-mode-common-hook) (defun my-c++-mode-hook () (setq c-offsets-alist (append '((statement-cont . c-lineup-math) (inline-open . 0)) c-offsets-alist)) (let ((innamespaceindent (if (and (buffer-file-name) (string-equal "ccfg" (file-name-extension (buffer-file-name)))) 2 0)) ) (setq c-offsets-alist (append `((innamespace . ,innamespaceindent) ) c-offsets-alist)) ) (modify-syntax-entry ?_ "w" c++-mode-syntax-table) (setq c-macro-cppflags "-x c++") (setq c-macro-prompt-flag t) (when (functionp 'helm-dash) (setq-local dash-docs-docsets '("C" "C++" "Boost" "Qt"))) (when (functionp 'consult-dash) (setq-local consult-dash-docsets '("C" "C++" "Boost" "Qt"))) ) (defun ravi/c++-hook-adder () (add-hook 'c++-mode-hook 'my-c++-mode-hook)) (ravi/c++-hook-adder) ;; Annoyance in emacs 25.1+: c-tnt-chng-cleanup calls set-mark/deactivate-mark (advice-add 'c-tnt-chng-cleanup :around #'ravi/without-region-bindings-mode) ;; Stuff from kde-emacs (defvar kde-header-protection-parts-to-show 1 "Set this variable to the number of parts from the file name you want to be used for the defined word in the header-protection function.. E.g. setting this to 3 makes header-protection define KIG_MISC_NEWTYPE_H for a file named /home/domi/src/kdenonbeta/kig/misc/newtype.h") (defun kde-header-protection-definable-string () (let* ((definablestring (concat "_" (ravi-random-ucn-string 6)) ) (f (buffer-file-name)) (parts (nreverse (split-string f "/"))) (i) (first-iter t) (iters (min (length parts) kde-header-protection-parts-to-show))) (dotimes (i iters) (let ((part (pop parts))) (setq definablestring (concat (upcase (replace-regexp-in-string "[\\.-]" "_" part)) (if (not first-iter) "_" "") definablestring ) ) (setq first-iter nil) ) ) definablestring ) ) ;; Creates the ifndef/define/endif statements necessary for a header file (defun header-protection () (interactive) (save-excursion (goto-char (point-min)) (insert "#pragma once\n\n"))) (defun ravi-license-insert () "Insert license header from `ravi-license-header'" (let ((start (point-min)) (comment-style 'box) (license-header (ravi-license-header)) (end)) (when license-header (goto-char start) (insert license-header) (setq end (point)) (comment-region start end) (insert ?\n)))) (defun ravi-insert-file-comment () (insert "/**\n * \\file\n *\n * \n */\n")) (defun ravi-start-c++-header () "Start a new C++ header by inserting include guards ( see \ header-protection function ), inserting a license statement \ and putting (point) at the correct position" (interactive) (header-protection) (save-excursion (ravi-license-insert)) (end-of-buffer) (ravi-insert-file-comment)) (setq auto-insert-query nil) (define-auto-insert "\\.\\([Cc]\\|cc\\|cpp\\|cxx\\|tcc\\)\\'" 'ravi-auto-insert-cpp) (define-auto-insert "\\.\\([Hh]\\|hh\\|hpp\\)\\'" 'ravi-start-c++-header) (defun ravi-auto-insert-cpp () (progn (ravi-license-insert) (ravi-insert-file-comment))) (defun ravi/add-space-before-paren-c (&rest ignored) (message "Handler invoked") (when (looking-back "\\b\\(for\\|while\\|if\\|switch\\)(" nil) (save-excursion (backward-char) (insert-char ?\s) ) )) (defadvice c-electric-paren (after ravi/add-space-around-parens activate) (unless (c-in-literal) (cond ((looking-back "([[:space:]]*" nil) (progn ;; Open parenthesis was the last command; add a space before it if ;; necessary, and ensure that the point is placed after a space (save-excursion (re-search-backward "([[:space:]]*") (unless (looking-at "(") (message "Logic error; opening paren not found")) (when (looking-back "\\b\\(for\\|while\\|if\\|switch\\|catch\\)" nil) (insert-char ?\s))) (if (and (looking-at " ") (not (looking-at " )"))) (forward-char) (insert-char ?\s)) ) ) ((looking-back ")[[:space:]]*" nil) ;; Closing parenthesis was the last command; add spaces if necessary (progn ;; Compact empty function calls (when (looking-back "([[:space:]]+)[[:space:]]*" nil) (save-excursion (re-search-backward ")[[:space:]]*") (delete-horizontal-space) ) ) ;; Add space before closing parenthesis (unless (looking-back "()[[:space:]]*" nil) (save-excursion (re-search-backward ")[[:space:]]*") (unless (looking-at ")") (message "Logic error; closing paren not found")) (unless (looking-back " " nil) (insert-char ?\s))) ) (let (need-to-add-newline) (save-excursion (c-backward-sexp) (c-backward-syntactic-ws) ;; FIXME: do-while loops are not handled correctly (setq need-to-add-newline (looking-back "\\b\\(for\\|while\\|if\\|switch\\|catch\\)" nil)) ) (when need-to-add-newline (c-newline-and-indent))) ) ) )) ) (defadvice c-electric-semi&comma (after ravi/add-space-after-comma activate) (cond ((looking-back "," nil) (if (looking-at " [^)]") (forward-char) (insert-char ?\s))) ((looking-back ") ;[[:space:]\n]*" nil) (save-excursion (re-search-backward " ;[[:space:]\n]*") (delete-char 1))))) ;; Do not activate region as it interferes with region-bindings-mode (defadvice c-electric-brace (after ravi/do-not-activate-region activate) (deactivate-mark)) (defun ravi/insert-closing-delimiter(arg) (interactive "p") (if (c-in-literal) (self-insert-command arg) (progn ;; Find nearest unmatched opening delimiter (let (brace-type) (save-excursion (backward-up-list) (cond ((looking-at "{") (setq brace-type 'brace-type-brace)) ((looking-at "(") (setq brace-type 'brace-type-paren)) ((looking-at "\\[") (setq brace-type 'brace-type-square)) (t (setq brace-type 'brace-type-none)) )) (cond ((eq brace-type 'brace-type-brace) (setq unread-command-events (listify-key-sequence "}"))) ((eq brace-type 'brace-type-paren) (setq unread-command-events (listify-key-sequence ")"))) ; Uncommenting the line below would lead to infinite recursion ;((eq 'brace-type 'brace-type-square) (setq unread-command-events (listify-key-sequence "]"))) (t (self-insert-command 1)) ) )))) (bind-key "]" 'ravi/insert-closing-delimiter c-mode-base-map) (use-package rtags :load-path (lambda () (ravi/emacs-file "site-lisp/rtags/src")) :config (progn (setq use-rtags-hydra t) (setq rtags-path (ravi/emacs-file "site-lisp/rtags/bin")) (setq rtags-autostart-diagnostics t) (setq rtags-completions-enabled t) (when (equal ravi/use-selection-system 'helm) (setq rtags-display-result-backend 'helm)) ;; Extra support for company-c-headers if we can get include paths ;; from the compilation command line from rtags ;; (require 'dash) (require 's) (defun ravi/set-company-c-headers-paths-from-rtags () (interactive) (let* ((compile-flags (rtags-compilation-flags)) (nostdinc (-contains? compile-flags "-nostdinc")) (isystem-found) (i-found) (system-dirs) (user-dirs)) (when compile-flags (setq isystem-found nil i-found nil system-dirs (list) user-dirs (list)) (dolist (flag compile-flags) (cond (isystem-found (add-to-list 'system-dirs flag t) (setq isystem-found nil)) (i-found (add-to-list 'user-dirs flag t) (setq i-found nil)) ((s-equals? "-isystem" flag) (setq isystem-found t)) ((s-equals? "-I" flag) (setq i-found t)) ((s-starts-with? "-I" flag) (add-to-list 'user-dirs (s-chop-prefix "-I" flag) t)) (t nil))) (setq user-dirs (-concat (-difference user-dirs system-dirs) system-dirs)) ;(message "User dirs: %s" user-dirs) (if nostdinc (set (make-local-variable 'company-c-headers-path-system) user-dirs) (when user-dirs (make-local-variable 'company-c-headers-path-system) (setq company-c-headers-path-system (-concat user-dirs company-c-headers-path-system))))))) (defun ravi/rtags-add-hook () (unless (file-remote-p default-directory) (rtags-start-process-unless-running) ;; Set company C headers from rtags only after rtags starts running (ravi/set-company-c-headers-paths-from-rtags))) (add-hook 'c-mode-hook 'ravi/rtags-add-hook) (add-hook 'c++-mode-hook 'ravi/rtags-add-hook) (use-package company-rtags :config (progn (bind-key "C-" 'company-rtags c-mode-base-map) (add-to-list 'company-backends 'company-rtags))))) ;; Choices of different LSP backends (setq ravi/use-cpp-lsp-backends '(ravi/use-cpp-ccls ravi/use-cpp-none)) (setq ravi/use-cpp-lsp-backend 'ravi/use-cpp-ccls) (unless (memq ravi/use-cpp-lsp-backend ravi/use-cpp-lsp-backends) (message "Invalid value for C++ LSP backend") (setq ravi/use-cpp-lsp-backend 'ravi/use-cpp-none)) (use-package ccls :if (eq ravi/use-cpp-lsp-backend 'ravi/use-cpp-ccls) :config (setq ccls-executable (ravi/emacs-file "site-lisp/ccls/Release/ccls")) (setq ccls-args `(,(concat "--log-file=" (ravi/past-file "ccls.log")))) ; to do: use relative path ) (setq hide-ifdef-initially nil) (pretty-hydra-define ravi/hydra-cextra (:color blue :hint nil) ("Hide" (("i" hide-ifdef-block "hide ifdef") ("C-i" show-ifdef-block "show ifdef") ("b" hs-toggle-hiding "hide/show block") ("o" ff-find-other-file "other file")))) (when (bound-and-true-p use-rtags-hydra) (pretty-hydra-define+ ravi/hydra-cextra () ("At point" (("." rtags-find-symbol-at-point "symbol") ("," rtags-find-references-at-point "references") ("v" rtags-find-virtuals-at-point "virtuals") ("V" rtags-print-enum-value-at-point "enum value") ("/" rtags-find-all-references-at-point "all references") ("G" rtags-guess-function-at-point "guess function") ("X" rtags-fix-fixit-at-point "fix fixit") ("M" rtags-symbol-info "symbol info")) "Location" (("[" rtags-location-stack-back "back" :exit nil) ("]" rtags-location-stack-forward "forward" :exit nil) ("L" rtags-copy-and-print-current-location "copy") ("O" rtags-goto-offset "to offset") ("F" rtags-fixit "fixit") ("K" rtags-make-member "member") ("Z" rtags-location-stack-visualize "stach") ("A" rtags-find-functions-called-by-this-function "functions")) "File" (("p" rtags-dependency-tree "dependencies") ("e" rtags-reparse-file "reparse") ("E" rtags-preprocess-file "preprocess") ("C" rtags-compile-file "compile") ("I" rtags-imenu "imenu") ("T" rtags-taglist "tag list") (";" rtags-find-file "find file") ("a" rtags-print-source-arguments "source args")) "Information" (("D" rtags-diagnostics "diagnostics") ("S" rtags-display-summary-as-message "summary") ("B" rtags-show-rtags-buffer "rtags buffer") ("h" rtags-print-class-hierarchy "class hierarchy") (">" rtags-find-symbol "find symbol") ("<" rtags-find-references "find references") ("R" rtags-rename-symbol "rename symbol") ("P" rtags-dependency-tree-all "all deps")) )) (bind-key "" 'ravi/hydra-cextra/body c-mode-base-map)) (bind-key "" 'compile-dwim c-mode-base-map) (use-package multi-compile :commands multi-compile-run :init (bind-key (if ravi-ergodox-mode "" "") 'multi-compile-run c-mode-base-map) :config (progn (when (fboundp 'ravi/multi-compile-local-setup) (ravi/multi-compile-local-setup)))) ;; Enable Malinka (use-package malinka :disabled t :config (progn ;; At this point, malinka has been loaded, so f.el has also ;; been loaded and is ready for use. (defun ravi/get-project-name-and-build-dir (proj-root-dir) (interactive "D") (let* ((proj-parent (and (f-exists? (f-dirname proj-root-dir)) (f-dirname proj-root-dir))) (proj-build (and proj-parent (f-exists? (f-join proj-parent "build")) (f-join proj-parent "build"))) (proj-root-dir-last (car (last (f-split proj-root-dir)))) (proj-name (if (and (s-equals? proj-root-dir-last "source") proj-parent) (car (last (f-split proj-parent))) proj-root-dir-last))) (list proj-name proj-build))) (defun ravi/find-current-project () (interactive) (unless (or (not (buffer-file-name)) (malinka--file-belongs-to-project (buffer-file-name))) (message "Searching for project for %s" (buffer-file-name)) (let* ((proj-root (or (and (boundp 'ravi-project-root) ravi-project-root) (malinka--project-detect-root))) (proj-name-and-build-dir (and proj-root (ravi/get-project-name-and-build-dir proj-root))) (proj-name (or (and (boundp 'ravi-project-name) ravi-project-name) (and proj-root (nth 0 proj-name-and-build-dir)))) (proj-build-dir (or (and (boundp 'ravi-project-build-dir) ravi-project-build-dir) (and proj-root (nth 1 proj-name-and-build-dir)))) (proj-compilation-database (and (boundp 'ravi-project-compilation-database) ravi-project-compilation-database)) (proj-configure-cmd (or (and (boundp 'ravi-project-configure-cmd) ravi-project-configure-cmd) (and proj-root (f-exists? (f-join proj-root "CMakeLists.txt")) "cmake")))) (when proj-root (message "%s %s %s %s" proj-root proj-name proj-build-dir proj-compilation-database) (malinka-define-project :name proj-name :root-directory proj-root :build-directory proj-build-dir :configure-cmd proj-configure-cmd))))) (defun ravi/setup-malinka () (interactive) (unless (file-remote-p default-directory) (malinka-mode 1) (ravi/find-current-project))) (add-hook 'c-mode-common-hook 'ravi/setup-malinka))) ) ) (use-package gud :commands gud-gdb :bind ("C-x H-g" . show-debugger) :init (defun show-debugger () (interactive) (let ((gud-buf (catch 'found (dolist (buf (buffer-list)) (if (string-match "\\*gud-" (buffer-name buf)) (throw 'found buf)))))) (if gud-buf (switch-to-buffer-other-window gud-buf) (call-interactively 'gud-gdb)))) :config (defhydra gud-gdb-hydra (:color red) "gdb" ("l" gud-refresh "refresh") ("s" gud-step "step") ("n" gud-next "next") ("i" gud-stepi "stepi") ("p" gud-print "print") ("r" gud-cont "cont") ("d" gud-remove "remove breakpoint") ("t" gud-tbreak "temporary breakpoint") ("u" gud-up "up frame") (">" gud-down "down frame") ("u" gud-until "until") ("f" gud-finish "finish") ("j" gud-jump "jump to line")) (bind-key "M-g u" 'gud-gdb-hydra/body) ) (provide 'ravi-init-cpp) ;;; ravi-init-cpp.el ends here