#+TITLE: Development #+OPTIONS: toc:nil #+PROPERTY: header-args:emacs-lisp :shebang ";;; -*- lexical-binding: t; -*-\n" ** Table of Contents :toc_4: - [[#company][Company]] - [[#git][Git]] - [[#project][Project]] - [[#languages][Languages]] - [[#language-server-support][Language Server Support]] - [[#debug-adapter-support][Debug Adapter Support]] - [[#cc][C/C++]] - [[#cmake][CMake]] - [[#glsl][GLSL]] - [[#html][HTML]] - [[#kotlin][Kotlin]] - [[#lua][Lua]] - [[#php][PHP]] - [[#python][Python]] - [[#yaml][YAML]] - [[#quality-of-life][Quality of Life]] - [[#flycheck][Flycheck]] - [[#flyspell][Flyspell]] - [[#rainbow-delimiters][Rainbow Delimiters]] - [[#rainbow-mode][Rainbow Mode]] - [[#yasnippet][YASnippet]] ** Company #+BEGIN_SRC emacs-lisp (use-package company :hook ((c-mode-common emacs-lisp-mode latex-mode org-mode php-mode shell-mode shell-script-mode) . company-mode) :config (setq company-idle-delay 0.2) (setq company-minimum-prefix-length 2) (setq company-show-numbers t) (setq company-tooltip-align-annotations 't)) #+END_SRC Sort Company completions. #+BEGIN_SRC emacs-lisp (use-package company-prescient :after (company prescient) :config (company-prescient-mode)) #+END_SRC Auto-completion for C/C++ headers. #+BEGIN_SRC emacs-lisp (use-package company-c-headers :after company :config (add-to-list 'company-backends 'company-c-headers)) #+END_SRC GLSL integration with company requires the package: ~glslang~. #+BEGIN_SRC emacs-lisp (when (executable-find "glslangValidator") (use-package company-glsl :after company :config (add-to-list 'company-backends 'company-glsl))) #+END_SRC ** Git #+BEGIN_SRC emacs-lisp (use-package diff-hl :demand :hook (prog-mode . turn-on-diff-hl-mode) :hook (prog-mode . dot/diff-hl-enable-flydiff-and-fringe) :config (defun dot/diff-hl-enable-flydiff-and-fringe () "Enable on the fly diff checking if file is under version control." (let ((buffer buffer-file-name)) (when (and buffer (vc-registered buffer)) (diff-hl-flydiff-mode) (dot/toggle-fringe 1))))) (use-package transient :defer t :config (setq transient-history-file (concat dot-cache-dir "/transient/history.el"))) (use-package magit :after (diff-hl transient) :hook (git-commit-setup . git-commit-turn-on-auto-fill) :hook (git-commit-setup . git-commit-turn-on-flyspell) :hook (magit-pre-refresh . diff-hl-magit-pre-refresh) :hook (magit-post-refresh . diff-hl-magit-post-refresh) :config (setq git-commit-fill-column 72) (setq git-commit-summary-max-length 70) (setq magit-completing-read-function #'selectrum-completing-read) (setq magit-diff-paint-whitespace-lines 'both) (setq magit-repository-directories '(("~/dotfiles" . 0) ("~/code" . 3))) (put 'magit-log-select-pick :advertised-binding [?\M-c]) (put 'magit-log-select-quit :advertised-binding [?\M-k])) #+END_SRC ** Project Project manager. [[https://michael.stapelberg.ch/posts/2021-04-02-emacs-project-override/][Adding to project.el project directory detection]]. #+BEGIN_SRC emacs-lisp (use-package project :defer t :init (setq project-list-file (concat dot-cache-dir "/projects")) :config (defun dot/project-find (dir) (let ((root (locate-dominating-file dir ".project"))) (and root (cons 'vc root)))) (add-hook 'project-find-functions #'dot/project-find) (defun dot/find-project-root () "Return root of the project, determined by `.git/' and `.project', `default-directory' otherwise." (let ((project (project-current))) (if project (project-root project) default-directory))) (defun dot/find-file-in-project-root () "Find file in project root." (interactive) (let ((default-directory (dot/find-project-root))) (call-interactively 'find-file))) (defun dot/project-remember-projects-under (dir maxdepth) "Index all projects below directory DIR recursively, until MAXDEPTH." (let ((files (mapcar 'file-name-directory (dot/directory-files-recursively-depth dir "\\.git$\\|\\.project$" t maxdepth)))) (dolist (path files) (project-remember-projects-under path)))) (unless (file-exists-p project-list-file) (project-remember-projects-under "~/dotfiles") (dot/project-remember-projects-under "~/code" 4)) (defun dot/project-project-name () "Return project name." (let ((project (project-current))) (if project (file-name-nondirectory (directory-file-name (project-root project))) "-"))) (defun dot/project-save-project-buffers () "Save all project buffers." (interactive) (let ((buffers (cl-remove-if (lambda (buffer) (not (buffer-file-name buffer))) (project-buffers (project-current))))) (save-some-buffers t (lambda () (member (current-buffer) buffers)))))) #+END_SRC ** Languages *** Language Server Support Language Server Protocol. #+BEGIN_SRC emacs-lisp (use-package lsp-mode :commands lsp :after which-key :hook ((c-mode ; clangd c++-mode ; clangd lua-mode ; lua-language-server php-mode ; nodejs-intelephense latex-mode ; texlab kotlin-mode ; kotlin-language-server web-mode) . lsp-deferred) :config (setq lsp-auto-guess-root t) (setq lsp-clients-clangd-args '("-j=2" "--background-index" "--clang-tidy" "--compile-commands-dir=build" "--log=error" "--pch-storage=memory" "--enable-config")) (setq lsp-clients-lua-language-server-bin "/usr/bin/lua-language-server") (setq lsp-clients-lua-language-server-install-dir "/usr/lib/lua-language-server/") (setq lsp-clients-lua-language-server-main-location "/usr/lib/lua-language-server/main.lua") (setq lsp-enable-xref t) (setq lsp-headerline-breadcrumb-enable nil) (setq lsp-intelephense-storage-path (concat dot-cache-dir "/lsp-cache")) (setq lsp-keep-workspace-alive nil) (setq lsp-prefer-flymake nil) (setq lsp-session-file (concat dot-cache-dir "/lsp-session-v1")) ;; Mark clangd args variable as safe to modify via .dir-locals.el (put 'lsp-clients-clangd-args 'safe-local-variable #'listp) ;; Enable which-key descriptions (dolist (leader-key (list dot/leader-key dot/leader-alt-key)) (let ((lsp-keymap-prefix (concat leader-key " l"))) (lsp-enable-which-key-integration))) (defun dot/lsp-format-buffer-or-region () "Format the selection (or buffer) with LSP." (interactive) (unless (bound-and-true-p lsp-mode) (message "Not in an LSP buffer")) (call-interactively (if (use-region-p) #'lsp-format-region #'lsp-format-buffer))) ;; This is cached to prevent unneeded I/O (setq lsp-in-cpp-project-cache nil) (defun dot/lsp-format-cpp-buffer () "Format buffer in C++ projects." (unless lsp-in-cpp-project-cache (set (make-local-variable 'lsp-in-cpp-project-cache) (list (if (and (eq major-mode 'c++-mode) (bound-and-true-p lsp-mode) (or (locate-dominating-file "." ".clang-format") (locate-dominating-file "." "_clang-format"))) t nil)))) (when (car lsp-in-cpp-project-cache) (lsp-format-buffer))) (add-hook 'before-save-hook #'dot/lsp-format-cpp-buffer)) ;; TODO: add lsp-signature keybinds (use-package lsp-ui :commands lsp-ui-mode :after (flycheck lsp-mode) :config (setq lsp-ui-doc-border (face-foreground 'default)) (setq lsp-ui-doc-delay 0.5) (setq lsp-ui-doc-enable t) (setq lsp-ui-doc-header t) (setq lsp-ui-doc-include-signature t) (setq lsp-ui-doc-position 'top) (setq lsp-ui-doc-use-childframe t) (setq lsp-ui-flycheck-list-position 'right) (setq lsp-ui-imenu-enable nil) (setq lsp-ui-peek-enable nil) (setq lsp-ui-sideline-enable nil)) #+END_SRC *** Debug Adapter Support Debug Adapter Protocol. #+BEGIN_SRC emacs-lisp (use-package treemacs :after all-the-icons :hook (treemacs-mode . dot/hook-disable-line-numbers) :config (setq treemacs-persist-file (concat dot-cache-dir "/treemacs/persist"))) (use-package dap-mode :after (treemacs lsp-mode) :hook (lsp-after-initialize . dot/dap-install-debug-adapters) :config (setq dap-auto-configure-features '(sessions locals expressions controls tooltip)) (setq dap-breakpoints-file (concat dot-cache-dir "/dap/breakpoints")) (setq dap-utils-extension-path (concat dot-cache-dir "/dap")) ;; Create dap extension directory (unless (file-directory-p dap-utils-extension-path) (make-directory dap-utils-extension-path t)) (defun dot/dap-install-debug-adapters () "Install and Load debug adapters." (interactive) (unless (bound-and-true-p lsp-mode) (user-error "Not in an LSP buffer")) (when (string-equal major-mode "c++-mode") (require 'dap-cpptools) (dap-cpptools-setup)))) #+END_SRC *** C/C++ #+BEGIN_SRC emacs-lisp (use-package c-mode :ensure nil :defer t ;; C++ // line comment style in c-mode :hook (c-mode . (lambda () (c-toggle-comment-style -1)))) #+END_SRC *** CMake #+BEGIN_SRC emacs-lisp (use-package cmake-mode :config (setq cmake-tab-width 4) :defer t) #+END_SRC *** GLSL #+BEGIN_SRC emacs-lisp (use-package glsl-mode :defer t) #+END_SRC *** HTML #+BEGIN_SRC emacs-lisp (use-package web-mode :defer t) #+END_SRC *** Kotlin #+BEGIN_SRC emacs-lisp (use-package kotlin-mode :defer t) #+END_SRC *** Lua #+BEGIN_SRC emacs-lisp (use-package lua-mode :defer t :config (setq lua-indent-level 4)) #+END_SRC *** PHP #+BEGIN_SRC emacs-lisp (use-package php-mode :defer t :hook (php-mode . (lambda () (setq indent-tabs-mode t)))) (use-package restclient :defer t) #+END_SRC *** Python #+BEGIN_SRC emacs-lisp (use-package python-mode :ensure nil :defer t :hook (python-mode . (lambda () (setq indent-tabs-mode t) (setq python-indent-offset 4) (setq tab-width 4)))) #+END_SRC *** YAML #+BEGIN_SRC emacs-lisp (use-package yaml-mode :defer t) #+END_SRC ** Quality of Life **** Flycheck On the fly syntax checking. #+BEGIN_SRC emacs-lisp (use-package flycheck :hook ((c-mode-common emacs-lisp-mode latex-mode org-mode php-mode sh-mode shell-mode shell-script-mode) . flycheck-mode) :config (setq flycheck-clang-language-standard "c++20") (setq flycheck-gcc-language-standard "c++20")) ;; For .el files which are intended to be packages (use-package flycheck-package :after flycheck :config (add-to-list 'flycheck-checkers 'flycheck-emacs-lisp-package) (flycheck-package-setup)) (use-package flycheck-clang-tidy :after flycheck :hook (flycheck-mode . flycheck-clang-tidy-setup) :config (setq flycheck-clang-tidy-extra-options "--format-style=file")) #+END_SRC **** Flyspell Give Flyspell a selection menu. #+BEGIN_SRC emacs-lisp (use-package flyspell-correct :after flyspell :demand :hook (org-mode . flyspell-mode) :config (setq flyspell-issue-message-flag nil) (setq flyspell-issue-welcome-flag nil) (defun dot/flyspell-toggle () "Toggle Flyspell, prompt for language." (interactive) (if (symbol-value flyspell-mode) (flyspell-mode -1) (call-interactively 'ispell-change-dictionary) (if (derived-mode-p 'prog-mode) (flyspell-prog-mode) (flyspell-mode)) (flyspell-buffer)))) #+END_SRC **** Rainbow Delimiters #+BEGIN_SRC emacs-lisp (use-package rainbow-delimiters :hook (prog-mode . rainbow-delimiters-mode)) #+END_SRC **** Rainbow Mode #+BEGIN_SRC emacs-lisp (use-package rainbow-mode :hook (css-mode . rainbow-mode)) #+END_SRC **** YASnippet #+BEGIN_SRC emacs-lisp (use-package yasnippet :defer t :init (setq yas-snippet-dirs (list (concat dot-emacs-dir "/snippets"))) (setq yas-prompt-functions '(yas-completing-prompt)) :config (yas-global-mode)) (use-package yasnippet-snippets :after yasnippet) #+END_SRC https://stackoverflow.com/questions/22735895/configuring-a-yasnippet-for-two-scenarios-1-region-is-active-2-region-is