init-cc.el 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. ;;; C/C++
  2. ;;; TODO: Should we split this into mode-c and mode-c++?
  3. (dolist (map (list c-mode-map c++-mode-map))
  4. (ambrevar/define-keys map "C-c m" 'cc-main
  5. "<f5>" 'ambrevar/cc-clean
  6. "M-." 'semantic-ia-fast-jump
  7. "C-c C-d" 'semantic-ia-show-doc
  8. "M-<tab>" 'semantic-complete-analyze-inline)
  9. (when (require 'company nil t)
  10. (define-key map (kbd "M-<tab>") (if (require 'helm-company nil t) 'helm-company 'company-complete)))
  11. (when (require 'gtk-look nil 'noerror)
  12. (define-key map (kbd "C-c d") 'gtk-lookup-symbol)))
  13. ;; (define-key map (kbd "C-c o") 'ff-find-other-file)
  14. (when (and (require 'gtk-look nil 'noerror)
  15. (require 'w3m nil 'noerror))
  16. ;; Browse GTK documentation within Emacs.
  17. ;; TODO: Use eww instead of w3m. For some reason it hangs for almost a minute
  18. ;; with eww.
  19. (add-to-list 'browse-url-browser-function '("file:///.*/gtk-doc/html/.*" . w3m-browse-url)))
  20. (defvaralias 'c-basic-offset 'tab-width)
  21. ;;; C additional faces.
  22. ;;; Useless in quasi-monochrome.
  23. ;; (dolist (mode '(c-mode c++-mode))
  24. ;; (font-lock-add-keywords
  25. ;; mode
  26. ;; '(("\\<\\(and\\|or\\|not\\)\\>" . font-lock-keyword-face)
  27. ;; ;; Functions.
  28. ;; ("\\<\\(\\sw+\\)(" 1 'font-lock-function-name-face)
  29. ;; ("\\<\\(\\sw+\\)<\\sw+>(" 1 'font-lock-function-name-face))))
  30. (defvar-local ambrevar/cc-ldlibs "-lm -pthread"
  31. "Custom linker flags for C/C++ linkage.")
  32. (defvar-local ambrevar/cc-ldflags ""
  33. "Custom linker libs for C/C++ linkage.")
  34. (defun ambrevar/cc-set-compiler (&optional nomakefile)
  35. "Set compile command to be nearest Makefile or a generic command.
  36. The Makefile is looked up in parent folders. If no Makefile is
  37. found (or if NOMAKEFILE is non-nil or if function was called with
  38. universal argument), then a configurable commandline is
  39. provided."
  40. (interactive "P")
  41. (hack-local-variables)
  42. ;; Alternatively, if a Makefile is found, we could change default directory
  43. ;; and leave the compile command to "make". Changing `default-directory'
  44. ;; could have side effects though.
  45. (let ((makefile-dir (locate-dominating-file "." "Makefile")))
  46. (if (and makefile-dir (not nomakefile))
  47. (setq compile-command (concat "make -k -C " (shell-quote-argument (file-name-directory makefile-dir))))
  48. (setq compile-command
  49. (let
  50. ((c++-p (eq major-mode 'c++-mode))
  51. (file (file-name-nondirectory buffer-file-name)))
  52. (format "%s %s -o '%s' %s %s %s"
  53. (if c++-p
  54. (or (getenv "CXX") "g++")
  55. (or (getenv "CC") "gcc"))
  56. (shell-quote-argument file)
  57. (shell-quote-argument (file-name-sans-extension file))
  58. (if c++-p
  59. (or (getenv "CXXFLAGS") "-Wall -Wextra -Wshadow -DDEBUG=9 -g3 -O0")
  60. (or (getenv "CFLAGS") "-ansi -pedantic -std=c11 -Wall -Wextra -Wshadow -DDEBUG=9 -g3 -O0"))
  61. (or (getenv "LDFLAGS") ambrevar/cc-ldflags)
  62. (or (getenv "LDLIBS") ambrevar/cc-ldlibs)))))))
  63. (defun ambrevar/cc-clean ()
  64. "Find Makefile and call the `clean' rule. If no Makefile is
  65. found, no action is taken. The previous `compile' command is
  66. restored."
  67. (interactive)
  68. (let (compile-command
  69. (makefile-dir (locate-dominating-file "." "Makefile")))
  70. (when makefile-dir
  71. (compile (format "make -k -C %s clean" (shell-quote-argument makefile-dir))))))
  72. (defun ambrevar/cc-force-compile ()
  73. "If current `compile-command' is `make', force compilation.
  74. This is done by appending the -B flag (--always-make) temporarily.
  75. If the command is not `make', run it normally. "
  76. (interactive)
  77. (let ((compile-command compile-command))
  78. (if (string-prefix-p "make " compile-command)
  79. (compile (format "%s -B" compile-command))
  80. (recompile))))
  81. ;; TODO: See https://github.com/koko1000ban/emacs-uncrustify-mode.
  82. (defun ambrevar/cc-format-with-uncrustify (&optional cfg-file start end)
  83. "Run uncrustify(1) on current buffer or region."
  84. (interactive "f\nr")
  85. (let (status
  86. (start (or (and (region-active-p) start)
  87. (point-min)))
  88. (end (or (and (region-active-p) end)
  89. (point-max)))
  90. (formatbuf (get-buffer-create " *C format buffer*"))
  91. (stderr (make-temp-file "uncrustify")))
  92. (setq status
  93. (call-process-region start end
  94. "uncrustify"
  95. nil (list formatbuf stderr) nil
  96. ;; "-lc" ; Uncrustify should be able to auto-detect.
  97. "-c"
  98. (or cfg-file
  99. (expand-file-name ".uncrustify.cfg" (getenv "HOME")))))
  100. (if (/= status 0)
  101. (let ((error-message
  102. (with-temp-buffer
  103. (insert-file-contents-literally stderr)
  104. (buffer-string))))
  105. (delete-file stderr)
  106. (kill-buffer formatbuf)
  107. (error "error running uncrustify: %s" error-message))
  108. (delete-file stderr)
  109. (let ((old-line (line-number-at-pos))
  110. (old-column (current-column))
  111. (old-window-start-line (- (line-number-at-pos)
  112. (line-number-at-pos (window-start)))))
  113. (buffer-disable-undo)
  114. (save-mark-and-excursion
  115. (delete-region (or start (point-min)) (or end (point-max)))
  116. (insert-buffer-substring formatbuf))
  117. (goto-line old-line)
  118. (move-to-column old-column)
  119. (ignore-errors
  120. ;; recenter won't work if selected window is not the target buffer.
  121. (recenter old-window-start-line))
  122. (buffer-enable-undo))
  123. (kill-buffer formatbuf)))
  124. ;; Return nil if in a `write-file-functions'.
  125. nil)
  126. (defun ambrevar/cc-format-file-lookup ()
  127. "Find .clang-format or .uncrustify.cfg in parent folder up to Git root.
  128. Return nil if non is found or if not a Git repository."
  129. (unless (require 'magit nil 'noerror)
  130. (error "Magit is missing"))
  131. (when (or (magit-get-current-branch) (magit-get-current-tag))
  132. (let ((git-root (magit-rev-parse "--show-toplevel"))
  133. (default-directory default-directory))
  134. (while (and (string= (magit-rev-parse "--show-toplevel") git-root)
  135. (not (file-exists-p ".clang-format"))
  136. (not (file-exists-p ".uncrustify.cfg")))
  137. (cd ".."))
  138. (or (and (file-exists-p ".clang-format")
  139. (expand-file-name ".clang-format" default-directory))
  140. (and (file-exists-p ".uncrustify.cfg")
  141. (expand-file-name ".uncrustify.cfg" default-directory))))))
  142. (defun ambrevar/cc-format ()
  143. "Format C file.
  144. It uses `ambrevar/cc-format-file-lookup' to find the format rules.
  145. This is suitable for a `before-save-hook'."
  146. (interactive)
  147. (let ((cfg-file (ambrevar/cc-format-file-lookup)))
  148. (cond
  149. ((and (string= (file-name-base cfg-file) ".clang-format")
  150. (require 'clang-format nil 'noerror))
  151. (clang-format-buffer))
  152. ((and (string= (file-name-base cfg-file) ".uncrustify")
  153. (executable-find "uncrustify"))
  154. (ambrevar/cc-format-with-uncrustify cfg-file)))))
  155. (defun ambrevar/cc-turn-on-format ()
  156. "Add `ambrevar/cc-format' to `before-save-hook' locally.
  157. You can add your mode hook (e.g. `c-mode-hook')."
  158. (add-hook 'write-file-functions 'ambrevar/cc-format nil 'local))
  159. ;;; GMP documentation
  160. (with-eval-after-load "info-look"
  161. (let ((mode-value (assoc 'c-mode (assoc 'symbol info-lookup-alist))))
  162. (setcar (nthcdr 3 mode-value)
  163. (cons '("(gmp)Function Index" nil "^ -.* " "\\>")
  164. (nth 3 mode-value)))))
  165. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  166. ;;; Options
  167. ;; We don't want semantic in Scheme and others.
  168. (setq semantic-new-buffer-setup-functions
  169. '((c-mode . semantic-default-c-setup)
  170. (c++-mode . semantic-default-c-setup)))
  171. ;;; Make sure Semanticdb folders is set before starting semantic.
  172. (semantic-mode 1)
  173. ;; (global-semantic-stickyfunc-mode)
  174. ;; (global-semantic-decoration-mode)
  175. ;; Eldoc does not work but there is Semantic.
  176. (global-semantic-idle-summary-mode)
  177. (setq semantic-idle-scheduler-idle-time 5)
  178. ;; Add support for some subdir-includes.
  179. ;; TODO: This could be automated by parsing `pkg-config --list-all` or maybe
  180. ;; `find -L . -maxdepth 1 -type d`.
  181. (setq semantic-c-dependency-system-include-path
  182. (append (mapcar (lambda (lib)
  183. (expand-file-name
  184. lib
  185. (or (getenv "CPATH")
  186. (if (file-directory-p (expand-file-name "~/.guix-profile/include"))
  187. "~/.guix-profile/include"
  188. "/usr/include"))))
  189. '("glib-2.0" "gtk-3.0" "webkitgtk-4.0"))
  190. '("/usr/include")))
  191. (c-add-style
  192. "ambrevar"
  193. `((c-comment-only-line-offset . 0)
  194. (c-auto-align-backslashes . nil)
  195. (c-basic-offset . ,tab-width)
  196. (c-offsets-alist
  197. (arglist-cont-nonempty . +)
  198. (arglist-intro . +)
  199. (c . 0)
  200. (case-label . 0)
  201. (cpp-define-intro . 0)
  202. (cpp-macro . 0)
  203. (knr-argdecl-intro . 0)
  204. (label . 0)
  205. (statement-block-intro . +)
  206. (statement-cont . +)
  207. (substatement-label . 0)
  208. (substatement-open . 0))))
  209. (nconc c-default-style '((c-mode . "ambrevar") (c++-mode . "ambrevar")))
  210. ;;; Note that in Emacs 24, cc-mode calls its hooks manually in each mode init
  211. ;;; function. Since cc modes belong to prog-mode, each hook is called another
  212. ;;; time at the end of the initialization. No big deal since we only set some
  213. ;;; variables.
  214. (dolist (hook '(c-mode-hook c++-mode-hook))
  215. (when (require 'company nil t)
  216. (add-hook hook 'company-mode))
  217. (add-hook hook 'ambrevar/cc-turn-on-format)
  218. (add-hook hook 'ambrevar/cc-set-compiler))
  219. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  220. ;;; Skeletons
  221. ;;; Note that it is possible to extend the skel syntax with
  222. ;;; `skeleton-further-elements'. For instance:
  223. ; (setq skeleton-further-elements '((q "\"")))
  224. (define-skeleton cc-debug
  225. "Insert debug macros."
  226. nil
  227. > "#ifdef DEBUG
  228. #define DEBUG_CMD(CMD) do {CMD;} while(0)
  229. #else
  230. #define DEBUG_CMD(CMD) do {} while(0)
  231. #endif
  232. "
  233. '(insert-and-indent
  234. "#define DEBUG_PRINT(...) DEBUG_CMD( \\
  235. fprintf(stderr, \"%s:%d:\\t(%s)\\t\", __FILE__, __LINE__, __func__); \\
  236. fprintf(stderr, __VA_ARGS__); \\
  237. fprintf(stderr, \"\\n\"); \\
  238. )"))
  239. (define-skeleton cc-getopt
  240. "Insert a getopt template."
  241. nil
  242. > "int opt;" \n
  243. "while ((opt = getopt(argc, argv, \":hV\")) != -1) {" \n
  244. "switch(opt) {" \n
  245. "case 'h':" > \n
  246. "usage(argv[0]);" \n
  247. "return 0;" \n
  248. "case 'V':" > \n
  249. "version();" \n
  250. "return 0;" \n
  251. "case ':':" > \n
  252. "fprintf(stderr, \"ERROR: -%c needs an argument.\\nTry '%s -h' for more information.\\n\", optopt, argv[0]);" \n
  253. "return EXIT_FAILURE;" \n
  254. "case '?':" > \n
  255. "fprintf(stderr, \"ERROR: Unknown argument %c.\\nTry '%s -h' for more information.\\n\", optopt, argv[0]);" \n
  256. "return EXIT_FAILURE;" \n
  257. "default:" > \n
  258. "usage(argv[0]);" \n
  259. "return EXIT_SUCCESS;" \n
  260. "}" > \n
  261. "}" > "\n" \n
  262. "if (optind >= argc) {" \n
  263. "fprintf(stderr, \"Expected argument after options\\n\");" \n
  264. "exit(EXIT_FAILURE);" \n
  265. "}" > \n)
  266. (define-skeleton cc-loadfile
  267. "Insert loadfile function."
  268. nil
  269. "unsigned long loadfile(const char *path, char **buffer_ptr) {" \n
  270. "#define MAX_FILESIZE 1073741824 /* One gigabyte */" > "\n" \n
  271. "/* Handle variable. */" \n
  272. "char *buffer;" "\n" \n
  273. "FILE *file = fopen(path, \"rb\");" \n
  274. "if (file == NULL) {" \n
  275. "perror(path);" \n
  276. "return 0;" \n
  277. "}" > "\n" \n
  278. "fseek(file, 0, SEEK_END);" \n
  279. "long length = ftell(file);" \n
  280. "/* fprintf(stdout, \"Note: file %s is %u bytes long.\\n\", path, length); */" "\n" \n
  281. "if (length > MAX_FILESIZE) {" \n
  282. "fprintf(stderr, \"%s size %ld is bigger than %d bytes.\\n\", path, length, MAX_FILESIZE);" \n
  283. "fclose(file);" \n
  284. "return 0;" \n
  285. "}" > "\n" \n
  286. "fseek(file, 0, SEEK_SET);" \n
  287. "buffer = (char *)malloc(length + 1);" \n
  288. "if (buffer == NULL) {" \n
  289. "perror(\"malloc\");" \n
  290. "fclose(file);" \n
  291. "return 0;" \n
  292. "}" > "\n" \n
  293. "if (fread(buffer, 1, length, file) == 0) {" \n
  294. "fclose(file);" \n
  295. "return 0;" \n
  296. "}" > "\n" \n
  297. "buffer[length] = '\\0';" \n
  298. "fclose(file);" "\n" \n
  299. "*buffer_ptr = buffer;" \n
  300. "return length;" \n
  301. "}" > \n)
  302. (define-skeleton cc-main
  303. "Insert main function with basic includes."
  304. nil
  305. > "#include <inttypes.h>
  306. #include <stdbool.h>
  307. #include <stdint.h>
  308. #include <stdio.h>
  309. #include <stdlib.h>
  310. #include <string.h>
  311. #include <unistd.h>
  312. int main(int argc, char **argv) {" \n
  313. > @ _ \n
  314. > "return 0;
  315. }" \n)
  316. (define-skeleton cc-usage-version
  317. "Insert usage() and version() functions."
  318. "Synopsis: "
  319. > "static void usage(const char *executable) {" \n
  320. "printf(\"Usage: %s [OPTIONS]\\n\\n\", executable);" \n
  321. "puts(\"" str "\\n\");" "\n" \n
  322. "puts(\"Options:\");" \n
  323. "puts(\" -h Print this help.\");" \n
  324. "puts(\" -V Print version information.\");" "\n" \n
  325. "puts(\"\");" \n
  326. "printf(\"See %s for more information.\\n\", MANPAGE);" \n
  327. "}" > "\n" \n
  328. "static void version() {" \n
  329. "printf(\"%s %s\\n\", APPNAME, VERSION);" \n
  330. "printf(\"Copyright © %s %s\\n\", YEAR, AUTHOR);" \n
  331. "}" > \n)
  332. (provide 'init-cc)