From 92ebbb28e3dac921c30a7b09759329daf971406c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20=C3=96stlund?= Date: Sat, 21 Nov 2020 15:08:50 +0100 Subject: [PATCH 1/8] Beginning move to non-org-mode config --- new-config.el | 312 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 new-config.el diff --git a/new-config.el b/new-config.el new file mode 100644 index 0000000..cdf0ecd --- /dev/null +++ b/new-config.el @@ -0,0 +1,312 @@ +;;; Code: + +;;;; Package installation and management + +;; This section initializes `package' with a set of archives and installs `use-package' which is +;; used to install other package dependencies. + +(require 'package) + +(setq package-archives + '(("gnu" . "https://elpa.gnu.org/packages/") + ("melpa" . "https://melpa.org/packages/"))) + +(unless (package-installed-p 'use-package) ; install use-package if it's not already installed + (package-refresh-contents) + (package-install 'use-package)) + +(eval-when-compile + (require 'use-package) + (setq use-package-always-ensure)) + + +;;;; System local configuration + +;; This section handles configurations that are local to the system and thus should not be under +;; version control, for instance credentials and system specific feature flags. + +(load-file (expand-file-name "local-settings.el" + user-emacs-directory)) + + +;;;; Basic setup + +;; This contains some basic default configuration which does not depend on any external packages. + +;; Cleanup the UI by removing the menu tool and scroll bars. +(mapc (lambda (mode) + (when (fboundp mode) + (funcall mode -1))) + '(menu-bar-mode tool-bar-mode scroll-bar-mode)) + +;; Remove the warning bell. +(setq ring-bell-function 'ignore) + +;; Remove the GNU/Emacs startup screen to boot direct to the Scratch buffer. +(setq inhibit-startup-screen t) + +;; Make sure that buffer names become unique when opening multiple files of the same name. +(setq-default frame-title-format "%b (%f)" + uniquify-buffer-name-style 'post-forward + uniquify-separator ":")a + +;; Ask before killing Emacs. +(setq confirm-kill-emacs 'y-or-n-p) + +;; Put any Emacs generated customizations into ./custom.el instead of ./init.el. +(setq custom-file (expand-file-name "custom.el" + user-emacs-directory)) + +;; Improve performance by increasing the garbage collector threshold and max LISP evaluation depth. +(setq gc-cons-threshold 100000000 + max-lisp-eval-depth 2000) + +;; Move backups and auto-saves to ~/.emacs.d/.local/.saves. +(setq create-lockfiles nil + backup-directory-alist `(("." . ,(expand-file-name ".local/.saves/" user-emacs-directory))) + backup-by-copying t + delete-old-versions t + kept-new-versions 6) + +;; Keep some files in ~/.emacs.d/.local to avoid cluttering the configuration root directory. +(setq url-configuration-directory (expand-file-name ".local/uri/" user-emacs-directory) + image-dired-dir (expand-file-name ".local/image-dired-thumbnails/" user-emacs-directory) + bookmark-default-file (expand-file-name ".local/bookmarks" user-emacs-directory) + tramp-auto-save-directory (expand-file-name ".local/tramp-autosaves/" user-emacs-directory)) + +;; Setup authentication file and pinentry for entering passwords. +(setq auth-sources '("~/.authinfo.gpg" + "~/.netrc")) + +;; Auto scroll through output in compilation buffers. +(setq compilation-scroll-output t) + + +;;;; Editor default settings + +;; This section changes the default text editing behavior of Emacs so it is more inline with my +;; expectations. + +;; Use spaces instead of tabs. +(setq-default indent-tabs-mode nil + tab-width 2) + +(setq-default require-final-newline t) ; Files should always have a final newline. + +(setq-default sentence-end-double-space nil) ; Sentence end does not require two spaces. + +;; Fix buffer scrolling behavior. +(setq-default scroll-conservatively 0 + scroll-step 4 + next-screen-context-lines 20) + +(add-hook 'before-save-hook 'delete-trailing-whitespace) ; Delete trailing whitespace on save. + +(delete-selection-mode 1) ; Replace selected text with typed text. + +;; Enable narrowing to region and page. Pages are delimited by ^L. +(put 'narrow-to-page 'disabled nil) +(put 'narrow-to-region 'disabled nil) + +;;;;; Programming mode defaults + +(defun iensu--prog-mode-hook () + "Defaults for programming modes" + (subword-mode 1) ; delimit words at camelCase boundries + (eldoc-mode 1) ; display documentation in minibuffer + (show-paren-mode 1) ; highlight matching parentheses + (setq-default show-paren-when-point-in-periphery t + show-paren-when-point-inside-paren t) + (hs-minor-mode 1) ; hide-show code and comment blocks + (outline-minor-mode 1)) ; Navigate by outlines + +(add-hook 'prog-mode-hook #'iensu--prog-mode-hook) + + +;;;; Utility packages + +;;;;; Hydra + +;; Install `hydra' with `pretty-hydra' which simplifies hydra definitions +(use-package hydra) +(use-package pretty-hydra :after (hydra)) + + +;;;; Global keybindings + +(global-set-key (kbd "C-") 'delete-indentation) +(global-set-key (kbd "C-h C-s") 'iensu/toggle-scratch-buffer) +(global-set-key (kbd "C-x C-b") 'ibuffer) +(global-set-key (kbd "M-") 'fixup-whitespace) +(global-set-key (kbd "M-i") 'imenu) +(global-set-key (kbd "M-o") 'occur) + +;; `windmove' enables navigation using `-'. These bindings conflict `org-mode' so +;; we make `windmove' take precedence. +(windmove-default-keybindings) +(setq org-replace-disputed-keys t) ; This line needs to occur before `org-mode' is loaded. + +;;;;; Global hydra + +;; Setup a global hydra with keybindings I use very often. +(pretty-hydra-define iensu-hydra + (:color teal :quit-key "q" :title "Global commands") + ("Email" + (("e u" mu4u-update-index "update" :exit nil) + ("e e" mu4e "open email") + ("e c" mu4e-compose-new "write email") + ("e s" mu4e-headers-search "search email")) + "Elfeed" + (("f f" elfeed) + ("f u" elfeed-update)) + "Org clock" + (("c c" org-clock-in "start clock") + ("c r" org-clock-in-last "resume clock") + ("c s" org-clock-out "stop clock") + ("c g" org-clock-goto "goto clocked task")) + "Utilities" + (; ("d" iensu/duplicate-line "duplicate line" :exit nil) + ("s" deadgrep "search") + ("t" toggle-truncate-lines "truncate lines") + ("u" revert-buffer "reload buffer") + ; ("l" iensu/cycle-ispell-dictionary "change dictionary")) + "Misc" + (("P" iensu/project-todo-list "project todo list") + ("i" iensu/open-init-file "open emacs config") + ("9" iensu/refresh-work-calendar "update calendar") + ("+" enlarge-window-horizontally "enlarge window" :exit nil) + ("-" shrink-window-horizontally "shrink window" :exit nil)) + "Hide/show" + (("h h" hs-toggle-hiding "Toggle block visibility") + ("h l" hs-hide-level "Hide all blocks at same level") + ("h a" hs-hide-all "Hide all") + ("h s" hs-show-all "Show all")))) + +;;;;; macOS specific keybindings + +;; Set command to act as `meta' (`M-') and disable the `option' key since that button is needed to +;; type various characters on a Swedish keyboard. Also make the right `option' key act as `hyper' +;; (`H-') to give us more keybindings to work with. +(setq mac-command-modifier 'meta + mac-option-modifier 'none + mac-right-option-modifier 'hyper) + +;; An unfortunate workaround required when switching to an external keyboard. +(defun iensu/switch-left-and-right-option-keys () + "Switch left and right option keys. + + On some external keyboards the left and right Mac `option' keys are swapped, + this command switches the keys so that they work as expected." + (interactive) + (let ((current-left mac-option-modifier) + (current-right mac-right-option-modifier)) + (setq mac-option-modifier current-right + mac-right-option-modifier current-left))) + + +;;;; Make Emacs prettier + +(setq-default cursor-type '(bar . 2)) +(global-prettify-symbols-mode 1) +(global-font-lock-mode 1) + +;; Use dark mode on macOS. +(add-to-list 'default-frame-alist '(ns-transparent-titlebar . t)) +(add-to-list 'default-frame-alist '(ns-appearence . dark)) + +;; Use different colored parentheses based on scope. +(use-package rainbow-delimiters + :hook (prog-mode . #'rainbow-delimiters-mode)) + +;; Use icons where applicable. +(use-package all-the-icons) + +;; Use a clean mode line +(use-package doom-modeline + :init (doom-modeline-mode 1)) + +;; Set theme +(use-package modus-vivendi-theme + :config + (load-theme 'modus-vivendi t) + (set-face-attribute 'font-lock-comment-face nil :slant 'italic) + (set-face-attribute 'font-lock-comment-delimiter-face nil :slant 'italic) + (set-face-attribute 'default nil :font "Fira Code-13") + (set-face-attribute 'fixed-pitch nil :font "Fira Code-13") + (set-face-attribute 'variable-pitch nil :font "Cantarell-14")) + + +;;;; Version control + +;; Make `magit' and other version control tools follow symlinks. +(setq vc-follow-symlinks t) + +;; Use `magit' for a great `git' experience. +(use-package magit + :bind (("C-x g" . magit-status)) + :custom + (magit-bury-buffer-function 'quit-window) + :config + (when (executable-find "/usr/bin/git") ; Speeds up git operations on macOS + (setq magit-git-executable "/usr/bin/git")) + (setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)) + +;; `smerge-mode' is a merge conflict resolution tool which is great but unfortunately has awful +;; default keybindings. Here I define a hydra to make `smerge' easier to work with. +(use-package smerge-mode + :ensure nil + :bind (:map smerge-mode-map (("C-c ö" . smerge-mode-hydra/body))) + :pretty-hydra + ((:color teal :quit-key "q" :title "Smerge - Git conflicts") + ("Resolving" + (("RET" smerge-keep-current "Keep current" :exit nil) + ("l" smerge-keep-lower "Keep lower" :exit nil) + ("u" smerge-keep-upper "Keep upper" :exit nil) + ("b" smerge-keep-base "Keep base" :exit nil) + ("C" smerge-combine-with-next "Combine with next") + ("a" smerge-keep-all "Keep all" :exit nil) + ("r" smerge-resolve "Resolve")) + "Navigation" + (("n" smerge-next "Next conflict" :exit nil) + ("p" smerge-prev "Previous conflict" :exit nil) + ("R" smerge-refine "Highlight differences" :exit nil)) + "Misc" + (("E" smerge-ediff "Open in Ediff"))))) + + + +;;;; Org mode + +;;;;; Agenda + +(setq calendar-week-start-day 1) ; The week starts on Monday. + + +;;;; Emacs server + +;; The Emacs server keeps the Emacs instance running in the background so opening files in Emacs +;; will be snappy. +(unless (server-running-p) + (server-start)) + + +;;;; Custom commands + +(defun iensu/toggle-scratch-buffer () + "Based on a great idea from Eric Skoglund (https://github.com/EricIO/emacs-configuration/)." + (interactive) + (if (string-equal (buffer-name (current-buffer)) + "*scratch*") + (switch-to-buffer (other-buffer)) + (switch-to-buffer "*scratch*"))) + +(defun iensu/toggle-profiler () + "Starts or stops the profiler, displaying the report when stopped." + (interactive) + (if (profiler-running-p) + (progn + (profiler-stop) + (profiler-report)) + (progn + (profiler-reset) + (profiler-start 'cpu+mem)))) From a75dbfec0b3c69498cb60eab4b625761a1deff96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20=C3=96stlund?= Date: Tue, 24 Nov 2020 22:19:59 +0100 Subject: [PATCH 2/8] WIP --- .gitignore | 1 + features/elfeed.el | 4 + features/email.el | 50 +++ features/gcal.el | 15 + features/lang-csharp.el | 17 + features/lang-docker.el | 3 + features/lang-elm.el | 6 + features/lang-fsharp.el | 9 + features/lang-graphql.el | 1 + features/lang-graphviz.el | 5 + features/lang-java.el | 9 + features/lang-javascript.el | 59 +++ features/lang-json.el | 3 + features/lang-markdown.el | 6 + features/lang-nix.el | 1 + features/lang-python.el | 1 + features/lang-rust.el | 18 + features/lang-scala.el | 17 + features/lang-sql.el | 8 + features/lang-terraform.el | 12 + features/lang-toml.el | 2 + features/lang-typescript.el | 15 + features/lang-wasm.el | 2 + features/lang-yaml.el | 10 + features/org-agenda.el | 34 ++ features/web-dev.el | 38 ++ new-config.el | 782 +++++++++++++++++++++++++++++++++--- 27 files changed, 1065 insertions(+), 63 deletions(-) create mode 100644 features/elfeed.el create mode 100644 features/email.el create mode 100644 features/gcal.el create mode 100644 features/lang-csharp.el create mode 100644 features/lang-docker.el create mode 100644 features/lang-elm.el create mode 100644 features/lang-fsharp.el create mode 100644 features/lang-graphql.el create mode 100644 features/lang-graphviz.el create mode 100644 features/lang-java.el create mode 100644 features/lang-javascript.el create mode 100644 features/lang-json.el create mode 100644 features/lang-markdown.el create mode 100644 features/lang-nix.el create mode 100644 features/lang-python.el create mode 100644 features/lang-rust.el create mode 100644 features/lang-scala.el create mode 100644 features/lang-sql.el create mode 100644 features/lang-terraform.el create mode 100644 features/lang-toml.el create mode 100644 features/lang-typescript.el create mode 100644 features/lang-wasm.el create mode 100644 features/lang-yaml.el create mode 100644 features/org-agenda.el create mode 100644 features/web-dev.el diff --git a/.gitignore b/.gitignore index f306997..21e1e9b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ test.org transient/ .dir-locals.el features.el +local-settings.el /projectile-bookmarks.eld flycheck_init.el diff --git a/features/elfeed.el b/features/elfeed.el new file mode 100644 index 0000000..fc16c80 --- /dev/null +++ b/features/elfeed.el @@ -0,0 +1,4 @@ +(pretty-hydra-define+ iensu-hydra + ("Elfeed" + (("f f" elfeed) + ("f u" elfeed-update)))) diff --git a/features/email.el b/features/email.el new file mode 100644 index 0000000..4aa1aa0 --- /dev/null +++ b/features/email.el @@ -0,0 +1,50 @@ +(use-package mu4e + :ensure nil + :load-path "/usr/local/share/emacs/site-lisp/mu/mu4e" + :bind (:map mu4e-view-mode-map + ("" . shr-next-link) + ("" . shr-previous-link)) + :hook + (mu4e-view-mode . visual-line-mode) + :init + (require 'mu4e) + :config + (setq mail-user-agent 'mu4e-user-agent) + (setq mu4e-mu-binary "/usr/local/bin/mu") + (setq mu4e-maildir "~/Mail") + (setq mu4e-confirm-quit nil) + (setq mu4e-context-policy 'pick-first) + + ;; Configuration for viewing emails + (setq mu4e-view-show-images t) + (setq mu4e-show-images t) + (setq mu4e-view-image-max-width 800) + (setq mu4e-compose-format-flowed t) + (setq mu4e-view-show-addresses t) + (setq mu4e-headers-fields '((:human-date . 12) + (:flags . 6) + (:tags . 16) + (:from . 22) + (:subject))) + + ;; Configuration for composing/sending emails + (setq user-mail-address "jens.ostlund@futurice.com") + (setq user-full-name "Jens Östlund") + (setq message-send-mail-function 'smtpmail-send-it) + (setq smtpmail-debug-info t) + (setq mu4e-sent-messages-behavior 'delete) + (setq message-kill-buffer-on-exit t) + (setq mu4e-compose-context-policy 'ask-if-none) + + (add-hook 'mu4e-compose-mode-hook (lambda () (auto-fill-mode -1))) + + ;; Add email viewing modes + (add-to-list 'mu4e-view-actions '("EWW" . iensu--mu4e-view-in-eww) t) + (add-to-list 'mu4e-view-actions '("ViewInBrowser" . mu4e-action-view-in-browser) t)) + +(pretty-hydra-define+ iensu-hydra + ("Email" + (("e u" mu4u-update-index "update" :exit nil) + ("e e" mu4e "open email") + ("e c" mu4e-compose-new "write email") + ("e s" mu4e-headers-search "search email")))) diff --git a/features/gcal.el b/features/gcal.el new file mode 100644 index 0000000..295e53e --- /dev/null +++ b/features/gcal.el @@ -0,0 +1,15 @@ +;; Stores google calendar events to my org `work-calendar' file. Sync by running `M-x org-gcal-sync'. +(use-package org-gcal + :init + (setq org-gcal-token-file (expand-file-name ".local/org-gcal/org-gcal-token" user-emacs-directory) + org-gcal-dir (expand-file-name ".local/org-gcal/" user-emacs-directory)) + :config + (setq org-gcal-client-id iensu-gcal-client-id + org-gcal-client-secret iensu-gcal-client-secret + org-gcal-file-alist `(("jens.ostlund@futurice.com" . ,(expand-file-name "calendars/work.org" + org-directory))))) + +(defun iensu/refresh-work-calendar () + "Fetch Google calendar events and add the proper file tag(s)." + (interactive) + (org-gcal-fetch)) diff --git a/features/lang-csharp.el b/features/lang-csharp.el new file mode 100644 index 0000000..9599fef --- /dev/null +++ b/features/lang-csharp.el @@ -0,0 +1,17 @@ +(use-package csharp-mode + :mode ("\\.cs$" "\\.cshtml") + :hook (csharp-mode . lsp) + :custom + (flycheck-check-syntax-automatically '(save mode-enabled)) + :config + (defun iensu--csharp-mode-hook () + (c-set-offset 'arglist-intro '+) + (setq c-basic-offset 2) + (flet ((lsp-format-buffer #'ignore) + (lsp-format-region #'ignore)))) + (add-hook 'csharp-mode-hook #'iensu--csharp-mode-hook)) + +(use-package emacs + :config + (add-to-list 'auto-mode-alist '("\\.csproj$" . xml-mode)) + (add-to-list 'auto-mode-alist '("function.proj$" . xml-mode))) diff --git a/features/lang-docker.el b/features/lang-docker.el new file mode 100644 index 0000000..3ab8355 --- /dev/null +++ b/features/lang-docker.el @@ -0,0 +1,3 @@ +;; https://github.com/Silex/docker.el +(use-package docker) +(use-package dockerfile-mode) diff --git a/features/lang-elm.el b/features/lang-elm.el new file mode 100644 index 0000000..b6b644c --- /dev/null +++ b/features/lang-elm.el @@ -0,0 +1,6 @@ +(use-package elm-mode + :config + (setq elm-tags-on-save t + elm-sort-imports-on-save t + elm-format-on-save t) + (add-hook 'elm-mode-hook #'lsp)) diff --git a/features/lang-fsharp.el b/features/lang-fsharp.el new file mode 100644 index 0000000..372f2fb --- /dev/null +++ b/features/lang-fsharp.el @@ -0,0 +1,9 @@ +(use-package fsharp-mode + :defer t + :hook + (fsharp-mode . lsp) + :mode ("\\.fs$" . fsharp-mode)) + +(use-package emacs + :config + (add-to-list 'auto-mode-alist '("\\.fsproj$" . xml-mode))) diff --git a/features/lang-graphql.el b/features/lang-graphql.el new file mode 100644 index 0000000..f1219f2 --- /dev/null +++ b/features/lang-graphql.el @@ -0,0 +1 @@ +(use-package graphql-mode) diff --git a/features/lang-graphviz.el b/features/lang-graphviz.el new file mode 100644 index 0000000..9bbeb64 --- /dev/null +++ b/features/lang-graphviz.el @@ -0,0 +1,5 @@ +(use-package graphviz-dot-mode + :bind (:map graphviz-dot-mode-map + ("C-c C-c" . graphviz-dot-preview)) + :config + (add-to-list 'org-src-lang-modes '("dot" . graphviz-dot))) diff --git a/features/lang-java.el b/features/lang-java.el new file mode 100644 index 0000000..68d3de5 --- /dev/null +++ b/features/lang-java.el @@ -0,0 +1,9 @@ +(use-package java-mode + :mode "\\.java$" + :hook + (java-mode-hook . electric-pair-mode)) + +(use-package lsp-java + :after lsp + :hook + (java-mode . lsp)) diff --git a/features/lang-javascript.el b/features/lang-javascript.el new file mode 100644 index 0000000..30ad74f --- /dev/null +++ b/features/lang-javascript.el @@ -0,0 +1,59 @@ +(use-package emacs :custom (flycheck-disabled-checkers (append flycheck-disabled-checkers '(javascript-jshint)))) + +(use-package js + :custom + (js-switch-indent-offset 2) + :config + (define-key js-mode-map (kbd "M-.") nil)) + +(use-package js2-mode + :mode ("\\.js\\'") + :interpreter ("node" "nodejs") + :custom + (js2-basic-offset 2) + (js2-highlight-level 3) + :hook + (js2-mode . electric-indent-mode) + (js2-mode . rainbow-delimiters-mode) + (js2-mode . smartparens-mode) + (js2-mode . lsp) + (js2-mode . prettier-js-mode) + :config + (add-hook 'xref-backend-functions #'xref-js2-xref-backend nil t) + (js2-mode-hide-warnings-and-errors) + (flycheck-add-mode 'javascript-eslint 'js2-mode)) + +(use-package rjsx-mode + :mode ("\\.jsx\\'") + :hook + (rjsx-mode . electric-indent-mode) + (rjsx-mode . rainbow-delimiters-mode) + (rjsx-mode . smartparens-mode) + (rjsx-mode . emmet-mode) + (rjsx-mode . lsp) + (rjsx-mode . prettier-js-mode) + :init + (add-to-list 'magic-mode-alist + '((lambda () (and buffer-file-name + (string-equal "js" (file-name-extension buffer-file-name)) + (string-match "^import .* from [\"']react[\"']" (buffer-string)))) + . rjsx-mode)) + :config + (flycheck-add-mode 'javascript-eslint 'rjsx-mode) + (add-hook 'rjsx-mode-hook (lambda () (setq emmet-expand-jsx-className? t)))) + +(use-package js2-refactor + :hook + (rjsx-mode . js2-refactor-mode) + (js2-mode . js2-refactor-mode)) + +(use-package xref-js2) +(use-package nvm) +(use-package add-node-modules-path + :config + (eval-after-load 'js2-mode + '(add-hook 'js-mode-hook #'add-node-modules-path)) + (eval-after-load 'rjsx-mode + '(add-hook 'js-mode-hook #'add-node-modules-path)) + (eval-after-load 'typescript-mode + '(add-hook 'js-mode-hook #'add-node-modules-path))) diff --git a/features/lang-json.el b/features/lang-json.el new file mode 100644 index 0000000..52750d1 --- /dev/null +++ b/features/lang-json.el @@ -0,0 +1,3 @@ +(use-package json-mode + :mode ("\\.json$") + :custom (js-indent-level 2)) diff --git a/features/lang-markdown.el b/features/lang-markdown.el new file mode 100644 index 0000000..3fc6e38 --- /dev/null +++ b/features/lang-markdown.el @@ -0,0 +1,6 @@ +(use-package markdown-mode + :commands (markdown-mode gfm-mode) + :mode (("\\.md\\'" . gfm-mode) + ("\\.markdown\\'" . markdown-mode))) + +(use-package markdown-toc) diff --git a/features/lang-nix.el b/features/lang-nix.el new file mode 100644 index 0000000..77b98c2 --- /dev/null +++ b/features/lang-nix.el @@ -0,0 +1 @@ +(use-package nix-mode) diff --git a/features/lang-python.el b/features/lang-python.el new file mode 100644 index 0000000..5a78991 --- /dev/null +++ b/features/lang-python.el @@ -0,0 +1 @@ +(add-hook 'python-mode-hook #'lsp) diff --git a/features/lang-rust.el b/features/lang-rust.el new file mode 100644 index 0000000..1e67971 --- /dev/null +++ b/features/lang-rust.el @@ -0,0 +1,18 @@ +(defun iensu--rust-mode-hook () + (setq-local lsp-rust-server 'rust-analyzer) + (setq-local lsp-rust-clippy-preference "on") + (setq-local lsp-rust-all-features t)) + +(use-package rust-mode + :bind (:map rust-mode-map + ("C-c C-c" . rust-compile)) + :hook + (rust-mode . lsp) + (rust-mode . iensu--rust-mode-hook) + :config + (setq rust-format-on-save t)) + +(use-package flycheck-rust + :after (rust-mode) + :hook + (flycheck-mode . flycheck-rust-setup)) diff --git a/features/lang-scala.el b/features/lang-scala.el new file mode 100644 index 0000000..a279d74 --- /dev/null +++ b/features/lang-scala.el @@ -0,0 +1,17 @@ +(use-package scala-mode + :hook + (scala-mode . lsp) + :mode "\\.s\\(cala\\|bt\\)$") + +(use-package sbt-mode + :commands (sbt-start sbt-command) + :custom + ;; sbt-supershell kills sbt-mode: https://github.com/hvesalai/emacs-sbt-mode/issues/152 + (sbt:program-options '("-Dsbt.supershell=false")) + :config + ;; WORKAROUND: https://github.com/ensime/emacs-sbt-mode/issues/31 + ;; allows using SPACE when in the minibuffer + (substitute-key-definition + 'minibuffer-complete-word + 'self-insert-command + minibuffer-local-completion-map)) diff --git a/features/lang-sql.el b/features/lang-sql.el new file mode 100644 index 0000000..9717011 --- /dev/null +++ b/features/lang-sql.el @@ -0,0 +1,8 @@ +(use-package sql-mode + :ensure nil + :mode "\\.psql$" + :config + (add-hook 'sql-mode-hook + (lambda () + (when (string= (file-name-extension buffer-file-name) "psql") + (setq-local sql-product 'postgres))))) diff --git a/features/lang-terraform.el b/features/lang-terraform.el new file mode 100644 index 0000000..771231b --- /dev/null +++ b/features/lang-terraform.el @@ -0,0 +1,12 @@ +(use-package terraform-mode + :config + (defun iensu--terraform-format () + (when (executable-find "terraform") + (let ((fname (buffer-file-name))) + (when (file-exists-p fname) + (shell-command (format "terraform fmt %s" fname)) + (revert-buffer nil t))))) + + (add-hook 'terraform-mode-hook + (lambda () + (add-hook 'after-save-hook #'iensu--terraform-format nil 'local)))) diff --git a/features/lang-toml.el b/features/lang-toml.el new file mode 100644 index 0000000..c39c497 --- /dev/null +++ b/features/lang-toml.el @@ -0,0 +1,2 @@ +(use-package toml-mode + :mode ("\\.toml$" "_redirects$")) diff --git a/features/lang-typescript.el b/features/lang-typescript.el new file mode 100644 index 0000000..923265e --- /dev/null +++ b/features/lang-typescript.el @@ -0,0 +1,15 @@ +(use-package typescript-mode + :mode ("\\.ts$" "\\.tsx$") + :hook + (typescript-mode . lsp) + (typescript-mode . prettier-js-mode) + :custom + (flycheck-check-syntax-automatically '(save mode-enabled)) + (typescript-indent-level 2) + :config + (flycheck-add-mode 'typescript-tslint 'web-mode) + (add-hook 'web-mode-hook + (lambda () + (when (and buffer-file-name + (string-equal "tsx" (file-name-extension buffer-file-name))) + (lsp))))) diff --git a/features/lang-wasm.el b/features/lang-wasm.el new file mode 100644 index 0000000..a31da39 --- /dev/null +++ b/features/lang-wasm.el @@ -0,0 +1,2 @@ +(use-package wat-mode + :load-path (lambda () (expand-file-name "packages/wat-mode" user-emacs-directory))) diff --git a/features/lang-yaml.el b/features/lang-yaml.el new file mode 100644 index 0000000..d0038a4 --- /dev/null +++ b/features/lang-yaml.el @@ -0,0 +1,10 @@ +(use-package yaml-mode + :hook + (yaml-mode . display-line-numbers-mode) + (yaml-mode . flyspell-mode-off) + ;; YAML mode inherits from text-mode, so need to disable some settings + (yaml-mode . (lambda () + (visual-line-mode -1) + (visual-fill-column-mode -1)))) + +(use-package highlight-indentation :hook (yaml-mode . highlight-indentation-mode)) diff --git a/features/org-agenda.el b/features/org-agenda.el new file mode 100644 index 0000000..2f85920 --- /dev/null +++ b/features/org-agenda.el @@ -0,0 +1,34 @@ +(require 'org-agenda) + +(setq iensu-org-agenda-files + (iensu--org-remove-file-if-match "\\.org\\.gpg")) + +(dolist (agenda-command + '(("z" "Two week agenda" + ((tags-todo "-books-music-movies" + ((org-agenda-overriding-header "TODOs") + (org-agenda-prefix-format " ") + (org-agenda-sorting-strategy '(priority-down deadline-up)) + (org-agenda-max-entries 20))) + (agenda "" + ((org-agenda-start-day "0d") + (org-agenda-span 14) + (org-agenda-start-on-weekday nil))))))) + (add-to-list 'org-agenda-custom-commands agenda-command)) + +(setq org-agenda-files iensu-org-agenda-files + org-agenda-dim-blocked-tasks nil + org-deadline-warning-days -7 + org-agenda-block-separator "") + +(plist-put org-agenda-clockreport-parameter-plist :maxlevel 6) + +;; Clean-up agenda view +(setq org-agenda-prefix-format + '((agenda . " %?-12t % s") + (todo . " %i %-12:c") + (tags . " %i %-12:c") + (search . " %i %-12:c"))) + +;; Update the calendar to contain Swedish holidays etc. +(load-file (iensu--config-file "packages/kalender.el")) diff --git a/features/web-dev.el b/features/web-dev.el new file mode 100644 index 0000000..efb70ca --- /dev/null +++ b/features/web-dev.el @@ -0,0 +1,38 @@ +(use-package web-mode + :mode ("\\.html$" "\\.hbs$" "\\.handlebars$" "\\.jsp$" "\\.eex$" "\\.vue$" "\\.php$") + :hook + (web-mode . emmet-mode) + :custom + (web-mode-css-indent-offset 2) + (web-mode-code-indent-offset 2) + (web-mode-markup-indent-offset 2) + (web-mode-attr-indent-offset 2) + (web-mode-attr-value-indent-offset 2) + (web-mode-enable-css-colorization t) + (web-mode-enable-current-element-highlight t) + (web-mode-enable-current-column-highlight t) + :config + (add-hook 'web-mode-hook + (lambda () (yas-activate-extra-mode 'js-mode))) + (flycheck-add-mode 'javascript-eslint 'web-mode)) + +(use-package css-mode + :bind (:map css-mode-map + ("C-." . company-complete-common-or-cycle)) + :hook + (css-mode-hook . emmet-mode) + (css-mode-hook . rainbow-delimiters-mode) + :custom + (css-indent-offset 2)) + +(use-package rainbow-mode :hook (css-mode)) + +(use-package scss-mode :mode ("\\.scss$" "\\.styl$")) + +(use-package emmet-mode + :config + (add-hook 'emmet-mode-hook + (lambda () + (when (or (string-suffix-p ".jsx" (buffer-name)) + (string-suffix-p ".tsx" (buffer-name))) + (setq emmet-expand-jsx-className? t))))) diff --git a/new-config.el b/new-config.el index cdf0ecd..1c133f3 100644 --- a/new-config.el +++ b/new-config.el @@ -17,7 +17,7 @@ (eval-when-compile (require 'use-package) - (setq use-package-always-ensure)) + (setq use-package-always-ensure t)) ;;;; System local configuration @@ -25,9 +25,38 @@ ;; This section handles configurations that are local to the system and thus should not be under ;; version control, for instance credentials and system specific feature flags. +;; System local variables which can be set in local-settings.el +(defvar iensu-org-dir nil + "Directory containing Org files.") + +(defvar iensu-org-refile-targets nil + "Org files which can be used as refiling targets.") + +(defvar iensu-org-agenda-files nil + "Org files which should be used by org-agenda to generate TODO lists etc.") + +(defvar iensu-gcal-client-id nil + "Client ID for Gmail integration.") + +(defvar iensu-gcal-client-secret nil + "Client secret for Gmail integration.") + +(defvar iensu-org-capture-templates nil + "Capture templates to be used by Org mode.") + + +;; Load settings (load-file (expand-file-name "local-settings.el" user-emacs-directory)) + +;;;; Helper functions + +(defun iensu-add-to-list (list items) + "Add multiple items to a list." + (dolist (item items) + (add-to-list list item))) + ;;;; Basic setup @@ -48,7 +77,7 @@ ;; Make sure that buffer names become unique when opening multiple files of the same name. (setq-default frame-title-format "%b (%f)" uniquify-buffer-name-style 'post-forward - uniquify-separator ":")a + uniquify-separator ":") ;; Ask before killing Emacs. (setq confirm-kill-emacs 'y-or-n-p) @@ -68,13 +97,24 @@ delete-old-versions t kept-new-versions 6) +;; Remember recent files +(use-package recentf + :ensure nil + :custom + (recentf-max-menu-items 50) + :config + (recentf-load-list) + :init + (recentf-mode 1) + (setq recentf-save-file (expand-file-name ".local/recentf" user-emacs-directory))) + ;; Keep some files in ~/.emacs.d/.local to avoid cluttering the configuration root directory. (setq url-configuration-directory (expand-file-name ".local/uri/" user-emacs-directory) image-dired-dir (expand-file-name ".local/image-dired-thumbnails/" user-emacs-directory) bookmark-default-file (expand-file-name ".local/bookmarks" user-emacs-directory) tramp-auto-save-directory (expand-file-name ".local/tramp-autosaves/" user-emacs-directory)) -;; Setup authentication file and pinentry for entering passwords. +;; Setup authentication file. (setq auth-sources '("~/.authinfo.gpg" "~/.netrc")) @@ -108,7 +148,31 @@ (put 'narrow-to-page 'disabled nil) (put 'narrow-to-region 'disabled nil) -;;;;; Programming mode defaults +;; Use the editorconfig package to conform to project formatting rules if present. +(use-package editorconfig + :hook + (prog-mode . editorconfig-mode) + (text-mode . editorconfig-mode)) + +;; Enable multiple cursors for convenient editing. Use `iedit' for quick and dirty multi-cursor +;; functionality. +(use-package iedit) +(use-package multiple-cursors + :bind + (("M-=" . mc/edit-lines) + ("C-S-" . mc/mark-next-like-this) + ("C-S-" . mc/mark-previous-like-this) + ("C-S-" . mc/add-cursor-on-click)) + :custom + (mc/list-file (expand-file-name ".local/.mc-lists.el" user-emacs-directory))) + +;; Expand region from current region or point. +(use-package expand-region + :bind + (("C-=" . er/expand-region) + ("C-M-=" . er/contract-region))) + +;;;;; Programming mode related settings (defun iensu--prog-mode-hook () "Defaults for programming modes" @@ -122,15 +186,229 @@ (add-hook 'prog-mode-hook #'iensu--prog-mode-hook) +;; Manipulate parentheses and other code structures. +(use-package smartparens + :init + (require 'smartparens-config) + :bind (:map smartparens-mode-map + ("M-s" . sp-unwrap-sexp) + ("C-" . sp-down-sexp) + ("C-" . sp-up-sexp) + ("M-" . sp-backward-down-sexp) + ("M-" . sp-backward-up-sexp) + ("C-" . sp-forward-slurp-sexp) + ("M-" . sp-forward-barf-sexp) + ("C-" . sp-backward-slurp-sexp) + ("M-" . sp-backward-barf-sexp)) + :hook ((prog-mode . smartparens-mode) ; non-strict by default, but keep strict in LISPs + (repl-mode . smartparens-strict-mode) + (lisp-mode . smartparens-strict-mode) + (emacs-lisp-mode . smartparens-strict-mode))) + +;;;;; Text editing tools + +;; Spellcheck using flyspell +(use-package flyspell + :ensure nil + :bind (:map flyspell-mode-map ("C-:" . flyspell-popup-correct)) + :custom + (ispell-extra-args '("--sug-mode=ultra")) + (ispell-list-command "--list") + (ispell-dictionary "en_US") + :config + (defvar iensu--language-ring nil + "Ispell language ring used to toggle current selected ispell dictionary") + + (let ((languages '("swedish" "en_US"))) + (setq iensu--language-ring (make-ring (length languages))) + (dolist (elem languages) (ring-insert iensu--language-ring elem))) + + (defun iensu/cycle-ispell-dictionary () + "Cycle through the languages defined in `iensu--language-ring'." + (interactive) + (let ((language (ring-ref iensu--language-ring -1))) + (ring-insert iensu--language-ring language) + (ispell-change-dictionary language) + (message (format "Switched to dictionary: %s" language))))) +(use-package flyspell-popup :after (flyspell)) + +;; Use synosaurus to look up synonyms +(use-package synosaurus + :custom + (synosaurus-backend 'synosaurus-backend-wordnet) + (synosaurus-choose-method 'popup)) + +;; Emoji support because reasons... +(use-package emojify + :custom + (emojify-emojis-dir (expand-file-name ".local/emojis" user-emacs-directory))) + +;; `visual-fill-column' makes it possible to visually wrap and center text which is good for +;; document-like editing. +(use-package visual-fill-column + :config + (setq-default visual-fill-column-width 100) + (setq-default visual-fill-column-center-text t)) + +(defun iensu/text-editing-mode-hook () + "Enables text editing tools such as spell checking and thesaurus support" + (interactive) + (flyspell-mode 1) + (synosaurus-mode 1) + (emojify-mode 1) + (visual-line-mode 1) + (visual-fill-column-mode 1)) + +(add-hook 'text-mode-hook #'iensu/configure-text-editing-tools) + ;;;; Utility packages -;;;;; Hydra - ;; Install `hydra' with `pretty-hydra' which simplifies hydra definitions (use-package hydra) (use-package pretty-hydra :after (hydra)) +;; Make system path variables accessible in Emacs +(use-package exec-path-from-shell + :custom + (exec-path-from-shell-check-startup-files nil) + :init + (exec-path-from-shell-initialize)) + +;; Password entry in minibuffer +(use-package pinentry + :init + (setq epa-pinentry-mode 'loopback) + (pinentry-start)) + +;; Improved file browsing +(use-package dired+ + :load-path (lambda () (expand-file-name "packages" user-emacs-directory)) + :custom + (dired-listing-switches "-alGh --group-directories-first") + (dired-dwim-target t) + :config + (when (executable-find "gls") ;; native OSX ls works differently then GNU ls + (setq insert-directory-program "/usr/local/bin/gls"))) + +;;;; Navigation + +;; This section adds packages which enables quick navigation and search. + +(use-package deadgrep) + +;; `counsel', `ivy' and `swiper' constitute a very useful completion framework. +(use-package counsel + :delight ivy-mode + :init + (ivy-mode 1) + :bind (("M-x" . counsel-M-x) + ("C-x C-f" . counsel-find-file) + ("C-x C-r" . counsel-recentf) + ("C-c k" . counsel-ag) + ("C-x b" . ivy-switch-buffer) + ("M-y" . counsel-yank-pop) + ("C-s" . swiper-isearch) + :map ivy-minibuffer-map + ("M-y" . ivy-next-line)) + :custom + (ivy-use-virtual-buffers t) + (ivy-use-selectable-prompt t) + (ivy-count-format "(%d/%d) ") + (ivy-magic-slash-non-match-action 'ivy-magic-non-match-create) + (counsel-ag-base-command "ag --nocolor --nogroup --hidden %s") + (ivy-display-style 'fancy) + (ivy-re-builders-alist '((swiper . ivy--regex-plus) + (swiper-isearch . ivy--regex-plus) + (counsel-find-file . ivy--regex-plus) + (counsel-projectile-find-file . ivy--regex-plus) + (t . ivy--regex-plus)))) + +(use-package ivy-rich :config (ivy-rich-mode 1)) ; Add documentation to ivy results + +;; `prescient' ranks search candidates by most recent. +(use-package prescient :config (prescient-persist-mode 1)) +(use-package ivy-prescient + :config + (ivy-prescient-mode 1) + (setq ivy-prescient-enable-sorting t) + (setq ivy-prescient-enable-filtering t)) +(use-package company-prescient + :config + (company-prescient-mode 1)) + +;; Snippet expansion for less repetitive text editing +(use-package yasnippet + :delight yas-minor-mode + :init + (yas-global-mode 1) + (setq yas-snippet-dirs (add-to-list 'yas-snippet-dirs (expand-file-name "snippets" user-emacs-directory))) + :config + (add-hook 'snippet-mode-hook (lambda () + (setq mode-require-final-newline nil + require-final-newline nil)))) + +;; When all else fails +(use-package dumb-jump + :config + (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)) + + +;;;; Custom commands + +(defun iensu/toggle-scratch-buffer () + "Based on a great idea from Eric Skoglund (https://github.com/EricIO/emacs-configuration/)." + (interactive) + (if (string-equal (buffer-name (current-buffer)) + "*scratch*") + (switch-to-buffer (other-buffer)) + (switch-to-buffer "*scratch*"))) + +(defun iensu/toggle-profiler () + "Starts or stops the profiler, displaying the report when stopped." + (interactive) + (if (profiler-running-p) + (progn + (profiler-stop) + (profiler-report)) + (progn + (profiler-reset) + (profiler-start 'cpu+mem)))) + +(defun iensu/duplicate-line (n) + "Copy the current line N times and insert it below." + (interactive "P") + (let ((cur-pos (point))) + (dotimes (i (prefix-numeric-value n)) + (move-beginning-of-line nil) + (kill-line) + (yank) + (newline) + (insert (string-trim-right (car kill-ring))) + (goto-char cur-pos)))) + +(defun iensu/move-file (new-location) + "Write this file to NEW-LOCATION, and delete the old one. Copied from http://zck.me/emacs-move-file." + (interactive (list (if buffer-file-name + (read-file-name "Move file to: ") + (read-file-name "Move file to: " + default-directory + (expand-file-name (file-name-nondirectory (buffer-name)) + default-directory))))) + (when (file-exists-p new-location) + (delete-file new-location)) + (let ((old-location (buffer-file-name))) + (write-file new-location t) + (when (and old-location + (file-exists-p new-location) + (not (string-equal old-location new-location))) + (delete-file old-location)))) + +(defun iensu/finish-item () + "Sets a `Finished' property on an org-mode item. The value is the current time as an inactive timestamp." + (interactive) + (org-set-property "Finished" (iensu--get-current-inactive-timestamp))) + ;;;; Global keybindings @@ -151,29 +429,20 @@ ;; Setup a global hydra with keybindings I use very often. (pretty-hydra-define iensu-hydra (:color teal :quit-key "q" :title "Global commands") - ("Email" - (("e u" mu4u-update-index "update" :exit nil) - ("e e" mu4e "open email") - ("e c" mu4e-compose-new "write email") - ("e s" mu4e-headers-search "search email")) - "Elfeed" - (("f f" elfeed) - ("f u" elfeed-update)) - "Org clock" + ("Org clock" (("c c" org-clock-in "start clock") ("c r" org-clock-in-last "resume clock") ("c s" org-clock-out "stop clock") ("c g" org-clock-goto "goto clocked task")) "Utilities" - (; ("d" iensu/duplicate-line "duplicate line" :exit nil) + (("d" iensu/duplicate-line "duplicate line" :exit nil) ("s" deadgrep "search") ("t" toggle-truncate-lines "truncate lines") ("u" revert-buffer "reload buffer") - ; ("l" iensu/cycle-ispell-dictionary "change dictionary")) + ("l" iensu/cycle-ispell-dictionary "change dictionary")) "Misc" (("P" iensu/project-todo-list "project todo list") ("i" iensu/open-init-file "open emacs config") - ("9" iensu/refresh-work-calendar "update calendar") ("+" enlarge-window-horizontally "enlarge window" :exit nil) ("-" shrink-window-horizontally "shrink window" :exit nil)) "Hide/show" @@ -182,6 +451,9 @@ ("h a" hs-hide-all "Hide all") ("h s" hs-show-all "Show all")))) +;; Enhance explorability with by listing possible completions while doing key chords. +(use-package which-key :config (which-key-mode)) + ;;;;; macOS specific keybindings ;; Set command to act as `meta' (`M-') and disable the `option' key since that button is needed to @@ -193,15 +465,15 @@ ;; An unfortunate workaround required when switching to an external keyboard. (defun iensu/switch-left-and-right-option-keys () - "Switch left and right option keys. + "Switch left and right option keys. On some external keyboards the left and right Mac `option' keys are swapped, this command switches the keys so that they work as expected." - (interactive) - (let ((current-left mac-option-modifier) - (current-right mac-right-option-modifier)) - (setq mac-option-modifier current-right - mac-right-option-modifier current-left))) + (interactive) + (let ((current-left mac-option-modifier) + (current-right mac-right-option-modifier)) + (setq mac-option-modifier current-right + mac-right-option-modifier current-left))) ;;;; Make Emacs prettier @@ -227,13 +499,13 @@ ;; Set theme (use-package modus-vivendi-theme - :config - (load-theme 'modus-vivendi t) - (set-face-attribute 'font-lock-comment-face nil :slant 'italic) - (set-face-attribute 'font-lock-comment-delimiter-face nil :slant 'italic) - (set-face-attribute 'default nil :font "Fira Code-13") - (set-face-attribute 'fixed-pitch nil :font "Fira Code-13") - (set-face-attribute 'variable-pitch nil :font "Cantarell-14")) + :config + (load-theme 'modus-vivendi t) + (set-face-attribute 'font-lock-comment-face nil :slant 'italic) + (set-face-attribute 'font-lock-comment-delimiter-face nil :slant 'italic) + (set-face-attribute 'default nil :font "Fira Code-13") + (set-face-attribute 'fixed-pitch nil :font "Fira Code-13") + (set-face-attribute 'variable-pitch nil :font "Cantarell-14")) ;;;; Version control @@ -243,13 +515,13 @@ ;; Use `magit' for a great `git' experience. (use-package magit - :bind (("C-x g" . magit-status)) - :custom - (magit-bury-buffer-function 'quit-window) - :config - (when (executable-find "/usr/bin/git") ; Speeds up git operations on macOS - (setq magit-git-executable "/usr/bin/git")) - (setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)) + :bind (("C-x g" . magit-status)) + :custom + (magit-bury-buffer-function 'quit-window) + :config + (when (executable-find "/usr/bin/git") ; Speeds up git operations on macOS + (setq magit-git-executable "/usr/bin/git")) + (setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)) ;; `smerge-mode' is a merge conflict resolution tool which is great but unfortunately has awful ;; default keybindings. Here I define a hydra to make `smerge' easier to work with. @@ -273,14 +545,420 @@ "Misc" (("E" smerge-ediff "Open in Ediff"))))) - ;;;; Org mode - -;;;;; Agenda +(defun iensu--get-current-inactive-timestamp () + (concat "[" (format-time-string "%F %a %H:%M") "]")) + +(use-package org + :bind (("C-c c" . org-capture) + ("C-c a" . org-agenda) + ("C-c l" . org-store-link) + :map org-mode-map + ("H-." . org-time-stamp-inactive)) + :hook + (org-mode . (lambda () + (org-num-mode 1) + (visual-line-mode 1) + (variable-pitch-mode 1))) + :config + (setq org-directory iensu-org-dir) + (setq org-default-notes-file (expand-file-name "notes.org" org-directory)) + (setq org-refile-targets '((iensu-org-refile-targets :maxlevel . 10))) + (setq org-refile-allow-creating-parent-nodes 'confirm) + (setq org-refile-use-outline-path 'file) + (setq org-latex-listings t) + (setq org-cycle-separator-lines 1) + (setq org-src-fontify-natively t) + (setq org-format-latex-options (plist-put org-format-latex-options :scale 1.5)) + (setq truncate-lines t) + (setq org-image-actual-width nil) + (setq line-spacing 1) + (setq outline-blank-line t) + (setq org-adapt-indentation nil) + (setq org-fontify-quote-and-verse-blocks t) + (setq org-fontify-done-headline t) + (setq org-fontify-whole-heading-line t) + (setq org-hide-leading-stars t) + (setq org-indent-indentation-per-level 2) + (setq org-checkbox-hierarchical-statistics nil) + (setq org-log-done 'time) + (setq org-outline-path-complete-in-steps nil) + (setq org-html-htmlize-output-type 'css) + (setq org-export-initial-scope 'subtree) + (setq org-catch-invisible-edits 'show-and-error) + (setq org-archive-location "archive/%s_archive::") + (setq org-capture-templates iensu-org-capture-templates) + + (setq org-clock-in-switch-to-state "DOING") + (setq org-log-into-drawer t) + (setq org-modules '(org-protocol)) + + (org-load-modules-maybe t) + (dolist (lang-mode '(("javascript" . js2) ("es" . es) ("wat" . wat)) + (add-to-list 'org-src-lang-modes lang-mode))) + + (org-babel-do-load-languages + 'org-babel-load-languages '((emacs-lisp . t) + (shell . t) + (js . t) + (python . t) + (dot . t))) + + (let ((additional-org-templates (if (version< (org-version) "9.2") + '(("ssh" "#+begin_src shell \n?\") + ("sel" " \n?\")) + '(("ssh" . "src shell") + ("sel" . "src emacs-lisp") + ("sr" . "src restclient") + ("sR" . "src rust"))))) + (dolist (template additional-org-templates) + (add-to-list 'org-structure-template-alist template))) + + ;; standard markdown + (require 'ox-md) + + ;; Github-flavoured markdown + (use-package ox-gfm + :init + (eval-after-load "org" + '(require 'ox-gfm nil t)))) + +(add-to-list 'iensu-org-capture-templates + `("t" "TODO with link" entry (file ,(expand-file-name "refile.org" iensu-org-dir)) + ,(concat "* TODO %?\n" + "%U\n" + "%a") + :empty-lines 1)) + (add-to-list 'iensu-org-capture-templates + `("T" "TODO" entry (file ,(expand-file-name "refile.org" iensu-org-dir)) + ,(concat "* TODO %?\n" + "%U") + :empty-lines 1)) + (add-to-list 'iensu-org-capture-templates + `("j" "Journal" entry (file+datetree ,(expand-file-name "journal.org.gpg" iensu-org-dir)) + ,(concat "* %^{Titel}\n" + "%U, %^{Location|Stockholm, Sverige}\n\n" + "%?") + :empty-lines 1)) + (add-to-list 'iensu-org-capture-templates + `("l" "Link" entry (file ,(expand-file-name "refile.org" iensu-org-dir)) + ,(concat "* %? %^L %^G \n" + "%U") + :prepend t)) + (add-to-list 'iensu-org-capture-templates + `("L" "Browser Link" entry (file ,(expand-file-name "refile.org" iensu-org-dir)) + ,(concat "* TODO %:description\n" + "%U\n\n" + "%:link") + :prepend t :immediate-finish t :empty-lines 1)) + (add-to-list 'iensu-org-capture-templates + `("p" "Browser Link and Selection" entry (file ,(expand-file-name "refile.org" iensu-org-dir)) + ,(concat "* TODO %^{Title}\n" + "Source: %u, %c\n\n" + "#+BEGIN_QUOTE\n" + "%i\n" + "#+END_QUOTE\n\n\n%?") + :prepend t :empty-lines 1)) + (add-to-list 'iensu-org-capture-templates + `("b" "Book" entry (file+headline ,(expand-file-name "private.org" iensu-org-dir) "Reading list") + ,(concat "* %^{Title}" + " %^{Author}p" + " %^{Genre}p" + " %^{Published}p" + " %(org-set-property \"Added\" (iensu--get-current-inactive-timestamp))") + :prepend t + :empty-lines 1)) (setq calendar-week-start-day 1) ; The week starts on Monday. +;; `org-protocol' enables capturing from outside of Emacs. +;;(eval-after-load "org" +;; (require 'org-protocol)) + +(defadvice org-capture-finalize + (after delete-capture-frame activate) + "Advise capture-finalize to close the frame" + (if (equal "capture" (frame-parameter nil 'name)) + (delete-frame))) + +(defadvice org-capture-destroy + (after delete-capture-frame activate) + "Advise capture-destroy to close the frame" + (if (equal "capture" (frame-parameter nil 'name)) + (delete-frame))) + +;; Add support for YAML files +(defun org-babel-execute:yaml (body params) body) + +(setq org-todo-keywords + '((sequence "TODO(t)" "DOING(d!)" "BLOCKED(b@/!)" + "|" + "CANCELED(C@/!)" "POSTPONED(P@/!)" "DONE(D@/!)"))) + +(setq org-todo-keyword-faces + '(("BLOCKED" . (:foreground "#dd0066" :weight bold)) + ("CANCELED" . (:foreground "#6272a4")) + ("POSTPONED" . (:foreground "#3388ff")))) + +;; Customize PRIORITIES +(setq org-highest-priority ?A + org-default-priority ?D + org-lowest-priority ?E) + +;;;;; Make org-mode prettier + +;; Make view more compact +(setq org-cycle-separator-lines 0) + +;; Only display one bullet per headline for a cleaner look. +(use-package org-superstar + :after (org) + :init + (add-hook 'org-mode-hook (lambda () (org-superstar-mode 1))) + :config + (setq org-superstar-headline-bullets-list '(?◉))) + +;;;;; Autosaving org buffers +(defun iensu/org-save-buffers () + "Saves all org buffers." + (interactive) + (save-some-buffers 'no-confirm + (lambda () + (string-match-p + (expand-file-name org-directory) + (buffer-file-name (current-buffer)))))) + +(defvar iensu--timer:org-save-buffers nil + "Org save buffers timer object. Can be used to cancel the timer.") + +(setq iensu--timer:org-save-buffers + (run-at-time t (* 5 60) #'iensu/org-save-buffers)) + + +;;;; Project management + +;; Settings which help with handling and navigating projects. + +(use-package projectile + :bind + (("C-c p" . projectile-hydra/body)) + :custom + (projectile-completion-system 'ivy) + (projectile-cache-file (expand-file-name ".local/projectile.cache" user-emacs-directory)) + (projectile-known-projects-file (expand-file-name ".local/projectile-bookmarks.eld" user-emacs-directory)) + (projectile-git-submodule-command nil) + (projectile-sort-order 'access-time) + (projectile-globally-ignored-files '("TAGS" ".DS_Store" ".projectile")) + :pretty-hydra + ((:color teal :quit-key "q" :title "Project") + ("Project" + (("p" counsel-projectile-switch-project "open project") + ("k" projectile-kill-buffers "close project") + ("t" projectile-test-project "test project" :exit t) + ("c" projectile-compile-project "compile project" :exit t)) + "Files & Buffers" + (("f" counsel-projectile-find-file "open project file") + ("o" iensu/open-project-org-file "open project org file") + ("T" iensu/project-todo-list "open project TODO list") + ("b" counsel-projectile-switch-to-buffer "open project buffer") + ("S" projectile-save-buffers "save project buffers")) + "Search" + (("s" projectile-ripgrep "search") + ("r" projectile-replace "replace literal") + ("R" projectile-replace-regexp "replace regex")))) + :config + (projectile-global-mode) + (projectile-register-project-type 'node-npm '("package.json") + :compile "npm run build" + :test "npm test") + (projectile-register-project-type 'rust-cargo '("cargo.toml") + :compile "cargo check" + :test "cargo test" + :run "cargo run") + (projectile-register-project-type 'java-maven '("pom.xml") + :compile "mvn compile" + :test "mvn test")) + +(use-package counsel-projectile :init (counsel-projectile-mode 1)) + +(use-package ibuffer-projectile :after (projectile) + :hook + (ibuffer-mode . (lambda () + (ibuffer-projectile-set-filter-groups) + (unless (eq ibuffer-sorting-mode 'alphabetic) + (ibuffer-do-sort-by-alphabetic))))) + +;; `treemacs' for a visual project tree structure. +(use-package treemacs + :defer t + :bind + (:map global-map + ("M-0" . treemacs-select-window) + ("C-x t 1" . treemacs-delete-other-windows) + ("C-x t t" . treemacs) + ("C-x t B" . treemacs-bookmark) + ("C-x t C-t" . treemacs-find-file) + ("C-x t M-t" . treemacs-find-tag) + ("C-x t w" . treemacs-switch-workspace))) +(use-package treemacs-magit :after treemacs magit) +(use-package treemacs-projectile :after treemacs projectile) + +;;;;; Project-based TODO lists + +;; Create a TODO list based on TODO items in a project's `.project-notes.org' file. The +;; `org-agenda-files' variable is temporarily set the only the project notes file and then reverted +;; back to its previous value upon closing the TODO list buffer. + +(defvar iensu--project-agenda-buffer-name "*Project Agenda*") + +(defun iensu--org-capture-project-notes-file () + (concat (projectile-project-root) ".project-notes.org")) + +(defun iensu/project-todo-list () + (interactive) + (let ((project-notes-file (expand-file-name ".project-notes.org" + (projectile-project-root)))) + (if (file-exists-p project-notes-file) + (progn + (setq org-agenda-files `(,project-notes-file)) + (org-todo-list) + (rename-buffer iensu--project-agenda-buffer-name 'unique)) + (message "Could not locate any project notes file")))) + +(defun iensu/reset-org-agenda-files () + (interactive) + (when (string-equal iensu--project-agenda-buffer-name + (buffer-name (current-buffer))) + (setq org-agenda-files iensu-org-agenda-files))) + +;; Reset org-agenda-files when the project TODO list buffer is closed +(add-hook 'kill-buffer-hook #'iensu/reset-org-agenda-files) + +;; Add some org-capture templates for project notes. +(eval-after-load 'org + (progn + (add-to-list 'iensu-org-capture-templates + `("m" "Project note" entry (file+headline iensu--org-capture-project-notes-file "Notes") + ,(concat "* %^{Title}\n" + "%U\n\n" + "%?") + :empty-lines 1)) + (add-to-list 'iensu-org-capture-templates + `("n" "Project note with link" entry (file+headline iensu--org-capture-project-notes-file "Notes") + ,(concat "* %^{Title}\n" + "%U\n\n" + "Link: %a\n\n" + "%?") + :empty-lines 1)) + (add-to-list 'iensu-org-capture-templates + `("N" "Project note with link + code quote" entry (file+headline iensu--org-capture-project-notes-file "Notes") + ,(concat "* %^{Title}\n" + "%U\n\n" + "Link: %a\n\n" + "#+begin_src %^{Language}\n" + "%i\n" + "\n\n" + "%?") + :empty-lines 1)))) + + +;;;; IDE features + +;; Highlight TODOs in programming buffers +(use-package hl-todo :hook ((prog-mode . hl-todo-mode))) + +;;;;; Autocompletion and intellisense + +;; Company as a completion frontend +(use-package company + :init (global-company-mode) + :config + (setq company-idle-delay 0.3) + (setq company-minimum-prefix-length 2) + (setq company-selection-wrap-around t) + (setq company-auto-complete t) + (setq company-tooltip-align-annotations t) + (setq company-dabbrev-downcase nil) + (setq company-auto-complete-chars nil) + (add-hook 'emacs-lisp-mode-hook + (lambda () + (add-to-list 'company-backends 'company-elisp))) + (eval-after-load 'company (company-quickhelp-mode 1))) +(use-package company-quickhelp + :bind (:map company-active-map + ("M-h" . company-quickhelp-manual-begin)) + :config + (setq company-quickhelp-delay 1)) + +;; Flycheck for on the fly error reporting +(use-package flycheck + :init + (global-flycheck-mode t) + :config + (setq-default flycheck-disabled-checkers '(emacs-lisp-checkdoc))) +(use-package flycheck-popup-tip) + +;; LSP for intellisense +(use-package lsp-mode + :commands (lsp lsp-deferred) + :bind (:map lsp-mode-map + ("C-c l" . lsp-mode-hydra/body)) + :init + (setq lsp-keymap-prefix "C-ä") + :hook + (lsp-mode . lsp-enable-which-key-integration) + :config + (setq lsp-auto-guess-root nil) + + :pretty-hydra + ((:title "LSP" :quit-key "q" :color teal) + ("Exploration" + (("l" lsp-find-references "list references") + ("s" lsp-ivy-workspace-symbol "search symbol in workspace") + ("d" lsp-describe-thing-at-point "describe") + ("e" flycheck-list-errors "list buffer errors") + ("å" flycheck-previous-error "goto previous error in buffer") + ("ä" flycheck-next-error "goto next error in buffer ") + ("E" lsp-treemacs-errors-list "list workspace errors") + ("T" lsp-goto-type-definition "find type definition")) + "Refactoring" + (("a" lsp-execute-code-action "execute code action") + ("n" lsp-rename "rename symbol") + ("i" lsp-organize-imports "organize imports") + ("f" lsp-format-buffer "format buffer")) + "Misc" + (("w" lsp-restart-workspace "restart LSP server"))))) + +;; `lsp-ui' enables in buffer documentation popups etc. +(use-package lsp-ui + :commands lsp-ui-mode + :config + (lsp-ui-sideline-mode 1) + (setq lsp-ui-sideline-show-diagnostics t + lsp-ui-sideline-show-code-actions nil + lsp-ui-sideline-show-symbol nil + lsp-ui-sideline-show-hover nil) + (lsp-ui-doc-mode 1)) + +(use-package company-lsp :commands company-lsp) +(use-package lsp-treemacs :commands lsp-treemacs-errors-list) +(use-package lsp-ivy :commands lsp-ivy-workspace-symbol) + +;; Autoformatting +(use-package prettier-js) + +;; HTTP requests +(use-package restclient :mode ("\\.rest$" "\\.restclient$")) +;; HTTP requests in Org files +(use-package ob-restclient + :after (org) + :config + (org-babel-do-load-languages 'org-babel-load-languages '((restclient . t)))) + +;; Handle .direnv as shell file +(add-to-list 'auto-mode-alist '("\\.envrc$" . sh-mode)) + ;;;; Emacs server @@ -288,25 +966,3 @@ ;; will be snappy. (unless (server-running-p) (server-start)) - - -;;;; Custom commands - -(defun iensu/toggle-scratch-buffer () - "Based on a great idea from Eric Skoglund (https://github.com/EricIO/emacs-configuration/)." - (interactive) - (if (string-equal (buffer-name (current-buffer)) - "*scratch*") - (switch-to-buffer (other-buffer)) - (switch-to-buffer "*scratch*"))) - -(defun iensu/toggle-profiler () - "Starts or stops the profiler, displaying the report when stopped." - (interactive) - (if (profiler-running-p) - (progn - (profiler-stop) - (profiler-report)) - (progn - (profiler-reset) - (profiler-start 'cpu+mem)))) From 441f13d022a935c38dbc0e83fd11bc34dc4d2f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20=C3=96stlund?= Date: Thu, 26 Nov 2020 12:00:29 +0100 Subject: [PATCH 3/8] Transitioned to modularized configuration --- .gitignore | 2 +- README.md | 2 - configuration.org | 2047 ----------------------------------- features/elfeed.el | 2 +- features/email.el | 2 +- features/lang-csharp.el | 6 +- features/lang-fsharp.el | 4 +- features/lang-javascript.el | 11 +- features/org-agenda.el | 5 +- features/org.el | 203 ++++ features/web-dev.el | 16 +- init.el | 834 +++++++++++++- new-config.el | 968 ----------------- 13 files changed, 1021 insertions(+), 3081 deletions(-) delete mode 100644 configuration.org create mode 100644 features/org.el delete mode 100644 new-config.el diff --git a/.gitignore b/.gitignore index 21e1e9b..49bdd92 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,8 @@ emojis/ test.org transient/ .dir-locals.el -features.el local-settings.el +local-feature-settings.el /projectile-bookmarks.eld flycheck_init.el diff --git a/README.md b/README.md index bc6b957..952cca9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1 @@ # Emacs configuration - -[Emacs configuration written in org-mode](configuration.org) diff --git a/configuration.org b/configuration.org deleted file mode 100644 index f1c3357..0000000 --- a/configuration.org +++ /dev/null @@ -1,2047 +0,0 @@ -#+startup: showeverything - -* Emacs configuration - -** Table of Contents :TOC_3_gh: -- [[#emacs-configuration][Emacs configuration]] - - [[#helper-functions][Helper functions]] - - [[#custom-hooks][Custom hooks]] - - [[#external-package-installation][External package installation]] - - [[#baseline-setup][Baseline setup]] - - [[#window-configuration][Window configuration]] - - [[#sane-editor-defaults][Sane editor defaults]] - - [[#global-keybindings][Global keybindings]] - - [[#setting-up-keys-for-macos][Setting up keys for macOS]] - - [[#various-global-keybindings][Various global keybindings]] - - [[#make-it-pretty][Make it pretty]] - - [[#utility-packages][Utility packages]] - - [[#hydra][Hydra]] - - [[#make-binaries-on-the-path-accessible-in-emacs][Make binaries on the =PATH= accessible in Emacs.]] - - [[#remember-recent-files][Remember recent files.]] - - [[#password-entry-in-minibuffer][Password entry in minibuffer]] - - [[#editor-functionality][Editor functionality]] - - [[#searching-and-finding-stuff][Searching and finding stuff]] - - [[#project-management][Project management]] - - [[#file-browsing][File browsing]] - - [[#text-editing-tools][Text editing tools]] - - [[#treemacs][Treemacs]] - - [[#source-code-buffer-screenshots][Source code buffer screenshots]] - - [[#org-mode][Org-mode]] - - [[#custom-variables][Custom variables]] - - [[#helper-functions-1][Helper functions]] - - [[#org-directory-and-file-definitions][Org directory and file definitions]] - - [[#org-capture-configuration][Org-Capture configuration]] - - [[#main-org-mode-configuration][Main org-mode configuration]] - - [[#org-agenda-configuration][Org-agenda configuration]] - - [[#add-extra-language-support-in-org-source-blocks][Add extra language support in org source blocks]] - - [[#add-more-block-expansion-templates][Add more block expansion templates]] - - [[#add-extra-exporting-options][Add extra exporting options]] - - [[#customize-todo-keyword-sequence][Customize TODO keyword sequence]] - - [[#customize-priorities][Customize PRIORITIES]] - - [[#table-of-contents-generation][Table of contents-generation]] - - [[#make-it-prettier][Make it prettier]] - - [[#swedish-holidays][Swedish holidays]] - - [[#capturing-outside-of-emacs][Capturing outside of emacs]] - - [[#save-org-buffers-every-5-minutes][Save org buffers every 5 minutes]] - - [[#google-calendar-integration][Google calendar integration]] - - [[#email][Email]] - - [[#mu4e][MU4E]] - - [[#elfeed][Elfeed]] - - [[#erc][ERC]] - - [[#programming][Programming]] - - [[#lsp-mode][LSP mode]] - - [[#language-support][Language support]] - - [[#load-theme][Load theme]] - - [[#custom-commands][Custom commands]] - - [[#loading-private-settings][Loading private settings]] - -** Helper functions - -Define helper functions needed for the rest of the configuration - -#+begin_src emacs-lisp - (defun iensu--config-file (file) - "Take a configuration FILE name and return the full file path." - (expand-file-name file user-emacs-directory)) - - (defun iensu--connected-to-internet-p () - "Return t if able to connect to the internet." - (= 0 (call-process "ping" nil nil nil "-c" "1" "-W" "1" - "www.google.com"))) - - (defun iensu--assign-key-bindings (key-definitions &optional keymap) - "Assign a list of KEY-DEFINITIONS, optionally to a KEYMAP." - (dolist (key-def key-definitions) - (if keymap - (define-key keymap (kbd (car key-def)) (cdr key-def)) - (global-set-key (kbd (car key-def)) (cdr key-def))))) -#+end_src - -** Custom hooks - -#+begin_src emacs-lisp - (defvar after-load-theme-hook nil - "Hook run after a color theme is loaded using `load-theme' in order to override some of the theme's settings.") - (defadvice load-theme (after run-after-load-theme-hook activate) - "Run `after-load-theme-hook'." - (run-hooks 'after-load-theme-hook)) -#+end_src - -** External package installation - -Setup =package.el= and =use-package= for clean package installation. - -#+begin_src emacs-lisp - ;; --- Setting up package.el - (require 'package) - - (setq package-archives - '(("gnu" . "https://elpa.gnu.org/packages/") - ("melpa" . "https://melpa.org/packages/") - ("melpa-stable" . "https://stable.melpa.org/packages/"))) - - (when (version< emacs-version "27") - (package-initialize)) - - ;; --- Setting up use-package.el - (unless (package-installed-p 'use-package) - (package-refresh-contents) - (package-install 'use-package)) - - (eval-when-compile - (require 'use-package) - (setq use-package-always-ensure t)) -#+end_src - - -** Baseline setup - -Load credentials and secret stuff - -#+begin_src emacs-lisp - (load-file (iensu--config-file "credentials.el")) -#+end_src - -Basic configuration which does not depend on any external packages. - -#+begin_src emacs-lisp - ;; Maximize screen real estate by disabling menu-bar, tool-bar and scroll-bar - (mapc - (lambda (mode) - (when (fboundp mode) - (funcall mode -1))) - '(menu-bar-mode tool-bar-mode scroll-bar-mode)) - - ;; Enlarge the initial frame - (setq initial-frame-alist '((width . 120) - (height . 60))) - - ;; Improve displayed buffer names - (setq-default frame-title-format "%b (%f)" - uniquify-buffer-name-style 'post-forward - uniquify-separator ":") - - ;; Shorter confirmation prompts - (fset 'yes-or-no-p 'y-or-n-p) - - (setq default-directory "~/" - custom-file (iensu--config-file "custom.el") - - gc-cons-threshold 100000000 - max-lisp-eval-depth 2000 - - inhibit-startup-message t - ring-bell-function 'ignore - confirm-kill-emacs 'y-or-n-p - - create-lockfiles nil - auto-save-default nil - - backup-directory-alist `(("." . ,(iensu--config-file ".local/.saves"))) - backup-by-copying t - delete-old-versions t - kept-new-versions 6 - - calendar-week-start-day 1 - - vc-follow-symlinks t - - url-configuration-directory (iensu--config-file ".local/url") - - image-dired-dir (iensu--config-file ".local/image-dired") - bookmark-default-file (iensu--config-file ".local/bookmarks") - tramp-auto-save-directory (iensu--config-file ".local/tramp") - - ;; Need to setup identity using `gpg --gen-key` before using gpg - ;; on Mac install pinentry-mac from homebrew - ;; https://www.gnupg.org/software/pinentry/index.html - auth-sources '("~/.authinfo.gpg" "~/.authinfo" "~/.netrc") - epa-pinentry-mode 'loopback) - - (setq compilation-scroll-output t) -#+end_src - -*** Window configuration - -#+begin_src emacs-lisp - (use-package emacs - :custom - (display-buffer-alist - '(("\\*e?shell\\.*" - (display-buffer-in-side-window) - (window-height . 0.25) - (side . bottom) - (slot . 0)) - - ("\\*[Hh]elp\\.*" - (display-buffer-in-side-window) - (window-height . 0.25) - (side . bottom) - (slot . 1)))) - :config - (defun iensu/make-frame-without-minibuffer () - (interactive) - (make-frame '((minibuffer . nil) - (mode-line-format . (" "))))) - - (defun iensu/buffer->bottom-window () - (interactive) - (if (> 2 (length (window-list))) - (message "Must have 2 or more windows in frame.") - (let ((buffer (current-buffer))) - (delete-window) - (display-buffer-at-bottom buffer))))) -#+end_src - -** Sane editor defaults - -Set editor defaults to be more in line with expectations. - -#+begin_src emacs-lisp - (setq-default indent-tabs-mode nil - tab-width 2 - - fill-column 100 - - require-final-newline t - - sentence-end-double-space nil - - word-wrap t - truncate-lines t - - scroll-conservatively 0 - scroll-step 4 - next-screen-context-lines 20 - - show-paren-when-point-inside-paren t - show-paren-when-point-in-periphery t) - - (add-hook 'before-save-hook 'delete-trailing-whitespace) - - (delete-selection-mode 1) - - (global-auto-revert-mode 1) - (setq global-auto-revert-non-file-buffers t - auto-revert-verbose nil) -#+end_src - -Setup basic configuration for programming modes. - -#+begin_src emacs-lisp - (defun iensu--prog-mode-hook () - "Defaults for programming modes" - (subword-mode 1) - (column-number-mode 1) - (display-line-numbers-mode 1) - (eldoc-mode 1) - (show-paren-mode 1) - (hs-minor-mode 1) - (outline-minor-mode 1)) - - (add-hook 'prog-mode-hook #'iensu--prog-mode-hook) -#+end_src - -Start the emacsclient server so that emacs can be invoked from the command line without starting a new process. - -#+begin_src emacs-lisp - (server-start) -#+end_src - -** Global keybindings - -*** Setting up keys for macOS - -Set the command button to be =meta= (=M=). - -#+begin_src emacs-lisp - (setq mac-command-modifier 'meta) -#+end_src - -Unset the option key (=meta= by default) to allow it to be used for typing -extra characters. - -#+begin_src emacs-lisp - (setq mac-option-modifier 'none) -#+end_src - -Set the right option modifier to =hyper= which gives us more keybindings to work with. - -#+begin_src emacs-lisp - (setq mac-right-option-modifier 'hyper) -#+end_src - -On macOS, remember to disable the built-in dictionary lookup command (=C-M-d=) -by running the following command followed by a restart of the computer: - -#+begin_src shell :eval never - defaults write com.apple.symbolichotkeys AppleSymbolicHotKeys -dict-add 70 'enabled' -#+end_src - -*** Various global keybindings - -Avoid suspending frame by accident. - -#+begin_src emacs-lisp - ;; Unsets (suspend-frame) key-binding - (global-unset-key (kbd "C-z")) - (global-unset-key (kbd "C-x C-z")) -#+end_src - -Add a bunch of globally applied keybindings. - -#+begin_src emacs-lisp - (iensu--assign-key-bindings '(("C-" . delete-indentation) - ("C-h C-s" . iensu/toggle-scratch-buffer) - ("C-x C-b" . ibuffer) - ("M-" . fixup-whitespace) - ("M-i" . imenu) - ("M-o" . occur))) -#+end_src - -Enable window (visible buffer) navigation with =-=. - -#+begin_src emacs-lisp - (windmove-default-keybindings) -#+end_src - -** Make it pretty - -#+begin_src emacs-lisp - (use-package emacs - :custom - (cursor-type '(bar . 2)) - :config - (global-prettify-symbols-mode 1) - (global-font-lock-mode 1) - - ;; Fix titlebar on MacOS - (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t)) - (add-to-list 'default-frame-alist '(ns-appearence . dark))) - - (use-package rainbow-delimiters :delight) - - (use-package all-the-icons) -#+end_src - -** Utility packages - -*** Hydra - -#+begin_src emacs-lisp - (use-package hydra) - (use-package pretty-hydra - :after (hydra)) -#+end_src - -Setup global hydra. - -#+begin_src emacs-lisp - (pretty-hydra-define iensu-hydra - (:color teal :quit-key "q" :title "Global commands") - ("Email" - (("e u" mu4u-update-index "update" :exit nil) - ("e e" mu4e "open email") - ("e c" mu4e-compose-new "write email") - ("e s" mu4e-headers-search "search email")) - "Elfeed" - (("f f" elfeed) - ("f u" elfeed-update)) - "Org clock" - (("c c" org-clock-in "start clock") - ("c r" org-clock-in-last "resume clock") - ("c s" org-clock-out "stop clock") - ("c g" org-clock-goto "goto clocked task")) - "Utilities" - (("d" iensu/duplicate-line "duplicate line" :exit nil) - ("s" deadgrep "search") - ("t" toggle-truncate-lines "truncate lines") - ("u" revert-buffer "reload buffer") - ("l" iensu/cycle-ispell-dictionary "change dictionary")) - "Misc" - (("P" iensu/project-todo-list "project todo list") - ("i" iensu/open-init-file "open emacs config") - ("9" iensu/refresh-work-calendar "update calendar") - ("+" enlarge-window-horizontally "enlarge window" :exit nil) - ("-" shrink-window-horizontally "shrink window" :exit nil)) - "Hide/show" - (("h h" hs-toggle-hiding "Toggle block visibility") - ("h l" hs-hide-level "Hide all blocks at same level") - ("h a" hs-hide-all "Hide all") - ("h s" hs-show-all "Show all")))) - - (use-package emacs - :bind (("C-å" . iensu-hydra/body))) -#+end_src - -*** Make binaries on the =PATH= accessible in Emacs. - -#+begin_src emacs-lisp - (use-package exec-path-from-shell - :custom - (exec-path-from-shell-check-startup-files nil) - :init - (exec-path-from-shell-initialize)) -#+end_src - -*** Remember recent files. - -#+begin_src emacs-lisp - (use-package recentf - :custom - (recentf-max-menu-items 50) - :config - (recentf-load-list) - :init - (recentf-mode 1) - (setq recentf-save-file "~/.emacs.d/.local/recentf")) -#+end_src - -*** Password entry in minibuffer - -#+begin_src emacs-lisp :tangle (iensu/tangle-is-feature-enabled 'pinentry) - (use-package pinentry :init (pinentry-start)) -#+end_src - -*** Editor functionality - -#+begin_src emacs-lisp - (use-package editorconfig - :delight - :init - (add-hook 'prog-mode-hook (editorconfig-mode 1)) - (add-hook 'text-mode-hook (editorconfig-mode 1))) - - (use-package multiple-cursors - :bind - (("M-=" . mc/edit-lines) - ("C-S-" . mc/mark-next-like-this) - ("C-S-" . mc/mark-previous-like-this) - ("C-S-" . mc/add-cursor-on-click)) - :custom - (mc/list-file (iensu--config-file ".local/.mc-lists.el"))) - - (use-package expand-region - :bind - (("C-=" . er/expand-region) - ("C-M-=" . er/contract-region))) - - (use-package iedit) - - (use-package smartparens - :delight - :init - (require 'smartparens-config) - :bind (:map smartparens-mode-map - ("M-s" . sp-unwrap-sexp) - ("C-" . sp-down-sexp) - ("C-" . sp-up-sexp) - ("M-" . sp-backward-down-sexp) - ("M-" . sp-backward-up-sexp) - ("C-" . sp-forward-slurp-sexp) - ("M-" . sp-forward-barf-sexp) - ("C-" . sp-backward-slurp-sexp) - ("M-" . sp-backward-barf-sexp)) - :hook ((prog-mode . smartparens-mode) - (repl-mode . smartparens-strict-mode) - (lisp-mode . smartparens-strict-mode) - (emacs-lisp-mode . smartparens-strict-mode))) -#+end_src - -*** Searching and finding stuff - -#+begin_src emacs-lisp - (use-package deadgrep) -#+end_src - -**** Ivy and Counsel - -#+begin_src emacs-lisp - (use-package counsel - :delight ivy-mode - :init - (ivy-mode 1) - :bind (("M-x" . counsel-M-x) - ("C-x C-f" . counsel-find-file) - ("C-x C-r" . counsel-recentf) - (" f" . counsel-describe-function) - (" v" . counsel-describe-variable) - (" l" . counsel-find-library) - (" i" . counsel-info-lookup-symbol) - (" u" . acounsel-unicode-char) - ("C-c k" . counsel-ag) - ("C-x l" . counsel-locate) - ("C-x b" . ivy-switch-buffer) - ("M-y" . counsel-yank-pop) - ("C-s" . swiper-isearch) - :map ivy-minibuffer-map - ("M-y" . ivy-next-line)) - :custom - (ivy-use-virtual-buffers t) - (ivy-use-selectable-prompt t) - (ivy-count-format "(%d/%d) ") - (ivy-magic-slash-non-match-action 'ivy-magic-non-match-create) - (counsel-ag-base-command "ag --nocolor --nogroup --hidden %s") - (ivy-display-style 'fancy) - (ivy-re-builders-alist '((swiper . ivy--regex-plus) - (swiper-isearch . ivy--regex-plus) - (counsel-find-file . ivy--regex-plus) - (counsel-projectile-find-file . ivy--regex-plus) - (t . ivy--regex-plus)))) -#+end_src - -Add descriptions to candidates if available. - -#+begin_src emacs-lisp - (use-package ivy-rich - :delight - :config - (ivy-rich-mode 1)) -#+end_src - -Sort candidates by most recently used. - -#+begin_src emacs-lisp - (use-package prescient - :config - (prescient-persist-mode 1)) - (use-package ivy-prescient - :config - (ivy-prescient-mode 1) - (setq ivy-prescient-enable-sorting t) - (setq ivy-prescient-enable-filtering t)) - (use-package company-prescient - :config - (company-prescient-mode 1)) -#+end_src - -*** Project management - -**** Version control - -Setup =magit= which is a great tool for working with =git= repositories. - -#+begin_src emacs-lisp - (use-package magit - :bind (("C-x g" . magit-status)) - :custom - (magit-bury-buffer-function 'quit-window) - :config - (setq magit-git-executable "/usr/bin/git") ; Speeds up magit on macOS - (setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)) -#+end_src - -=smerge-mode= is a merge conflict resolution tool which is great but unfortunately has awful default keybindings. Here I define a hydra to make =smerge= easier to work with. - -#+begin_src emacs-lisp -(use-package smerge-mode - :ensure nil - :bind (:map smerge-mode-map (("C-c ö" . smerge-mode-hydra/body))) - :pretty-hydra - ((:color teal :quit-key "q" :title "Smerge - Git conflicts") - ("Resolving" - (("RET" smerge-keep-current "Keep current" :exit nil) - ("l" smerge-keep-lower "Keep lower" :exit nil) - ("u" smerge-keep-upper "Keep upper" :exit nil) - ("b" smerge-keep-base "Keep base" :exit nil) - ("C" smerge-combine-with-next "Combine with next") - ("a" smerge-keep-all "Keep all" :exit nil) - ("r" smerge-resolve "Resolve")) - "Navigation" - (("n" smerge-next "Next conflict" :exit nil) - ("p" smerge-prev "Previous conflict" :exit nil) - ("R" smerge-refine "Highlight differences" :exit nil)) - "Misc" - (("E" smerge-ediff "Open in Ediff"))))) -#+end_src - -**** Projectile - -#+begin_src emacs-lisp - (defun iensu/open-project-org-file () - (interactive) - (if (boundp 'iensu-org-project-file) - (find-file iensu-org-project-file) - (message "No org project file specified."))) -#+end_src - -#+begin_src emacs-lisp - (use-package projectile - :delight '(:eval (let ((project-name (projectile-project-name))) - (if (string-equal project-name "-") - "" - (concat " [" project-name "]")))) - :bind - (("C-c p" . projectile-hydra/body)) - :custom - (projectile-completion-system 'ivy) - (projectile-cache-file (iensu--config-file ".local/projectile.cache")) - (projectile-known-projects-file (iensu--config-file ".local/projectile-bookmarks.eld")) - (projectile-git-submodule-command nil) - (projectile-sort-order 'access-time) - (projectile-globally-ignored-files '("TAGS" ".DS_Store" ".projectile")) - :pretty-hydra - ((:color teal :quit-key "q" :title "Project") - ("Project" - (("p" counsel-projectile-switch-project "open project") - ("k" projectile-kill-buffers "close project") - ("t" projectile-test-project "test project" :exit t) - ("c" projectile-compile-project "compile project" :exit t)) - "Files & Buffers" - (("f" counsel-projectile-find-file "open project file") - ("o" iensu/open-project-org-file "open project org file") - ("T" iensu/project-todo-list "open project TODO list") - ("b" counsel-projectile-switch-to-buffer "open project buffer") - ("S" projectile-save-buffers "save project buffers")) - "Search" - (("s" projectile-ripgrep "search") - ("r" projectile-replace "replace literal") - ("R" projectile-replace-regexp "replace regex")))) - :config - (projectile-global-mode) - (projectile-register-project-type - 'node-npm '("package.json") - :compile "npm run build" - :test "npm test") - (projectile-register-project-type - 'rust-cargo '("cargo.toml") - :compile "cargo check" - :test "cargo test" - :run "cargo run") - (projectile-register-project-type - 'java-maven '("pom.xml") - :compile "mvn compile" - :test "mvn test")) - - (use-package counsel-projectile :init (counsel-projectile-mode 1)) - - (use-package ibuffer-projectile :after (projectile) - :hook - (ibuffer-mode . (lambda () - (ibuffer-projectile-set-filter-groups) - (unless (eq ibuffer-sorting-mode 'alphabetic) - (ibuffer-do-sort-by-alphabetic))))) -#+end_src - -*** File browsing - -#+begin_src emacs-lisp - (use-package dired+ - :load-path (lambda () (iensu--config-file "packages")) - :custom - (dired-listing-switches "-alGh --group-directories-first") - (dired-dwim-target t) - :config - (when (executable-find "gls") ;; native OSX ls works differently then GNU ls - (setq insert-directory-program "/usr/local/bin/gls"))) -#+end_src - -*** Text editing tools - -Setup spell-checking and dictionary rotation. - -#+begin_src emacs-lisp - (use-package flyspell - :delight - '(:eval (concat " FlyS:" (or ispell-local-dictionary ispell-dictionary))) - :bind - (:map flyspell-mode-map - ("C-:" . flyspell-popup-correct)) - :custom - (ispell-extra-args '("--sug-mode=ultra")) - (ispell-list-command "--list") - (ispell-dictionary "en_US") - - :config - (defvar iensu--language-ring nil - "Ispell language ring used to toggle current selected ispell dictionary") - - (let ((languages '("swedish" "en_US"))) - (setq iensu--language-ring (make-ring (length languages))) - (dolist (elem languages) (ring-insert iensu--language-ring elem))) - - (defun iensu/cycle-ispell-dictionary () - "Cycle through the languages defined in `iensu--language-ring'." - (interactive) - (let ((language (ring-ref iensu--language-ring -1))) - (ring-insert iensu--language-ring language) - (ispell-change-dictionary language) - (message (format "Switched to dictionary: %s" language))))) - - (use-package flyspell-popup - :delight - :after (flyspell)) -#+end_src - -Add =synosaurus= as a thesaurus to look up synonyms. - -#+begin_src emacs-lisp - (use-package synosaurus - :delight - :custom - (synosaurus-backend 'synosaurus-backend-wordnet) - (synosaurus-choose-method 'popup)) -#+end_src - -Add emoji support because it's essential... - -#+begin_src emacs-lisp - (use-package emojify - :custom - (emojify-emojis-dir (iensu--config-file ".local/emojis"))) -#+end_src - -SDCV adds support for additional dictionary look-ups. - -#+begin_src emacs-lisp - (use-package sdcv-mode - :delight - :load-path (lambda () (iensu--config-file "packages"))) -#+end_src - -=visual-fill-column= makes it possible to visually wrap and center text which is good for document-like editing. - -#+begin_src emacs-lisp - (use-package visual-fill-column - :config - (setq-default visual-fill-column-width 100) - (setq-default visual-fill-column-center-text t)) -#+end_src - -Enable tools installed above in document-like modes such as =org-mode= and =markdown-mode= etc - -#+begin_src emacs-lisp - (use-package emacs - :config - (defun iensu/configure-text-editing-tools () - "Enables text editing tools such as spell checking and thesaurus support" - (interactive) - (flyspell-mode 1) - (synosaurus-mode 1) - (emojify-mode 1) - (visual-line-mode 1) - (visual-fill-column-mode 1)) - - ;; for some timing-related (?) reason use-package :hook fails to load this hook - (add-hook 'org-mode-hook #'iensu/configure-text-editing-tools) - (add-hook 'mu4e-compose-mode-hook #'iensu/configure-text-editing-tools) - (add-hook 'markdown-mode-hook #'iensu/configure-text-editing-tools) - (add-hook 'gfm-mode-hook #'iensu/configure-text-editing-tools) - (add-hook 'text-mode-hook #'iensu/configure-text-editing-tools)) -#+end_src - -*** Treemacs - -#+begin_src emacs-lisp - (use-package winum) - - (use-package treemacs - :defer t - :init - (with-eval-after-load 'winum - (define-key winum-keymap (kbd "M-0") #'treemacs-select-window)) - :bind - (:map global-map - ("M-0" . treemacs-select-window) - ("C-x t 1" . treemacs-delete-other-windows) - ("C-x t t" . treemacs) - ("C-x t B" . treemacs-bookmark) - ("C-x t C-t" . treemacs-find-file) - ("C-x t M-t" . treemacs-find-tag) - ("C-x t w" . treemacs-switch-workspace))) - - (use-package treemacs-magit - :after treemacs magit) - - (use-package treemacs-projectile - :after treemacs projectile) -#+end_src - - -*** Source code buffer screenshots - -=silicon= enables creating PNG screenshots of the current source code buffer. It relies on the [[https://github.com/Aloxaf/silicon][Silicon]] command line tool. - -#+begin_src emacs-lisp -(use-package silicon - :load-path "~/Projects/private/emacs/silicon-el") -#+end_src - -** Org-mode - -*** Custom variables - -#+begin_src emacs-lisp - (defvar iensu-org-dir) - (defvar iensu-org-files-alist) - (defvar iensu-org-refile-targets) - (defvar iensu-org-agenda-files) - (defvar iensu-org-capture-templates-alist) - (defvar iensu-org-project-file) -#+end_src - -*** Helper functions - -#+begin_src emacs-lisp - (defun iensu--org-remove-file-if-match (&rest regexes) - "Return a list of org file entries from `iensu-org-files-alist' not matching REGEXES." - (let ((regex (string-join regexes "\\|"))) - (cl-remove-if (lambda (file) (string-match regex file)) - (mapcar 'cadr iensu-org-files-alist)))) - - (defun iensu/org-save-buffers () - "Saves all org buffers." - (interactive) - (save-some-buffers 'no-confirm - (lambda () - (string-match-p - (expand-file-name org-directory) - (buffer-file-name (current-buffer)))))) - - (defun iensu-org-file (key) - "Return file path for org file matching KEY. KEY must be in `iensu-org-files-alist'." - (cadr (assoc key iensu-org-files-alist))) - - (defun iensu--org-capture-project-notes-file () - (concat (projectile-project-root) ".project-notes.org")) -#+end_src - -*** Org directory and file definitions - -#+begin_src emacs-lisp - (setq iensu-org-dir "~/Dropbox/org") - - (setq iensu-org-files-alist - '((futurice "~/Dropbox/org/futurice.org") - (work-calendar "~/Dropbox/org/calendars/work.org") - (ekonomi "~/Dropbox/org/ekonomi.org.gpg") - (journal "~/Dropbox/org/journal.org.gpg") - (private "~/Dropbox/org/private.org") - (refile "~/Dropbox/org/refile.org"))) - - (setq iensu-org-refile-targets - (iensu--org-remove-file-if-match "calendars" - "journal" - "refile")) - - (setq org-archive-location "archive/%s_archive::") -#+end_src - -*** Org-Capture configuration - -Setup capture templates. - -#+begin_src emacs-lisp - (defun iensu--get-current-inactive-timestamp () - (concat "[" (format-time-string "%F %a %H:%M") "]")) - - (setq iensu-org-capture-templates-alist - `(("t" "TODO with link" entry (file ,(iensu-org-file 'refile)) - ,(concat "* TODO %?\n" - "%U\n" - "%a") - :empty-lines 1) - - ("T" "TODO" entry (file ,(iensu-org-file 'refile)) - ,(concat "* TODO %?\n" - "%U") - :empty-lines 1) - - ("j" "Journal" entry (file+datetree ,(iensu-org-file 'journal)) - ,(concat "* %^{Titel}\n" - "%U, %^{Location|Stockholm, Sverige}\n\n" - "%?") - :empty-lines 1) - - ("l" "Link" entry (file ,(iensu-org-file 'refile)) - ,(concat "* %? %^L %^G \n" - "%U") - :prepend t) - - ("L" "Browser Link" entry (file ,(iensu-org-file 'refile)) - ,(concat "* TODO %:description\n" - "%U\n\n" - "%:link") - :prepend t :immediate-finish t :empty-lines 1) - - ("p" "Browser Link and Selection" entry (file ,(iensu-org-file 'refile)) - ,(concat "* TODO %^{Title}\n" - "Source: %u, %c\n\n" - "#+BEGIN_QUOTE\n" - "%i\n" - "#+END_QUOTE\n\n\n%?") - :prepend t :empty-lines 1) - - ("m" "Project note" entry (file+headline iensu--org-capture-project-notes-file "Notes") - ,(concat "* %^{Title}\n" - "%U\n\n" - "%?") - :empty-lines 1) - - ("n" "Project note with link" entry (file+headline iensu--org-capture-project-notes-file "Notes") - ,(concat "* %^{Title}\n" - "%U\n\n" - "Link: %a\n\n" - "%?") - :empty-lines 1) - - ("N" "Project note with link + code quote" entry (file+headline iensu--org-capture-project-notes-file "Notes") - ,(concat "* %^{Title}\n" - "%U\n\n" - "Link: %a\n\n" - "#+begin_src %^{Language}\n" - "%i\n" - "#+end_src\n\n" - "%?") - :empty-lines 1) - - ("b" "Book" entry (file+headline ,(iensu-org-file 'private) "Reading list") - ,(concat "* %^{Title}" - " %^{Author}p" - " %^{Genre}p" - " %^{Published}p" - " %(org-set-property \"Added\" (iensu--get-current-inactive-timestamp))") - :prepend t - :empty-lines 1))) - - (setq org-capture-templates iensu-org-capture-templates-alist) -#+end_src - -*** Main org-mode configuration - -#+begin_src emacs-lisp - (use-package org - :bind (("C-c c" . org-capture) - ("C-c a" . org-agenda) - ("C-c l" . org-store-link) - :map org-mode-map - ("H-." . org-time-stamp-inactive)) - :hook - (org-mode . (lambda () - (org-num-mode 1) - (visual-line-mode 1) - (variable-pitch-mode 1))) - :config - (setq org-default-notes-file (iensu-org-file 'notes)) - (setq org-directory iensu-org-dir) - (setq org-refile-targets '((iensu-org-refile-targets :maxlevel . 10))) - (setq org-refile-allow-creating-parent-nodes 'confirm) - (setq org-refile-use-outline-path 'file) - (setq org-latex-listings t) - (setq org-cycle-separator-lines 1) - (setq org-src-fontify-natively t) - (setq org-format-latex-options (plist-put org-format-latex-options :scale 1.5)) - (setq truncate-lines t) - (setq org-image-actual-width nil) - (setq line-spacing 1) - (setq outline-blank-line t) - (setq org-adapt-indentation nil) - (setq org-fontify-quote-and-verse-blocks t) - (setq org-fontify-done-headline t) - (setq org-fontify-whole-heading-line t) - (setq org-hide-leading-stars t) - (setq org-indent-indentation-per-level 2) - (setq org-checkbox-hierarchical-statistics nil) - (setq org-log-done 'time) - (setq org-outline-path-complete-in-steps nil) - (setq org-html-htmlize-output-type 'css) - (setq org-export-initial-scope 'subtree) - (setq org-catch-invisible-edits 'show-and-error) - - (setq org-clock-in-switch-to-state "DOING") - (setq org-log-into-drawer t) - - (org-load-modules-maybe t) - (dolist (lang-mode '(("javascript" . js2) ("es" . es) ("wat" . wat)) - (add-to-list 'org-src-lang-modes lang-mode)))) -#+end_src - -*** Org-agenda configuration - -#+begin_src emacs-lisp - (require 'org-agenda) - - (setq iensu-org-agenda-files - (iensu--org-remove-file-if-match "\\.org\\.gpg")) - - (dolist (agenda-command - '(("z" "Two week agenda" - ((tags-todo "-books-music-movies" - ((org-agenda-overriding-header "TODOs") - (org-agenda-prefix-format " ") - (org-agenda-sorting-strategy '(priority-down deadline-up)) - (org-agenda-max-entries 20))) - (agenda "" - ((org-agenda-start-day "0d") - (org-agenda-span 14) - (org-agenda-start-on-weekday nil))))))) - (add-to-list 'org-agenda-custom-commands agenda-command)) - - (setq org-agenda-files iensu-org-agenda-files - org-agenda-dim-blocked-tasks nil - org-deadline-warning-days -7 - org-agenda-block-separator "") - - (plist-put org-agenda-clockreport-parameter-plist :maxlevel 6) -#+end_src - -**** Project-based TODO lists - -Create a TODO list based on TODO items in a project's =.project-notes.org= file. -The =org-agenda-files= variable is temporarily set the only the project notes -file and then reverted back to its previous value upon closing the TODO list buffer. - -#+begin_src emacs-lisp - (defvar iensu--project-agenda-buffer-name "*Project Agenda*") - - (defun iensu/project-todo-list () - (interactive) - (let ((project-notes-file (expand-file-name ".project-notes.org" - (projectile-project-root)))) - (if (file-exists-p project-notes-file) - (progn - (setq org-agenda-files `(,project-notes-file)) - (org-todo-list) - (rename-buffer iensu--project-agenda-buffer-name 'unique)) - (message "Could not locate any project notes file")))) - - (defun iensu/reset-org-agenda-files () - (interactive) - (when (string-equal iensu--project-agenda-buffer-name - (buffer-name (current-buffer))) - (setq org-agenda-files iensu-org-agenda-files))) - - ;; Reset org-agenda-files when the project TODO list buffer is closed - (add-hook 'kill-buffer-hook #'iensu/reset-org-agenda-files) -#+end_src - -*** Add extra language support in org source blocks - -#+begin_src emacs-lisp - (org-babel-do-load-languages - 'org-babel-load-languages '((emacs-lisp . t) - (shell . t) - (js . t) - (python . t) - (dot . t))) - - ;; Add support for YAML files - (defun org-babel-execute:yaml (body params) body) - - (defun org-babel-execute:rust (body params) body) -#+end_src - -*** Add more block expansion templates - -#+begin_src emacs-lisp - (let ((additional-org-templates (if (version< (org-version) "9.2") - '(("ssh" "#+begin_src shell \n?\n#+end_src") - ("sel" "#+begin_src emacs-lisp \n?\n#+end_src")) - '(("ssh" . "src shell") - ("sel" . "src emacs-lisp") - ("sr" . "src restclient") - ("sR" . "src rust"))))) - (dolist (template additional-org-templates) - (add-to-list 'org-structure-template-alist template))) -#+end_src - -*** Add extra exporting options - -#+begin_src emacs-lisp - ;; presentations using LaTeX - (require 'ox-beamer) - ;; standard markdown - (require 'ox-md) - ;; Github-flavoured markdown - (use-package ox-gfm - :init - (eval-after-load "org" - '(require 'ox-gfm nil t))) -#+end_src - -*** Customize TODO keyword sequence - -#+begin_src emacs-lisp - (setq org-todo-keywords - '((sequence "TODO(t)" "DOING(d!)" "BLOCKED(b@/!)" - "|" - "CANCELED(C@/!)" "POSTPONED(P@/!)" "DONE(D@/!)"))) - - (setq org-todo-keyword-faces - '(("BLOCKED" . (:foreground "#dd0066" :weight bold)) - ("CANCELED" . (:foreground "#6272a4")) - ("POSTPONED" . (:foreground "#3388ff")))) -#+end_src - -*** Customize PRIORITIES - -#+begin_src emacs-lisp - (setq org-highest-priority ?A - org-default-priority ?D - org-lowest-priority ?E) -#+end_src - -*** Table of contents-generation - -Automatically generate Table of Contents entries for the current org file under -headings marked with a =:TOC:= tag. - -#+begin_src emacs-lisp - (use-package toc-org - :config - (add-hook 'org-mode-hook 'toc-org-mode)) -#+end_src - -*** Make it prettier - -Make view more compact - -#+begin_src emacs-lisp - (setq org-cycle-separator-lines 0) -#+end_src - -Only display one bullet per headline for a cleaner look. - -#+begin_src emacs-lisp - (use-package org-superstar - :init - (add-hook 'org-mode-hook (lambda () (org-superstar-mode 1))) - :config - (setq org-superstar-headline-bullets-list '(?◉))) -#+end_src - -Clean-up agenda view - -#+begin_src emacs-lisp - (setq org-agenda-prefix-format - '((agenda . " %?-12t % s") - (todo . " %i %-12:c") - (tags . " %i %-12:c") - (search . " %i %-12:c"))) -#+end_src - -*** Swedish holidays - -Update the calendar to contain Swedish holidays etc. - -#+begin_src emacs-lisp - (load-file (iensu--config-file "packages/kalender.el")) -#+end_src - -*** Capturing outside of emacs - -=org-protocol= enables capturing from outside of Emacs. - -#+begin_src emacs-lisp - (require 'org-protocol) -#+end_src - -#+begin_src emacs-lisp - (defadvice org-capture-finalize - (after delete-capture-frame activate) - "Advise capture-finalize to close the frame" - (if (equal "capture" (frame-parameter nil 'name)) - (delete-frame))) - - (defadvice org-capture-destroy - (after delete-capture-frame activate) - "Advise capture-destroy to close the frame" - (if (equal "capture" (frame-parameter nil 'name)) - (delete-frame))) -#+end_src - -*** Save org buffers every 5 minutes - -#+begin_src emacs-lisp - (defvar iensu--timer:org-save-buffers nil - "Org save buffers timer object. Can be used to cancel the timer.") - - (setq iensu--timer:org-save-buffers - (run-at-time t (* 5 60) #'iensu/org-save-buffers)) -#+end_src - -*** Google calendar integration -:PROPERTIES: -:header-args: :tangle (iensu/tangle-is-feature-enabled 'gcal-calendar) -:END: - -Stores google calendar events to my org =work-calendar= file. Sync by running -=M-x org-gcal-sync=. - -#+begin_src emacs-lisp - (use-package org-gcal - :init - (setq org-gcal-token-file (iensu--config-file ".local/org-gcal/org-gcal-token") - org-gcal-dir (iensu--config-file ".local/org-gcal/")) - :config - (setq org-gcal-client-id *user-gcal-client-id* - org-gcal-client-secret *user-gcal-client-secret* - org-gcal-file-alist `(("jens.ostlund@futurice.com" . ,(iensu-org-file 'work-calendar))))) -#+end_src - -** Email -:PROPERTIES: -:header-args: :tangle (iensu/tangle-is-feature-enabled 'email) -:END: - -*** MU4E -#+begin_src emacs-lisp :exports none - (use-package mu4e - :ensure nil - :load-path "/usr/local/share/emacs/site-lisp/mu/mu4e" - :bind (:map mu4e-view-mode-map - ("" . shr-next-link) - ("" . shr-previous-link)) - :hook - (mu4e-view-mode . visual-line-mode) - :init - (require 'mu4e) - :config - (setq mail-user-agent 'mu4e-user-agent) - (setq mu4e-mu-binary "/usr/local/bin/mu") - (setq mu4e-maildir "~/Mail") - (setq mu4e-confirm-quit nil) - (setq mu4e-context-policy 'pick-first) - - ;; Configuration for viewing emails - (setq mu4e-view-show-images t) - (setq mu4e-show-images t) - (setq mu4e-view-image-max-width 800) - (setq mu4e-compose-format-flowed t) - (setq mu4e-view-show-addresses t) - (setq mu4e-headers-fields '((:human-date . 12) - (:flags . 6) - (:tags . 16) - (:from . 22) - (:subject))) - - ;; Configuration for composing/sending emails - (setq user-mail-address "jens.ostlund@futurice.com") - (setq user-full-name "Jens Östlund") - (setq message-send-mail-function 'smtpmail-send-it) - (setq smtpmail-debug-info t) - (setq mu4e-sent-messages-behavior 'delete) - (setq message-kill-buffer-on-exit t) - (setq mu4e-compose-context-policy 'ask-if-none) - - (add-hook 'mu4e-compose-mode-hook (lambda () (auto-fill-mode -1))) - - ;; Add email viewing modes - (add-to-list 'mu4e-view-actions '("EWW" . iensu--mu4e-view-in-eww) t) - (add-to-list 'mu4e-view-actions '("ViewInBrowser" . mu4e-action-view-in-browser) t)) - - (use-package org-mu4e :ensure nil) - - ;; sending html emails - (use-package htmlize) - (use-package org-mime - :load-path (lambda () (iensu--config-file "packages")) - :init - (require 'org-mime) - (setq org-mime-library 'mml) - :config - (add-hook 'org-mime-html-hook - (lambda () - (org-mime-change-element-style - "pre" (format "color: %s; background-color: %s; padding: 0.5em;" - "#E6E1DC" "#232323")))) - (add-hook 'org-mime-html-hook - (lambda () - (org-mime-change-element-style - "blockquote" "border-left: 2px solid gray; padding-left: 4px;")))) - - (use-package shr - :custom - (shr-use-fonts nil) - (shr-use-colors nil) - (shr-max-image-proportion 0.2) - (shr-width (current-fill-column))) -#+end_src - - -** Elfeed - -#+begin_src emacs-lisp - (use-package elfeed - :config - (setq-default elfeed-search-filter "@1-months-ago +unread -saved") - - (defun iensu/elfeed-toggle-saved () - "Toggle `saved' tag on selected item(s)." - (interactive) - (let ((entry elfeed-show-entry)) - (if entry - (if (elfeed-tagged-p 'saved entry) - (elfeed-show-untag 'saved) - (elfeed-show-tag 'saved)) - (elfeed-search-toggle-all 'saved)))) - - (define-key elfeed-show-mode-map (kbd "t") 'iensu/elfeed-toggle-saved) - (define-key elfeed-search-mode-map (kbd "t") 'iensu/elfeed-toggle-saved)) -#+end_src - -** ERC - -#+begin_src emacs-lisp - (use-package erc - :ensure nil - :bind (:map erc-mode-map - ("RET" . nil) - ("C-" . erc-send-current-line)) - :custom - (erc-prompt-for-password nil) - (erc-fill-function 'erc-fill-static) - (erc-fill-static-center 22) - (erc-autojoin-channels-alist '(("freenode.net" "#emacs"))) - (erc-join-buffer 'bury) - (erc-autojoin-timing 'ident) - (erc-server-reconnect-attempts 5) - (erc-server-reconnect-timeout 3) - :config - (add-to-list 'erc-modules 'spelling) - (erc-update-modules) - - (defun iensu/erc-freenode () - "Connect to irc.freenode.net" - (interactive) - (erc :server "irc.freenode.net" :port 6667 :nick *erc-nick*))) - - (use-package erc-hl-nicks :after erc) - - (use-package erc-image :after erc) -#+end_src - -** Programming - -Setup auto-completion. - -#+begin_src emacs-lisp - (use-package company - :delight - :init (global-company-mode) - :config - (setq company-idle-delay 0.3) - (setq company-minimum-prefix-length 2) - (setq company-selection-wrap-around t) - (setq company-auto-complete t) - (setq company-tooltip-align-annotations t) - (setq company-dabbrev-downcase nil) - (setq company-auto-complete-chars nil) - (add-hook 'emacs-lisp-mode-hook - (lambda () - (add-to-list 'company-backends 'company-elisp))) - (eval-after-load 'company (company-quickhelp-mode 1))) - - (use-package company-quickhelp - :bind (:map company-active-map - ("M-h" . company-quickhelp-manual-begin)) - :config - (setq company-quickhelp-delay 1)) -#+end_src - -Setup snippet expansions. - -#+begin_src emacs-lisp - (use-package yasnippet - :delight yas-minor-mode - :init - (yas-global-mode 1) - (setq yas-snippet-dirs (add-to-list 'yas-snippet-dirs (iensu--config-file "snippets"))) - :config - (add-hook 'snippet-mode-hook (lambda () - (setq mode-require-final-newline nil - require-final-newline nil)))) -#+end_src - -Setup flycheck for on the fly linting. - -#+begin_src emacs-lisp - (use-package flycheck - :init - (global-flycheck-mode t) - :config - (setq-default flycheck-disabled-checkers '(emacs-lisp-checkdoc))) - - (use-package flycheck-popup-tip) -#+end_src - -Highlight TODO keywords. - -#+begin_src emacs-lisp - (use-package hl-todo - :hook - ((prog-mode . hl-todo-mode))) -#+end_src - -*** LSP mode - -Basic LSP setup. - -#+begin_src emacs-lisp - (defvar iensu-prog-run-tests-fn - (lambda () (message "`iensu--prog-run-tests-fn' is not defined for current mode,")) - "Function to run tests for a specific programming language mode,") - - (defvar iensu-prog-compile-project-fn - (lambda () (message "`iensu--prog-compile-project-fn' is not defined for current mode.")) - "Function to compile a project in a specific programming language mode.") - - (use-package lsp-mode - :commands (lsp lsp-deferred) - :bind (:map lsp-mode-map - ("C-c l" . lsp-mode-hydra/body)) - :init - (setq lsp-keymap-prefix "C-ä") - :hook - (lsp-mode . lsp-enable-which-key-integration) - :config - (setq lsp-auto-guess-root nil) - - :pretty-hydra - ((:title "LSP" :quit-key "q" :color teal) - ("Exploration" - (("l" lsp-find-references "list references") - ("s" lsp-ivy-workspace-symbol "search symbol in workspace") - ("d" lsp-describe-thing-at-point "describe") - ("e" flycheck-list-errors "list buffer errors") - ("å" flycheck-previous-error "goto previous error in buffer") - ("ä" flycheck-next-error "goto next error in buffer ") - ("E" lsp-treemacs-errors-list "list workspace errors") - ("T" lsp-goto-type-definition "find type definition")) - "Refactoring" - (("a" lsp-execute-code-action "execute code action") - ("n" lsp-rename "rename symbol") - ("i" lsp-organize-imports "organize imports") - ("f" lsp-format-buffer "format buffer")) - "Misc" - (("w" lsp-restart-workspace "restart LSP server") - ("t" (funcall `,iensu-prog-run-tests-fn) "run tests") - ("c" (funcall `,iensu-prog-compile-project-fn) "compile project"))))) -#+end_src - -=lsp-ui= enables in buffer documentation popups etc. - -#+begin_src emacs-lisp - (use-package lsp-ui - :commands lsp-ui-mode - :config - (lsp-ui-sideline-mode 1) - (setq lsp-ui-sideline-show-diagnostics t - lsp-ui-sideline-show-code-actions nil - lsp-ui-sideline-show-symbol nil - lsp-ui-sideline-show-hover nil) - (lsp-ui-doc-mode 1)) -#+end_src - -Integrate LSP with company mode. - -#+begin_src emacs-lisp - (use-package company-lsp :commands company-lsp) -#+end_src - -Integrate LSP with treemacs. - -#+begin_src emacs-lisp - (use-package lsp-treemacs :commands lsp-treemacs-errors-list) -#+end_src - -Integrate LSP with ivy to search through symbols in a workspace. - -#+begin_src emacs-lisp - (use-package lsp-ivy :commands lsp-ivy-workspace-symbol) -#+end_src - -#+begin_src emacs-lisp - (use-package which-key - :config - (which-key-mode)) -#+end_src - - -*** Language support - -**** Web development - -***** General - -#+begin_src emacs-lisp - (use-package emmet-mode - :config - (add-hook 'emmet-mode-hook - (lambda () - (when (or (string-suffix-p ".jsx" (buffer-name)) - (string-suffix-p ".tsx" (buffer-name))) - (setq emmet-expand-jsx-className? t))))) - - (use-package prettier-js - :delight - :after (js-mode web-mode yaml-mode) - :hook (web-mode js2-mode yaml-mode)) - - (use-package json-mode - :mode ("\\.json$") - :custom - (js-indent-level 2)) - - (use-package restclient - :init - (add-to-list 'auto-mode-alist '("\\.rest$" . restclient-mode)) - (add-to-list 'auto-mode-alist '("\\.restclient$" . restclient-mode))) - - (use-package ob-restclient - :after (org) - :config - (org-babel-do-load-languages 'org-babel-load-languages '((restclient . t)))) -#+end_src - -***** CSS - -#+begin_src emacs-lisp - (use-package rainbow-mode - :hook (css-mode)) - - (use-package css-mode - :bind (:map css-mode-map - ("C-." . company-complete-common-or-cycle)) - :hook - (css-mode-hook . emmet-mode) - (css-mode-hook . rainbow-delimiters-mode) - :custom - (css-indent-offset 2)) -#+end_src - -#+begin_src emacs-lisp - (use-package scss-mode - :ensure nil - :mode ("\\.scss$" "\\.styl$")) -#+end_src - -***** Web mode - -#+begin_src emacs-lisp - (use-package web-mode - :mode ("\\.html$" "\\.hbs$" "\\.handlebars$" "\\.jsp$" "\\.eex$" "\\.vue$" "\\.php$") - :hook - (web-mode . emmet-mode) - :custom - (web-mode-css-indent-offset 2) - (web-mode-code-indent-offset 2) - (web-mode-markup-indent-offset 2) - (web-mode-attr-indent-offset 2) - (web-mode-attr-value-indent-offset 2) - (web-mode-enable-css-colorization t) - (web-mode-enable-current-element-highlight t) - (web-mode-enable-current-column-highlight t) - :config - (add-hook 'web-mode-hook - (lambda () (yas-activate-extra-mode 'js-mode))) - (flycheck-add-mode 'javascript-eslint 'web-mode) - ;; Use web-mode for choo files - (add-to-list 'magic-mode-alist - '("^const html = require.*choo/html" . web-mode))) -#+end_src - -**** JavaScript - -#+begin_src emacs-lisp - (use-package emacs - :custom - (flycheck-disabled-checkers - (append flycheck-disabled-checkers '(javascript-jshint)))) - - (use-package js - :ensure nil - :custom - (js-switch-indent-offset 2) - :config - (define-key js-mode-map (kbd "M-.") nil)) - - (use-package js2-mode - :mode ("\\.js\\'") - :interpreter ("node" "nodejs") - :custom - (js2-basic-offset 2) - (js2-highlight-level 3) - :hook - (js2-mode . electric-indent-mode) - (js2-mode . rainbow-delimiters-mode) - (js2-mode . smartparens-mode) - (js2-mode . lsp) - (js2-mode . prettier-js-mode) - :config - (add-hook 'xref-backend-functions #'xref-js2-xref-backend nil t) - (js2-mode-hide-warnings-and-errors) - (flycheck-add-mode 'javascript-eslint 'js2-mode)) - - (use-package rjsx-mode - :mode ("\\.jsx\\'") - :hook - (rjsx-mode . electric-indent-mode) - (rjsx-mode . rainbow-delimiters-mode) - (rjsx-mode . smartparens-mode) - (rjsx-mode . emmet-mode) - (rjsx-mode . lsp) - (rjsx-mode . prettier-js-mode) - :init - (add-to-list 'magic-mode-alist - '((lambda () (and buffer-file-name - (string-equal "js" (file-name-extension buffer-file-name)) - (string-match "^import .* from [\"']react[\"']" (buffer-string)))) - . rjsx-mode)) - :config - (flycheck-add-mode 'javascript-eslint 'rjsx-mode) - (add-hook 'rjsx-mode-hook (lambda () (setq emmet-expand-jsx-className? t)))) - - (use-package js2-refactor - :delight js2-refactor-mode - :hook - (rjsx-mode . js2-refactor-mode) - (js2-mode . js2-refactor-mode)) - - (use-package xref-js2 - :defer nil) - - (use-package mocha) - - (use-package nvm) - - (use-package add-node-modules-path - :config - (eval-after-load 'js2-mode - '(add-hook 'js-mode-hook #'add-node-modules-path)) - (eval-after-load 'rjsx-mode - '(add-hook 'js-mode-hook #'add-node-modules-path)) - (eval-after-load 'typescript-mode - '(add-hook 'js-mode-hook #'add-node-modules-path))) -#+end_src - -**** TypeScript - -#+begin_src emacs-lisp - (use-package typescript-mode - :delight - (typescript-mode :major) - :mode ("\\.ts$" "\\.tsx$") - :hook - (typescript-mode . lsp) - (typescript-mode . prettier-js-mode) - :custom - (flycheck-check-syntax-automatically '(save mode-enabled)) - (typescript-indent-level 2) - :config - (flycheck-add-mode 'typescript-tslint 'web-mode) - (add-hook 'web-mode-hook - (lambda () - (when (and buffer-file-name - (string-equal "tsx" (file-name-extension buffer-file-name))) - (lsp))))) -#+end_src - -**** GraphQL - -#+begin_src emacs-lisp - (use-package graphql-mode) -#+end_src - -**** Elm - -#+begin_src emacs-lisp - (use-package elm-mode - :config - (setq elm-tags-on-save t - elm-sort-imports-on-save t - elm-format-on-save t) - (add-hook 'elm-mode-hook #'lsp)) -#+end_src - -**** Shell - -#+begin_src emacs-lisp - (use-package emacs - :config - (add-to-list 'auto-mode-alist '("\\.envrc$" . sh-mode))) -#+end_src - -**** Markdown - -#+begin_src emacs-lisp - (use-package markdown-mode - :commands (markdown-mode gfm-mode) - :mode (("\\.md\\'" . gfm-mode) - ("\\.markdown\\'" . markdown-mode))) - - (use-package markdown-toc) -#+end_src - -**** YAML - -#+begin_src emacs-lisp - (use-package yaml-mode - :hook - (yaml-mode . display-line-numbers-mode) - (yaml-mode . flyspell-mode-off) - ;; YAML mode inherits from text-mode, so need to disable some settings - (yaml-mode . (lambda () - (visual-line-mode -1) - (visual-fill-column-mode -1)))) -#+end_src - -#+begin_src emacs-lisp - (use-package highlight-indentation - :hook - (yaml-mode . highlight-indentation-mode)) -#+end_src - -**** TOML - -#+begin_src emacs-lisp - (use-package toml-mode - :mode ("\\.toml$" "_redirects$")) -#+end_src - -**** Python - -#+begin_src emacs-lisp - (use-package emacs - :hook - (python-mode . lsp)) -#+end_src - -**** Rust - -#+begin_src emacs-lisp - (defun iensu--rust-mode-hook () - (setq-local outline-regexp "\s*//>+") - (setq-local iensu-prog-run-tests-fn 'rust-test) - (setq-local iensu-prog-compile-project-fn 'rust-compile) - (setq-local lsp-rust-server 'rust-analyzer) - (setq-local lsp-rust-clippy-preference "on") - (setq-local lsp-rust-all-features t)) - - (use-package rust-mode - :bind (:map rust-mode-map - ("C-c C-c" . rust-compile)) - :hook - (rust-mode . lsp) - (rust-mode . iensu--rust-mode-hook) - :config - (setq rust-format-on-save t)) -#+end_src - -#+begin_src emacs-lisp - (use-package flycheck-rust - :after (rust-mode) - :hook - (flycheck-mode . flycheck-rust-setup)) -#+end_src - -**** Terraform - -#+begin_src emacs-lisp - (use-package terraform-mode - :config - (defun iensu--terraform-format () - (when (executable-find "terraform") - (let ((fname (buffer-file-name))) - (when (file-exists-p fname) - (shell-command (format "terraform fmt %s" fname)) - (revert-buffer nil t))))) - - (add-hook 'terraform-mode-hook - (lambda () - (add-hook 'after-save-hook #'iensu--terraform-format nil 'local)))) -#+end_src - -**** Graphviz Dot Mode - -#+begin_src emacs-lisp - (use-package graphviz-dot-mode - :bind (:map graphviz-dot-mode-map - ("C-c C-c" . graphviz-dot-preview)) - :config - (add-to-list 'org-src-lang-modes '("dot" . graphviz-dot))) -#+end_src - -**** Java - -#+begin_src emacs-lisp - (use-package java-mode - :ensure nil - :mode "\\.java$" - :hook - (java-mode-hook . electric-pair-mode)) - - (use-package lsp-java - :after lsp - :hook - (java-mode . lsp) - :init - (require 'dap-java) - :bind (:map java-mode-map - ("C-c l f" . lsp-execute-code-action) - ("C-c l n" . lsp-rename) - ("C-c l F" . lsp-format-buffer) - ("C-c l h" . lsp-symbol-highlight)) - ("C-c l i" . lsp-java-add-import)) -#+end_src - -**** Scala - -#+begin_src emacs-lisp - (use-package scala-mode - :hook - (scala-mode . lsp) - :mode "\\.s\\(cala\\|bt\\)$") - - (use-package sbt-mode - :commands (sbt-start sbt-command) - :custom - ;; sbt-supershell kills sbt-mode: https://github.com/hvesalai/emacs-sbt-mode/issues/152 - (sbt:program-options '("-Dsbt.supershell=false")) - :config - ;; WORKAROUND: https://github.com/ensime/emacs-sbt-mode/issues/31 - ;; allows using SPACE when in the minibuffer - (substitute-key-definition - 'minibuffer-complete-word - 'self-insert-command - minibuffer-local-completion-map)) - - (use-package dumb-jump - :config - (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)) -#+end_src - -**** C# - -#+begin_src emacs-lisp - (use-package csharp-mode - :mode ("\\.cs$" "\\.cshtml") - :hook (csharp-mode . lsp) - :custom - (flycheck-check-syntax-automatically '(save mode-enabled)) - :config - (defun iensu--csharp-mode-hook () - (c-set-offset 'arglist-intro '+) - (setq c-basic-offset 2) - (flet ((lsp-format-buffer #'ignore) - (lsp-format-region #'ignore)))) - (add-hook 'csharp-mode-hook #'iensu--csharp-mode-hook)) - - (use-package emacs - :config - (add-to-list 'auto-mode-alist '("\\.csproj$" . xml-mode)) - (add-to-list 'auto-mode-alist '("function.proj$" . xml-mode))) -#+end_src - -**** F# - -#+begin_src emacs-lisp - (use-package fsharp-mode - :defer t - :hook - (fsharp-mode . lsp) - :mode ("\\.fs$" . fsharp-mode)) - - (use-package emacs - :config - (add-to-list 'auto-mode-alist '("\\.fsproj$" . xml-mode))) -#+end_src - -**** SQL - -#+begin_src emacs-lisp - (use-package sql-mode - :ensure nil - :mode "\\.psql$" - :config - (add-hook 'sql-mode-hook - (lambda () - (when (string= (file-name-extension buffer-file-name) "psql") - (setq-local sql-product 'postgres))))) - -#+end_src - -**** Docker - -https://github.com/Silex/docker.el - -#+begin_src emacs-lisp - (use-package docker) - (use-package dockerfile-mode) -#+end_src - -**** WebAssembly - -#+begin_src emacs-lisp - (use-package wat-mode - :load-path (lambda () (iensu--config-file "packages/wat-mode"))) -#+end_src - -**** NixOS files - -Add syntax highlighting for NixOS configuration files. - -#+begin_src emacs-lisp - (use-package nix-mode) -#+end_src - -** Load theme - -Load doom modeline. - -#+begin_src emacs-lisp -(use-package doom-modeline - :ensure t - :init (doom-modeline-mode 1)) -#+end_src - -Load theme. - -#+begin_src emacs-lisp - (use-package modus-vivendi-theme - :config - (load-theme 'modus-vivendi t) - ;; (load-theme 'modus-operandi t) - (set-face-attribute 'font-lock-comment-face nil :slant 'italic) - (set-face-attribute 'font-lock-comment-delimiter-face nil :slant 'italic) - (set-face-attribute 'default nil :font "Fira Code-13") - (set-face-attribute 'fixed-pitch nil :font "Fira Code-13") - (set-face-attribute 'variable-pitch nil :font "Cantarell-14")) -#+end_src - -#+end_src - -** Custom commands - -#+begin_src emacs-lisp - (defun iensu/open-init-file () - "Open my emacs configuration file." - (interactive) - (find-file (iensu--config-file "configuration.org"))) - - (defun iensu/open-refile-file () - "Open refile file." - (interactive) - (find-file (iensu-org-file 'refile))) - - (defun iensu/duplicate-line (n) - "Copy the current line N times and insert it below." - (interactive "P") - (let ((cur-pos (point))) - (dotimes (i (prefix-numeric-value n)) - (move-beginning-of-line nil) - (kill-line) - (yank) - (newline) - (insert (string-trim-right (car kill-ring))) - (goto-char cur-pos)))) - - (defun iensu/toggle-scratch-buffer () - "Based on a great idea from Eric Skoglund (https://github.com/EricIO/emacs-configuration/)." - (interactive) - (if (string-equal (buffer-name (current-buffer)) - "*scratch*") - (switch-to-buffer (other-buffer)) - (switch-to-buffer "*scratch*"))) - - (defun iensu/move-file (new-location) - "Write this file to NEW-LOCATION, and delete the old one. Copied from http://zck.me/emacs-move-file." - (interactive (list (if buffer-file-name - (read-file-name "Move file to: ") - (read-file-name "Move file to: " - default-directory - (expand-file-name (file-name-nondirectory (buffer-name)) - default-directory))))) - (when (file-exists-p new-location) - (delete-file new-location)) - (let ((old-location (buffer-file-name))) - (write-file new-location t) - (when (and old-location - (file-exists-p new-location) - (not (string-equal old-location new-location))) - (delete-file old-location)))) - - (defun iensu/switch-left-and-right-option-keys () - "Switch left and right option keys. - - On some external keyboards the left and right option keys are swapped, - this command switches the keys so that they work as expected." - (interactive) - (let ((current-left mac-option-modifier) - (current-right mac-right-option-modifier)) - (setq mac-option-modifier current-right - mac-right-option-modifier current-left))) - - (defun iensu/refresh-work-calendar () - "Fetch Google calendar events and add the proper file tag(s)." - (interactive) - (org-gcal-fetch)) - - (defun iensu/finish-item () - "Sets a `Finished' property on an org-mode item. The value is the current time as an inactive timestamp." - (interactive) - (org-set-property "Finished" (iensu--get-current-inactive-timestamp))) - - (defun iensu/wat-to-wasm () - "Translate a WebAssembly text format file to binary wasm." - (interactive) - (if (commandp "wat2wasm") - (let* ((file-path (expand-file-name (buffer-file-name))) - (output-path (concat (file-name-sans-extension file-path) - ".wasm"))) - (compile (string-join `("wat2wasm" - ,file-path - "--output" - ,output-path) - " ")) - (message (format "Wrote file %s" output-path))))) - - (defun iensu/toggle-show-paren-style () - "Toggles the current style of matching parentheses." - (interactive) - (setq show-paren-style - (if (eq show-paren-style 'parenthesis) - 'expression - 'parenthesis))) - - (defun iensu/toggle-profiler () - "Starts or stops the profiler, displaying the report when stopped." - (interactive) - (if (profiler-running-p) - (progn - (profiler-stop) - (profiler-report)) - (progn - (profiler-reset) - (profiler-start 'cpu+mem)))) -#+end_src - -** Loading private settings - -#+begin_src emacs-lisp - (load custom-file 'noerror) - - (let ((private-settings (expand-file-name "private.el" user-emacs-directory))) - (when (file-exists-p private-settings) - (load private-settings))) -#+end_src diff --git a/features/elfeed.el b/features/elfeed.el index fc16c80..d567c70 100644 --- a/features/elfeed.el +++ b/features/elfeed.el @@ -1,4 +1,4 @@ -(pretty-hydra-define+ iensu-hydra +(pretty-hydra-define+ iensu-hydra () ("Elfeed" (("f f" elfeed) ("f u" elfeed-update)))) diff --git a/features/email.el b/features/email.el index 4aa1aa0..f02f953 100644 --- a/features/email.el +++ b/features/email.el @@ -42,7 +42,7 @@ (add-to-list 'mu4e-view-actions '("EWW" . iensu--mu4e-view-in-eww) t) (add-to-list 'mu4e-view-actions '("ViewInBrowser" . mu4e-action-view-in-browser) t)) -(pretty-hydra-define+ iensu-hydra +(pretty-hydra-define+ iensu-hydra () ("Email" (("e u" mu4u-update-index "update" :exit nil) ("e e" mu4e "open email") diff --git a/features/lang-csharp.el b/features/lang-csharp.el index 9599fef..f3fedbc 100644 --- a/features/lang-csharp.el +++ b/features/lang-csharp.el @@ -11,7 +11,5 @@ (lsp-format-region #'ignore)))) (add-hook 'csharp-mode-hook #'iensu--csharp-mode-hook)) -(use-package emacs - :config - (add-to-list 'auto-mode-alist '("\\.csproj$" . xml-mode)) - (add-to-list 'auto-mode-alist '("function.proj$" . xml-mode))) +(add-to-list 'auto-mode-alist '("\\.csproj$" . xml-mode)) +(add-to-list 'auto-mode-alist '("function.proj$" . xml-mode)) diff --git a/features/lang-fsharp.el b/features/lang-fsharp.el index 372f2fb..68d3a4c 100644 --- a/features/lang-fsharp.el +++ b/features/lang-fsharp.el @@ -4,6 +4,4 @@ (fsharp-mode . lsp) :mode ("\\.fs$" . fsharp-mode)) -(use-package emacs - :config - (add-to-list 'auto-mode-alist '("\\.fsproj$" . xml-mode))) +(add-to-list 'auto-mode-alist '("\\.fsproj$" . xml-mode)) diff --git a/features/lang-javascript.el b/features/lang-javascript.el index 30ad74f..96116a1 100644 --- a/features/lang-javascript.el +++ b/features/lang-javascript.el @@ -1,16 +1,13 @@ -(use-package emacs :custom (flycheck-disabled-checkers (append flycheck-disabled-checkers '(javascript-jshint)))) +(setq flycheck-disabled-checkers (append flycheck-disabled-checkers '(javascript-jshint))) -(use-package js - :custom - (js-switch-indent-offset 2) - :config - (define-key js-mode-map (kbd "M-.") nil)) +(setq js-switch-indent-offset 2) +(define-key js-mode-map (kbd "M-.") nil) (use-package js2-mode :mode ("\\.js\\'") :interpreter ("node" "nodejs") :custom - (js2-basic-offset 2) + (js2-indent-level 2) (js2-highlight-level 3) :hook (js2-mode . electric-indent-mode) diff --git a/features/org-agenda.el b/features/org-agenda.el index 2f85920..b6befb1 100644 --- a/features/org-agenda.el +++ b/features/org-agenda.el @@ -1,8 +1,5 @@ (require 'org-agenda) -(setq iensu-org-agenda-files - (iensu--org-remove-file-if-match "\\.org\\.gpg")) - (dolist (agenda-command '(("z" "Two week agenda" ((tags-todo "-books-music-movies" @@ -31,4 +28,4 @@ (search . " %i %-12:c"))) ;; Update the calendar to contain Swedish holidays etc. -(load-file (iensu--config-file "packages/kalender.el")) +(load-file (expand-file-name "packages/kalender.el" user-emacs-directory)) diff --git a/features/org.el b/features/org.el new file mode 100644 index 0000000..8aba735 --- /dev/null +++ b/features/org.el @@ -0,0 +1,203 @@ +;;;; Helper functions and variables +(defun iensu--get-current-inactive-timestamp () + (concat "[" (format-time-string "%F %a %H:%M") "]")) + +(defun iensu/org-save-buffers () + "Saves all org buffers." + (interactive) + (save-some-buffers 'no-confirm + (lambda () + (string-match-p + (expand-file-name org-directory) + (buffer-file-name (current-buffer)))))) + +(defvar iensu--timer:org-save-buffers nil + "Org save buffers timer object. Can be used to cancel the timer.") + +(use-package org + :bind (("C-c c" . org-capture) + ("C-c a" . org-agenda) + ("C-c l" . org-store-link) + :map org-mode-map + ("H-." . org-time-stamp-inactive)) + :hook + (org-mode . (lambda () + (org-num-mode 1) + (visual-line-mode 1) + (variable-pitch-mode 1))) + :init + ;; Necessary to make Org-mode stuff available + (require 'org) + :config + (setq org-directory iensu-org-dir) + (setq org-default-notes-file (expand-file-name "notes.org" org-directory)) + (setq org-refile-targets '((iensu-org-refile-targets :maxlevel . 10))) + (setq org-refile-allow-creating-parent-nodes 'confirm) + (setq org-refile-use-outline-path 'file) + (setq org-latex-listings t) + (setq org-cycle-separator-lines 1) + (setq org-src-fontify-natively t) + (setq org-format-latex-options (plist-put org-format-latex-options :scale 1.5)) + (setq truncate-lines t) + (setq org-image-actual-width nil) + (setq line-spacing 1) + (setq outline-blank-line t) + (setq org-adapt-indentation nil) + (setq org-fontify-quote-and-verse-blocks t) + (setq org-fontify-done-headline t) + (setq org-fontify-whole-heading-line t) + (setq org-hide-leading-stars t) + (setq org-indent-indentation-per-level 2) + (setq org-checkbox-hierarchical-statistics nil) + (setq org-log-done 'time) + (setq org-outline-path-complete-in-steps nil) + (setq org-html-htmlize-output-type 'css) + (setq org-export-initial-scope 'subtree) + (setq org-catch-invisible-edits 'show-and-error) + (setq org-archive-location "archive/%s_archive::") + (setq org-capture-templates iensu-org-capture-templates) + + (setq org-modules '(org-protocol)) + + (org-load-modules-maybe t) + (dolist (lang-mode '(("javascript" . js2) ("es" . es) ("wat" . wat)) + (add-to-list 'org-src-lang-modes lang-mode))) + + (org-babel-do-load-languages + 'org-babel-load-languages '((emacs-lisp . t) + (shell . t) + (js . t) + (python . t) + (dot . t))) + + (let ((additional-org-templates (if (version< (org-version) "9.2") + '(("ssh" "#+begin_src shell \n?\") + ("sel" " \n?\")) + '(("ssh" . "src shell") + ("sel" . "src emacs-lisp") + ("sr" . "src restclient") + ("sR" . "src rust"))))) + (dolist (template additional-org-templates) + (add-to-list 'org-structure-template-alist template)))) + +(setq calendar-week-start-day 1) ; The week starts on Monday. + +;;;; Markdown export +;; standard markdown +(require 'ox-md) + +;; Github-flavoured markdown +(use-package ox-gfm + :init + (eval-after-load "org" + '(require 'ox-gfm nil t))) + +;;;; YAML support in source blocks +(defun org-babel-execute:yaml (body params) body) + +;;;; Capture things everywhere + +;;`org-protocol' enables capturing from outside of Emacs. +(require 'org-protocol) + +(defadvice org-capture-finalize + (after delete-capture-frame activate) + "Advise capture-finalize to close the frame" + (if (equal "capture" (frame-parameter nil 'name)) + (delete-frame))) + +(defadvice org-capture-destroy + (after delete-capture-frame activate) + "Advise capture-destroy to close the frame" + (if (equal "capture" (frame-parameter nil 'name)) + (delete-frame))) + +;;;; TODO keyword and priorities setup +(setq org-todo-keywords + '((sequence "TODO(t)" "DOING(d!)" "BLOCKED(b@/!)" + "|" + "CANCELED(C@/!)" "POSTPONED(P@/!)" "DONE(D@/!)"))) + +(setq org-todo-keyword-faces + '(("BLOCKED" . (:foreground "#dd0066" :weight bold)) + ("CANCELED" . (:foreground "#6272a4")) + ("POSTPONED" . (:foreground "#3388ff")))) + +;; Customize PRIORITIES +(setq org-highest-priority ?A + org-default-priority ?D + org-lowest-priority ?E) + +(setq org-clock-in-switch-to-state "DOING") +(setq org-log-into-drawer t) + + +;;;;; Make org-mode prettier + +;; Make view more compact +(setq org-cycle-separator-lines 0) + +;; Only display one bullet per headline for a cleaner look. +(use-package org-superstar + :after (org) + :init + (add-hook 'org-mode-hook (lambda () (org-superstar-mode 1))) + :config + (setq org-superstar-headline-bullets-list '(?◉))) + +;;;; Autosaving org buffers +(setq iensu--timer:org-save-buffers + (run-at-time t (* 5 60) #'iensu/org-save-buffers)) + +;;;; Capture templates +(iensu-add-to-list 'iensu-org-capture-templates + `("t" "TODO with link" entry (file ,(expand-file-name "refile.org" iensu-org-dir)) + ,(concat "* TODO %?\n" + "%U\n" + "%a") + :empty-lines 1) + + `("T" "TODO" entry (file ,(expand-file-name "refile.org" iensu-org-dir)) + ,(concat "* TODO %?\n" + "%U") + :empty-lines 1) + + `("j" "Journal" entry (file+datetree ,(expand-file-name "journal.org.gpg" iensu-org-dir)) + ,(concat "* %^{Titel}\n" + "%U, %^{Location|Stockholm, Sverige}\n\n" + "%?") + :empty-lines 1) + + `("l" "Link" entry (file ,(expand-file-name "refile.org" iensu-org-dir)) + ,(concat "* %? %^L %^G \n" + "%U") + :prepend t) + + `("L" "Browser Link" entry (file ,(expand-file-name "refile.org" iensu-org-dir)) + ,(concat "* TODO %:description\n" + "%U\n\n" + "%:link") + :prepend t :immediate-finish t :empty-lines 1) + + `("p" "Browser Link and Selection" entry (file ,(expand-file-name "refile.org" iensu-org-dir)) + ,(concat "* TODO %^{Title}\n" + "Source: %u, %c\n\n" + "#+BEGIN_QUOTE\n" + "%i\n" + "#+END_QUOTE\n\n\n%?") + :prepend t :empty-lines 1) + + `("b" "Book" entry (file+headline ,(expand-file-name "private.org" iensu-org-dir) "Reading list") + ,(concat "* %^{Title}" + " %^{Author}p" + " %^{Genre}p" + " %^{Published}p" + " %(org-set-property \"Added\" (iensu--get-current-inactive-timestamp))") + :prepend t :empty-lines 1)) + +(pretty-hydra-define+ iensu-hydra () + ("Org clock" + (("c c" org-clock-in "start clock") + ("c r" org-clock-in-last "resume clock") + ("c s" org-clock-out "stop clock") + ("c g" org-clock-goto "goto clocked task")))) diff --git a/features/web-dev.el b/features/web-dev.el index efb70ca..911a8d2 100644 --- a/features/web-dev.el +++ b/features/web-dev.el @@ -16,14 +16,16 @@ (lambda () (yas-activate-extra-mode 'js-mode))) (flycheck-add-mode 'javascript-eslint 'web-mode)) +;;;; CSS (use-package css-mode - :bind (:map css-mode-map - ("C-." . company-complete-common-or-cycle)) - :hook - (css-mode-hook . emmet-mode) - (css-mode-hook . rainbow-delimiters-mode) - :custom - (css-indent-offset 2)) + :ensure nil + :bind (:map css-mode-map + ("C-." . company-complete-common-or-cycle)) + :hook + (css-mode-hook . emmet-mode) + (css-mode-hook . rainbow-delimiters-mode) + :custom + (css-indent-offset 2)) (use-package rainbow-mode :hook (css-mode)) diff --git a/init.el b/init.el index c7321d0..039a7f1 100644 --- a/init.el +++ b/init.el @@ -1,36 +1,798 @@ -(defvar iensu-extra-features-alist '() - "List of enabled extra features which might require additional configurations.") - -(defun iensu/tangle-is-feature-enabled (feature) - "Returns the string `yes' if feature is enabled, `no' otherwise. This is to simplify conditional tangling." - (if (member feature iensu-extra-features-alist) - "yes" - "no")) - -;; disable - in org-mode -;; needs to be set before org-mode is loaded -(setq org-replace-disputed-keys t) - -;; Load features.el if it exists. -;; features.el should set `iensu-extra-features-alist' to enable features for the -;; specific environment. -(let ((features-file (expand-file-name "features.el" - user-emacs-directory))) - (when (file-exists-p features-file) - (load-file features-file))) - -;; Always re-tangle configuration.el to make sure that it is up to date. -(let ((elisp-config (expand-file-name "configuration.el" user-emacs-directory))) - (when (file-exists-p elisp-config) - (message "Removing configuration.el") - (delete-file elisp-config))) - -(require 'org) -(package-initialize) - -;; Tangle configuration.org into configuration.el, while making sure that explicit :tangle yes -;; declarations do not erase already tangled source blocks. -(cl-letf (((symbol-function 'delete-file) #'ignore)) - (org-babel-load-file - (expand-file-name "configuration.org" - user-emacs-directory))) +;;; Code: + +;;;; Package installation and management + +;; This section initializes `package' with a set of archives and installs `use-package' which is +;; used to install other package dependencies. + +(require 'package) + +(setq package-archives + '(("gnu" . "https://elpa.gnu.org/packages/") + ("melpa" . "https://melpa.org/packages/"))) + +(unless (package-installed-p 'use-package) ; install use-package if it's not already installed + (package-refresh-contents) + (package-install 'use-package)) + +(eval-when-compile + (require 'use-package) + (setq use-package-always-ensure t)) + + +;;;; System local configuration + +;; This section handles configurations that are local to the system and thus should not be under +;; version control, for instance credentials and system specific feature flags. + +;; System local variables which can be set in local-settings.el +(defvar iensu-org-dir nil + "Directory containing Org files.") + +(defvar iensu-org-refile-targets nil + "Org files which can be used as refiling targets.") + +(defvar iensu-org-agenda-files nil + "Org files which should be used by org-agenda to generate TODO lists etc.") + +(defvar iensu-gcal-client-id nil + "Client ID for Gmail integration.") + +(defvar iensu-gcal-client-secret nil + "Client secret for Gmail integration.") + +(defvar iensu-org-capture-templates nil + "Capture templates to be used by Org mode.") + +(defvar iensu-enabled-features-alist nil + "Locally enabled features. Available features are stored in the `features/' directory.") + + +;; Load settings +(load-file (expand-file-name "local-settings.el" + user-emacs-directory)) + + +;;;; Helper functions + +(defun iensu-add-to-list (list &rest items) + "Add multiple items to a list." + (dolist (item items) + (add-to-list list item))) + + +;;;; Basic setup + +;; This contains some basic default configuration which does not depend on any external packages. + +;; Cleanup the UI by removing the menu tool and scroll bars. +(mapc (lambda (mode) + (when (fboundp mode) + (funcall mode -1))) + '(menu-bar-mode tool-bar-mode scroll-bar-mode)) + +;; Remove the warning bell. +(setq ring-bell-function 'ignore) + +;; Remove the GNU/Emacs startup screen to boot direct to the Scratch buffer. +(setq inhibit-startup-screen t) + +;; Make sure that buffer names become unique when opening multiple files of the same name. +(setq-default frame-title-format "%b (%f)" + uniquify-buffer-name-style 'post-forward + uniquify-separator ":") + +;; Ask before killing Emacs. +(setq confirm-kill-emacs 'y-or-n-p) + +;; Always just ask y/n instead of yes/no +(fset #'yes-or-no-p #'y-or-n-p) + +;; Put any Emacs generated customizations into ./custom.el instead of ./init.el. +(setq custom-file (expand-file-name "custom.el" + user-emacs-directory)) + +;; Improve performance by increasing the garbage collector threshold and max LISP evaluation depth. +(setq gc-cons-threshold 100000000 + max-lisp-eval-depth 2000) + +;; Move backups and auto-saves to ~/.emacs.d/.local/.saves. +(setq create-lockfiles nil + backup-directory-alist `(("." . ,(expand-file-name ".local/.saves/" user-emacs-directory))) + backup-by-copying t + delete-old-versions t + kept-new-versions 6) + +;; Remember recent files +(use-package recentf + :ensure nil + :custom + (recentf-max-menu-items 50) + :config + (recentf-load-list) + :init + (recentf-mode 1) + (setq recentf-save-file (expand-file-name ".local/recentf" user-emacs-directory))) + +;; Keep some files in ~/.emacs.d/.local to avoid cluttering the configuration root directory. +(setq url-configuration-directory (expand-file-name ".local/uri/" user-emacs-directory) + image-dired-dir (expand-file-name ".local/image-dired-thumbnails/" user-emacs-directory) + bookmark-default-file (expand-file-name ".local/bookmarks" user-emacs-directory) + tramp-auto-save-directory (expand-file-name ".local/tramp-autosaves/" user-emacs-directory)) + +;; Setup authentication file. +(setq auth-sources '("~/.authinfo.gpg" + "~/.netrc")) + +;; Auto scroll through output in compilation buffers. +(setq compilation-scroll-output t) + +;; Save bookmarks when changed +(setq bookmark-save-flag 1) + + +;;;; Editor default settings + +;; This section changes the default text editing behavior of Emacs so it is more inline with my +;; expectations. + +;; Use spaces instead of tabs. +(setq-default indent-tabs-mode nil + tab-width 2) + +(setq-default require-final-newline t) ; Files should always have a final newline. + +(setq-default sentence-end-double-space nil) ; Sentence end does not require two spaces. + +;; Fix buffer scrolling behavior. +(setq-default scroll-conservatively 0 + scroll-step 4 + next-screen-context-lines 20) + +(add-hook 'before-save-hook 'delete-trailing-whitespace) ; Delete trailing whitespace on save. + +(delete-selection-mode 1) ; Replace selected text with typed text. + +;; Enable narrowing to region and page. Pages are delimited by ^L. +(put 'narrow-to-page 'disabled nil) +(put 'narrow-to-region 'disabled nil) + +;; Use the editorconfig package to conform to project formatting rules if present. +(use-package editorconfig + :hook + (prog-mode . editorconfig-mode) + (text-mode . editorconfig-mode)) + +;; Enable multiple cursors for convenient editing. Use `iedit' for quick and dirty multi-cursor +;; functionality. +(use-package iedit) +(use-package multiple-cursors + :bind + (("M-=" . mc/edit-lines) + ("C-S-" . mc/mark-next-like-this) + ("C-S-" . mc/mark-previous-like-this) + ("C-S-" . mc/add-cursor-on-click)) + :custom + (mc/list-file (expand-file-name ".local/.mc-lists.el" user-emacs-directory))) + +;; Expand region from current region or point. +(use-package expand-region + :bind + (("C-=" . er/expand-region) + ("C-M-=" . er/contract-region))) + +;;;;; Programming mode related settings + +(defun iensu--prog-mode-hook () + "Defaults for programming modes" + (subword-mode 1) ; delimit words at camelCase boundries + (eldoc-mode 1) ; display documentation in minibuffer + (show-paren-mode 1) ; highlight matching parentheses + (setq-default show-paren-when-point-in-periphery t + show-paren-when-point-inside-paren t) + (hs-minor-mode 1) ; hide-show code and comment blocks + (outline-minor-mode 1)) ; Navigate by outlines + +(add-hook 'prog-mode-hook #'iensu--prog-mode-hook) + +;; Manipulate parentheses and other code structures. +(use-package smartparens + :init + (require 'smartparens-config) + :bind (:map smartparens-mode-map + ("M-s" . sp-unwrap-sexp) + ("C-" . sp-down-sexp) + ("C-" . sp-up-sexp) + ("M-" . sp-backward-down-sexp) + ("M-" . sp-backward-up-sexp) + ("C-" . sp-forward-slurp-sexp) + ("M-" . sp-forward-barf-sexp) + ("C-" . sp-backward-slurp-sexp) + ("M-" . sp-backward-barf-sexp)) + :hook + (prog-mode . smartparens-mode) ; non-strict by default, but keep strict in LISPs + (repl-mode . smartparens-strict-mode) + (lisp-mode . smartparens-strict-mode) + (emacs-lisp-mode . smartparens-strict-mode)) + +;;;;; Text editing tools + +;; Spellcheck using flyspell +(use-package flyspell + :ensure nil + :bind (:map flyspell-mode-map ("C-:" . flyspell-popup-correct)) + :custom + (ispell-extra-args '("--sug-mode=ultra")) + (ispell-list-command "--list") + (ispell-dictionary "en_US") + :config + (defvar iensu--language-ring nil + "Ispell language ring used to toggle current selected ispell dictionary") + + (let ((languages '("swedish" "en_US"))) + (setq iensu--language-ring (make-ring (length languages))) + (dolist (elem languages) (ring-insert iensu--language-ring elem))) + + (defun iensu/cycle-ispell-dictionary () + "Cycle through the languages defined in `iensu--language-ring'." + (interactive) + (let ((language (ring-ref iensu--language-ring -1))) + (ring-insert iensu--language-ring language) + (ispell-change-dictionary language) + (message (format "Switched to dictionary: %s" language))))) +(use-package flyspell-popup :after (flyspell)) + +;; Use synosaurus to look up synonyms +(use-package synosaurus + :custom + (synosaurus-backend 'synosaurus-backend-wordnet) + (synosaurus-choose-method 'popup)) + +;; Emoji support because reasons... +(use-package emojify + :custom + (emojify-emojis-dir (expand-file-name ".local/emojis" user-emacs-directory))) + +;; `visual-fill-column' makes it possible to visually wrap and center text which is good for +;; document-like editing. +(use-package visual-fill-column + :config + (setq-default visual-fill-column-width 100) + (setq-default visual-fill-column-center-text t)) + +(defun iensu/text-editing-mode-hook () + "Enables text editing tools such as spell checking and thesaurus support" + (interactive) + (flyspell-mode 1) + (synosaurus-mode 1) + (emojify-mode 1) + (visual-line-mode 1) + (visual-fill-column-mode 1)) + +(add-hook 'text-mode-hook #'iensu/text-editing-mode-hook) + + +;;;; Utility packages + +;; Install `hydra' with `pretty-hydra' which simplifies hydra definitions +(use-package hydra) +(use-package pretty-hydra :after (hydra)) + +;; Make system path variables accessible in Emacs +(use-package exec-path-from-shell + :custom + (exec-path-from-shell-check-startup-files nil) + :init + (exec-path-from-shell-initialize)) + +;; Password entry in minibuffer +(use-package pinentry + :init + (setq epa-pinentry-mode 'loopback) + (pinentry-start)) + +;; Improved file browsing +(use-package dired+ + :load-path (lambda () (expand-file-name "packages" user-emacs-directory)) + :custom + (dired-listing-switches "-alGh --group-directories-first") + (dired-dwim-target t) + :config + (when (executable-find "gls") ;; native OSX ls works differently then GNU ls + (setq insert-directory-program "/usr/local/bin/gls"))) + +;;;; Navigation + +;; This section adds packages which enables quick navigation and search. + +(use-package deadgrep) + +;; `counsel', `ivy' and `swiper' constitute a very useful completion framework. +(use-package counsel + :delight ivy-mode + :init + (ivy-mode 1) + :bind (("M-x" . counsel-M-x) + ("C-x C-f" . counsel-find-file) + ("C-x C-r" . counsel-recentf) + ("C-c k" . counsel-ag) + ("C-x b" . ivy-switch-buffer) + ("M-y" . counsel-yank-pop) + ("C-s" . swiper-isearch) + :map ivy-minibuffer-map + ("M-y" . ivy-next-line)) + :custom + (ivy-use-virtual-buffers t) + (ivy-use-selectable-prompt t) + (ivy-count-format "(%d/%d) ") + (ivy-magic-slash-non-match-action 'ivy-magic-non-match-create) + (counsel-ag-base-command "ag --nocolor --nogroup --hidden %s") + (ivy-display-style 'fancy) + (ivy-re-builders-alist '((swiper . ivy--regex-plus) + (swiper-isearch . ivy--regex-plus) + (counsel-find-file . ivy--regex-plus) + (counsel-projectile-find-file . ivy--regex-plus) + (t . ivy--regex-plus)))) + +(use-package ivy-rich :config (ivy-rich-mode 1)) ; Add documentation to ivy results + +;; `prescient' ranks search candidates by most recent. +(use-package prescient :config (prescient-persist-mode 1)) +(use-package ivy-prescient + :config + (ivy-prescient-mode 1) + (setq ivy-prescient-enable-sorting t) + (setq ivy-prescient-enable-filtering t)) +(use-package company-prescient + :config + (company-prescient-mode 1)) + +;; Snippet expansion for less repetitive text editing +(use-package yasnippet + :delight yas-minor-mode + :init + (yas-global-mode 1) + (setq yas-snippet-dirs (add-to-list 'yas-snippet-dirs (expand-file-name "snippets" user-emacs-directory))) + :config + (add-hook 'snippet-mode-hook (lambda () + (setq mode-require-final-newline nil + require-final-newline nil)))) + +;; When all else fails +(use-package dumb-jump + :config + (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)) + + +;;;; Custom commands + +(defun iensu/toggle-scratch-buffer () + "Based on a great idea from Eric Skoglund (https://github.com/EricIO/emacs-configuration/)." + (interactive) + (if (string-equal (buffer-name (current-buffer)) + "*scratch*") + (switch-to-buffer (other-buffer)) + (switch-to-buffer "*scratch*"))) + +(defun iensu/toggle-profiler () + "Starts or stops the profiler, displaying the report when stopped." + (interactive) + (if (profiler-running-p) + (progn + (profiler-stop) + (profiler-report)) + (progn + (profiler-reset) + (profiler-start 'cpu+mem)))) + +(defun iensu/duplicate-line (n) + "Copy the current line N times and insert it below." + (interactive "P") + (let ((cur-pos (point))) + (dotimes (i (prefix-numeric-value n)) + (move-beginning-of-line nil) + (kill-line) + (yank) + (newline) + (insert (string-trim-right (car kill-ring))) + (goto-char cur-pos)))) + +(defun iensu/move-file (new-location) + "Write this file to NEW-LOCATION, and delete the old one. Copied from http://zck.me/emacs-move-file." + (interactive (list (if buffer-file-name + (read-file-name "Move file to: ") + (read-file-name "Move file to: " + default-directory + (expand-file-name (file-name-nondirectory (buffer-name)) + default-directory))))) + (when (file-exists-p new-location) + (delete-file new-location)) + (let ((old-location (buffer-file-name))) + (write-file new-location t) + (when (and old-location + (file-exists-p new-location) + (not (string-equal old-location new-location))) + (delete-file old-location)))) + +(defun iensu/finish-item () + "Sets a `Finished' property on an org-mode item. The value is the current time as an inactive timestamp." + (interactive) + (org-set-property "Finished" (iensu--get-current-inactive-timestamp))) + + +;;;; Global keybindings + +(global-set-key (kbd "C-") 'delete-indentation) +(global-set-key (kbd "C-h C-s") 'iensu/toggle-scratch-buffer) +(global-set-key (kbd "C-x C-b") 'ibuffer) +(global-set-key (kbd "M-") 'fixup-whitespace) +(global-set-key (kbd "M-i") 'imenu) +(global-set-key (kbd "M-o") 'occur) + +;; `windmove' enables navigation using `-'. These bindings conflict `org-mode' so +;; we make `windmove' take precedence. +(windmove-default-keybindings) +(setq org-replace-disputed-keys t) ; This line needs to occur before `org-mode' is loaded. + +;;;;; Global hydra + +;; Setup a global hydra with keybindings I use very often. +(pretty-hydra-define iensu-hydra + (:color teal :quit-key "q" :title "Global commands") + ("Utilities" + (("d" iensu/duplicate-line "duplicate line" :exit nil) + ("s" deadgrep "search") + ("t" toggle-truncate-lines "truncate lines") + ("u" revert-buffer "reload buffer") + ("D" iensu/cycle-ispell-dictionary "change dictionary")) + "Bookmarks" + (("l" list-bookmarks "list bookmarks") + ("b" bookmark-set "set bookmark")) + "Misc" + (("P" iensu/project-todo-list "project todo list") + ("i" list-bookmarks "list bookmarks") + ("+" enlarge-window-horizontally "enlarge window" :exit nil) + ("-" shrink-window-horizontally "shrink window" :exit nil)) + "Hide/show" + (("h h" hs-toggle-hiding "toggle block visibility") + ("h l" hs-hide-level "hide all blocks at same level") + ("h a" hs-hide-all "hide all") + ("h s" hs-show-all "show all")))) + +(global-set-key (kbd "C-å") #'iensu-hydra/body) + +;; Enhance explorability with by listing possible completions while doing key chords. +(use-package which-key :config (which-key-mode)) + +;;;;; macOS specific keybindings + +;; Set command to act as `meta' (`M-') and disable the `option' key since that button is needed to +;; type various characters on a Swedish keyboard. Also make the right `option' key act as `hyper' +;; (`H-') to give us more keybindings to work with. +(setq mac-command-modifier 'meta + mac-option-modifier 'none + mac-right-option-modifier 'hyper) + +;; An unfortunate workaround required when switching to an external keyboard. +(defun iensu/switch-left-and-right-option-keys () + "Switch left and right option keys. + + On some external keyboards the left and right Mac `option' keys are swapped, + this command switches the keys so that they work as expected." + (interactive) + (let ((current-left mac-option-modifier) + (current-right mac-right-option-modifier)) + (setq mac-option-modifier current-right + mac-right-option-modifier current-left))) + + +;;;; Make Emacs prettier + +(setq-default cursor-type '(bar . 2)) +(global-prettify-symbols-mode 1) +(global-font-lock-mode 1) + +;; Use dark mode on macOS. +(add-to-list 'default-frame-alist '(ns-transparent-titlebar . t)) +(add-to-list 'default-frame-alist '(ns-appearence . dark)) + +;; Use different colored parentheses based on scope. +;;(use-package rainbow-delimiters +;; :hook (prog-mode . #'rainbow-delimiters-mode)) + +;; Use icons where applicable. +(use-package all-the-icons) + +;; Use a clean mode line +(use-package doom-modeline + :init (doom-modeline-mode 1)) + +;; Set theme +(use-package modus-vivendi-theme + :config + (load-theme 'modus-vivendi t) + (set-face-attribute 'font-lock-comment-face nil :slant 'italic) + (set-face-attribute 'font-lock-comment-delimiter-face nil :slant 'italic) + (set-face-attribute 'default nil :font "Fira Code-13") + (set-face-attribute 'fixed-pitch nil :font "Fira Code-13") + (set-face-attribute 'variable-pitch nil :font "Cantarell-14")) + + +;;;; Version control + +;; Make `magit' and other version control tools follow symlinks. +(setq vc-follow-symlinks t) + +;; Use `magit' for a great `git' experience. +(use-package magit + :bind (("C-x g" . magit-status)) + :custom + (magit-bury-buffer-function 'quit-window) + :config + (when (executable-find "/usr/bin/git") ; Speeds up git operations on macOS + (setq magit-git-executable "/usr/bin/git")) + (setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)) + +;; `smerge-mode' is a merge conflict resolution tool which is great but unfortunately has awful +;; default keybindings. Here I define a hydra to make `smerge' easier to work with. +(use-package smerge-mode + :ensure nil + :bind (:map smerge-mode-map (("C-c ö" . smerge-mode-hydra/body))) + :pretty-hydra + ((:color teal :quit-key "q" :title "Smerge - Git conflicts") + ("Resolving" + (("RET" smerge-keep-current "Keep current" :exit nil) + ("l" smerge-keep-lower "Keep lower" :exit nil) + ("u" smerge-keep-upper "Keep upper" :exit nil) + ("b" smerge-keep-base "Keep base" :exit nil) + ("C" smerge-combine-with-next "Combine with next") + ("a" smerge-keep-all "Keep all" :exit nil) + ("r" smerge-resolve "Resolve")) + "Navigation" + (("n" smerge-next "Next conflict" :exit nil) + ("p" smerge-prev "Previous conflict" :exit nil) + ("R" smerge-refine "Highlight differences" :exit nil)) + "Misc" + (("E" smerge-ediff "Open in Ediff"))))) + + +;;;; Project management + +;; Settings which help with handling and navigating projects. + +(use-package projectile + :bind + (("C-c p" . projectile-hydra/body)) + :custom + (projectile-completion-system 'ivy) + (projectile-cache-file (expand-file-name ".local/projectile.cache" user-emacs-directory)) + (projectile-known-projects-file (expand-file-name ".local/projectile-bookmarks.eld" user-emacs-directory)) + (projectile-git-submodule-command nil) + (projectile-sort-order 'access-time) + (projectile-globally-ignored-files '("TAGS" ".DS_Store" ".projectile")) + :pretty-hydra + ((:color teal :quit-key "q" :title "Project") + ("Project" + (("p" counsel-projectile-switch-project "open project") + ("k" projectile-kill-buffers "close project") + ("t" projectile-test-project "test project" :exit t) + ("c" projectile-compile-project "compile project" :exit t)) + "Files & Buffers" + (("f" counsel-projectile-find-file "open project file") + ("o" iensu/open-project-org-file "open project org file") + ("T" iensu/project-todo-list "open project TODO list") + ("b" counsel-projectile-switch-to-buffer "open project buffer") + ("S" projectile-save-buffers "save project buffers")) + "Search" + (("s" projectile-ripgrep "search") + ("r" projectile-replace "replace literal") + ("R" projectile-replace-regexp "replace regex")))) + :config + (projectile-global-mode) + (projectile-register-project-type 'node-npm '("package.json") + :compile "npm run build" + :test "npm test") + (projectile-register-project-type 'rust-cargo '("cargo.toml") + :compile "cargo check" + :test "cargo test" + :run "cargo run") + (projectile-register-project-type 'java-maven '("pom.xml") + :compile "mvn compile" + :test "mvn test")) + +(use-package counsel-projectile :init (counsel-projectile-mode 1)) + +(use-package ibuffer-projectile :after (projectile) + :hook + (ibuffer-mode . (lambda () + (ibuffer-projectile-set-filter-groups) + (unless (eq ibuffer-sorting-mode 'alphabetic) + (ibuffer-do-sort-by-alphabetic))))) + +;; `treemacs' for a visual project tree structure. +(use-package treemacs + :defer t + :bind + (:map global-map + ("M-0" . treemacs-select-window) + ("C-x t 1" . treemacs-delete-other-windows) + ("C-x t t" . treemacs) + ("C-x t B" . treemacs-bookmark) + ("C-x t C-t" . treemacs-find-file) + ("C-x t M-t" . treemacs-find-tag) + ("C-x t w" . treemacs-switch-workspace))) +(use-package treemacs-magit :after treemacs magit) +(use-package treemacs-projectile :after treemacs projectile) + +;;;;; Project-based TODO lists + +;; Create a TODO list based on TODO items in a project's `.project-notes.org' file. The +;; `org-agenda-files' variable is temporarily set the only the project notes file and then reverted +;; back to its previous value upon closing the TODO list buffer. + +(defvar iensu--project-agenda-buffer-name "*Project Agenda*") + +(defun iensu--org-capture-project-notes-file () + (concat (projectile-project-root) ".project-notes.org")) + +(defun iensu/project-todo-list () + (interactive) + (let ((project-notes-file (expand-file-name ".project-notes.org" + (projectile-project-root)))) + (if (file-exists-p project-notes-file) + (progn + (setq org-agenda-files `(,project-notes-file)) + (org-todo-list) + (rename-buffer iensu--project-agenda-buffer-name 'unique)) + (message "Could not locate any project notes file")))) + +(defun iensu/reset-org-agenda-files () + (interactive) + (when (string-equal iensu--project-agenda-buffer-name + (buffer-name (current-buffer))) + (setq org-agenda-files iensu-org-agenda-files))) + +;; Reset org-agenda-files when the project TODO list buffer is closed +(add-hook 'kill-buffer-hook #'iensu/reset-org-agenda-files) + +;; Add some org-capture templates for project notes. +(iensu-add-to-list 'iensu-org-capture-templates + `("m" "Project note" entry (file+headline iensu--org-capture-project-notes-file "Notes") + ,(concat "* %^{Title}\n" + "%U\n\n" + "%?") + :empty-lines 1) + + `("n" "Project note with link" entry (file+headline iensu--org-capture-project-notes-file "Notes") + ,(concat "* %^{Title}\n" + "%U\n\n" + "Link: %a\n\n" + "%?") + :empty-lines 1) + + `("N" "Project note with link + code quote" entry (file+headline iensu--org-capture-project-notes-file "Notes") + ,(concat "* %^{Title}\n" + "%U\n\n" + "Link: %a\n\n" + "#+begin_src %^{Language}\n" + "%i\n" + "\n\n" + "%?") + :empty-lines 1)) + + +;;;; IDE features + +;; Highlight TODOs in programming buffers +(use-package hl-todo :hook ((prog-mode . hl-todo-mode))) + +;;;;; Autocompletion and intellisense + +;; Company as a completion frontend +(use-package company + :init (global-company-mode) + :config + (setq company-idle-delay 0.3) + (setq company-minimum-prefix-length 2) + (setq company-selection-wrap-around t) + (setq company-auto-complete t) + (setq company-tooltip-align-annotations t) + (setq company-dabbrev-downcase nil) + (setq company-auto-complete-chars nil) + (add-hook 'emacs-lisp-mode-hook + (lambda () + (add-to-list 'company-backends 'company-elisp))) + (eval-after-load 'company (company-quickhelp-mode 1))) +(use-package company-quickhelp + :bind (:map company-active-map + ("M-h" . company-quickhelp-manual-begin)) + :config + (setq company-quickhelp-delay 1)) + +;; Flycheck for on the fly error reporting +(use-package flycheck + :init + (global-flycheck-mode t) + :config + (setq-default flycheck-disabled-checkers '(emacs-lisp-checkdoc))) +(use-package flycheck-popup-tip) + +;; LSP for intellisense +(use-package lsp-mode + :commands (lsp lsp-deferred) + :bind (:map lsp-mode-map + ("C-c l" . lsp-mode-hydra/body)) + :init + (setq lsp-keymap-prefix "C-ä") + :hook + (lsp-mode . lsp-enable-which-key-integration) + :config + (setq lsp-auto-guess-root nil) + + :pretty-hydra + ((:title "LSP" :quit-key "q" :color teal) + ("Exploration" + (("l" lsp-find-references "list references") + ("s" lsp-ivy-workspace-symbol "search symbol in workspace") + ("d" lsp-describe-thing-at-point "describe") + ("e" flycheck-list-errors "list buffer errors") + ("å" flycheck-previous-error "goto previous error in buffer") + ("ä" flycheck-next-error "goto next error in buffer ") + ("E" lsp-treemacs-errors-list "list workspace errors") + ("T" lsp-goto-type-definition "find type definition")) + "Refactoring" + (("a" lsp-execute-code-action "execute code action") + ("n" lsp-rename "rename symbol") + ("i" lsp-organize-imports "organize imports") + ("f" lsp-format-buffer "format buffer")) + "Misc" + (("w" lsp-restart-workspace "restart LSP server"))))) + +;; `lsp-ui' enables in buffer documentation popups etc. +(use-package lsp-ui + :commands lsp-ui-mode + :config + (lsp-ui-sideline-mode 1) + (setq lsp-ui-sideline-show-diagnostics t + lsp-ui-sideline-show-code-actions nil + lsp-ui-sideline-show-symbol nil + lsp-ui-sideline-show-hover nil) + (lsp-ui-doc-mode 1)) + +(use-package company-lsp :commands company-lsp) +(use-package lsp-treemacs :commands lsp-treemacs-errors-list) +(use-package lsp-ivy :commands lsp-ivy-workspace-symbol) + +;; Autoformatting +(use-package prettier-js) + +;; HTTP requests +(use-package restclient :mode ("\\.rest$" "\\.restclient$")) +;; HTTP requests in Org files +(use-package ob-restclient + :after (org) + :config + (org-babel-do-load-languages 'org-babel-load-languages '((restclient . t)))) + +;; Handle .direnv as shell file +(add-to-list 'auto-mode-alist '("\\.envrc$" . sh-mode)) + + +;;;; Load features + +(dolist (feature iensu-enabled-features-alist) + (load-file (expand-file-name (concat "features/" feature ".el") + user-emacs-directory))) + +;; Load additional local feature configurations +(let ((feature-conf (expand-file-name "local-feature-settings.el"))) + (when (file-exists-p feature-conf) + (load-file feature-conf))) + + +;;;; Emacs server + +;; The Emacs server keeps the Emacs instance running in the background so opening files in Emacs +;; will be snappy. +(unless (server-running-p) + (server-start)) diff --git a/new-config.el b/new-config.el deleted file mode 100644 index 1c133f3..0000000 --- a/new-config.el +++ /dev/null @@ -1,968 +0,0 @@ -;;; Code: - -;;;; Package installation and management - -;; This section initializes `package' with a set of archives and installs `use-package' which is -;; used to install other package dependencies. - -(require 'package) - -(setq package-archives - '(("gnu" . "https://elpa.gnu.org/packages/") - ("melpa" . "https://melpa.org/packages/"))) - -(unless (package-installed-p 'use-package) ; install use-package if it's not already installed - (package-refresh-contents) - (package-install 'use-package)) - -(eval-when-compile - (require 'use-package) - (setq use-package-always-ensure t)) - - -;;;; System local configuration - -;; This section handles configurations that are local to the system and thus should not be under -;; version control, for instance credentials and system specific feature flags. - -;; System local variables which can be set in local-settings.el -(defvar iensu-org-dir nil - "Directory containing Org files.") - -(defvar iensu-org-refile-targets nil - "Org files which can be used as refiling targets.") - -(defvar iensu-org-agenda-files nil - "Org files which should be used by org-agenda to generate TODO lists etc.") - -(defvar iensu-gcal-client-id nil - "Client ID for Gmail integration.") - -(defvar iensu-gcal-client-secret nil - "Client secret for Gmail integration.") - -(defvar iensu-org-capture-templates nil - "Capture templates to be used by Org mode.") - - -;; Load settings -(load-file (expand-file-name "local-settings.el" - user-emacs-directory)) - - -;;;; Helper functions - -(defun iensu-add-to-list (list items) - "Add multiple items to a list." - (dolist (item items) - (add-to-list list item))) - - -;;;; Basic setup - -;; This contains some basic default configuration which does not depend on any external packages. - -;; Cleanup the UI by removing the menu tool and scroll bars. -(mapc (lambda (mode) - (when (fboundp mode) - (funcall mode -1))) - '(menu-bar-mode tool-bar-mode scroll-bar-mode)) - -;; Remove the warning bell. -(setq ring-bell-function 'ignore) - -;; Remove the GNU/Emacs startup screen to boot direct to the Scratch buffer. -(setq inhibit-startup-screen t) - -;; Make sure that buffer names become unique when opening multiple files of the same name. -(setq-default frame-title-format "%b (%f)" - uniquify-buffer-name-style 'post-forward - uniquify-separator ":") - -;; Ask before killing Emacs. -(setq confirm-kill-emacs 'y-or-n-p) - -;; Put any Emacs generated customizations into ./custom.el instead of ./init.el. -(setq custom-file (expand-file-name "custom.el" - user-emacs-directory)) - -;; Improve performance by increasing the garbage collector threshold and max LISP evaluation depth. -(setq gc-cons-threshold 100000000 - max-lisp-eval-depth 2000) - -;; Move backups and auto-saves to ~/.emacs.d/.local/.saves. -(setq create-lockfiles nil - backup-directory-alist `(("." . ,(expand-file-name ".local/.saves/" user-emacs-directory))) - backup-by-copying t - delete-old-versions t - kept-new-versions 6) - -;; Remember recent files -(use-package recentf - :ensure nil - :custom - (recentf-max-menu-items 50) - :config - (recentf-load-list) - :init - (recentf-mode 1) - (setq recentf-save-file (expand-file-name ".local/recentf" user-emacs-directory))) - -;; Keep some files in ~/.emacs.d/.local to avoid cluttering the configuration root directory. -(setq url-configuration-directory (expand-file-name ".local/uri/" user-emacs-directory) - image-dired-dir (expand-file-name ".local/image-dired-thumbnails/" user-emacs-directory) - bookmark-default-file (expand-file-name ".local/bookmarks" user-emacs-directory) - tramp-auto-save-directory (expand-file-name ".local/tramp-autosaves/" user-emacs-directory)) - -;; Setup authentication file. -(setq auth-sources '("~/.authinfo.gpg" - "~/.netrc")) - -;; Auto scroll through output in compilation buffers. -(setq compilation-scroll-output t) - - -;;;; Editor default settings - -;; This section changes the default text editing behavior of Emacs so it is more inline with my -;; expectations. - -;; Use spaces instead of tabs. -(setq-default indent-tabs-mode nil - tab-width 2) - -(setq-default require-final-newline t) ; Files should always have a final newline. - -(setq-default sentence-end-double-space nil) ; Sentence end does not require two spaces. - -;; Fix buffer scrolling behavior. -(setq-default scroll-conservatively 0 - scroll-step 4 - next-screen-context-lines 20) - -(add-hook 'before-save-hook 'delete-trailing-whitespace) ; Delete trailing whitespace on save. - -(delete-selection-mode 1) ; Replace selected text with typed text. - -;; Enable narrowing to region and page. Pages are delimited by ^L. -(put 'narrow-to-page 'disabled nil) -(put 'narrow-to-region 'disabled nil) - -;; Use the editorconfig package to conform to project formatting rules if present. -(use-package editorconfig - :hook - (prog-mode . editorconfig-mode) - (text-mode . editorconfig-mode)) - -;; Enable multiple cursors for convenient editing. Use `iedit' for quick and dirty multi-cursor -;; functionality. -(use-package iedit) -(use-package multiple-cursors - :bind - (("M-=" . mc/edit-lines) - ("C-S-" . mc/mark-next-like-this) - ("C-S-" . mc/mark-previous-like-this) - ("C-S-" . mc/add-cursor-on-click)) - :custom - (mc/list-file (expand-file-name ".local/.mc-lists.el" user-emacs-directory))) - -;; Expand region from current region or point. -(use-package expand-region - :bind - (("C-=" . er/expand-region) - ("C-M-=" . er/contract-region))) - -;;;;; Programming mode related settings - -(defun iensu--prog-mode-hook () - "Defaults for programming modes" - (subword-mode 1) ; delimit words at camelCase boundries - (eldoc-mode 1) ; display documentation in minibuffer - (show-paren-mode 1) ; highlight matching parentheses - (setq-default show-paren-when-point-in-periphery t - show-paren-when-point-inside-paren t) - (hs-minor-mode 1) ; hide-show code and comment blocks - (outline-minor-mode 1)) ; Navigate by outlines - -(add-hook 'prog-mode-hook #'iensu--prog-mode-hook) - -;; Manipulate parentheses and other code structures. -(use-package smartparens - :init - (require 'smartparens-config) - :bind (:map smartparens-mode-map - ("M-s" . sp-unwrap-sexp) - ("C-" . sp-down-sexp) - ("C-" . sp-up-sexp) - ("M-" . sp-backward-down-sexp) - ("M-" . sp-backward-up-sexp) - ("C-" . sp-forward-slurp-sexp) - ("M-" . sp-forward-barf-sexp) - ("C-" . sp-backward-slurp-sexp) - ("M-" . sp-backward-barf-sexp)) - :hook ((prog-mode . smartparens-mode) ; non-strict by default, but keep strict in LISPs - (repl-mode . smartparens-strict-mode) - (lisp-mode . smartparens-strict-mode) - (emacs-lisp-mode . smartparens-strict-mode))) - -;;;;; Text editing tools - -;; Spellcheck using flyspell -(use-package flyspell - :ensure nil - :bind (:map flyspell-mode-map ("C-:" . flyspell-popup-correct)) - :custom - (ispell-extra-args '("--sug-mode=ultra")) - (ispell-list-command "--list") - (ispell-dictionary "en_US") - :config - (defvar iensu--language-ring nil - "Ispell language ring used to toggle current selected ispell dictionary") - - (let ((languages '("swedish" "en_US"))) - (setq iensu--language-ring (make-ring (length languages))) - (dolist (elem languages) (ring-insert iensu--language-ring elem))) - - (defun iensu/cycle-ispell-dictionary () - "Cycle through the languages defined in `iensu--language-ring'." - (interactive) - (let ((language (ring-ref iensu--language-ring -1))) - (ring-insert iensu--language-ring language) - (ispell-change-dictionary language) - (message (format "Switched to dictionary: %s" language))))) -(use-package flyspell-popup :after (flyspell)) - -;; Use synosaurus to look up synonyms -(use-package synosaurus - :custom - (synosaurus-backend 'synosaurus-backend-wordnet) - (synosaurus-choose-method 'popup)) - -;; Emoji support because reasons... -(use-package emojify - :custom - (emojify-emojis-dir (expand-file-name ".local/emojis" user-emacs-directory))) - -;; `visual-fill-column' makes it possible to visually wrap and center text which is good for -;; document-like editing. -(use-package visual-fill-column - :config - (setq-default visual-fill-column-width 100) - (setq-default visual-fill-column-center-text t)) - -(defun iensu/text-editing-mode-hook () - "Enables text editing tools such as spell checking and thesaurus support" - (interactive) - (flyspell-mode 1) - (synosaurus-mode 1) - (emojify-mode 1) - (visual-line-mode 1) - (visual-fill-column-mode 1)) - -(add-hook 'text-mode-hook #'iensu/configure-text-editing-tools) - - -;;;; Utility packages - -;; Install `hydra' with `pretty-hydra' which simplifies hydra definitions -(use-package hydra) -(use-package pretty-hydra :after (hydra)) - -;; Make system path variables accessible in Emacs -(use-package exec-path-from-shell - :custom - (exec-path-from-shell-check-startup-files nil) - :init - (exec-path-from-shell-initialize)) - -;; Password entry in minibuffer -(use-package pinentry - :init - (setq epa-pinentry-mode 'loopback) - (pinentry-start)) - -;; Improved file browsing -(use-package dired+ - :load-path (lambda () (expand-file-name "packages" user-emacs-directory)) - :custom - (dired-listing-switches "-alGh --group-directories-first") - (dired-dwim-target t) - :config - (when (executable-find "gls") ;; native OSX ls works differently then GNU ls - (setq insert-directory-program "/usr/local/bin/gls"))) - -;;;; Navigation - -;; This section adds packages which enables quick navigation and search. - -(use-package deadgrep) - -;; `counsel', `ivy' and `swiper' constitute a very useful completion framework. -(use-package counsel - :delight ivy-mode - :init - (ivy-mode 1) - :bind (("M-x" . counsel-M-x) - ("C-x C-f" . counsel-find-file) - ("C-x C-r" . counsel-recentf) - ("C-c k" . counsel-ag) - ("C-x b" . ivy-switch-buffer) - ("M-y" . counsel-yank-pop) - ("C-s" . swiper-isearch) - :map ivy-minibuffer-map - ("M-y" . ivy-next-line)) - :custom - (ivy-use-virtual-buffers t) - (ivy-use-selectable-prompt t) - (ivy-count-format "(%d/%d) ") - (ivy-magic-slash-non-match-action 'ivy-magic-non-match-create) - (counsel-ag-base-command "ag --nocolor --nogroup --hidden %s") - (ivy-display-style 'fancy) - (ivy-re-builders-alist '((swiper . ivy--regex-plus) - (swiper-isearch . ivy--regex-plus) - (counsel-find-file . ivy--regex-plus) - (counsel-projectile-find-file . ivy--regex-plus) - (t . ivy--regex-plus)))) - -(use-package ivy-rich :config (ivy-rich-mode 1)) ; Add documentation to ivy results - -;; `prescient' ranks search candidates by most recent. -(use-package prescient :config (prescient-persist-mode 1)) -(use-package ivy-prescient - :config - (ivy-prescient-mode 1) - (setq ivy-prescient-enable-sorting t) - (setq ivy-prescient-enable-filtering t)) -(use-package company-prescient - :config - (company-prescient-mode 1)) - -;; Snippet expansion for less repetitive text editing -(use-package yasnippet - :delight yas-minor-mode - :init - (yas-global-mode 1) - (setq yas-snippet-dirs (add-to-list 'yas-snippet-dirs (expand-file-name "snippets" user-emacs-directory))) - :config - (add-hook 'snippet-mode-hook (lambda () - (setq mode-require-final-newline nil - require-final-newline nil)))) - -;; When all else fails -(use-package dumb-jump - :config - (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)) - - -;;;; Custom commands - -(defun iensu/toggle-scratch-buffer () - "Based on a great idea from Eric Skoglund (https://github.com/EricIO/emacs-configuration/)." - (interactive) - (if (string-equal (buffer-name (current-buffer)) - "*scratch*") - (switch-to-buffer (other-buffer)) - (switch-to-buffer "*scratch*"))) - -(defun iensu/toggle-profiler () - "Starts or stops the profiler, displaying the report when stopped." - (interactive) - (if (profiler-running-p) - (progn - (profiler-stop) - (profiler-report)) - (progn - (profiler-reset) - (profiler-start 'cpu+mem)))) - -(defun iensu/duplicate-line (n) - "Copy the current line N times and insert it below." - (interactive "P") - (let ((cur-pos (point))) - (dotimes (i (prefix-numeric-value n)) - (move-beginning-of-line nil) - (kill-line) - (yank) - (newline) - (insert (string-trim-right (car kill-ring))) - (goto-char cur-pos)))) - -(defun iensu/move-file (new-location) - "Write this file to NEW-LOCATION, and delete the old one. Copied from http://zck.me/emacs-move-file." - (interactive (list (if buffer-file-name - (read-file-name "Move file to: ") - (read-file-name "Move file to: " - default-directory - (expand-file-name (file-name-nondirectory (buffer-name)) - default-directory))))) - (when (file-exists-p new-location) - (delete-file new-location)) - (let ((old-location (buffer-file-name))) - (write-file new-location t) - (when (and old-location - (file-exists-p new-location) - (not (string-equal old-location new-location))) - (delete-file old-location)))) - -(defun iensu/finish-item () - "Sets a `Finished' property on an org-mode item. The value is the current time as an inactive timestamp." - (interactive) - (org-set-property "Finished" (iensu--get-current-inactive-timestamp))) - - -;;;; Global keybindings - -(global-set-key (kbd "C-") 'delete-indentation) -(global-set-key (kbd "C-h C-s") 'iensu/toggle-scratch-buffer) -(global-set-key (kbd "C-x C-b") 'ibuffer) -(global-set-key (kbd "M-") 'fixup-whitespace) -(global-set-key (kbd "M-i") 'imenu) -(global-set-key (kbd "M-o") 'occur) - -;; `windmove' enables navigation using `-'. These bindings conflict `org-mode' so -;; we make `windmove' take precedence. -(windmove-default-keybindings) -(setq org-replace-disputed-keys t) ; This line needs to occur before `org-mode' is loaded. - -;;;;; Global hydra - -;; Setup a global hydra with keybindings I use very often. -(pretty-hydra-define iensu-hydra - (:color teal :quit-key "q" :title "Global commands") - ("Org clock" - (("c c" org-clock-in "start clock") - ("c r" org-clock-in-last "resume clock") - ("c s" org-clock-out "stop clock") - ("c g" org-clock-goto "goto clocked task")) - "Utilities" - (("d" iensu/duplicate-line "duplicate line" :exit nil) - ("s" deadgrep "search") - ("t" toggle-truncate-lines "truncate lines") - ("u" revert-buffer "reload buffer") - ("l" iensu/cycle-ispell-dictionary "change dictionary")) - "Misc" - (("P" iensu/project-todo-list "project todo list") - ("i" iensu/open-init-file "open emacs config") - ("+" enlarge-window-horizontally "enlarge window" :exit nil) - ("-" shrink-window-horizontally "shrink window" :exit nil)) - "Hide/show" - (("h h" hs-toggle-hiding "Toggle block visibility") - ("h l" hs-hide-level "Hide all blocks at same level") - ("h a" hs-hide-all "Hide all") - ("h s" hs-show-all "Show all")))) - -;; Enhance explorability with by listing possible completions while doing key chords. -(use-package which-key :config (which-key-mode)) - -;;;;; macOS specific keybindings - -;; Set command to act as `meta' (`M-') and disable the `option' key since that button is needed to -;; type various characters on a Swedish keyboard. Also make the right `option' key act as `hyper' -;; (`H-') to give us more keybindings to work with. -(setq mac-command-modifier 'meta - mac-option-modifier 'none - mac-right-option-modifier 'hyper) - -;; An unfortunate workaround required when switching to an external keyboard. -(defun iensu/switch-left-and-right-option-keys () - "Switch left and right option keys. - - On some external keyboards the left and right Mac `option' keys are swapped, - this command switches the keys so that they work as expected." - (interactive) - (let ((current-left mac-option-modifier) - (current-right mac-right-option-modifier)) - (setq mac-option-modifier current-right - mac-right-option-modifier current-left))) - - -;;;; Make Emacs prettier - -(setq-default cursor-type '(bar . 2)) -(global-prettify-symbols-mode 1) -(global-font-lock-mode 1) - -;; Use dark mode on macOS. -(add-to-list 'default-frame-alist '(ns-transparent-titlebar . t)) -(add-to-list 'default-frame-alist '(ns-appearence . dark)) - -;; Use different colored parentheses based on scope. -(use-package rainbow-delimiters - :hook (prog-mode . #'rainbow-delimiters-mode)) - -;; Use icons where applicable. -(use-package all-the-icons) - -;; Use a clean mode line -(use-package doom-modeline - :init (doom-modeline-mode 1)) - -;; Set theme -(use-package modus-vivendi-theme - :config - (load-theme 'modus-vivendi t) - (set-face-attribute 'font-lock-comment-face nil :slant 'italic) - (set-face-attribute 'font-lock-comment-delimiter-face nil :slant 'italic) - (set-face-attribute 'default nil :font "Fira Code-13") - (set-face-attribute 'fixed-pitch nil :font "Fira Code-13") - (set-face-attribute 'variable-pitch nil :font "Cantarell-14")) - - -;;;; Version control - -;; Make `magit' and other version control tools follow symlinks. -(setq vc-follow-symlinks t) - -;; Use `magit' for a great `git' experience. -(use-package magit - :bind (("C-x g" . magit-status)) - :custom - (magit-bury-buffer-function 'quit-window) - :config - (when (executable-find "/usr/bin/git") ; Speeds up git operations on macOS - (setq magit-git-executable "/usr/bin/git")) - (setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)) - -;; `smerge-mode' is a merge conflict resolution tool which is great but unfortunately has awful -;; default keybindings. Here I define a hydra to make `smerge' easier to work with. -(use-package smerge-mode - :ensure nil - :bind (:map smerge-mode-map (("C-c ö" . smerge-mode-hydra/body))) - :pretty-hydra - ((:color teal :quit-key "q" :title "Smerge - Git conflicts") - ("Resolving" - (("RET" smerge-keep-current "Keep current" :exit nil) - ("l" smerge-keep-lower "Keep lower" :exit nil) - ("u" smerge-keep-upper "Keep upper" :exit nil) - ("b" smerge-keep-base "Keep base" :exit nil) - ("C" smerge-combine-with-next "Combine with next") - ("a" smerge-keep-all "Keep all" :exit nil) - ("r" smerge-resolve "Resolve")) - "Navigation" - (("n" smerge-next "Next conflict" :exit nil) - ("p" smerge-prev "Previous conflict" :exit nil) - ("R" smerge-refine "Highlight differences" :exit nil)) - "Misc" - (("E" smerge-ediff "Open in Ediff"))))) - - -;;;; Org mode -(defun iensu--get-current-inactive-timestamp () - (concat "[" (format-time-string "%F %a %H:%M") "]")) - -(use-package org - :bind (("C-c c" . org-capture) - ("C-c a" . org-agenda) - ("C-c l" . org-store-link) - :map org-mode-map - ("H-." . org-time-stamp-inactive)) - :hook - (org-mode . (lambda () - (org-num-mode 1) - (visual-line-mode 1) - (variable-pitch-mode 1))) - :config - (setq org-directory iensu-org-dir) - (setq org-default-notes-file (expand-file-name "notes.org" org-directory)) - (setq org-refile-targets '((iensu-org-refile-targets :maxlevel . 10))) - (setq org-refile-allow-creating-parent-nodes 'confirm) - (setq org-refile-use-outline-path 'file) - (setq org-latex-listings t) - (setq org-cycle-separator-lines 1) - (setq org-src-fontify-natively t) - (setq org-format-latex-options (plist-put org-format-latex-options :scale 1.5)) - (setq truncate-lines t) - (setq org-image-actual-width nil) - (setq line-spacing 1) - (setq outline-blank-line t) - (setq org-adapt-indentation nil) - (setq org-fontify-quote-and-verse-blocks t) - (setq org-fontify-done-headline t) - (setq org-fontify-whole-heading-line t) - (setq org-hide-leading-stars t) - (setq org-indent-indentation-per-level 2) - (setq org-checkbox-hierarchical-statistics nil) - (setq org-log-done 'time) - (setq org-outline-path-complete-in-steps nil) - (setq org-html-htmlize-output-type 'css) - (setq org-export-initial-scope 'subtree) - (setq org-catch-invisible-edits 'show-and-error) - (setq org-archive-location "archive/%s_archive::") - (setq org-capture-templates iensu-org-capture-templates) - - (setq org-clock-in-switch-to-state "DOING") - (setq org-log-into-drawer t) - (setq org-modules '(org-protocol)) - - (org-load-modules-maybe t) - (dolist (lang-mode '(("javascript" . js2) ("es" . es) ("wat" . wat)) - (add-to-list 'org-src-lang-modes lang-mode))) - - (org-babel-do-load-languages - 'org-babel-load-languages '((emacs-lisp . t) - (shell . t) - (js . t) - (python . t) - (dot . t))) - - (let ((additional-org-templates (if (version< (org-version) "9.2") - '(("ssh" "#+begin_src shell \n?\") - ("sel" " \n?\")) - '(("ssh" . "src shell") - ("sel" . "src emacs-lisp") - ("sr" . "src restclient") - ("sR" . "src rust"))))) - (dolist (template additional-org-templates) - (add-to-list 'org-structure-template-alist template))) - - ;; standard markdown - (require 'ox-md) - - ;; Github-flavoured markdown - (use-package ox-gfm - :init - (eval-after-load "org" - '(require 'ox-gfm nil t)))) - -(add-to-list 'iensu-org-capture-templates - `("t" "TODO with link" entry (file ,(expand-file-name "refile.org" iensu-org-dir)) - ,(concat "* TODO %?\n" - "%U\n" - "%a") - :empty-lines 1)) - (add-to-list 'iensu-org-capture-templates - `("T" "TODO" entry (file ,(expand-file-name "refile.org" iensu-org-dir)) - ,(concat "* TODO %?\n" - "%U") - :empty-lines 1)) - (add-to-list 'iensu-org-capture-templates - `("j" "Journal" entry (file+datetree ,(expand-file-name "journal.org.gpg" iensu-org-dir)) - ,(concat "* %^{Titel}\n" - "%U, %^{Location|Stockholm, Sverige}\n\n" - "%?") - :empty-lines 1)) - (add-to-list 'iensu-org-capture-templates - `("l" "Link" entry (file ,(expand-file-name "refile.org" iensu-org-dir)) - ,(concat "* %? %^L %^G \n" - "%U") - :prepend t)) - (add-to-list 'iensu-org-capture-templates - `("L" "Browser Link" entry (file ,(expand-file-name "refile.org" iensu-org-dir)) - ,(concat "* TODO %:description\n" - "%U\n\n" - "%:link") - :prepend t :immediate-finish t :empty-lines 1)) - (add-to-list 'iensu-org-capture-templates - `("p" "Browser Link and Selection" entry (file ,(expand-file-name "refile.org" iensu-org-dir)) - ,(concat "* TODO %^{Title}\n" - "Source: %u, %c\n\n" - "#+BEGIN_QUOTE\n" - "%i\n" - "#+END_QUOTE\n\n\n%?") - :prepend t :empty-lines 1)) - (add-to-list 'iensu-org-capture-templates - `("b" "Book" entry (file+headline ,(expand-file-name "private.org" iensu-org-dir) "Reading list") - ,(concat "* %^{Title}" - " %^{Author}p" - " %^{Genre}p" - " %^{Published}p" - " %(org-set-property \"Added\" (iensu--get-current-inactive-timestamp))") - :prepend t - :empty-lines 1)) - -(setq calendar-week-start-day 1) ; The week starts on Monday. - -;; `org-protocol' enables capturing from outside of Emacs. -;;(eval-after-load "org" -;; (require 'org-protocol)) - -(defadvice org-capture-finalize - (after delete-capture-frame activate) - "Advise capture-finalize to close the frame" - (if (equal "capture" (frame-parameter nil 'name)) - (delete-frame))) - -(defadvice org-capture-destroy - (after delete-capture-frame activate) - "Advise capture-destroy to close the frame" - (if (equal "capture" (frame-parameter nil 'name)) - (delete-frame))) - -;; Add support for YAML files -(defun org-babel-execute:yaml (body params) body) - -(setq org-todo-keywords - '((sequence "TODO(t)" "DOING(d!)" "BLOCKED(b@/!)" - "|" - "CANCELED(C@/!)" "POSTPONED(P@/!)" "DONE(D@/!)"))) - -(setq org-todo-keyword-faces - '(("BLOCKED" . (:foreground "#dd0066" :weight bold)) - ("CANCELED" . (:foreground "#6272a4")) - ("POSTPONED" . (:foreground "#3388ff")))) - -;; Customize PRIORITIES -(setq org-highest-priority ?A - org-default-priority ?D - org-lowest-priority ?E) - -;;;;; Make org-mode prettier - -;; Make view more compact -(setq org-cycle-separator-lines 0) - -;; Only display one bullet per headline for a cleaner look. -(use-package org-superstar - :after (org) - :init - (add-hook 'org-mode-hook (lambda () (org-superstar-mode 1))) - :config - (setq org-superstar-headline-bullets-list '(?◉))) - -;;;;; Autosaving org buffers -(defun iensu/org-save-buffers () - "Saves all org buffers." - (interactive) - (save-some-buffers 'no-confirm - (lambda () - (string-match-p - (expand-file-name org-directory) - (buffer-file-name (current-buffer)))))) - -(defvar iensu--timer:org-save-buffers nil - "Org save buffers timer object. Can be used to cancel the timer.") - -(setq iensu--timer:org-save-buffers - (run-at-time t (* 5 60) #'iensu/org-save-buffers)) - - -;;;; Project management - -;; Settings which help with handling and navigating projects. - -(use-package projectile - :bind - (("C-c p" . projectile-hydra/body)) - :custom - (projectile-completion-system 'ivy) - (projectile-cache-file (expand-file-name ".local/projectile.cache" user-emacs-directory)) - (projectile-known-projects-file (expand-file-name ".local/projectile-bookmarks.eld" user-emacs-directory)) - (projectile-git-submodule-command nil) - (projectile-sort-order 'access-time) - (projectile-globally-ignored-files '("TAGS" ".DS_Store" ".projectile")) - :pretty-hydra - ((:color teal :quit-key "q" :title "Project") - ("Project" - (("p" counsel-projectile-switch-project "open project") - ("k" projectile-kill-buffers "close project") - ("t" projectile-test-project "test project" :exit t) - ("c" projectile-compile-project "compile project" :exit t)) - "Files & Buffers" - (("f" counsel-projectile-find-file "open project file") - ("o" iensu/open-project-org-file "open project org file") - ("T" iensu/project-todo-list "open project TODO list") - ("b" counsel-projectile-switch-to-buffer "open project buffer") - ("S" projectile-save-buffers "save project buffers")) - "Search" - (("s" projectile-ripgrep "search") - ("r" projectile-replace "replace literal") - ("R" projectile-replace-regexp "replace regex")))) - :config - (projectile-global-mode) - (projectile-register-project-type 'node-npm '("package.json") - :compile "npm run build" - :test "npm test") - (projectile-register-project-type 'rust-cargo '("cargo.toml") - :compile "cargo check" - :test "cargo test" - :run "cargo run") - (projectile-register-project-type 'java-maven '("pom.xml") - :compile "mvn compile" - :test "mvn test")) - -(use-package counsel-projectile :init (counsel-projectile-mode 1)) - -(use-package ibuffer-projectile :after (projectile) - :hook - (ibuffer-mode . (lambda () - (ibuffer-projectile-set-filter-groups) - (unless (eq ibuffer-sorting-mode 'alphabetic) - (ibuffer-do-sort-by-alphabetic))))) - -;; `treemacs' for a visual project tree structure. -(use-package treemacs - :defer t - :bind - (:map global-map - ("M-0" . treemacs-select-window) - ("C-x t 1" . treemacs-delete-other-windows) - ("C-x t t" . treemacs) - ("C-x t B" . treemacs-bookmark) - ("C-x t C-t" . treemacs-find-file) - ("C-x t M-t" . treemacs-find-tag) - ("C-x t w" . treemacs-switch-workspace))) -(use-package treemacs-magit :after treemacs magit) -(use-package treemacs-projectile :after treemacs projectile) - -;;;;; Project-based TODO lists - -;; Create a TODO list based on TODO items in a project's `.project-notes.org' file. The -;; `org-agenda-files' variable is temporarily set the only the project notes file and then reverted -;; back to its previous value upon closing the TODO list buffer. - -(defvar iensu--project-agenda-buffer-name "*Project Agenda*") - -(defun iensu--org-capture-project-notes-file () - (concat (projectile-project-root) ".project-notes.org")) - -(defun iensu/project-todo-list () - (interactive) - (let ((project-notes-file (expand-file-name ".project-notes.org" - (projectile-project-root)))) - (if (file-exists-p project-notes-file) - (progn - (setq org-agenda-files `(,project-notes-file)) - (org-todo-list) - (rename-buffer iensu--project-agenda-buffer-name 'unique)) - (message "Could not locate any project notes file")))) - -(defun iensu/reset-org-agenda-files () - (interactive) - (when (string-equal iensu--project-agenda-buffer-name - (buffer-name (current-buffer))) - (setq org-agenda-files iensu-org-agenda-files))) - -;; Reset org-agenda-files when the project TODO list buffer is closed -(add-hook 'kill-buffer-hook #'iensu/reset-org-agenda-files) - -;; Add some org-capture templates for project notes. -(eval-after-load 'org - (progn - (add-to-list 'iensu-org-capture-templates - `("m" "Project note" entry (file+headline iensu--org-capture-project-notes-file "Notes") - ,(concat "* %^{Title}\n" - "%U\n\n" - "%?") - :empty-lines 1)) - (add-to-list 'iensu-org-capture-templates - `("n" "Project note with link" entry (file+headline iensu--org-capture-project-notes-file "Notes") - ,(concat "* %^{Title}\n" - "%U\n\n" - "Link: %a\n\n" - "%?") - :empty-lines 1)) - (add-to-list 'iensu-org-capture-templates - `("N" "Project note with link + code quote" entry (file+headline iensu--org-capture-project-notes-file "Notes") - ,(concat "* %^{Title}\n" - "%U\n\n" - "Link: %a\n\n" - "#+begin_src %^{Language}\n" - "%i\n" - "\n\n" - "%?") - :empty-lines 1)))) - - -;;;; IDE features - -;; Highlight TODOs in programming buffers -(use-package hl-todo :hook ((prog-mode . hl-todo-mode))) - -;;;;; Autocompletion and intellisense - -;; Company as a completion frontend -(use-package company - :init (global-company-mode) - :config - (setq company-idle-delay 0.3) - (setq company-minimum-prefix-length 2) - (setq company-selection-wrap-around t) - (setq company-auto-complete t) - (setq company-tooltip-align-annotations t) - (setq company-dabbrev-downcase nil) - (setq company-auto-complete-chars nil) - (add-hook 'emacs-lisp-mode-hook - (lambda () - (add-to-list 'company-backends 'company-elisp))) - (eval-after-load 'company (company-quickhelp-mode 1))) -(use-package company-quickhelp - :bind (:map company-active-map - ("M-h" . company-quickhelp-manual-begin)) - :config - (setq company-quickhelp-delay 1)) - -;; Flycheck for on the fly error reporting -(use-package flycheck - :init - (global-flycheck-mode t) - :config - (setq-default flycheck-disabled-checkers '(emacs-lisp-checkdoc))) -(use-package flycheck-popup-tip) - -;; LSP for intellisense -(use-package lsp-mode - :commands (lsp lsp-deferred) - :bind (:map lsp-mode-map - ("C-c l" . lsp-mode-hydra/body)) - :init - (setq lsp-keymap-prefix "C-ä") - :hook - (lsp-mode . lsp-enable-which-key-integration) - :config - (setq lsp-auto-guess-root nil) - - :pretty-hydra - ((:title "LSP" :quit-key "q" :color teal) - ("Exploration" - (("l" lsp-find-references "list references") - ("s" lsp-ivy-workspace-symbol "search symbol in workspace") - ("d" lsp-describe-thing-at-point "describe") - ("e" flycheck-list-errors "list buffer errors") - ("å" flycheck-previous-error "goto previous error in buffer") - ("ä" flycheck-next-error "goto next error in buffer ") - ("E" lsp-treemacs-errors-list "list workspace errors") - ("T" lsp-goto-type-definition "find type definition")) - "Refactoring" - (("a" lsp-execute-code-action "execute code action") - ("n" lsp-rename "rename symbol") - ("i" lsp-organize-imports "organize imports") - ("f" lsp-format-buffer "format buffer")) - "Misc" - (("w" lsp-restart-workspace "restart LSP server"))))) - -;; `lsp-ui' enables in buffer documentation popups etc. -(use-package lsp-ui - :commands lsp-ui-mode - :config - (lsp-ui-sideline-mode 1) - (setq lsp-ui-sideline-show-diagnostics t - lsp-ui-sideline-show-code-actions nil - lsp-ui-sideline-show-symbol nil - lsp-ui-sideline-show-hover nil) - (lsp-ui-doc-mode 1)) - -(use-package company-lsp :commands company-lsp) -(use-package lsp-treemacs :commands lsp-treemacs-errors-list) -(use-package lsp-ivy :commands lsp-ivy-workspace-symbol) - -;; Autoformatting -(use-package prettier-js) - -;; HTTP requests -(use-package restclient :mode ("\\.rest$" "\\.restclient$")) -;; HTTP requests in Org files -(use-package ob-restclient - :after (org) - :config - (org-babel-do-load-languages 'org-babel-load-languages '((restclient . t)))) - -;; Handle .direnv as shell file -(add-to-list 'auto-mode-alist '("\\.envrc$" . sh-mode)) - - -;;;; Emacs server - -;; The Emacs server keeps the Emacs instance running in the background so opening files in Emacs -;; will be snappy. -(unless (server-running-p) - (server-start)) From 6663014dcc05529eea912361b3e89fbf72333c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20=C3=96stlund?= Date: Thu, 26 Nov 2020 13:33:26 +0100 Subject: [PATCH 4/8] Fix local feature settings --- init.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.el b/init.el index 039a7f1..4c604fd 100644 --- a/init.el +++ b/init.el @@ -785,7 +785,7 @@ user-emacs-directory))) ;; Load additional local feature configurations -(let ((feature-conf (expand-file-name "local-feature-settings.el"))) +(let ((feature-conf (expand-file-name "local-feature-settings.el" user-emacs-directory))) (when (file-exists-p feature-conf) (load-file feature-conf))) From c8b79c4ecc145193726a91ce25e9da52950481c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20=C3=96stlund?= Date: Thu, 26 Nov 2020 13:33:39 +0100 Subject: [PATCH 5/8] Font sizes should be local --- init.el | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/init.el b/init.el index 4c604fd..8eba7b2 100644 --- a/init.el +++ b/init.el @@ -512,10 +512,7 @@ :config (load-theme 'modus-vivendi t) (set-face-attribute 'font-lock-comment-face nil :slant 'italic) - (set-face-attribute 'font-lock-comment-delimiter-face nil :slant 'italic) - (set-face-attribute 'default nil :font "Fira Code-13") - (set-face-attribute 'fixed-pitch nil :font "Fira Code-13") - (set-face-attribute 'variable-pitch nil :font "Cantarell-14")) + (set-face-attribute 'font-lock-comment-delimiter-face nil :slant 'italic)) ;;;; Version control From acafe095f7f48225f31f7f4e5e513c955537b9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20=C3=96stlund?= Date: Thu, 26 Nov 2020 13:33:54 +0100 Subject: [PATCH 6/8] Enable line numbers in programming modes --- init.el | 1 + 1 file changed, 1 insertion(+) diff --git a/init.el b/init.el index 8eba7b2..3ed128a 100644 --- a/init.el +++ b/init.el @@ -187,6 +187,7 @@ "Defaults for programming modes" (subword-mode 1) ; delimit words at camelCase boundries (eldoc-mode 1) ; display documentation in minibuffer + (display-line-numbers-mode 1) ;; display line numbers (show-paren-mode 1) ; highlight matching parentheses (setq-default show-paren-when-point-in-periphery t show-paren-when-point-inside-paren t) From 1f18b57106f73f772a5ce10955867abe9a424472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20=C3=96stlund?= Date: Thu, 26 Nov 2020 13:34:09 +0100 Subject: [PATCH 7/8] Setup auto saves and backups --- init.el | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/init.el b/init.el index 3ed128a..45750bb 100644 --- a/init.el +++ b/init.el @@ -96,12 +96,17 @@ (setq gc-cons-threshold 100000000 max-lisp-eval-depth 2000) -;; Move backups and auto-saves to ~/.emacs.d/.local/.saves. +;; Move backups and auto-saves (setq create-lockfiles nil - backup-directory-alist `(("." . ,(expand-file-name ".local/.saves/" user-emacs-directory))) + backup-directory-alist `(("." . ,(expand-file-name ".local/backups/" user-emacs-directory))) backup-by-copying t delete-old-versions t - kept-new-versions 6) + kept-new-versions 6 + + auto-save-list-file-name (expand-file-name ".local/auto-saves-list" user-emacs-directory)) + +;; Enable autosaves +(auto-save-mode 1) ;; Remember recent files (use-package recentf From 78c6ee8bee4152357581d3a955d24eecb02d543a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20=C3=96stlund?= Date: Fri, 27 Nov 2020 09:35:06 +0100 Subject: [PATCH 8/8] The emacs server is no longer needed since running as daemon --- init.el | 8 -------- 1 file changed, 8 deletions(-) diff --git a/init.el b/init.el index 45750bb..59c7b00 100644 --- a/init.el +++ b/init.el @@ -791,11 +791,3 @@ (let ((feature-conf (expand-file-name "local-feature-settings.el" user-emacs-directory))) (when (file-exists-p feature-conf) (load-file feature-conf))) - - -;;;; Emacs server - -;; The Emacs server keeps the Emacs instance running in the background so opening files in Emacs -;; will be snappy. -(unless (server-running-p) - (server-start))