#+TITLE: Emacs Literate Configuration #+AUTHOR: Daniel Kraus #+BABEL: :cache yes #+PROPERTY: header-args :tangle yes #+PROPERTY: header-args:emacs-lisp :comments link * Contents :PROPERTIES: :TOC: :include all :END: :CONTENTS: - [[init.org#contents][Contents]] - [[init.org#about][About]] - [[init.org#installation][Installation]] - [[init.org#external-packages][External packages]] - [[init.org#emacs-initialization][Emacs Initialization]] - [[init.org#set-some-early-ui-settings][Set some early UI settings]] - [[init.org#package-management][Package Management]] - [[init.org#org-config-tangle][Org config tangle]] - [[init.org#package-settings][Package Settings]] - [[init.org#borg][Borg]] - [[init.org#use-package][Use-Package]] - [[init.org#epkg][Epkg]] - [[init.org#dont-litter-configs][Don't litter configs]] - [[init.org#moe-theme][Moe theme:]] - [[init.org#personal-information][Personal Information]] - [[init.org#some-config-helper-functions][Some config helper functions]] - [[init.org#change-emacs-default-config][Change Emacs default config]] - [[init.org#ui][UI]] - [[init.org#configure-emacs-builtin-packages][Configure Emacs builtin packages]] - [[init.org#simple-change-some-default-keybinding-kill-this-buffer-updowncase-dwim][Simple: Change some default keybinding (kill-this-buffer, up/downcase-dwim)]] - [[init.org#abbrev][Abbrev]] - [[init.org#auto-revert-revert-buffer-when-file-changes-on-disk][Auto-revert: Revert buffer when file changes on disk]] - [[init.org#epa-the-easypg-assistant-transparent-file-encryption-gpg][Epa: the EasyPG Assistant, transparent file encryption (gpg)]] - [[init.org#saveplace-remember-your-location-in-a-file][Saveplace: Remember your location in a file]] - [[init.org#savehist-keep-track-of-minibuffer-history][Savehist: Keep track of minibuffer history]] - [[init.org#so-long-mitigating-slowness-due-to-extremely-long-lines][So-long: Mitigating slowness due to extremely long lines]] - [[init.org#ansi-color][Ansi-color]] - [[init.org#compile][Compile]] - [[init.org#comint][Comint]] - [[init.org#subword-camelcase-aware-editing-operations][Subword: CamelCase aware editing operations]] - [[init.org#shr-simple-html-renderer][Shr: Simple html renderer]] - [[init.org#info-view-info-pages][Info: View info pages]] - [[init.org#makefile][Makefile]] - [[init.org#goto-addr-buttonize-urls-and-e-mail-addresses-in-the-current-buffer][Goto-addr: Buttonize URLs and e-mail addresses in the current buffer]] - [[init.org#time][Time]] - [[init.org#calendar][Calendar]] - [[init.org#network-net-utils][Network net-utils]] - [[init.org#ui][UI]] - [[init.org#alert-growl-style-notification-system][Alert: Growl-style notification system]] - [[init.org#sauron-event-log-listen-to-d-bus-and-other-messages-and-show-them][Sauron: Event log (listen to d-bus and other messages and show them)]] - [[init.org#eldoc-display-help][Eldoc: Display help]] - [[init.org#dimmer-visually-highlight-the-selected-buffer][Dimmer: Visually highlight the selected buffer]] - [[init.org#hl-todo-highlight-and-navigate-todo-keywords][Hl-todo: Highlight and navigate TODO keywords]] - [[init.org#fill-column-indicator][Fill-column-indicator]] - [[init.org#volatile-highlights][Volatile highlights]] - [[init.org#beacon-highlight-current-linecursor-when-switching-frames][beacon: Highlight current line/cursor when switching frames]] - [[init.org#which-key-display-available-keybindings-in-popup][which-key: Display available keybindings in popup]] - [[init.org#which-func-show-the-name-of-the-current-function-definition-in-the-modeline][which-func: Show the name of the current function definition in the modeline]] - [[init.org#nicer-buffer-name-for-buffers-with-same-name][Nicer buffer name for buffers with same name]] - [[init.org#highlight-indentions][Highlight indentions]] - [[init.org#symbol-overlay][Symbol-overlay]] - [[init.org#emoji-font][Emoji font]] - [[init.org#automatically-remove-trailing-whitespace-only-if-i-put-them-there][Automatically remove trailing whitespace (only if I put them there)]] - [[init.org#highlight-long-lines][Highlight long lines]] - [[init.org#default-text-scale-zoom-font-size-for-all-buffers][default-text-scale: Zoom font size for all buffers]] - [[init.org#zone-emacs-screen-saver][Zone: Emacs screen saver]] - [[init.org#sticky-buffer-lock-a-buffer-to-a-window][Sticky-buffer: Lock a buffer to a window]] - [[init.org#editor][Editor]] - [[init.org#aggressive-indent][Aggressive-indent]] - [[init.org#lossage-live-update-lossage-buffer][Lossage: Live update lossage buffer]] - [[init.org#hippie-exp-expand-by-fuzzy-matching-text-in-open-buffers][hippie-exp: Expand by fuzzy matching text in open buffers]] - [[init.org#rainbow-delimiters-different-color-for-each-parenthesis-level][rainbow-delimiters: Different color for each parenthesis level]] - [[init.org#fancy-narrow-fancier-narrow][fancy-narrow: Fancier narrow]] - [[init.org#crux-various-small-useful-utility-functions][crux: Various small useful utility functions]] - [[init.org#smartparens][smartparens]] - [[init.org#editorconfig][Editorconfig]] - [[init.org#misc][Misc]] - [[init.org#smart-region-smart-region-selection][Smart-region: Smart region selection]] - [[init.org#selected-one-key-keybindings-for-regions-when-selection-active][Selected: One key keybindings for regions when selection active]] - [[init.org#multiple-cursors][Multiple-cursors]] - [[init.org#smartrep-repeat-previous-command-without-prefix-key][smartrep: Repeat previous command without prefix key]] - [[init.org#copy-as-format-copy-text-as-githubslackjirahipchat-formatted-code][copy-as-format: Copy text as GitHub/Slack/JIRA/HipChat/... formatted code]] - [[init.org#zop-to-char-remove-multiple-characters-at-once][zop-to-char: Remove multiple characters at once]] - [[init.org#cycle-outline-and-code-visibility][Cycle outline and code visibility]] - [[init.org#edit-indirect-edit-a-region-in-a-separate-buffer][edit-indirect: Edit a region in a separate buffer]] - [[init.org#with-editor-use-local-emacs-instance-as-editor-eg-in-git-commit-or-crontab--e][with-editor: Use local Emacs instance as $EDITOR (e.g. in `git commit' or `crontab -e')]] - [[init.org#move-text][Move text]] - [[init.org#grep-wgreprgag][Grep (wgrep/rg/ag)]] - [[init.org#grep-context-get-more-context-for-compilationgrep-buffers-by-pressing--][Grep-context: Get more context for compilation/grep buffers by pressing +/-]] - [[init.org#searchreplace][Search/Replace]] - [[init.org#re-builder][Re-builder]] - [[init.org#visual-regex][Visual-regex:]] - [[init.org#prescient][Prescient]] - [[init.org#deadgrep-interface-for-ripgrep][deadgrep: Interface for ripgrep]] - [[init.org#company-auto-completion][Company: Auto completion]] - [[init.org#discover-my-major-display-a-list-of-keybindings-for-the-current-major-mode][Discover-my-major: Display a list of keybindings for the current major mode]] - [[init.org#helpful-a-better-help-buffer][Helpful: A better help buffer]] - [[init.org#elisp-demos-inject-elisp-demos-into-help-buffer][Elisp-demos: Inject elisp demos into help buffer]] - [[init.org#undo][Undo]] - [[init.org#spell-checker][Spell checker]] - [[init.org#ispell][Ispell]] - [[init.org#flyspell][Flyspell]] - [[init.org#flyspell-correct-show-list-of-correct-spelling-suggestions][Flyspell-correct: Show list of correct spelling suggestions]] - [[init.org#language-tool-grammar-style-and-spell-checker][Language tool: Grammar, Style and Spell Checker]] - [[init.org#guess-language-automatically-guess-languages-and-switch-ispell][Guess-language: Automatically guess languages and switch ispell]] - [[init.org#project-management][Project Management]] - [[init.org#projectile][Projectile]] - [[init.org#treemacs-a-tree-layout-file-explorer][Treemacs: A tree layout file explorer]] - [[init.org#ivy][Ivy]] - [[init.org#flx-fuzzy-search][Flx: Fuzzy search]] - [[init.org#smex-improved-m-x][Smex: Improved M-x]] - [[init.org#ivy][Ivy]] - [[init.org#swiper-search-with-ivy][Swiper: Search with ivy]] - [[init.org#counsel][Counsel]] - [[init.org#ivy-posframe-display-ivy-in-child-frames-instead-of-minibuffer][ivy-posframe: Display ivy in child frames instead of minibuffer]] - [[init.org#navigation][Navigation]] - [[init.org#avy-quickly-jump-to-any-character-on-screen][Avy: Quickly jump to any character on screen]] - [[init.org#ace-link-quickly-jump-to-any-link-on-screen][Ace-link: Quickly jump to any link on screen]] - [[init.org#ace-window-selectmoveswap-windows][Ace-window: Select/move/swap windows]] - [[init.org#dumb-jump-jump-to-definition-with-smart-regex-searches][Dumb-jump: Jump to definition with smart regex searches]] - [[init.org#ibuffer][ibuffer]] - [[init.org#imenu][Imenu]] - [[init.org#xref][Xref]] - [[init.org#sort-packages][Sort packages]] - [[init.org#recentf][Recentf]] - [[init.org#view-large-files][View Large Files]] - [[init.org#pdf-tools][PDF Tools]] - [[init.org#atomic-chrome--ghosttext-edit-text-area-in-browser][atomic-chrome / GhostText: Edit text area in browser]] - [[init.org#misc][Misc]] - [[init.org#dired][Dired]] - [[init.org#dired-hacks][Dired-hacks]] - [[init.org#treemacs-icons-dired-treemacs-icons-for-dired][Treemacs-icons-dired: Treemacs icons for dired]] - [[init.org#helm][Helm]] - [[init.org#hydras][Hydras]] - [[init.org#tramp][Tramp]] - [[init.org#eshell][Eshell]] - [[init.org#prompt][Prompt]] - [[init.org#z-cd-to-frequent-directory][Z: cd to frequent directory]] - [[init.org#eshel-up-quickly-go-to-a-specific-parent-directory-in-eshell][Eshel-up: Quickly go to a specific parent directory in eshell]] - [[init.org#eshell-fringe-status-show-last-status-in-fringe][Eshell-fringe-status: Show last status in fringe]] - [[init.org#autocomplete][Autocomplete]] - [[init.org#vterm][vterm]] - [[init.org#version-control][Version Control]] - [[init.org#emacs-vc-settings][Emacs vc settings]] - [[init.org#diff-hl-show-git-status-in-fringe][Diff-hl: Show git status in fringe]] - [[init.org#gitpatch-easily-send-patches-from-diredmagitibuffer][Gitpatch: Easily send patches from dired/magit/ibuffer]] - [[init.org#magit][Magit]] - [[init.org#smerge][Smerge]] - [[init.org#forge-work-with-git-forges-from-the-comfort-of-magit][Forge: Work with Git forges from the comfort of Magit]] - [[init.org#annotate-annotation-any-file-and-export-as-commented-unified-diff-or-as-code-comment][Annotate: Annotation any file and export as commented unified diff or as code comment]] - [[init.org#browse-at-remote-open-website-githubgitlab-for-current-bufferlinelog][Browse-at-remote: Open website (github/gitlab) for current buffer/line/log]] - [[init.org#programming][Programming]] - [[init.org#general-setup][General setup]] - [[init.org#apheleia-auto-formatting][Apheleia: Auto formatting]] - [[init.org#flycheck][Flycheck]] - [[init.org#lsp-language-server-protocol][LSP: Language Server Protocol]] - [[init.org#java][Java]] - [[init.org#abap][ABAP]] - [[init.org#crontab][Crontab]] - [[init.org#cc][C/C++]] - [[init.org#glsl-openglsl-shader][GLSL: OpenGLSL shader]] - [[init.org#graphviz][Graphviz]] - [[init.org#plantuml][PlantUML]] - [[init.org#configs-yamltomliniconfetc][Configs (yaml/toml/ini/.conf/etc)]] - [[init.org#arch-pkgbuild][Arch PKGBUILD]] - [[init.org#po-edit-gnu-gettext-po-files][PO: Edit GNU gettext PO files]] - [[init.org#yaml][YAML]] - [[init.org#toml][TOML]] - [[init.org#csv][CSV]] - [[init.org#cds-core-data-services][CDS Core Data Services]] - [[init.org#systemd][Systemd]] - [[init.org#nginx][Nginx]] - [[init.org#apache][Apache]] - [[init.org#docker][Docker]] - [[init.org#debugging][Debugging]] - [[init.org#elixir][Elixir]] - [[init.org#fish][Fish]] - [[init.org#scala][Scala]] - [[init.org#go][Go]] - [[init.org#haskell][Haskell]] - [[init.org#javascript][Javascript]] - [[init.org#lisps][Lisps]] - [[init.org#elisp][Elisp]] - [[init.org#helper-libraries-dash-s-marshal][Helper libraries (dash, s, marshal)]] - [[init.org#request][Request]] - [[init.org#auto-compile][Auto-compile]] - [[init.org#litable-live-preview-for-elisp][Litable: Live preview for elisp]] - [[init.org#package-helpers][Package helpers]] - [[init.org#el2markdown-convert-package-commentary-to-markdown][El2markdown: Convert package commentary to markdown]] - [[init.org#common-lisp][Common Lisp]] - [[init.org#clojure][Clojure]] - [[init.org#cider][CIDER]] - [[init.org#clj-refactor][clj-refactor]] - [[init.org#ivy-clojuredocs][ivy-clojuredocs]] - [[init.org#clj-kondo-flycheck-integration-with-the-clj-kondo-linter][clj-kondo Flycheck integration with the clj-kondo linter]] - [[init.org#datomic-yasnippets][Datomic yasnippets]] - [[init.org#datomic-utility-functions][Datomic utility functions]] - [[init.org#hy][Hy]] - [[init.org#lua][Lua]] - [[init.org#markup-languages][Markup Languages]] - [[init.org#nim][Nim]] - [[init.org#octave][Octave]] - [[init.org#php][PHP]] - [[init.org#python][Python]] - [[init.org#lsp-with-the-microsoft-language-server][LSP with the Microsoft language server]] - [[init.org#cython][Cython]] - [[init.org#jupyter][Jupyter]] - [[init.org#main-python-setup][Main Python setup]] - [[init.org#anaconda-code-navigation-documentation-lookup-and-completion-for-python][Anaconda: Code navigation, documentation lookup and completion for Python]] - [[init.org#pippel-list-install-upgrade-packages-with-pip][Pippel: List, install, upgrade packages with pip]] - [[init.org#pip-requirements][Pip requirements]] - [[init.org#sphinx][Sphinx]] - [[init.org#python-test-run-python-tests-with-unittest-pytest-django][Python-test: Run python tests with unittest, pytest, django]] - [[init.org#pyramid][Pyramid]] - [[init.org#django][Django]] - [[init.org#pydoc-nicer-documentation-view][Pydoc: Nicer documentation view]] - [[init.org#isort][Isort]] - [[init.org#blacken-auto-format-python-buffer-with-black][Blacken: Auto format Python buffer with black]] - [[init.org#virtualenvwrapper-automatically-switch-virtualenvs-on-projectile-switch-project][Virtualenvwrapper: Automatically switch virtualenvs on projectile switch project]] - [[init.org#prolog][Prolog]] - [[init.org#redis][Redis]] - [[init.org#ruby][Ruby]] - [[init.org#rust][Rust]] - [[init.org#solidity-ethereum][Solidity (Ethereum)]] - [[init.org#sql][SQL]] - [[init.org#mongodb][MongoDB]] - [[init.org#tex][Tex]] - [[init.org#typescript][Typescript]] - [[init.org#web][Web]] - [[init.org#personalel][personal.el]] - [[init.org#org][Org]] - [[init.org#org-packages][Org Packages]] - [[init.org#org-agenda][Org-agenda]] - [[init.org#org-super-agenda][Org-super-agenda]] - [[init.org#org-caldav-sync-your-calendars-with-your-agenda--org-tasks][Org-caldav: Sync your calendars with your agenda / org tasks]] - [[init.org#org-babel][Org-babel]] - [[init.org#org-src][Org-src]] - [[init.org#org-indent-indent-text-according-to-outline-structure][Org-indent: Indent text according to outline structure.]] - [[init.org#org-superstar-use-utf-8-characters-instead-of--as-bullet-points][Org-superstar: Use utf-8 characters instead of `*` as bullet points]] - [[init.org#org-capture][Org-capture]] - [[init.org#org-clock][Org-clock:]] - [[init.org#org-crypt-encrypt-parts-in-org-file-tagged-with-crypt][Org-crypt: Encrypt parts in org file tagged with CRYPT]] - [[init.org#org-export][Org-export]] - [[init.org#org-habit-track-habits][Org-habit: Track habits]] - [[init.org#org-man-make-org-links-work-with-man-pages][Org-man: Make org-links work with man pages]] - [[init.org#org-expiry-automatically-add-a-created-property-when-inserting-a-new-headline][Org-expiry: Automatically add a CREATED property when inserting a new headline]] - [[init.org#org-id-create-id-property-with-new-task][Org-id: Create ID property with new task]] - [[init.org#org-table][Org-table]] - [[init.org#org-toc-create-table-of-content-in-org-files][Org-toc: Create table of content in org files]] - [[init.org#org-pomodoro][Org-pomodoro]] - [[init.org#org-jira-sync-issues-with-jira][Org-jira: Sync issues with Jira]] - [[init.org#org-github-sync-issues-with-github][Org-github: Sync issues with GitHub]] - [[init.org#org-link][Org-link]] - [[init.org#orgit-org-link-support-for-magit-buffers][Orgit: org-link support for magit buffers]] - [[init.org#counsel-org-clock][Counsel-org-clock]] - [[init.org#irc][Irc]] - [[init.org#email][Email]] - [[init.org#mu4e][Mu4e]] - [[init.org#mail-mu4egnus-icalendar--org-integration][Mail (mu4e/gnus) icalendar / org integration]] - [[init.org#org-and-mails][Org and mails]] - [[init.org#mml-sec-auto-sign-mails][mml-sec: Auto sign mails]] - [[init.org#gnus-dired-attach-files-from-dired-c-c-ret-c-a][Gnus-dired: Attach files from dired (C-c RET C-a)]] - [[init.org#mu4e-contrib-display-html-messages][mu4e-contrib: Display html messages]] - [[init.org#mu4e-patch-colorize-patch-based-emails][mu4e-patch: Colorize patch-based emails]] - [[init.org#window-manager][Window Manager]] - [[init.org#exwm][Exwm]] - [[init.org#statusbar][Statusbar]] - [[init.org#gpastel-gpaste-clipboard-manager-synchronization-with-kill-ring][Gpastel: gpaste clipboard manager synchronization with kill-ring]] - [[init.org#pulseaudio][PulseAudio]] - [[init.org#xbacklight-adjust-screen-brightness][Xbacklight: Adjust screen brightness]] - [[init.org#network][Network]] - [[init.org#navigation][Navigation]] - [[init.org#switch-window][Switch window]] - [[init.org#winner-mode-undoredo-window-configurations][winner-mode: undo/redo window configurations]] - [[init.org#transmission-bittorent][Transmission: Bittorent]] - [[init.org#multimedia][Multimedia]] - [[init.org#brain-fm-stream-music-from-brainfm][Brain-fm: Stream music from brain.fm]] - [[init.org#emms][Emms]] - [[init.org#youtube-download][YouTube Download]] - [[init.org#image-magick][Image (magick)]] - [[init.org#scrot-screenshot-utility-using-scrot][Scrot: Screenshot utility using scrot]] - [[init.org#misc][Misc]] - [[init.org#atomx][Atomx]] - [[init.org#auto-display-battery-mode][Auto-display-battery-mode]] - [[init.org#aurel-search-vote-for-and-download-aur-packages][Aurel: Search, vote for and download AUR packages]] - [[init.org#gif-screencasts-one-frame-per-action-gif-recording][Gif-Screencasts: One-frame-per-action GIF recording]] - [[init.org#ipinfo-get-ip-info-from-ipinfoio][IPInfo: Get IP info from ipinfo.io]] - [[init.org#speed-type-type-a-text-and-measure-your-speed][Speed-type: Type a text and measure your speed]] - [[init.org#disk-usage-file-system-analyzer-tabulated-view-of-file-listings-sorted-by-size][Disk-usage: File system analyzer. Tabulated view of file listings sorted by size]] - [[init.org#systemctl][Systemctl]] - [[init.org#ovpn-openvpn-management-mode][OVPN: OpenVPN management mode]] - [[init.org#f5-vpn][F5 VPN]] - [[init.org#ledger-accounting][Ledger: Accounting]] - [[init.org#elfeed-atomxrss-news-reader][Elfeed: Atomx/RSS news reader]] - [[init.org#rdesktop][Rdesktop]] - [[init.org#info-beamer][Info-beamer]] - [[init.org#kdeconnect][KDEConnect]] - [[init.org#keepassxc][KeePassXC]] - [[init.org#nov-read-epubs][Nov: Read EPUBs]] - [[init.org#piper-shell-scripting-with-emacs][Piper: Shell scripting with Emacs]] - [[init.org#pocket-reader][Pocket reader]] - [[init.org#eww][Eww]] - [[init.org#wolfram-alpha][Wolfram alpha]] - [[init.org#tea-timer][Tea timer]] - [[init.org#web-server-a-web-server-running-handlers-written-in-emacs-lisp][Web Server: A web server running handlers written in Emacs Lisp]] - [[init.org#debug-emacs-init-startup-time][Debug emacs init startup time]] - [[init.org#umlaut-mode-a-mode-for-conveniently-inserting-umlauts][Umlaut mode: A mode for conveniently inserting Umlauts]] - [[init.org#unsortet-stuff-in-no-packages][Unsortet stuff in no packages]] - [[init.org#libraries][Libraries]] - [[init.org#jiralib2-provide-connectivity-to-jira-rest-services][jiralib2: Provide connectivity to JIRA REST services.]] - [[init.org#ejira-parser-parsing-to-and-from-jira-markup][ejira-parser: Parsing to and from JIRA markup.]] - [[init.org#language-detection-detect-programming-language-in-a-buffer][language-detection: Detect programming language in a buffer]] - [[init.org#oauth2-oauth-20-authorization-protocol][Oauth2: OAuth 2.0 Authorization Protocol]] - [[init.org#posframe-pop-a-child-frame-at-point][Posframe: Pop a child frame at point]] - [[init.org#post-initialization][Post Initialization]] :END: * About This config is a mix of looking at many different ~emacs.d~ repositories, reading blog posts, mailing lists, wikis etc for years and stealing what I found useful. Often I try to put a link with the credit near the source where I got the config snippet from, but sometimes I forgot and simply don't know anymore who first came up with an idea. I used [[https://github.com/bbatsov/prelude][prelude]] for a while, so a lot of things are from this setup. Other configs I looked at a lot include (but not only): - [[https://github.com/purcell/emacs.d.git][Steve Purcell]] - [[https://github.com/jwiegley/dot-emacs][John Wiegley]] - [[http://writequit.org/eos/eos.html][Emacs Operating System (EOS)]] - [[https://github.com/howardabrams/dot-files/blob/master/emacs.org][Howard Abrams]] - [[https://github.com/kaushalmodi/.emacs.d][Kaushalmodi]] ** Installation This config doesn't use ~package.el~ as package management but [[https://emacsmirror.net/manual/borg/][borg]] which uses git submodules. After first cloning this repository you have to call ~make bootstrap-borg~ to download the `borg` library and the you can execute ~make bootstrap~ to tangle this file and initialize all packages. After that you can just call ~make~ to tangle and rebuild the packages each time you make a change or ~make build-init~ if you only made changes to this file. *** External packages This config tangles a list of arch-packages that are not mandatory but useful to have with this config in =arch-pkglist.txt=. The missing packages can be installed with: #+BEGIN_SRC shell :tangle no yay -S --needed - < arch-pkglist.txt # or (no AUR packages) # pacman -S --needed $(comm -12 <(pacman -Slq | sort) <(sort pkglist.txt)) #+END_SRC * Emacs Initialization We want to use lexical scoping #+BEGIN_SRC emacs-lisp :comments nil ;;; init.el --- user-init-file -*- lexical-binding: t -*- #+END_SRC Show a few messages with timestamps to get a better overview of how fast Emacs is loading certain packages. #+BEGIN_SRC emacs-lisp (defvar before-user-init-time (current-time) "Value of `current-time' when Emacs begins loading `user-init-file'.") (message "Loading Emacs...done (%.3fs)" (float-time (time-subtract before-user-init-time before-init-time))) #+END_SRC We're going to increase the gc-cons-threshold to a very high number (2G) to decrease the load and compile time. We'll lower this value significantly after initialization has completed. We don't want to keep this value too high or it will result in long GC pauses during normal usage. #+BEGIN_SRC emacs-lisp (setq gc-cons-threshold (* 2 1024 1024 1024)) #+END_SRC Temporarily disable file name handlers as it's not needed on initialization #+BEGIN_SRC emacs-lisp (defvar file-name-handler-alist-old file-name-handler-alist) (setq file-name-handler-alist nil) #+END_SRC Increase max number of log messages #+BEGIN_SRC emacs-lisp (setq message-log-max 16384) #+END_SRC Disable certain byte compiler warnings to cut down on the noise. This is a personal choice and can be removed if you would like to see any and all byte compiler warnings. #+BEGIN_SRC emacs-lisp (setq byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local)) #+END_SRC ** Set some early UI settings Disable Tool- and Menubar in the early-init file via =default-frame-alist=. This is slightly faster than first loading the tool-/menu-bar and then turning it off again. #+BEGIN_SRC emacs-lisp :tangle early-init.el (push '(tool-bar-lines . 0) default-frame-alist) (push '(menu-bar-lines . 0) default-frame-alist) #+END_SRC Deactivate tool- and menu-bar for terminal Emacs as well. #+BEGIN_SRC emacs-lisp (unless (display-graphic-p) (tool-bar-mode -1) (menu-bar-mode -1)) #+END_SRC Disable the scroll-bar #+BEGIN_SRC emacs-lisp (scroll-bar-mode -1) #+END_SRC Set fringe to a small value so we don't have big borders in exwm but can still see our diff-hl colors in the fringe #+BEGIN_SRC emacs-lisp (fringe-mode '(20 . 2)) #+END_SRC Set the fringe color to the same color as the background #+BEGIN_SRC emacs-lisp ;; (set-face-background 'fringe (face-attribute 'default :background)) #+END_SRC Disable startup screen and startup echo area message and select the scratch buffer by default #+BEGIN_SRC emacs-lisp (setq inhibit-startup-buffer-menu t) (setq inhibit-startup-screen t) (setq inhibit-startup-echo-area-message "daniel") (setq initial-buffer-choice t) (setq initial-scratch-message nil) #+END_SRC ** Package Management *** Org config tangle There is no ~emacs.el~ in this repo. It will automatically created for you from this file (~emacs.org~) when you run ~make build-init~. Don't forget to call ~make~ every time you edit this file. If this annoys you, you could simply add an ~after-save-hook~ that calls ~make~ every time you save this file. *** Package Settings We use borg for package management and disable ~package.el~. Since Emacs 27.1 we have to disable package.el in the early init file. #+BEGIN_SRC emacs-lisp :tangle early-init.el (setq package-enable-at-startup nil) #+END_SRC #+BEGIN_SRC emacs-lisp ;; (package-initialize) (setq load-prefer-newer t) #+END_SRC *** Borg #+BEGIN_SRC emacs-lisp (setq user-init-file (or load-file-name buffer-file-name)) (setq user-emacs-directory (file-name-directory user-init-file)) (add-to-list 'load-path (expand-file-name "lib/borg" user-emacs-directory)) (require 'borg) (borg-initialize) #+END_SRC *** Use-Package #+BEGIN_SRC emacs-lisp ;;(defvar use-package-enable-imenu-support t) (require 'use-package) (if nil ; Toggle init debug (setq use-package-verbose t use-package-expand-minimally nil use-package-compute-statistics t debug-on-error t) (setq use-package-verbose nil use-package-expand-minimally t)) ;; For the :bind keyword (use-package bind-key :defer t) ;;(autoload #'use-package-autoload-keymap "use-package") #+END_SRC *** Epkg #+BEGIN_SRC emacs-lisp (use-package epkg :defer t :init (setq epkg-database-connector 'sqlite-builtin)) #+END_SRC ** Emacs native-comp I use the Emacs =native-comp= branch. Compile asyncronously all lexically bound .elc files being loaded. #+BEGIN_SRC emacs-lisp (use-package comp :config (setq inhibit-automatic-native-compilation nil) ;; Don't try to native compile this init file (add-to-list 'native-comp-jit-compilation-deny-list "init\\.el$")) #+END_SRC * Don't litter configs Put native compilation cache in no-litter var folder. NOTE: This is loaded in =early-init.el=. #+BEGIN_SRC emacs-lisp :tangle early-init.el (when (fboundp 'startup-redirect-eln-cache) (startup-redirect-eln-cache (convert-standard-filename (expand-file-name "var/eln-cache/" user-emacs-directory)))) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package no-littering :demand t :config ;; /etc is version controlled and I want to store mc-lists in git (setq mc/list-file (no-littering-expand-etc-file-name "mc-list.el")) ;; Put the auto-save and backup files in the var directory to the other data files (no-littering-theme-backups)) (use-package custom :config ;; We don't use custom and don't have to set custom-file even ;; in the case when we "accidentally" click save in a custom buffer, ;; `init.el' would get modified which gets overwrite the next time ;; we run `make'. ;; Treat all themes as safe (setf custom-safe-themes t)) #+END_SRC * Themes Put this in your =~/.Xresources= and load them in your =~/.xprofile= with ~xrdb .Xresources~ so Emacs loads the right font even before loading this config and don't flicker from white to black background when first starting. #+BEGIN_SRC conf :tangle no ! Emacs settings so they are set even before Emacs starts ! See: https://www.gnu.org/software/emacs/manual/html_node/emacs/Table-of-Resources.html#Table-of-Resources ! Emacs.font: Operator Mono-9:weight=light:width=normal ! Emacs.font: Operator Mono:size=7:slant=normal:weight=normal:width=normal Emacs.font: Fira Code-9:weight=regular:width=normal Emacs.reverseVideo: on Emacs.menuBar: 0 Emacs.toolBar: 0 Emacs.verticalScrollBars: off #+END_SRC If you don't set your Xresources, you can load the font with: #+BEGIN_SRC emacs-lisp ;; (add-to-list 'default-frame-alist '(font . "Operator Mono-9:weight=light:width=normal")) ;; (set-frame-font "Operator Mono-9:weight=light:width=normal" nil t) (add-to-list 'default-frame-alist '(font . "Fira Code-13:weight=regular:width=normal")) (set-frame-font "Fira Code-13:weight=regular:width=normal" nil t) ;; (set-frame-font "Victor Mono-9:weight=medium" nil t) ;; (set-frame-font "iA Writer Mono S-9:weight=regular" nil t) ;; (set-frame-font "B612 Mono-9:weight=regular" nil t) ;; (set-frame-font "Victor Mono-9" nil t) ;; (set-frame-font "Iosevka-9:slant=normal:weight=regular:width=normal" nil t) ;; (set-frame-font "Iosevka-9:weight=regular" nil t) ;; (set-frame-font "Iosevka-7:slant=normal:weight=bold:width=normal" nil t) ;; (set-frame-font "DejaVu Sans Mono-7:weight=medium:width=normal" nil t) ;; (set-frame-font "Hack-7:weight=medium:width=normal" nil t) ;; (set-frame-font "Source Code Pro:weight=medium:width=normal" nil t) ;; (set-frame-font "FiraCode-9:slant=normal:weight=regular:width=normal" nil t) ;; (set-frame-font "Fira Code-8:weight=regular:width=normal" nil t) ;; (set-frame-font "inconsolata:weight=medium:width=normal" nil t) #+END_SRC Some fonts #+BEGIN_SRC txt :tangle arch-pkglist.txt ttf-iosevka ttf-google-fonts-git ttf-carlito ttf-croscore #+END_SRC ** color-theme-sanityinc-tomorrow #+BEGIN_SRC emacs-lisp (use-package color-theme-sanityinc-tomorrow :disabled t :unless noninteractive :config (load-theme 'sanityinc-tomorrow-night 'no-confirm) (let ((line (face-attribute 'mode-line :underline))) (set-face-attribute 'mode-line nil :overline line) (set-face-attribute 'mode-line-inactive nil :overline line) (set-face-attribute 'mode-line-inactive nil :underline line) (set-face-attribute 'mode-line nil :box nil) (set-face-attribute 'mode-line-inactive nil :box nil))) #+END_SRC ** moe-theme #+BEGIN_SRC emacs-lisp (use-package moe-theme ;; :disabled t :unless noninteractive :config (load-theme 'moe-dark t)) #+END_SRC ** modus-themes #+BEGIN_SRC emacs-lisp (use-package modus-themes :disabled t :unless noninteractive :config (setq modus-themes-italic-constructs t modus-themes-bold-constructs t) ;; (setq modus-themes-common-palette-overrides ;; modus-themes-preset-overrides-intense) (setq modus-themes-common-palette-overrides '(;; Make the fringe invisible. (fringe unspecified))) ;; For the dark theme I want a lightly lighter background than completely black ;; (setq modus-themes-vivendi-color-overrides '((bg-main . "#101010"))) ;; (load-theme 'modus-operandi) ;; light theme (load-theme 'modus-vivendi) ;; dark theme ) #+END_SRC ** moody modeline #+BEGIN_SRC emacs-lisp (use-package moody ;; :disabled t :unless noninteractive ;; :defer 1 ;;:init ;;(set-background-color "black") ;;(set-foreground-color "white") ;; If you use the default Emacs black theme (no external theme loaded) you have to specify ;; a different color for mode-line-buffer-id or it will be the same as the background ;;(set-face-attribute 'mode-line-buffer-id nil :foreground "light sky blue" :weight 'bold) ;;(let ((line (face-attribute 'mode-line :underline))) ;; (set-face-attribute 'mode-line nil :overline line) ;; (set-face-attribute 'mode-line-inactive nil :overline line) ;; (set-face-attribute 'mode-line-inactive nil :underline line) ;; (set-face-attribute 'mode-line nil :box nil) ;; (set-face-attribute 'mode-line-inactive nil :box nil)) :config (setq x-underline-at-descent-line t) (setq moody-mode-line-height nil) ;; moody buffer-identification doesn't work with modus themes and ;; the following line needs to be commented out first or emacs hangs! (moody-replace-mode-line-buffer-identification) (moody-replace-vc-mode) (moody-replace-eldoc-minibuffer-message-function)) #+END_SRC ** minions: put minor modes from modeline in menu #+BEGIN_SRC emacs-lisp (use-package minions :unless noninteractive :defer 2 :config (setq minions-mode-line-lighter "+") (setq minions-prominent-modes '(projectile-mode flycheck-mode multiple-cursors-mode sticky-buffer-mode mu4e-modeline-mode)) (minions-mode)) #+END_SRC * Personal Information Let's set some variables with basic user information. #+BEGIN_SRC emacs-lisp (setq user-full-name "Daniel Kraus" user-mail-address "daniel@kraus.my") #+END_SRC * Some config helper functions These functions make it easy to define which environment variables should be marked as safe. E.g. to allow *all* all strings for ~DJANGO_SETTINGS_MODULE~ and ~FOOBAR~ in your ~python-shell-process-environment~ add: #+BEGIN_SRC emacs-lisp :tangle no (put 'python-shell-process-environment 'safe-local-variable (create-safe-env-p "DJANGO_SETTINGS_MODULE" "ENV_INI_PATH")) #+END_SRC In your ~.dir-locals.el~ you can then have something like #+BEGIN_SRC emacs-lisp :tangle no ((nil . ((python-shell-process-environment . ("DJANGO_SETTINGS_MODULE=shop_paessler_com.settings.base" "FOOBAR=SAFE_FOO_BAR"))))) #+END_SRC and you will not get prompted for unsafe dir-locals. #+BEGIN_SRC emacs-lisp (defun get-envvar-name (envvar) "Return environment variable name for ENVVAR. Code from `read-envvar-name'." (let ((str (substring envvar 0 (string-match "=" envvar)))) (if (multibyte-string-p str) (decode-coding-string str locale-coding-system t) str))) (defun create-safe-env-p (&rest keys) "Return predicate function that's non-NIL when it's argument KEY is in KEYS." (lambda (envlist) (-all-p (lambda (key) (-any-p (lambda (k) (string= (get-envvar-name key) k)) keys)) envlist))) #+END_SRC * Change Emacs default config #+BEGIN_SRC emacs-lisp ;; Don't quit Emacs on C-x C-c (when (daemonp) (global-set-key (kbd "C-x C-c") 'kill-buffer-and-window)) ;; Increase the amount of data which Emacs reads from the process ;; (Useful for LSP where the LSP responses are in the 800k - 3M range) (setq read-process-output-max (* 1024 1024)) ;; 1mb ;; Don't compact font caches during GC as it doesn't play too nice ;; with org-superstar-mode and some of my large org files (e.g. this file). ;; This might enlarge the Emacs memory footprint but I don't mind if Emacs ;; uses more memory but rather prefer speed. (setq inhibit-compacting-font-caches t) ;; Always just use left-to-right text ;; This makes Emacs a bit faster for very long lines (setq-default bidi-paragraph-direction 'left-to-right) ;; Don't use tabs to indent (setq-default indent-tabs-mode nil) ;; Leave default appearence of tab width 8 ;; (setq-default tab-width 8) ;; smart tab behavior - indent or complete (setq tab-always-indent 'complete) ;; Newline at end of file (setq require-final-newline t) ;; Default to utf-8 unix encoding (prefer-coding-system 'utf-8-unix) ;; Delete the selection with a keypress (delete-selection-mode t) ;; Activate character folding in searches i.e. searching for 'a' matches 'ä' as well (setq search-default-mode 'char-fold-to-regexp) ;; Only split vertically on very tall screens (setq split-height-threshold 120) ;; Only split horizontally if there are at least 90 chars column after splitting (setq split-width-threshold 180) ;; Paste with middle mouse button doesn't move the cursor (setq mouse-yank-at-point t) ;; Save whatever’s in the current (system) clipboard before ;; replacing it with the Emacs’ text. ;; https://github.com/dakrone/eos/blob/master/eos.org (setq save-interprogram-paste-before-kill t) (setq ffap-machine-p-known 'reject) ; don't "ping Germany" when typing test.de ;; Accept 'UTF-8' (uppercase) as a valid encoding in the coding header (define-coding-system-alias 'UTF-8 'utf-8) ;; Put authinfo.gpg first so new secrets will be stored there by default and not in plain text (setq auth-sources '("~/.authinfo.gpg" "~/.authinfo" "~/.netrc")) ;; Don't ask to store credentials in .authinfo.gpg (setq auth-source-save-behavior nil) ;; Silence ad-handle-definition about advised functions getting redefined (setq ad-redefinition-action 'accept) ;; Use 'fancy' ellipses for truncated strings (setq truncate-string-ellipsis "…") ;; Increase the limit to catch infinite recursions. ;; Large scala files need sometimes more and this value can safely be increased. (setq max-lisp-eval-depth 32768) #+END_SRC Allow some commands as safe by default #+BEGIN_SRC emacs-lisp ;; allow horizontal scrolling with "M-x >" (put 'scroll-left 'disabled nil) ;; enable narrowing commands (put 'narrow-to-region 'disabled nil) (put 'narrow-to-page 'disabled nil) (put 'narrow-to-defun 'disabled nil) ;; enabled change region case commands (put 'upcase-region 'disabled nil) (put 'downcase-region 'disabled nil) ;; enable erase-buffer command (put 'erase-buffer 'disabled nil) #+END_SRC ** UI #+BEGIN_SRC emacs-lisp ;; The blinking cursor is nothing, but an annoyance (blink-cursor-mode -1) ;; Disable the annoying bell ring (setq ring-bell-function 'ignore) ;; Nicer scrolling (setq scroll-margin 0 scroll-conservatively 100000 scroll-preserve-screen-position 1) ;; mode line settings (line-number-mode t) (column-number-mode t) (size-indication-mode t) ;; Disable auto vscroll (makes scrolling down a bit faster?) (setq auto-window-vscroll nil) ;; Enable y/n answers (fset 'yes-or-no-p 'y-or-n-p) ;; Some things don't work well with fish, just always use posix compatible shell (dash) (setq shell-file-name "/bin/sh") ;; highlight the current line (global-hl-line-mode +1) #+END_SRC * Configure Emacs builtin packages ** Simple: Change some default keybinding (kill-buffer, up/downcase-dwim) #+BEGIN_SRC emacs-lisp (use-package simple :bind (("C-/" . undo-only) ("C-z" . undo-only) ("C-S-z" . undo-redo) ("C-?" . undo-redo) ("C-x k" . kill-current-buffer) ("M-u" . dakra-upcase-dwim) ("M-l" . dakra-downcase-dwim) ("M-c" . dakra-capitalize-dwim)) :hook (((mu4e-compose-mode markdown-mode rst-mode git-commit-setup) . text-mode-autofill-setup) ((visual-fill-column-mode markdown-mode) . word-wrap-whitespace-mode)) :config ;; Hide commands in M-x which do not apply to the current mode. (setq read-extended-command-predicate #'command-completion-default-include-p) (defun text-mode-autofill-setup () "Set fill-column to 68 and turn on auto-fill-mode." (setq-local fill-column 68) (auto-fill-mode)) ;; Autofill (e.g. M-x autofill-paragraph or M-q) to 80 chars (default 70) (setq-default fill-column 80) (defmacro dakra-define-up/downcase-dwim (case) (let ((func (intern (concat "dakra-" case "-dwim"))) (doc (format "Like `%s-dwim' but %s from beginning when no region is active." case case)) (case-region (intern (concat case "-region"))) (case-word (intern (concat case "-word")))) `(defun ,func (arg) ,doc (interactive "*p") (save-excursion (if (use-region-p) (,case-region (region-beginning) (region-end)) (beginning-of-thing 'symbol) (,case-word arg)))))) (dakra-define-up/downcase-dwim "upcase") (dakra-define-up/downcase-dwim "downcase") (dakra-define-up/downcase-dwim "capitalize")) #+END_SRC ** Abbrev I often write German mails or comments but have a UK keyboard layout. Use abbrev mode to automatically convert words to the correct Umlaut spelling, e.g. fuer -> für #+BEGIN_SRC emacs-lisp (use-package abbrev :hook (text-mode . abbrev-mode) ;; :hook ((message-mode org-mode markdown-mode rst-mode) . abbrev-mode) :config ;; Don't ask to save abbrevs when saving all buffers (setq save-abbrevs 'silently) ;; I want abbrev saved in my config/version control and not in the var folder (setq abbrev-file-name (no-littering-expand-etc-file-name "abbrev.el"))) #+END_SRC ** Auto-revert: Revert buffer when file changes on disk #+BEGIN_SRC emacs-lisp (use-package autorevert :defer 1 ;;:hook (find-file . auto-revert-mode) :config ;; We only really need auto revert for git files ;; and we use magits `magit-auto-revert-mode' for that ;;; revert buffers automatically when underlying files are changed externally (global-auto-revert-mode nil) ;; auto revert dired buffers (setq global-auto-revert-non-file-buffers t) ;; Turn off auto revert messages (setq auto-revert-verbose nil)) #+END_SRC ** Select: Clipboard / primary selection #+BEGIN_SRC emacs-lisp (use-package select :config ;; Use clipboard and primary selection for copy/paste (setq select-enable-primary t) (defun select-add-selection-to-kill-ring () "Add clipboard and primary selection to the kill ring." (interactive) (when-let* ((primary (gui-get-primary-selection)) (not-empty? (not (string-empty-p primary)))) (kill-new primary)) (when-let* ((clipboard (gui-backend-get-selection 'CLIPBOARD 'STRING)) (not-empty? (not (string-empty-p clipboard))))))) #+END_SRC ** Epa: the EasyPG Assistant, transparent file encryption (gpg) #+BEGIN_SRC emacs-lisp (use-package epa :defer t :config ;; Always replace encrypted text with plain text version (setq epa-replace-original-text t)) (use-package epg :defer t :config ;; Let Emacs query the passphrase through the minibuffer (setq epg-pinentry-mode 'loopback)) #+END_SRC ** Saveplace: Remember your location in a file #+BEGIN_SRC emacs-lisp (use-package saveplace :unless noninteractive :config (setq save-place-limit 1000) (save-place-mode)) #+END_SRC ** Savehist: Keep track of minibuffer history #+BEGIN_SRC emacs-lisp (use-package savehist :unless noninteractive :defer 1 :config (setq savehist-additional-variables '(compile-command kill-ring regexp-search-ring)) (savehist-mode 1)) #+END_SRC ** So-long: Mitigating slowness due to extremely long lines #+BEGIN_SRC emacs-lisp (use-package so-long :config (global-so-long-mode)) #+END_SRC ** Ansi-color #+BEGIN_SRC emacs-lisp (use-package ansi-color :commands ansi-color-display :disabled t ;; Use xterm-color instead :hook (compilation-filter . colorize-compilation-buffer) :config (defun ansi-color-display (start end) "Display ansi colors in region or whole buffer." (interactive (if (region-active-p) (list (region-beginning) (region-end)) (list (point-min) (point-max)))) (let ((inhibit-read-only t)) (ansi-color-apply-on-region start end))) ;; Colorize output of Compilation Mode, see ;; http://stackoverflow.com/a/3072831/355252 (defun colorize-compilation-buffer () (let ((inhibit-read-only t)) (ansi-color-apply-on-region (point-min) (point-max))))) #+END_SRC ** Compile #+BEGIN_SRC emacs-lisp (use-package compile :bind (:map compilation-mode-map ("C-c -" . compilation-add-separator) ("-" . compilation-add-separator) :map comint-mode-map ("C-c -" . compilation-add-separator)) :init (put 'compilation-environment 'safe-local-variable (create-safe-env-p "SENTRY_DSN")) :config ;; xterm-color config (setq compilation-environment '("TERM=xterm-256color")) (defun my/advice-compilation-filter (f proc string) (funcall f proc (xterm-color-filter string))) (advice-add 'compilation-filter :around #'my/advice-compilation-filter) (defun compilation-add-separator () "Insert separator in read-only buffer." (interactive) (let ((inhibit-read-only t)) (insert "\n---------------------------------\n\n") (point-max) (comint-set-process-mark))) ;; Always save before compiling (setq compilation-ask-about-save nil) ;; Just kill old compile processes before starting the new one (setq compilation-always-kill t) ;; Scroll with the compilation output ;; Set to 'first-error to stop scrolling on first error (setq compilation-scroll-output t)) #+END_SRC ** Comint Create a terminfo file for Emacs with ANSI color codes and use it in comint. From https://old.reddit.com/r/emacs/comments/ad90w4/found_a_simple_solution_to_colorize_ls_on_shell/edf40xm/ #+BEGIN_SRC text :tangle ~/.terminfo/dumb-emacs-ansi.ti dumb-emacs-ansi|Emacs dumb terminal with ANSI color codes, am, colors#8, it#8, ncv#13, pairs#64, bold=\E[1m, cud1=^J, ht=^I, ind=^J, op=\E[39;49m, ritm=\E[23m, rmul=\E[24m, setab=\E[4%p1%dm, setaf=\E[3%p1%dm, sgr0=\E[m, sitm=\E[3m, smul=\E[4m, #+END_SRC #+BEGIN_SRC emacs-lisp (use-package comint :defer t :config ;; Set terminfo to a dumb terminal with ANSI color codes (setq comint-terminfo-terminal "dumb-emacs-ansi") ;; Increase comint buffer size. (setq comint-buffer-maximum-size 8192)) #+END_SRC ** Subword: CamelCase aware editing operations #+BEGIN_SRC emacs-lisp (use-package subword :hook ((python-mode yaml-ts-mode conf-mode go-mode go-ts-mode clojure-mode cider-repl-mode java-mode java-ts-mode cds-mode js-mode js-ts-mode) . subword-mode)) #+END_SRC ** Shr: Simple html renderer #+BEGIN_SRC emacs-lisp (use-package shr :defer t :config (setq shr-width 80) (setq shr-color-visible-luminance-min 80)) #+END_SRC ** Info: View info pages #+BEGIN_SRC emacs-lisp (use-package info :bind (:map Info-mode-map ("c" . info-copy-current-node-name-web)) :config ;; From: https://www.reddit.com/r/emacs/comments/9sp7hh/show_me_your_functions/e8s1mgg/ (defun info-copy-current-node-name-web (arg) "Copy the lispy form of the current node. With a prefix argument, copy the link to the online manual instead." (interactive "P") (let* ((manual (file-name-sans-extension (file-name-nondirectory Info-current-file))) (node Info-current-node) (link (if (not arg) (format "(info \"(%s) %s\")" manual node) ;; NOTE this will only work with emacs-related nodes... (format "https://www.gnu.org/software/emacs/manual/html_node/%s/%s.html" manual (if (string= node "Top") "index" (replace-regexp-in-string " " "-" node)))))) (kill-new link) (message link)))) #+END_SRC ** Makefile #+BEGIN_SRC emacs-lisp (use-package make-mode ;; Files like `Makefile.docker' are also gnu make :mode (("Makefile" . makefile-gmake-mode))) #+END_SRC ** generic-x: Basic syntax highlighting for many modes (fstab, sudoers, passwd, etc) #+BEGIN_SRC emacs-lisp (use-package generic-x :if (daemonp) :defer 30) #+END_SRC ** Goto-addr: Buttonize URLs and e-mail addresses in the current buffer #+BEGIN_SRC emacs-lisp (use-package goto-addr :hook ((compilation-mode prog-mode eshell-mode shell-mode) . goto-address-mode) :bind (:map goto-address-highlight-keymap ("" . goto-address-at-point) ("M-" . newline))) #+END_SRC ** Time #+BEGIN_SRC emacs-lisp (use-package time :defer t :config ;; Only show loads of above 0.9 in the modeline (setq display-time-load-average-threshold 0.9) ;; A list of timezones to show for `display-time-world` (setq zoneinfo-style-world-list '(("Europe/Berlin" "Berlin") ("Europe/London" "London") ("Asia/Kuala_Lumpur" "Kuala Lumpur") ("America/Los_Angeles" "Los Angeles") ("America/New_York" "New York") ("Australia/Sydney" "Sydney"))) (setq display-time-24hr-format t) ;; Enable to show time in modeline ;; (display-time-mode) ;; Right align time and org clocked-in task (require 'org-clock) (add-to-list 'global-mode-string '(:eval (propertize " " 'display `((space :align-to (- right ,(length display-time-string) ,(if (org-clocking-p) (length org-mode-line-string) 0) 1))))))) #+END_SRC ** Calendar #+BEGIN_SRC emacs-lisp (use-package calendar :hook (calendar-today-visible . calendar-mark-today) :config ;; Set coordinates so you can press `S` in the calendar for sunrise/sunset times ;;(setq calendar-latitude 34.103 ;; calendar-longitude -118.337 ;; calendar-location-name "Los Angeles, USA") ;;(setq calendar-latitude -37.841 ;; calendar-longitude 144.939 ;; calendar-location-name "Melbourne, Australia") ;;(setq calendar-latitude 3.143 ;; calendar-longitude 101.686 ;; calendar-location-name "Kuala Lumpur, Malaysia") (setq calendar-latitude 48.97 calendar-longitude 8.45 calendar-location-name "Karlsruhe, Germany") ;; Start week on Monday (setq calendar-week-start-day 1) ;; Highlight public holidays (setq calendar-holiday-marker t)) #+END_SRC ** Network net-utils #+BEGIN_SRC emacs-lisp (use-package net-utils :defer t :bind (:map mode-specific-map :prefix-map net-utils-prefix-map :prefix "n" ("p" . ping) ("i" . ifconfig) ("w" . iwconfig) ("n" . netstat) ("a" . arp) ("r" . route) ("h" . nslookup-host) ("d" . dig) ("s" . smbclient) ("t" . traceroute)) :config ;; Set default netstat options ;; so when I call it, it will list all open ports with processes (setq netstat-program-options '("-tulpn"))) #+END_SRC ** dstat #+BEGIN_SRC emacs-lisp (defun dstat () "Run `dstat' cli tool to show various system resource stats." (interactive) (let ((compilation-buffer-name-function (lambda (_mode) "*dstat*"))) (compile "dstat -ta"))) #+END_SRC ** Xwidget - Show a webkit view in a buffer #+BEGIN_SRC emacs-lisp (use-package xwidget :bind (:map xwidget-webkit-mode-map ("n" . xwidget-webkit-scroll-up-line) ("p" . xwidget-webkit-scroll-down-line) ("q" . kill-current-buffer) ("Q" . quit-window)) :config ;; Don't ask if I want to kill a xwidget buffer (remove-hook 'kill-buffer-query-functions #'xwidget-kill-buffer-query-function) ;; Slightly short buffer name (setq xwidget-webkit-buffer-name-format "*xwidget: %T*")) #+END_SRC * UI ** Alert: Growl-style notification system #+BEGIN_SRC emacs-lisp (use-package alert :defer t :config ;; send alerts by default to D-Bus (setq alert-default-style 'notifications)) #+END_SRC ** Eldoc: Display help #+BEGIN_SRC emacs-lisp (use-package eldoc :hook (prog-mode . eldoc-mode) :config (setq eldoc-documentation-default 'eldoc-documentation-compose-eagerly) (eldoc-add-command-completions "sp-") (eldoc-add-command-completions "paredit-")) #+END_SRC ** Dimmer: Visually highlight the selected buffer #+BEGIN_SRC emacs-lisp (use-package dimmer :unless noninteractive :defer 10 :config ;; Don't dim hydra, transient buffers or minibuffers (setq dimmer-buffer-exclusion-regexps '(" \\*\\(LV\\|transient\\)\\*" "^ \\*.*posframe.*buffer.*\\*$" "^\\*Minibuf-[0-9]+\\*" "^.\\*which-key\\*$" "^.\\*Echo.*\\*")) (setq dimmer-fraction 0.25) ;;(setq dimmer-use-colorspace ':rgb) (dimmer-mode)) #+END_SRC ** Hl-todo: Highlight and navigate TODO keywords #+BEGIN_SRC emacs-lisp (use-package hl-todo :defer 2 :config (global-hl-todo-mode)) #+END_SRC ** Fill-column-indicator #+BEGIN_SRC emacs-lisp (use-package display-fill-column-indicator :hook ((git-commit-setup) . display-fill-column-indicator-mode)) #+END_SRC ** visual-fill-column: fill-column for visual-line-mode #+begin_src emacs-lisp (use-package visual-fill-column :defer t ;; :config ;; Option to center text by default ;; (setq-default visual-fill-column-center-text t) ) #+end_src ** adaptive-wrap: visual indention for visual-fill-column #+begin_src emacs-lisp (use-package adaptive-wrap :hook ((visual-fill-column-mode markdown-mode) . adaptive-wrap-prefix-mode)) #+end_src ** Goggles: Visual feedback on some operations like yank,kill,undo #+BEGIN_SRC emacs-lisp (use-package goggles :disabled t :hook ((text-mode prog-mode) . googles-mode)) #+END_SRC ** beacon: Highlight current line/cursor when switching frames #+BEGIN_SRC emacs-lisp (use-package beacon :defer 5 :config (beacon-mode 1)) #+END_SRC ** which-key: Display available keybindings in popup which-key displays the key bindings following your currently entered incomplete command (a prefix) in a popup. For example, after enabling the minor mode if you enter C-x and wait for the default of 1 second the minibuffer will expand with all of the available key bindings that follow C-x (or as many as space allows given your settings). This includes prefixes like C-x 8 which are shown in a different face #+BEGIN_SRC emacs-lisp (use-package which-key :disabled t :defer 10 :config (which-key-mode 1)) #+END_SRC ** which-func: Show the name of the current function definition in the modeline #+BEGIN_SRC emacs-lisp (use-package which-func :defer 5 :config (which-function-mode 1)) #+END_SRC ** Nicer buffer name for buffers with same name #+BEGIN_SRC emacs-lisp (use-package uniquify :defer 5 :config (setq uniquify-ignore-buffers-re "^\\*") ; don't muck with special buffers (setq uniquify-buffer-name-style 'forward) (setq uniquify-separator "/")) #+END_SRC ** Highlight indentations #+BEGIN_SRC emacs-lisp ;; highlight indentations in python (use-package highlight-indent-guides ;; :disabled t ; Somehow misaligns often in my Python buffers ;; :hook ((python-mode python-ts-mode sass-mode yaml-ts-mode nim-mode) . highlight-indent-guides-mode) :hook ((yaml-ts-mode) . highlight-indent-guides-mode) :config ;; Don't highlight first level (that would be a line at column 1) (defun my-highlighter (level responsive display) (if (> 1 level) ; replace `1' with the number of guides you want to hide nil (highlight-indent-guides--highlighter-default level responsive display))) (setq highlight-indent-guides-highlighter-function 'my-highlighter) (setq highlight-indent-guides-method 'character) (setq highlight-indent-guides-character ?\|) ;; (setq highlight-indent-guides-auto-odd-face-perc 15) ;; (setq highlight-indent-guides-auto-even-face-perc 15) ;; (setq highlight-indent-guides-auto-character-face-perc 20) ;; (highlight-indent-guides-auto-set-faces) (setq highlight-indent-guides-auto-enabled nil) (set-face-foreground 'highlight-indent-guides-character-face "#3a3a3a")) #+END_SRC ** Symbol-overlay #+BEGIN_SRC emacs-lisp (use-package symbol-overlay :hook ((prog-mode html-mode css-mode) . symbol-overlay-mode) :bind (("C-c s" . symbol-overlay-put) :map symbol-overlay-mode-map ("M-n" . symbol-overlay-jump-next) ("M-p" . symbol-overlay-jump-prev) :map symbol-overlay-map ("M-n" . symbol-overlay-jump-next) ("M-p" . symbol-overlay-jump-prev) ("C-c C-s r" . symbol-overlay-rename) ("C-c C-s k" . symbol-overlay-remove-all) ("C-c C-s q" . symbol-overlay-query-replace) ("C-c C-s t" . symbol-overlay-toggle-in-scope) ("C-c C-s n" . symbol-overlay-jump-next) ("C-c C-s p" . symbol-overlay-jump-prev)) :init (setq symbol-overlay-scope t) :config ;;(set-face-background 'symbol-overlay-temp-face "gray30") ;; Remove all default bindings (setq symbol-overlay-map (make-sparse-keymap))) #+END_SRC ** Emoji font A font with emoji symbols (like twemoji, noto-fonts-emoji and/or ttf-symbola) has to be installed. #+BEGIN_SRC txt :tangle arch-pkglist.txt ttf-twemoji noto-fonts-emoji ttf-windows ttf-symbola #+END_SRC #+BEGIN_SRC emacs-lisp ;; Use "C-x 8 RET " (defun --set-emoji-font (frame) "Adjust the font settings of FRAME so Emacs can display emoji properly." (set-fontset-font t 'emoji (font-spec :family "Twemoji") frame) (set-fontset-font t 'emoji (font-spec :family "Noto Color Emoji") frame 'append) (set-fontset-font t 'emoji (font-spec :family "Segoe UI Emoji") frame 'append) (set-fontset-font t 'emoji (font-spec :family "Symbola") frame 'append)) ;; For when Emacs is started in GUI mode: (--set-emoji-font nil) ;; Hook for when a frame is created with emacsclient ;; see https://www.gnu.org/software/emacs/manual/html_node/elisp/Creating-Frames.html (add-hook 'after-make-frame-functions '--set-emoji-font) #+END_SRC ** Ligatures #+BEGIN_SRC emacs-lisp (use-package ligature :hook (prog-mode . ligature-mode) :config ;; Some ligatures supported by most fonts. E.g. Fira Code, Victor Mono (ligature-set-ligatures 'prog-mode '("~~>" "##" "|-" "-|" "|->" "|=" ">-" "<-" "<--" "->" "-->" "-<" ">->" ">>-" "<<-" "<->" "->>" "-<<" "<-<" "==>" "=>" "=/=" "!==" "!=" "<==" ">>=" "=>>" ">=>" "<=>" "<=<" "=<=" "=>=" "<<=" "=<<" "=:=" "=!=" "==" "=~" "!~" "===" "::" ":=" ":>" ">:" ";;" "__" "..." ".." "&&" "++"))) #+END_SRC ** form-feed: Show line-breaks (~^L~) characters as lines #+BEGIN_SRC emacs-lisp (use-package form-feed :hook (((clojure-mode emacs-lisp-mode) . form-feed-mode))) #+END_SRC ** Automatically remove trailing whitespace (only if I put them there) #+BEGIN_SRC emacs-lisp (use-package ws-butler :hook ((text-mode prog-mode) . ws-butler-mode) :config (setq ws-butler-keep-whitespace-before-point nil)) #+END_SRC ** whitespace-mode: Highlight long lines #+BEGIN_SRC emacs-lisp (use-package whitespace :defer t ;; :hook (prog-mode . whitespace-mode) :config ;; Uncomment this to highlight less than the defaults (e.g. NO spaces) ;; (setq whitespace-style '(face tabs empty trailing lines-tail)) ;; highlight lines with more than `fill-column' characters (setq whitespace-line-column nil)) #+END_SRC ** Zone: Emacs screen saver #+BEGIN_SRC emacs-lisp (use-package zone :defer t :config (defvar zone--window-config nil "Window configuration before running `zone'.") (defadvice zone (before zone-ad-clean-ui) "Maximize window before `zone' starts." (setq zone--window-config (current-window-configuration)) (delete-other-windows) ;; Lock screen when we're in X and `xtrlock' is installed (when (and (eq window-system 'x) (executable-find "xtrlock")) (start-process "xtrlock" nil "xtrlock"))) (defadvice zone (after zone-ad-restore-ui) "Restore window configuration." (when zone--window-config (set-window-configuration zone--window-config) (setq zone--window-config nil))) (ad-activate 'zone)) #+END_SRC ** Sticky-buffer: Lock a buffer to a window From http://lists.gnu.org/archive/html/help-gnu-emacs/2007-05/msg00975.html #+BEGIN_SRC emacs-lisp (define-minor-mode sticky-buffer-mode "Make the current window always display this buffer." :lighter " sticky" (set-window-dedicated-p (selected-window) sticky-buffer-mode)) #+END_SRC * Editor ** Aggressive-indent #+BEGIN_SRC emacs-lisp (use-package aggressive-indent :hook ((emacs-lisp-mode lisp-mode hy-mode clojure-mode css js-mode) . aggressive-indent-mode) :config ;; Normally this functions from `indent.el' always displays an ;; annoying "reporter" message that it's indenting the current region. ;; This patch disables that message (defun indent-region-line-by-line (start end) (save-excursion (setq end (copy-marker end)) (goto-char start) (while (< (point) end) (or (and (bolp) (eolp)) (indent-according-to-mode)) (forward-line 1)) (move-marker end nil)))) #+END_SRC ** Lossage: Live update lossage buffer #+BEGIN_SRC emacs-lisp (defun update-lossage-buffer () "Update the \"Lossage\" buffer. For this to work, visit the lossage buffer, and call M-x rename-buffer Lossage RET" (save-excursion (let ((b (get-buffer "Lossage"))) (when (buffer-live-p b) (with-current-buffer b (revert-buffer nil 'noconfirm)))))) (defun view-lossage-live () "Update lossage" (interactive) (add-hook 'post-command-hook #'update-lossage-buffer nil 'local)) #+END_SRC ** hippie-exp: Expand by fuzzy matching text in open buffers #+BEGIN_SRC emacs-lisp (use-package hippie-exp :bind (("M-/" . hippie-expand)) :config (setq hippie-expand-try-functions-list '(try-expand-dabbrev try-expand-dabbrev-all-buffers try-expand-dabbrev-from-kill try-complete-file-name-partially try-complete-file-name try-expand-all-abbrevs try-expand-list try-expand-line try-complete-lisp-symbol-partially try-complete-lisp-symbol))) #+END_SRC ** rainbow-delimiters: Different color for each parenthesis level #+BEGIN_SRC emacs-lisp (use-package rainbow-delimiters :hook ((emacs-lisp-mode lisp-mode hy-mode clojure-mode cider-repl-mode sql-mode) . rainbow-delimiters-mode)) #+END_SRC ** fancy-narrow: Fancier narrow #+BEGIN_SRC emacs-lisp (use-package fancy-narrow :bind (("C-x n" . fancy-narrow-or-widen-dwim) ("C-x N" . narrow-or-widen-dwim)) :config ;;; toggle narrow or widen (region or defun) with C-x n (defun fancy-narrow-or-widen-dwim (p) "Widen if buffer is narrowed, narrow-dwim otherwise. Dwim means: region, org-src-block, org-subtree, or defun, whichever applies first. Narrowing to org-src-block actually calls `org-edit-src-code'. With prefix P, don't widen, just narrow even if buffer is already narrowed." (interactive "P") ;; (declare (interactive-only)) (cond ((and (fancy-narrow-active-p) (not p)) (fancy-widen)) ((region-active-p) (fancy-narrow-to-region (region-beginning) (region-end))) ((derived-mode-p 'org-mode) ;; `org-edit-src-code' is not a real narrowing ;; command. Remove this first conditional if ;; you don't want it. (cond ((ignore-errors (org-edit-src-code) t)) ((ignore-errors (org-fancy-narrow-to-block) t)) (t (org-narrow-to-subtree)))) ((derived-mode-p 'latex-mode) (LaTeX-narrow-to-environment)) (t (fancy-narrow-to-defun)))) ;; Make consult-line work with fancy-narow (fancy-narrow--advise-function 'consult-line) (defun narrow-or-widen-dwim (p) "Widen if buffer is narrowed, narrow-dwim otherwise. Dwim means: region, org-src-block, org-subtree, or defun, whichever applies first. Narrowing to org-src-block actually calls `org-edit-src-code'. With prefix P, don't widen, just narrow even if buffer is already narrowed." (interactive "P") ;; (declare (interactive-only)) (cond ((and (buffer-narrowed-p) (not p)) (widen)) ((region-active-p) (narrow-to-region (region-beginning) (region-end))) ((derived-mode-p 'org-mode) ;; `org-edit-src-code' is not a real narrowing ;; command. Remove this first conditional if ;; you don't want it. (cond ((ignore-errors (org-edit-src-code) t)) ((ignore-errors (org-narrow-to-block) t)) (t (org-narrow-to-subtree)))) ((derived-mode-p 'latex-mode) (LaTeX-narrow-to-environment)) (t (narrow-to-defun))))) #+END_SRC ** crux: Various small useful utility functions #+BEGIN_SRC emacs-lisp (use-package crux :bind (("C-c U" . crux-view-url) ("C-c f c" . write-file) ("C-c f r" . rename-visited-file) ("C-c f d" . crux-delete-file-and-buffer) ;;("s-k" . crux-kill-whole-line) ;;("s-o" . crux-smart-open-line-above) ("C-a" . crux-move-beginning-of-line) ([(shift return)] . crux-smart-open-line) ([(control shift return)] . crux-smart-open-line-above))) #+END_SRC ** smartparens #+BEGIN_SRC emacs-lisp (use-package smartparens :defer 1 :hook (( emacs-lisp-mode lisp-mode lisp-data-mode clojure-mode cider-repl-mode hy-mode prolog-mode go-mode go-ts-mode cc-mode python-mode typescript-mode json-mode json-ts-mode javascript-mode java-mode java-ts-mode typescript-ts-mode python-ts-mode js-ts-mode json-ts-mode ) . smartparens-strict-mode) ;; :hook (prog-mode . smartparens-strict-mode) :bind (:map smartparens-mode-map ;; This is the paredit mode map minus a few key bindings ;; that I use in other modes (e.g. M-?) ("C-M-f" . sp-forward-sexp) ;; navigation ("C-M-b" . sp-backward-sexp) ("C-M-u" . sp-backward-up-sexp) ("C-M-d" . sp-down-sexp) ("C-M-p" . sp-backward-down-sexp) ("C-M-n" . sp-up-sexp) ("C-w" . whole-line-or-region-sp-kill-region) ("M-s" . sp-splice-sexp) ;; depth-changing commands ("M-r" . sp-splice-sexp-killing-around) ("M-(" . sp-wrap-round) ("C-)" . sp-forward-slurp-sexp) ;; barf/slurp ("C-" . sp-forward-slurp-sexp) ("C-}" . sp-forward-barf-sexp) ("C-" . sp-forward-barf-sexp) ("C-(" . sp-backward-slurp-sexp) ("C-M-" . sp-backward-slurp-sexp) ("C-{" . sp-backward-barf-sexp) ("C-M-" . sp-backward-barf-sexp) ("M-S" . sp-split-sexp) ;; misc ("M-j" . sp-join-sexp)) :config (require 'smartparens-config) (setq sp-base-key-bindings 'paredit) (setq sp-autoskip-closing-pair 'always) ;; Don't insert annoying colon after Python def (setq sp-python-insert-colon-in-function-definitions nil) ;; Always highlight matching parens (show-smartparens-global-mode +1) (setq blink-matching-paren nil) ;; Don't blink matching parens (defun whole-line-or-region-sp-kill-region (prefix) "Call `sp-kill-region' on region or PREFIX whole lines." (interactive "*p") (whole-line-or-region-wrap-beg-end 'sp-kill-region prefix)) ;; Create keybindings to wrap symbol/region in pairs (defun prelude-wrap-with (s) "Create a wrapper function for smartparens using S." `(lambda (&optional arg) (interactive "P") (sp-wrap-with-pair ,s))) (define-key prog-mode-map (kbd "M-(") (prelude-wrap-with "(")) (define-key prog-mode-map (kbd "M-[") (prelude-wrap-with "[")) (define-key prog-mode-map (kbd "M-{") (prelude-wrap-with "{")) (define-key prog-mode-map (kbd "M-\"") (prelude-wrap-with "\"")) (define-key prog-mode-map (kbd "M-'") (prelude-wrap-with "'")) (define-key prog-mode-map (kbd "M-`") (prelude-wrap-with "`")) ;; smart curly braces (sp-pair "{" nil :post-handlers '(((lambda (&rest _ignored) (crux-smart-open-line-above)) "RET"))) (sp-pair "[" nil :post-handlers '(((lambda (&rest _ignored) (crux-smart-open-line-above)) "RET"))) (sp-pair "(" nil :post-handlers '(((lambda (&rest _ignored) (crux-smart-open-line-above)) "RET"))) ;; Don't include semicolon ; when slurping (add-to-list 'sp-sexp-suffix '(java-mode regexp "")) (add-to-list 'sp-sexp-suffix '(java-ts-mode regexp "")) ;; use smartparens-mode everywhere (smartparens-global-mode)) #+END_SRC ** string-inflection: Convert camelCase - kebab-case - snake_case #+BEGIN_SRC emacs-lisp (use-package string-inflection :defer t) #+END_SRC ** Editorconfig Installing =editorconfig-core-c= is not a hard requirement but recommended. #+BEGIN_SRC txt :tangle arch-pkglist.txt editorconfig-core-c #+END_SRC #+BEGIN_SRC emacs-lisp (use-package editorconfig :defer 1 :config (setq editorconfig-trim-whitespaces-mode 'ws-butler-mode) (editorconfig-mode 1)) #+END_SRC ** Misc #+BEGIN_SRC emacs-lisp ;; comment-dwim-2 is a replacement for the Emacs' built-in command ;; comment-dwim which includes more comment features, including: ;; - commenting/uncommenting the current line (or region, if active) ;; - inserting an inline comment ;; - killing the inline comment ;; - reindenting the inline comment ;; comment-dwim-2 picks one behavior depending on the context but ;; contrary to comment-dwim can also be repeated several times to ;; switch between the different behaviors (use-package comment-dwim-2 :bind ("M-;" . comment-dwim-2)) ;; Do action that normally works on a region to the whole line if no region active. ;; That way you can just C-w to copy the whole line for example. (use-package whole-line-or-region :defer 1 :config (whole-line-or-region-global-mode t)) #+END_SRC ** Smart-region: Smart region selection Smart region guesses what you want to select by one command: - If you call this command multiple times at the same position, it expands the selected region (with `er/expand-region'). - Else, if you move from the mark and call this command, it selects the region rectangular (with `rectangle-mark-mode'). - Else, if you move from the mark and call this command at the same column as mark, it adds a cursor to each line (with `mc/edit-lines'). #+BEGIN_SRC emacs-lisp (use-package expand-region :defer t) (use-package smart-region ;; C-SPC is smart-region :bind (([remap set-mark-command] . smart-region))) #+END_SRC ** Selected: One key keybindings for regions when selection active #+BEGIN_SRC emacs-lisp (use-package selected :hook ((text-mode prog-mode) . selected-minor-mode) :init (defvar selected-org-mode-map (make-sparse-keymap)) :bind (:map selected-keymap ("q" . selected-off) ("u" . upcase-region) ("d" . downcase-region) ("w" . count-words-region) ("m" . apply-macro-to-region-lines) ;; multiple cursors ("v" . mc/vertical-align-with-space) ("a" . mc/mark-all-dwim) ("A" . mc/mark-all-like-this) ("m" . mc/mark-more-like-this-extended) ("p" . mc/mark-previous-like-this) ("P" . mc/unmark-previous-like-this) ("S" . mc/skip-to-previous-like-this) ("n" . mc/mark-next-like-this) ("N" . mc/unmark-next-like-this) ("s" . mc/skip-to-next-like-this) ("r" . mc/edit-lines) :map selected-org-mode-map ("t" . org-table-convert-region))) #+END_SRC ** Multiple-cursors #+BEGIN_SRC emacs-lisp (use-package multiple-cursors :bind (("C-c m" . mc/mark-all-dwim) ("C->" . mc/mark-next-like-this) ("C-<" . mc/mark-previous-like-this) :map mc/keymap ("C-x v" . mc/vertical-align-with-space) ("C-x n" . mc-hide-unmatched-lines-mode)) :config (global-unset-key (kbd "M-")) (global-set-key (kbd "M-") 'mc/add-cursor-on-click) (with-eval-after-load 'multiple-cursors-core ;; Immediately load mc list, otherwise it will show as ;; changed as empty in my git repo (mc/load-lists) (define-key mc/keymap (kbd "M-T") 'mc/reverse-regions) (define-key mc/keymap (kbd "C-,") 'mc/unmark-next-like-this) (define-key mc/keymap (kbd "C-.") 'mc/skip-to-next-like-this))) #+END_SRC ** smartrep: Repeat previous command without prefix key FIXME: replace with repeat-mode (Emacs 28.) [[file:/usr/local/share/emacs/29.0.50/etc/NEWS.28::New mode 'repeat-mode' to allow shorter key sequences.]] https://karthinks.com/software/it-bears-repeating/ https://www.reddit.com/r/emacs/comments/vs4jqm/it_bears_repeating_emacs_28_repeat_mode/ #+BEGIN_SRC emacs-lisp (use-package operate-on-number :defer t) (use-package smartrep :defer 5 :config (smartrep-define-key global-map "C-x" '(("{" . shrink-window-horizontally) ("}" . enlarge-window-horizontally) ("^" . enlarge-window) ("%" . shrink-window))) (smartrep-define-key global-map "C-c ." '(("+" . apply-operation-to-number-at-point) ("-" . apply-operation-to-number-at-point) ("*" . apply-operation-to-number-at-point) ("/" . apply-operation-to-number-at-point) ("\\" . apply-operation-to-number-at-point) ("^" . apply-operation-to-number-at-point) ("<" . apply-operation-to-number-at-point) (">" . apply-operation-to-number-at-point) ("#" . apply-operation-to-number-at-point) ("%" . apply-operation-to-number-at-point) ("'" . operate-on-number-at-point)))) #+END_SRC ** copy-as-format: Copy text as GitHub/Slack/JIRA/HipChat/... formatted code #+BEGIN_SRC emacs-lisp (use-package copy-as-format :bind (:map mode-specific-map :prefix-map copy-as-format-prefix-map :prefix "w" ("w" . copy-as-format) ("g" . copy-as-format-github) ("t" . copy-as-format-markdown-table) ("h" . copy-as-format-hipchat-pidgin) ("j" . copy-as-format-jira) ("m" . copy-as-format-markdown) ("o" . copy-as-format-org-mode) ("r" . copy-as-format-rst) ("s" . copy-as-format-slack) ("v" . org-copy-visible)) :config ;; (setq copy-as-format-default "slack") ;; Define own format since pidgin doesn't allow to begin a message with `/code' (defun copy-as-format--hipchat-pidgin (text _multiline) (format "/say /code %s" text)) (add-to-list 'copy-as-format-format-alist '("hipchat-pidgin" copy-as-format--hipchat-pidgin)) (defun copy-as-format-hipchat-pidgin () (interactive) (setq copy-as-format-default "hipchat-pidgin") (copy-as-format)) (defun copy-as-format--markdown-table (text _multiline) (s-replace "--+--" "--|--" text)) (add-to-list 'copy-as-format-format-alist '("markdown-table" copy-as-format--markdown-table)) (defun copy-as-format-hipchat-pidgin () (interactive) (setq copy-as-format-default "markdown-table") (copy-as-format))) #+END_SRC ** zop-to-char: Remove multiple characters at once #+BEGIN_SRC emacs-lisp ;; Replace zap-to-char functionaity with the more powerful zop-to-char (use-package zop-to-char :bind (("M-z" . zop-up-to-char) ("M-Z" . zop-to-char))) #+END_SRC ** Cycle outline and code visibility Minor modes to selectively hide/show code and comment blocks. ~hideshow~ is more for ~prog-modes~ and ~outline~ for ~text-modes~. #+BEGIN_SRC emacs-lisp (use-package hideshow :hook (prog-mode . hs-minor-mode) :bind (:map hs-minor-mode-map ([C-tab] . hs-toggle-hiding))) (use-package outline :hook ((yaml-ts-mode message-mode markdown-mode) . outline-minor-mode) :bind (:map outline-minor-mode-map ([C-tab] . outline-cycle) ([backtab] . outline-cycle-buffer))) (use-package bicycle :disabled t :after outline :bind (:map outline-minor-mode-map ([C-tab] . bicycle-cycle) ([backtab] . bicycle-cycle-global))) #+END_SRC ** TempEL - Simple templates with syntax from Tempo #+BEGIN_SRC emacs-lisp (use-package tempel :bind (("\C-c TAB" . tempel-complete) :map tempel-map ([tab] . tempel-next) ([backtab] . tempel-previous)) :hook ((text-mode prog-mode) . tempel-setup-capf) :config ;; Load templates from etc folder (setq tempel-path (no-littering-expand-etc-file-name "tempel.eld")) ;; Don't auto reload templates. ;; `(setq tempel--path-templates nil)' if you want to force a reload. (setq tempel-auto-reload nil) ;; Setup completion at point (defun tempel-setup-capf () ;; Add the Tempel Capf to `completion-at-point-functions'. ;; `tempel-expand' only triggers on exact matches. Alternatively use ;; `tempel-complete' if you want to see all matches, but then you ;; should also configure `tempel-trigger-prefix', such that Tempel ;; does not trigger too often when you don't expect it. NOTE: We add ;; `tempel-expand' *before* the main programming mode Capf, such ;; that it will be tried first. (setq-local completion-at-point-functions (cons #'tempel-expand completion-at-point-functions)))) #+END_SRC ** edit-indirect: Edit a region in a separate buffer #+BEGIN_SRC emacs-lisp (use-package edit-indirect :bind (("C-c '" . edit-indirect-dwim) :map edit-indirect-mode-map ("C-x n" . edit-indirect-commit)) :config (defvar edit-indirect-string nil) (put 'edit-indirect-string 'end-op (lambda () (while (nth 3 (syntax-ppss)) (forward-char)) (backward-char))) (put 'edit-indirect-string 'beginning-op (lambda () (let ((forward (nth 3 (syntax-ppss)))) (while (nth 3 (syntax-ppss)) (backward-char)) (when forward (forward-char))))) (defun edit-indirect-dwim (beg end &optional display-buffer) "DWIM version of edit-indirect-region. When region is selected, behave like `edit-indirect-region' but when no region is selected and the cursor is in a 'string' syntax mark the string and call `edit-indirect-region' with it." (interactive (if (or (use-region-p) (not transient-mark-mode)) (prog1 (list (region-beginning) (region-end) t) (deactivate-mark)) (if (nth 3 (syntax-ppss)) (list (beginning-of-thing 'edit-indirect-string) (end-of-thing 'edit-indirect-string) t) (user-error "No region marked and not inside a string.")))) (edit-indirect-region beg end display-buffer)) (defvar edit-indirect-guess-mode-history nil) (defun edit-indirect-guess-mode-fn (_buffer _beg _end) (let* ((lang (completing-read "Mode: " '("gfm" "rst" "emacs-lisp" "clojure" "python" "sql" "typescript" "js2" "web" "scss") nil nil nil 'edit-indirect-guess-mode-history)) (mode-str (concat lang "-mode")) (mode (intern mode-str))) (unless (functionp mode) (error "Invalid mode `%s'" mode-str)) (funcall mode))) (setq edit-indirect-guess-mode-function #'edit-indirect-guess-mode-fn)) #+END_SRC ** with-editor: Use local Emacs instance as $EDITOR (e.g. in `git commit' or `crontab -e') #+BEGIN_SRC emacs-lisp (use-package with-editor ;; Use local Emacs instance as $EDITOR (e.g. in `git commit' or `crontab -e') :hook ((shell-mode eshell-mode vterm-mode term-exec) . with-editor-export-editor)) #+END_SRC ** Move text Move current line or region with M-up or M-down. #+BEGIN_SRC emacs-lisp (use-package move-text :bind (([(control shift up)] . move-text-up) ([(control shift down)] . move-text-down) ([(meta shift up)] . move-text-up) ([(meta shift down)] . move-text-down))) #+END_SRC ** Grep (wgrep/rg/ag) #+BEGIN_SRC emacs-lisp (use-package wgrep :bind (:map grep-mode-map ("C-x C-q" . wgrep-change-to-wgrep-mode)) :config (setq wgrep-auto-save-buffer t)) (use-package wgrep-ag :after wgrep) #+END_SRC ** Grep-context: Get more context for compilation/grep buffers by pressing +/- #+BEGIN_SRC emacs-lisp (use-package grep-context :bind (:map compilation-mode-map ("+" . grep-context-more-around-point) ("-" . grep-context-less-around-point) :map grep-mode-map ("+" . grep-context-more-around-point) ("-" . grep-context-less-around-point))) #+END_SRC ** Search/Replace *** Re-builder #+BEGIN_SRC emacs-lisp ;; You can change syntax in regex-builder with "C-c TAB" ;; "read" is 'code' syntax ;; "string" is already read and no extra escaping. Like what Emacs prompts interactively (use-package re-builder :defer t :config (setq reb-re-syntax 'string)) #+END_SRC *** Visual-regex: #+BEGIN_SRC emacs-lisp (use-package visual-regexp :bind (:map mode-specific-map :prefix-map visual-regexp-prefix-map :prefix "r" ("r" . vr/query-replace) ("R" . vr/replace) ("m" . vr/mc-mark) ("s" . query-replace))) (use-package visual-regexp-steroids :after visual-regexp) #+END_SRC ** deadgrep: Interface for ripgrep #+BEGIN_SRC emacs-lisp (use-package deadgrep :bind (("" . deadgrep) :map deadgrep-mode-map ("C-x C-q" . wgrep-change-to-wgrep-mode)) :hook (deadgrep-finished . wgrep-deadgrep-setup)) #+END_SRC ** Company: Auto completion #+BEGIN_SRC emacs-lisp (use-package company :hook (((prog-mode markdown-mode rst-mode) . company-mode)) :bind (:map company-active-map ([return] . nil) ("RET" . nil) ("TAB" . company-complete-selection) ([tab] . company-complete-selection) ;; ("S-TAB" . company-select-previous) ;; ([backtab] . company-select-previous) ("C-j" . company-complete-selection)) :config (setq company-idle-delay 0.1) (setq company-tooltip-limit 10) (setq company-minimum-prefix-length 1) ;; Don't display icons (setq company-format-margin-function nil) ;; Aligns annotation to the right hand side (setq company-tooltip-align-annotations t) ;;(setq company-dabbrev-downcase nil) ;; invert the navigation direction if the the completion popup-isearch-match ;; is displayed on top (happens near the bottom of windows) ;;(setq company-tooltip-flip-when-above t) ;; Better sorting of candidates? (setq company-transformers '(company-sort-prefer-same-case-prefix company-sort-by-occurrence)) (use-package company-quickhelp :disabled t :config (company-quickhelp-mode 1)) ;; Add yasnippet support for all company backends (defvar company-mode/enable-yas t "Enable yasnippet for all backends.") (defun company-mode/backend-with-yas (backend) (if (or (not company-mode/enable-yas) (and (listp backend) (member 'company-yasnippet backend))) backend (append (if (consp backend) backend (list backend)) '(:with company-yasnippet)))) ;; (setq company-backends (mapcar #'company-mode/backend-with-yas company-backends)) ) #+END_SRC *** company-tng company-tng (tab and go) allows you to use TAB to both select a completion candidate from the list and to insert it into the buffer. It cycles the candidates like `yank-pop' or `dabbrev-expand' or Vim: Pressing TAB selects the first item in the completion menu and inserts it in the buffer. Pressing TAB again selects the second item and replaces the inserted item with the second one. This can continue as long as the user wishes to cycle through the menu. #+BEGIN_SRC emacs-lisp (use-package company-tng :disabled t :after company :bind (:map company-active-map ([return] . nil) ("RET" . nil) ("TAB" . company-select-next) ([tab] . company-select-next) ("S-TAB" . company-select-previous) ([backtab] . company-select-previous) ("C-j" . company-complete-selection)) :config (company-tng-mode)) #+END_SRC *** company-shell: Auto completion for shell #+BEGIN_SRC emacs-lisp (use-package company-shell :hook ((sh-mode shell-mode) . sh-mode-init) :config (defun sh-mode-init () (setq-local company-backends '((company-shell company-shell-env company-files company-dabbrev-code company-capf))))) #+END_SRC ** Discover-my-major: Display a list of keybindings for the current major mode #+BEGIN_SRC emacs-lisp (use-package discover-my-major :bind (("C-h C-m" . discover-my-major))) #+END_SRC ** Helpful: A better help buffer Helpful is a replacement for ~*help*~ buffers that provides much more contextual information. #+BEGIN_SRC emacs-lisp (use-package helpful :bind (("C-h f" . helpful-callable) ("C-h v" . helpful-variable) ("C-h s" . describe-symbol) ("C-h k" . helpful-key) ;; ("C-c h f" . helpful-callable) ;; ("C-c h v" . helpful-variable) ;; ("C-c h c" . helpful-command) ;; ("C-c h m" . helpful-macro) ("" . backward-button) :map helpful-mode-map ("M-?" . helpful-at-point) ("RET" . helpful-jump-to-org) :map emacs-lisp-mode-map ("M-?" . helpful-at-point) :map lisp-interaction-mode-map ; Scratch buffer ("M-?" . helpful-at-point)) :config (defun helpful-visit-reference () "Go to the reference at point." (interactive) (let* ((sym helpful--sym) (path (get-text-property (point) 'helpful-path)) (pos (get-text-property (point) 'helpful-pos)) (pos-is-start (get-text-property (point) 'helpful-pos-is-start))) (when (and path pos) ;; If we're looking at a source excerpt, calculate the offset of ;; point, so we don't just go the start of the excerpt. (when pos-is-start (save-excursion (let ((offset 0)) (while (and (get-text-property (point) 'helpful-pos) (not (eobp))) (backward-char 1) (setq offset (1+ offset))) ;; On the last iteration we moved outside the source ;; excerpt, so we overcounted by one character. (setq offset (1- offset)) ;; Set POS so we go to exactly the place in the source ;; code where point was in the helpful excerpt. (setq pos (+ pos offset))))) (find-file path) (when (or (< pos (point-min)) (> pos (point-max))) (widen)) (goto-char pos) (recenter 0) (save-excursion (let ((defun-end (scan-sexps (point) 1))) (while (re-search-forward (rx-to-string `(seq symbol-start ,(symbol-name sym) symbol-end)) defun-end t) (helpful--flash-region (match-beginning 0) (match-end 0))))) t))) (defun helpful-jump-to-org () (interactive) (when (helpful-visit-reference) (org-babel-tangle-jump-to-org)))) #+END_SRC ** Elisp-demos: Inject elisp demos into help buffer #+BEGIN_SRC emacs-lisp (use-package elisp-demos :after helpful :config (advice-add 'helpful-update :after #'elisp-demos-advice-helpful-update)) #+END_SRC ** Undo #+BEGIN_SRC emacs-lisp (use-package undo-fu-session :hook (after-init . undo-fu-session-global-mode) :config (setq undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'" "/git-rebase-todo\\'"))) (use-package vundo :config (setq vundo-compact-display t) (setq vundo-glyph-alist vundo-unicode-symbols)) #+END_SRC * Spell checker ** Jinx #+BEGIN_SRC emacs-lisp (use-package jinx :hook (((text-mode prog-mode conf-mode) . jinx-mode)) :bind (([remap ispell-word] . jinx-correct) ;; ispell-word bound to "M-$" ("C-M-$" . jinx-languages))) #+END_SRC ** Ispell #+BEGIN_SRC emacs-lisp (use-package ispell :defer t ;; :bind (("C-c I c" . ispell-comments-and-strings) ;; ("C-c I d" . ispell-change-dictionary) ;; ("C-c I k" . ispell-kill-ispell) ;; ("C-c I m" . ispell-message) ;; ("C-c I r" . ispell-region)) :config ;; Spell check camel case strings (setq ispell-program-name "aspell" ;; force the English dictionary, support Camel Case spelling check (tested with aspell 0.6) ispell-extra-args '("--sug-mode=ultra" "--run-together" "--run-together-limit=5" "--run-together-min=2"))) #+END_SRC ** Flyspell #+BEGIN_SRC emacs-lisp (use-package flyspell :disabled t :hook ((prog-mode . flyspell-prog-mode) ((org-mode mu4e-compose-mode markdown-mode rst-mode) . flyspell-mode)) :config ;; remove flyspess 'C-,' and `C-;' keybinding so we can use it for embark (unbind-key "C-;" flyspell-mode-map) (unbind-key "C-," flyspell-mode-map)) #+END_SRC ** Language tool: Grammar, Style and Spell Checker See https://languagetool.org/ and http://wiki.languagetool.org/finding-errors-using-n-gram-data You need to install the external languagetool java app and optional grammar files. #+BEGIN_SRC txt :tangle arch-pkglist.txt languagetool languagetool-ngrams-de #+END_SRC #+BEGIN_SRC emacs-lisp (use-package langtool :defer t :config ;; Langtool classpath in arch is not set per environment variable ;; Set classpath like in the `/usr/bin/languagetool' script (setq langtool-java-classpath (concat "/usr/share/languagetool:" (s-join ":" (directory-files "/usr/share/java/languagetool/" t ".jar\\'"))))) #+END_SRC ** Guess-language: Automatically guess languages and switch ispell #+BEGIN_SRC emacs-lisp (use-package guess-language ;; Only guess language for emails :hook (mu4e-compose-mode . guess-language-mode) :config (setq guess-language-langcodes '((en . ("en_GB" "English")) (de . ("de_DE" "German")))) (setq guess-language-languages '(en de)) (setq guess-language-min-paragraph-length 35)) #+END_SRC * Project Management ** Project.el #+BEGIN_SRC emacs-lisp (use-package project :bind-keymap (("s-p" . project-prefix-map) ; projectile-command-map ("C-c p" . project-prefix-map)) :bind (:map project-prefix-map ("SPC" . consult-project-extra-find) ("B" . babashka-find-project-file) ("d" . project-dired) ("D" . project-edit-deps-edn) ("s" . consult-ripgrep) ("t" . project-toggle-test) ("E" . project-edit-dir-locals) ("P" . project-run-python) ("T" . project-vterm)) :config ;; Ignore clj-kondo and cljs-runtime folder by default (setq project-vc-ignores '(".clj-kondo/" "cljs-runtime/")) (defun project-edit-dir-locals () "Open buffer with .dir-locals.el for current project." (interactive) (->> (project-current) (project-root) (expand-file-name ".dir-locals.el") (find-file))) (defun project-edit-deps-edn () "Open buffer with deps.edn for current project." (interactive) (->> (project-current) (project-root) (expand-file-name "deps.edn") (find-file))) (defun project-toggle-test () "Toggle between source and test buffers." (interactive) (let* ((root (project-root (project-current t))) (fn (file-relative-name (buffer-file-name) root)) (test-p (s-contains-p "test" fn)) (dir (file-name-directory fn)) (base (file-name-base fn)) (ext (file-name-extension fn)) (toggle-dir (if test-p (replace-regexp-in-string "/test" "/src" dir) (replace-regexp-in-string "/src" "/test" dir))) (toggle-base (if test-p (replace-regexp-in-string "_test$" "" base) (concat base "_test"))) (toggle-fn (concat root toggle-dir toggle-base "." ext)) (buf (find-buffer-visiting toggle-fn))) (if buf (pop-to-buffer buf) (if (file-exists-p toggle-fn) (find-file toggle-fn) (message "Test file not found"))))) (defun project-run-python () "Run a dedicated inferior Python process for the current project. Like `run-python' started with a prefix-arg and then choosing to created a dedicated process for the project." (interactive) (run-python (python-shell-calculate-command) 'project t)) (defun project-vterm () (interactive) (let* ((default-directory (project-root (project-current t))) (vterm-buffer-name (project-prefixed-buffer-name "vterm")) (vterm-buffer (get-buffer vterm-buffer-name))) (if (and vterm-buffer (not current-prefix-arg)) (pop-to-buffer vterm-buffer (bound-and-true-p display-comint-buffer-action)) (vterm)))) ;; (add-to-list 'project-switch-commands '(project-vterm "Vterm") t) ;; (add-to-list 'project-switch-commands '(magit-project-status "Magit" ?m) t) ;; Don't show a dispatch menu when switching projects but always choose project buffer/file (setq project-switch-commands #'consult-project-extra-find) (add-to-list 'project-kill-buffer-conditions '(major-mode . vterm-mode))) #+END_SRC ** Projectile #+BEGIN_SRC emacs-lisp (use-package projectile :disabled t ;; :defer t ;; :bind-keymap (("s-p" . projectile-command-map) ;; ;; ("C-c p" . projectile-command-map) ;; ) :bind (:map projectile-command-map ("SPC" . consult-project-extra-find) ("s s" . consult-ripgrep)) :init ;; Allow all file-local values for project root (put 'projectile-project-root 'safe-local-variable 'stringp) ;; Don't show "Projectile" as liter when not in a project (setq-default projectile-mode-line-prefix "") :config (add-to-list 'projectile-other-file-alist '("py" "sql" "py")) (add-to-list 'projectile-other-file-alist '("sql" "py")) ;; Register a java project type that uses gradle and has src and test dirs set (projectile-register-project-type 'java '("gradlew") :compile "./gradlew build" :test "./gradlew test" :test-suffix "Test" :src-dir "src/main" :test-dir "src/test/") ;; Shorten the mode line to only "P" and do not include the project type (defun projectile-short-mode-line () "Short version of the default projectile mode line." (format " P[%s]" (projectile-project-name))) (setq projectile-mode-line-function 'projectile-short-mode-line) ;; https://sideshowcoder.com/2017/10/24/projectile-and-tramp/ ;; (defadvice projectile-on (around exlude-tramp activate) ;; "This should disable projectile when visiting a remote file" ;; (unless (--any? (and it (file-remote-p it)) ;; (list ;; (buffer-file-name) ;; list-buffers-directory ;; default-directory ;; dired-directory)) ;; ad-do-it)) ;; (counsel-projectile-mode) ;; cache projectile project files ;; projectile-find-files will be much faster for large projects. ;; C-u C-c p f to clear cache before search. (setq projectile-enable-caching nil) (projectile-mode)) #+END_SRC ** Treemacs: A tree layout file explorer #+BEGIN_SRC emacs-lisp (use-package treemacs :bind (([f8] . treemacs-toggle-or-select) :map treemacs-mode-map ("C-t a" . treemacs-add-project-to-workspace) ("C-t d" . treemacs-remove-project) ("C-t r" . treemacs-rename-project) ;; If we only hide the treemacs buffer (default binding) then, when we switch ;; a frame to a different project and toggle treemacs again we still get the old project ("q" . treemacs-kill-buffer)) :config (defun treemacs-toggle-or-select (&optional arg) "Initialize or toggle treemacs. - If the treemacs window is visible and selected, hide it. - If the treemacs window is visible select it with cursor on current file. - If a treemacs buffer exists, but is not visible show it. - If no treemacs buffer exists for the current frame create and show it. - If the workspace is empty additionally ask for the root path of the first project to add. With one `C-u' prefix argument, display current project exclusively. With two `C-u' `C-u' prefix args, add and display current project." (interactive "p") (cond ((or (not arg) (eq arg 1)) (pcase (treemacs-current-visibility) ('visible (if (string-prefix-p treemacs--buffer-name-prefix (buffer-name)) (delete-window (treemacs-get-local-window)) (when (buffer-file-name) (treemacs-find-file)) (treemacs--select-visible-window))) ('exists (treemacs-select-window)) ('none (treemacs--init)))) ((eq arg 4) (treemacs-add-and-display-current-project-exclusively)) ((eq arg 16) (treemacs-add-and-display-current-project)))) (defun treemacs-ignore-python-files (file _) (or (s-ends-with-p ".pyc" file) (string= file "__pycache__"))) (add-to-list 'treemacs-ignored-file-predicates 'treemacs-ignore-python-files) ;; Read input from minibuffer instead of childframe (which requires an extra package) (setq treemacs-read-string-input 'from-minibuffer) (setq treemacs-follow-after-init t treemacs-indentation 1 treemacs-width 30 treemacs-collapse-dirs 5 treemacs-silent-refresh nil treemacs-is-never-other-window t) (treemacs-filewatch-mode t) (treemacs-follow-mode -1) (treemacs-git-mode 'simple)) (use-package treemacs-projectile :after (treemacs) :bind (:map treemacs-mode-map ("C-p p" . nil) ("C-p" . nil) ; I often still type C-p for UP ("C-t p" . treemacs-projectile)) :config (setq treemacs-header-function #'treemacs-projectile-create-header)) ;; Use magit hooks to notify treemacs of git changes (use-package treemacs-magit :after treemacs) #+END_SRC * Ivy ** Ivy #+BEGIN_SRC emacs-lisp (use-package ivy :disabled t :bind (("C-x b" . dakra-ivy-switch-buffer) ("C-x B" . ivy-switch-buffer-other-window) ("C-c C-r" . ivy-resume) ("C-c e" . ivy-switch-buffer-eshell) ("M-H" . ivy-resume) :map ivy-minibuffer-map ("C-j" . ivy-partial-or-done) ("" . ivy-call) ("C-r" . ivy-previous-line-or-history) ("M-r" . ivy-reverse-i-search)) :config (defun ivy-ignore-non-eshell-buffers (str) (let ((buf (get-buffer str))) (if buf (with-current-buffer buf (not (eq major-mode 'eshell-mode))) t))) (defun ivy-switch-buffer-eshell () "Like ivy-switch-buffer but only shows eshell buffers." (interactive) (let ((ivy-ignore-buffers (append ivy-ignore-buffers '(ivy-ignore-non-eshell-buffers)))) (ivy-switch-buffer))) (defun ivy-ignore-exwm-buffers (str) (let ((buf (get-buffer str))) (when buf (with-current-buffer buf (or (file-remote-p (or (buffer-file-name) default-directory)) (eq major-mode 'exwm-mode)))))) (defun ivy-ignore-non-exwm-buffers (str) (let ((buf (get-buffer str))) (if buf (with-current-buffer buf (or (file-remote-p (or (buffer-file-name) default-directory)) (not (eq major-mode 'exwm-mode)))) t))) (defun ivy-switch-buffer-exwm () "Like ivy-switch-buffer but only shows EXWM buffers." (interactive) (let ((ivy-ignore-buffers (append ivy-ignore-buffers '(ivy-ignore-non-exwm-buffers)))) (ivy-switch-buffer))) (defun ivy-switch-buffer-non-exwm () "Like ivy-switch-buffer but hides all EXWM buffers." (interactive) (let ((ivy-ignore-buffers (append ivy-ignore-buffers '(ivy-ignore-exwm-buffers)))) (ivy-switch-buffer))) (defun dakra-ivy-switch-buffer (p) "Like ivy-switch-buffer but by defaults hides all EXWM buffers. With one prefix arg, show only EXWM buffers. With two, show all buffers." (interactive "p") (back-button-push-mark-local-and-global) (cl-case p (1 (ivy-switch-buffer-non-exwm)) (4 (ivy-switch-buffer-exwm)) (16 (ivy-switch-buffer)))) ;; Extend searching to bookmarks and recentf (setq ivy-use-virtual-buffers t) ;; Show full path for virtual buffers (setq ivy-virtual-abbreviate 'full) ;; Display count displayed and total (setq ivy-count-format "%d/%d ") (setq ivy-height 18) ;; Press C-p when you're on the first candidate to select your input (setq ivy-use-selectable-prompt t) ;; Don't quit ivy when pressing backspace on already empty input (setq ivy-on-del-error-function nil) (ivy-mode 1)) (use-package ivy-hydra :disabled t :after (ivy hydra) :config (setq ivy-read-action-function #'ivy-hydra-read-action)) #+END_SRC ** Counsel For better search install =ripgrep= (rg) and alternative =silversearcher= (ag). #+BEGIN_SRC txt :tangle arch-pkglist.txt ripgrep silversearcher #+END_SRC #+BEGIN_SRC emacs-lisp (use-package counsel :disabled t :bind (("C-s" . counsel-grep-or-swiper) ("C-o" . nil) ; Remove old keybinding (open-line) ("C-o o" . counsel-org-agenda-headlines) ("C-o g" . counsel-org-agenda-headlines) ("C-o G" . counsel-org-goto) ("C-c o o" . counsel-org-agenda-headlines) ("C-c o g" . counsel-org-agenda-headlines) ("C-c o G" . counsel-org-goto) ("C-x C-f" . counsel-find-file) ("M-y" . counsel-yank-pop-selection) ("M-i" . counsel-imenu) ("M-x" . counsel-M-x) ("s-d" . counsel-linux-app)) :init (define-key minibuffer-local-map (kbd "M-r") 'counsel-minibuffer-history) :config (defun counsel-yank-pop-selection (&optional arg) "Ivy replacement for `yank-pop'. With a plain prefix argument (\\[universal-argument]), temporarily toggle the value of `counsel-yank-pop-after-point'. Any other value of ARG has the same meaning as in `yank-pop', but `counsel-yank-pop-preselect-last' determines its default value. See also `counsel-yank-pop-filter' for how to filter candidates. Note: Duplicate elements of `kill-ring' are always deleted." ;; Do not specify `*' to allow browsing `kill-ring' in read-only buffers (interactive "P") (when arg (select-add-selection-to-kill-ring)) (let ((kills (or (counsel--yank-pop-kills) (error "Kill ring is empty or blank"))) (preselect (let (interprogram-paste-function) (current-kill 0 t)))) (unless (eq last-command 'yank) (push-mark)) (ivy-read "kill-ring: " kills :require-match t :preselect preselect :action #'counsel-yank-pop-action :caller 'counsel-yank-pop))) ;; Set ivy initial inputs after loading counsel because counsel adds ;; it's own commands here after it's loaded and we don't want ;; E.g. counsel-M-x to always start with "^" (setq ivy-initial-inputs-alist '((Man-completion-table . "^") (woman . "^"))) ;; Hide pyc and elc files by default from `counsel-find-file' (setq counsel-find-file-ignore-regexp "\\.\\(pyc\\|elc\\)\\'") ;; Add action to open file literally ;; This makes opening of files with minified js or sql dumps possible. (ivy-add-actions 'counsel-find-file `(("l" find-file-literally "Open literally"))) ;; Use rg as backend for counsel-git (setq counsel-git-cmd "rg -S --files") ;; Use rg even for single files (setq counsel-grep-base-command "rg -S -M 240 --no-heading --line-number --color never %s %s") ;; Make ivy faster/more responsive ;; Update filter every 40ms and wait 20ms to refresh dynamic collection (setq counsel-async-filter-update-time 40000) (setq ivy-dynamic-exhibit-delay-ms 20) ;; Set help function to helpful- (setq counsel-describe-function-function #'helpful-function) (setq counsel-describe-variable-function #'helpful-variable) (setq counsel-describe-symbol-function #'helpful-symbol) (counsel-mode 1)) #+END_SRC * Minibuffer interaction Some useful configurations (from the Vertico readme) #+BEGIN_SRC emacs-lisp ;; Add prompt indicator to `completing-read-multiple'. ;; We display [CRM], e.g., [CRM,] if the separator is a comma. (defun crm-indicator (args) (cons (format "[CRM%s] %s" (replace-regexp-in-string "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" crm-separator) (car args)) (cdr args))) (advice-add #'completing-read-multiple :filter-args #'crm-indicator) ;; Do not allow the cursor in the minibuffer prompt (setq minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt)) (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) ;; Emacs 28: Hide commands in M-x which do not work in the current mode. (setq read-extended-command-predicate #'command-completion-default-include-p) ;; Enable recursive minibuffers (setq enable-recursive-minibuffers t) #+END_SRC ** Orderless #+BEGIN_SRC emacs-lisp (use-package orderless :init ;; Configure a custom style dispatcher (see the Consult wiki, orderless readme) ;; Add style dispatcher that removes entries if pattern starts or ends with ! (defun orderless-without-if-bang (pattern _index _total) (cond ((equal "!" pattern) '(orderless-literal . "")) ((string-suffix-p "!" pattern) `(orderless-without-literal . ,(substring pattern 0 -1))) ((string-prefix-p "!" pattern) `(orderless-without-literal . ,(substring pattern 1))))) (setq orderless-style-dispatchers '(orderless-without-if-bang)) ;; Make space separator escapable with backslash ;; (setq orderless-component-separator #'orderless-escapable-split-on-space) (setq completion-styles '(orderless basic) completion-category-defaults '((cider (styles basic))) ;; https://github.com/clojure-emacs/cider/pull/3226 completion-category-overrides '((file (styles basic partial-completion))))) #+END_SRC ** Vertico #+BEGIN_SRC emacs-lisp (use-package vertico :init (vertico-mode) ;; Different scroll margin ;; (setq vertico-scroll-margin 0) ;; Show more candidates ;; (setq vertico-count 20) ;; Grow and shrink the Vertico minibuffer ;; (setq vertico-resize t) ;; Optionally enable cycling for `vertico-next' and `vertico-previous'. ;; (setq vertico-cycle t) ) #+END_SRC *** vertico-buffer #+BEGIN_SRC emacs-lisp (use-package vertico-buffer :after vertico :config (setq vertico-buffer-display-action '(display-buffer-below-selected (window-height . ,(+ 3 vertico-count)))) (vertico-buffer-mode)) #+END_SRC *** vertico-multiform #+BEGIN_SRC emacs-lisp (use-package vertico-multiform :after vertico :config (setq vertico-multiform-commands '((consult-line buffer) (consult-buffer buffer) (consult-org-heading buffer) (consult-imenu buffer) (consult-project-buffer buffer) (consult-project-extra-find buffer))) ;; (add-to-list 'vertico-multiform-categories ;; '(jinx grid (vertico-grid-annotate . 35))) (vertico-multiform-mode)) #+END_SRC *** vertico-repeat #+BEGIN_SRC emacs-lisp (use-package vertico-repeat :after vertico :hook (minibuffer-setup . vertico-repeat-save) :bind ("M-r" . vertico-repeat)) #+END_SRC ** Consult #+BEGIN_SRC emacs-lisp (use-package consult ;; Replace bindings. Lazily loaded due by `use-package'. :bind (;; C-c bindings (mode-specific-map) ("C-c h" . consult-history) ("C-c M" . consult-mode-command) ("C-c k" . consult-kmacro) ;; C-x bindings (ctl-x-map) ("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command ("C-x b" . consult-buffer) ;; orig. switch-to-buffer ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window ("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame ("C-x r b" . consult-bookmark) ;; orig. bookmark-jump ;; Custom M-# bindings for fast register access ("M-#" . consult-register-load) ("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated) ("C-M-#" . consult-register) ;; Other custom bindings ("M-y" . consult-yank-pop) ;; orig. yank-pop (" a" . consult-apropos) ;; orig. apropos-command ;; M-g bindings (goto-map) ("M-g e" . consult-compile-error) ;; ("M-g f" . consult-flymake) ;; Alternative: consult-flycheck ("M-g g" . consult-goto-line) ;; orig. goto-line ("M-g M-g" . consult-goto-line) ;; orig. goto-line ("M-g o" . consult-outline) ;; Alternative: consult-org-heading ("M-g m" . consult-mark) ("M-g k" . consult-global-mark) ("M-i" . consult-imenu) ("M-g i" . consult-imenu) ("M-g I" . consult-imenu-multi) ;; M-s bindings (search-map) ("M-s d" . consult-find) ("M-s D" . consult-locate) ("M-s g" . consult-grep) ("M-s G" . consult-git-grep) ("M-s r" . consult-ripgrep) ("M-s l" . consult-line) ("M-s L" . consult-line-multi) ("M-s m" . consult-multi-occur) ("M-s k" . consult-keep-lines) ("M-s u" . consult-focus-lines) ;; Isearch integration ("C-s" . consult-line) ("M-s e" . consult-isearch-history) :map isearch-mode-map ("M-e" . consult-isearch-history) ;; orig. isearch-edit-string ("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string ("M-s l" . consult-line) ;; needed by consult-line to detect isearch ("M-s L" . consult-line-multi)) ;; needed by consult-line to detect isearch ;; Enable automatic preview at point in the *Completions* buffer. This is ;; relevant when you use the default completion UI. You may want to also ;; enable `consult-preview-at-point-mode` in Embark Collect buffers. :hook (completion-list-mode . consult-preview-at-point-mode) ;; The :init configuration is always executed (Not lazy) :init ;; Optionally configure the register formatting. This improves the register ;; preview for `consult-register', `consult-register-load', ;; `consult-register-store' and the Emacs built-ins. (setq register-preview-delay 0 register-preview-function #'consult-register-format) ;; Optionally tweak the register preview window. ;; This adds thin lines, sorting and hides the mode line of the window. (advice-add #'register-preview :override #'consult-register-window) ;; Use Consult to select xref locations with preview (setq xref-show-xrefs-function #'consult-xref xref-show-definitions-function #'consult-xref) ;; Configure other variables and modes in the :config section, ;; after lazily loading the package. :config ;; Optionally configure preview. The default value ;; is 'any, such that any key triggers the preview. ;; (setq consult-preview-key 'any) ;; (setq consult-preview-key (kbd "M-.")) ;; (setq consult-preview-key (list (kbd "") (kbd ""))) ;; For some commands and buffer sources it is useful to configure the ;; :preview-key on a per-command basis using the `consult-customize' macro. (consult-customize consult-theme :preview-key "M-." consult-ripgrep consult-git-grep consult-grep consult-bookmark consult-recent-file consult-xref consult--source-recent-file consult--source-project-recent-file consult--source-bookmark :preview-key '(:debounce 0.2 any)) ;; Optionally configure the narrowing key. ;; Both < and C-+ work reasonably well. (setq consult-narrow-key "<") ;; (kbd "C-+") ;; Optionally make narrowing help available in the minibuffer. ;; You may want to use `embark-prefix-help-command' or which-key instead. ;; (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help) ;; Optionally configure a function which returns the project root directory. ;; There are multiple reasonable alternatives to chose from. ;;;; 1. project.el (project-roots) ;; (setq consult-project-root-function ;; (lambda () ;; (when-let (project (project-current)) ;; (car (project-roots project))))) ;;;; 2. projectile.el (projectile-project-root) ;; (autoload 'projectile-project-root "projectile") ;; (setq consult-project-root-function #'projectile-project-root) ;;;; 3. vc.el (vc-root-dir) ;; (setq consult-project-root-function #'vc-root-dir) ;;;; 4. locate-dominating-file ;; (setq consult-project-root-function (lambda () (locate-dominating-file "." ".git"))) ;; (defun consult-buffer-project () ;; "Like consult-bufffer but narrowed to only project buffers." ;; (interactive) ;; (let ((unread-command-events (append unread-command-events (list ?p 32)))) ;; (consult-buffer))) (defun mode-buffer-exists-p (mode) (seq-some (lambda (buf) (provided-mode-derived-p (buffer-local-value 'major-mode buf) mode)) (buffer-list))) (defvar eshell-source `(:category 'consult-new :face 'font-lock-constant-face :action ,(lambda (_) (eshell)) :items ,(lambda () (unless (mode-buffer-exists-p 'eshell-mode) '("*eshell* (new)"))))) (defvar term-source `(:category 'consult-new :face 'font-lock-constant-face :action ,(lambda (_) (vterm t)) :items ,(lambda () (unless (mode-buffer-exists-p 'vterm-mode) '("*vterm* (new)"))))) (add-to-list 'consult-buffer-sources 'eshell-source 'append) (add-to-list 'consult-buffer-sources 'term-source 'append) ) ;; `consult-project-extra-find' command used in projectile-command-map (use-package consult-project-extra :defer t) #+END_SRC ** Marginalia in the minibuffer Enable richer annotations using the Marginalia package. #+BEGIN_SRC emacs-lisp (use-package marginalia ;; Either bind `marginalia-cycle` globally or only in the minibuffer :bind (("M-A" . marginalia-cycle) :map minibuffer-local-map ("M-A" . marginalia-cycle)) :init (marginalia-mode)) #+END_SRC ** Embark: Emacs Mini-Buffer Actions Rooted in Keymaps #+BEGIN_SRC emacs-lisp (use-package embark :bind (("C-." . embark-act) ;; pick some comfortable binding ("C-," . embark-dwim) ;; good alternative: M-. ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' :init ;; Optionally replace the key help with a completing-read interface (setq prefix-help-command #'embark-prefix-help-command) :config ;; Hide the mode line of the Embark live/completions buffers (add-to-list 'display-buffer-alist '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" nil (window-parameters (mode-line-format . none))))) ;; Consult users will also want the embark-consult package. (use-package embark-consult :after (embark consult) :demand t ; only necessary if you have the hook below ;; if you want to have consult previews as you move around an ;; auto-updating embark collect buffer :hook (embark-collect-mode . consult-preview-at-point-mode)) #+END_SRC * Navigation #+BEGIN_SRC emacs-lisp (use-package bookmark :defer t :config (setq bookmark-save-flag 1)) ;; Nicer mark ring navigation (C-x C-SPC or C-x C-Left/Right) (use-package back-button :defer 2 :config (back-button-mode)) #+END_SRC #+BEGIN_SRC emacs-lisp ;; Goto last change (use-package goto-chg :bind (("C-c \\" . goto-last-change) ("C-c |" . goto-last-change-reverse))) #+END_SRC ** Avy: Quickly jump to any character on screen #+BEGIN_SRC emacs-lisp (use-package avy :bind ("C-;" . avy-goto-char-timer) :config (setq avy-background t) (setq avy-style 'at-full) (setq avy-timeout-seconds 0.2)) #+END_SRC ** Ace-link: Quickly jump to any link on screen #+BEGIN_SRC emacs-lisp (use-package ace-link :after org ;; Otherwise can't bind to org-mode-map :bind (:map Info-mode-map ("o" . ace-link-info) :map help-mode-map ("o" . ace-link-help) :map compilation-mode-map ("o" . ace-link-compilation) :map org-mode-map ("M-o" . ace-link-org)) :init (eval-after-load "woman" `(define-key woman-mode-map ,"o" 'ace-link-woman)) (eval-after-load "eww" `(progn (define-key eww-link-keymap ,"o" 'ace-link-eww) (define-key eww-mode-map ,"o" 'ace-link-eww)))) #+END_SRC ** Ace-window: Select/move/swap windows #+BEGIN_SRC emacs-lisp (use-package ace-window :bind ("s-a" . ace-window)) #+END_SRC ** Dumb-jump: Jump to definition with smart regex searches Dumb Jump is an Emacs "jump to definition" package with support for 40+ programming languages that favors "just working" over speed or accuracy. This means minimal -- and ideally zero -- configuration with absolutely no stored indexes (TAGS) or persistent background processes. Dumb Jump performs best with The Silver Searcher `ag` or ripgrep `rg` installed. #+BEGIN_SRC emacs-lisp (use-package dumb-jump :bind (("M-g o" . xref-find-definitions-other-window) ("M-g j" . xref-find-definitions) ("M-g p" . xref-go-back)) :init (add-hook 'xref-backend-functions #'dumb-jump-xref-activate) :config (setq dumb-jump-selector 'ivy)) #+END_SRC ** ibuffer #+BEGIN_SRC emacs-lisp (use-package ibuffer :bind ("C-x C-b" . ibuffer)) (use-package ibuffer-projectile :hook (ibuffer . ibuffer-projectile-init) :commands ibuffer-projectile-init :config (defun ibuffer-projectile-init() (ibuffer-projectile-set-filter-groups) (unless (eq ibuffer-sorting-mode 'alphabetic) (ibuffer-do-sort-by-alphabetic)))) #+END_SRC ** Imenu #+BEGIN_SRC emacs-lisp (use-package imenu :defer t ;;:hook (emacs-lisp-mode . imenu-use-package) :config ;; Sometimes I want to customize imenu per project. ;; Mark imenu-generic-expression as safe for dir local usage (put 'imenu-generic-expression 'safe-local-variable 'listp) ;; Recenter window after imenu jump so cursor doesn't end up on the last line (add-hook 'imenu-after-jump-hook 'recenter) ; or 'reposition-window (set-default 'imenu-auto-rescan t)) ;; Use use-package-enable-imenu-support ;;(defun imenu-use-package () ;; (add-to-list 'imenu-generic-expression ;; '("Packages" "\\(^\\s-*(use-package +\\)\\(\\_<.+\\_>\\)" 2)))) (use-package imenu-anywhere :bind (("M-I" . ivy-imenu-anywhere) ("C-c i" . ivy-imenu-anywhere))) #+END_SRC ** Xref BROKEN #+BEGIN_SRC emacs-lisp (use-package xref :bind (("M-." . xref-find-definitions-maybe-other-window)) :config (defun xref-find-definitions-maybe-other-window (identifier) (interactive (list (xref--read-identifier "Find definitions of: "))) (if current-prefix-arg (xref-find-definitions-other-frame identifier) (xref-find-definitions identifier)) (message "xxxx")) ) BROKEN #+END_SRC * TODO Sort packages ** Recentf #+BEGIN_SRC emacs-lisp (use-package recentf :defer 2 :config (add-to-list 'recentf-exclude "^/\\(?:ssh\\|su\\|sudo\\)?:") (add-to-list 'recentf-exclude no-littering-var-directory) (setq recentf-max-saved-items 500 recentf-max-menu-items 15 ;; disable recentf-cleanup on Emacs start, because it can cause ;; problems with remote files recentf-auto-cleanup 'never) (recentf-mode)) #+END_SRC ** View Large Files #+BEGIN_SRC emacs-lisp ;; View Large Files (use-package vlf-setup ;; Require vlf-setup which autoloads `vlf' ;; to have vlf offered as choice when opening large files :config ;; Set batch size for browsing large files to 5MB (default 1) (setq vlf-batch-size 5242880) ;; warn when opening files bigger than 30MB (setq large-file-warning-threshold 30000000)) #+END_SRC ** Logview: View and filter log files Logview provides syntax highlighting, filtering and other features for various log files #+BEGIN_SRC emacs-lisp (use-package logview :mode ("log.out\\'" . logview-mode) :config (setq logview-additional-timestamp-formats '(("ISO 8601 datetime (with 'T') + millis + 'Z'" (java-pattern . "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") (datetime-options :any-decimal-separator t) (aliases "yyyy-MM-dd HH:mm:ss.SSS") (aliases "yyyy-MM-dd HH:mm:ss.SSSSSS") (aliases "yyyy-MM-dd'T'HH:mm:ss.SSS") (aliases "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") (aliases "yyyy-MM-dd'T'HH:mm:ss.SSSSSS") (aliases "HH:mm:ss.SSS") (aliases "HH:mm:ss.SSSSSS")))) (setq logview-additional-submodes '(("Timbre" (format . "TIMESTAMP LEVEL [NAME] -") (levels . "SLF4J")) ("Logback4me" (format . "TIMESTAMP [THREAD] {} LEVEL NAME -") (levels . "SLF4J"))))) #+END_SRC ** PDF Tools Better pdf viewer with search, annotate, highlighting etc =poppler= and =poppler-glib= must be installed. For "easier" printing with cups use =gtklp=. #+BEGIN_SRC txt :tangle arch-pkglist.txt poppler poppler-glib gtklp #+END_SRC #+BEGIN_SRC emacs-lisp (use-package pdf-tools ;; manually update ;; after each update we have to call: ;; Install pdf-tools but don't ask or raise error (otherwise daemon mode will wait for input) ;; (pdf-tools-install t t t) :magic ("%PDF" . pdf-view-mode) :mode (("\\.pdf\\'" . pdf-view-mode)) :hook ((pdf-view-mode . pdf-view-init)) :bind (:map pdf-view-mode-map ("C-s" . isearch-forward) ("M-w" . pdf-view-kill-ring-save)) :config (defun pdf-view-init () "Initialize pdf-tools view like enabline TOC functions or use dark theme at night." ;; Use dark theme when opening PDFs at night time (let ((hour (string-to-number (format-time-string "%H")))) (when (or (< hour 5) (< 20 hour)) (pdf-view-midnight-minor-mode))) ;; Enable pdf-tools minor mode to have features like TOC extraction by pressing `o'. (pdf-tools-enable-minor-modes) ;; Disable while-line-or-region to free keybindings. (whole-line-or-region-local-mode -1)) ;; more fine-grained zooming; +/- 10% instead of default 25% (setq pdf-view-resize-factor 1.1) ;; Always use midnight-mode and almost same color as default font. ;; Just slightly brighter background to see the page boarders (setq pdf-view-midnight-colors '("#c6c6c6" . "#363636"))) #+END_SRC ** atomic-chrome / GhostText: Edit text area in browser You need to install the Firefox extension [[https://github.com/GhostText/GhostText][GhostText]] #+BEGIN_SRC emacs-lisp (use-package atomic-chrome :if (daemonp) :defer 10 :config ;; Set port to a less common one ;; Must be changed in Firefox too! (setq atomic-chrome-server-ghost-text-port 8326) (setq atomic-chrome-default-major-mode 'gfm-mode) (setq atomic-chrome-url-major-mode-alist '(("reddit\\.com" . markdown-mode) ("github\\.com" . gfm-mode) ("gitlab\\.com" . gfm-mode) ("gitlab\\.paesslergmbh\\.de" . gfm-mode) ("gitlab\\.sovendus\\.com" . gfm-mode) ("lab\\.ebenefuenf\\.com" . gfm-mode) ("jira.sovendus.com" . jira-markup-mode) ("jira.paesslergmbh.de" . jira-markup-mode))) (atomic-chrome-start-server)) #+END_SRC ** Misc #+BEGIN_SRC emacs-lisp (use-package calc :bind ("" . quick-calc) :config ;; Multiplication should *not* have precedence over division. ;; E.g. default behavior of Emacs Calc is 10/2*5 = 10 / (2* 5) = 1 ;; but you would expect (10 / 2) * 5 = 25. (setq calc-multiplication-has-precedence nil)) #+END_SRC * Dired For =dired-dragon-popup= (drag-and-drop support) install #+BEGIN_SRC txt :tangle arch-pkglist.txt dragon-drop #+END_SRC (This is not really needed anymore as Emacs 29+ has native drag and drop support in dired) #+BEGIN_SRC emacs-lisp ;; dired config mostly from https://github.com/Fuco1/.emacs.d/blob/master/files/dired-defs.org (use-package dired :bind (("C-x d" . dired) :map dired-mode-map ("j" . consult-line) ("M-u" . dired-up-directory) ("M-RET" . emms-play-dired) ("C-RET" . dired-open-xdg) ([(control return)] . dired-open-xdg) ("e" . dired-ediff-files) ("C-c C-d" . dired-dragon-popup) ("C-c C-e" . dired-toggle-read-only)) :config ;; Tell dired-x to not bind "I" key to `dired-info' or "N" to `dired-man' (setq dired-bind-info nil) (setq dired-bind-man nil) ;; Allow drag and drop out of dired into other apps (e.g. browser) (setq dired-mouse-drag-files t) ;; When point is on a file name only search file names (setq dired-isearch-filenames 'dwim) ;; dired - reuse current buffer by pressing 'a' (put 'dired-find-alternate-file 'disabled nil) ;; Open directories in same buffer (setq dired-kill-when-opening-new-dired-buffer t) ;; always delete and copy recursively (setq dired-recursive-deletes 'always) (setq dired-recursive-copies 'always) ;; if there is a dired buffer displayed in the next window, use its ;; current subdir, instead of the current subdir of this dired buffer (setq dired-dwim-target t) (defconst my-dired-media-files-extensions '("mp3" "mp4" "MP3" "MP4" "avi" "mpg" "flv" "ogg") "Media files.") ;; dired list size in human-readable format and list directories first (setq dired-listing-switches "-AGhlv --group-directories-first --time-style=long-iso") ;; Not needed anymore since Emacs 29+ has native drag and drop support out of dired (defun dired-dragon-popup () "Open dragon (drag and drop) with the marked files or the file at point." (interactive) (make-process :name "dragon" :command (append '("dragon-drop") (dired-get-marked-files)))) ;; Easily diff 2 marked files in dired ;; https://oremacs.com/2017/03/18/dired-ediff/ (defun dired-ediff-files () (interactive) (let ((files (dired-get-marked-files)) (wnd (current-window-configuration))) (if (<= (length files) 2) (let ((file1 (car files)) (file2 (if (cdr files) (cadr files) (read-file-name "file: " (dired-dwim-target-directory))))) (if (file-newer-than-file-p file1 file2) (ediff-files file2 file1) (ediff-files file1 file2)) (add-hook 'ediff-after-quit-hook-internal (lambda () (setq ediff-after-quit-hook-internal nil) (set-window-configuration wnd)))) (error "no more than 2 files should be marked"))))) (use-package dired-aux :after dired :config ;; Add unrar to `dired-compress' (add-to-list 'dired-compress-file-suffixes '("\\.rar\\'" "" "unrar x %i"))) (use-package wdired :after dired :config ;; Make permission bits editable (setq wdired-allow-to-change-permissions t)) (use-package dired-x :bind ("C-x C-j" . dired-jump) :config (add-to-list 'dired-guess-shell-alist-user (list (concat "\\." (regexp-opt my-dired-media-files-extensions) "\\'") "mpv"))) ;; Display the recursive size of directories in Dired (use-package dired-du :after dired :config ;; human readable size format (setq dired-du-size-format t)) (use-package async) (use-package dired-async ; Part of async :after dired :config (dired-async-mode 1)) #+END_SRC ** dired-rsync #+BEGIN_SRC txt :tangle arch-pkglist.txt rsync #+END_SRC #+BEGIN_SRC emacs-lisp (use-package dired-rsync :defer t ;; No keybinding anymore as we use dired-transient-rsync ;; Needs to be after dired-x as it binds the "Y" key too ;; ;; :after dired-x ;; :bind (:map dired-mode-map ;; ("Y" . dired-rsync)) ) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package dired-rsync-transient ;; Needs to be after dired-x as it binds the "Y" key too :after dired-x :bind (:map dired-mode-map ("Y" . dired-rsync-transient))) #+END_SRC ** Dired-hacks #+BEGIN_SRC emacs-lisp (use-package dired-hacks-utils :hook (dired-mode . dired-utils-format-information-line-mode)) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package dired-rainbow :after dired :config (dired-rainbow-define html "#4e9a06" ("htm" "html" "xhtml")) (dired-rainbow-define xml "#b4fa70" ("xml" "xsd" "xsl" "xslt" "wsdl")) (dired-rainbow-define document font-lock-function-name-face ("doc" "docx" "odt" "pdb" "pdf" "ps" "rtf" "djvu" "epub")) (dired-rainbow-define excel "#3465a4" ("xlsx")) ;; FIXME: my-dired-media-files-extensions not defined? ;;(dired-rainbow-define media "#ce5c00" my-dired-media-files-extensions) (dired-rainbow-define image "#ff4b4b" ("jpg" "png" "jpeg" "gif")) (dired-rainbow-define log "#c17d11" ("log")) (dired-rainbow-define sourcefile "#fcaf3e" ("py" "c" "cc" "cpp" "h" "java" "pl" "rb" "R" "php" "go" "rust" "js" "ts" "hs")) (dired-rainbow-define executable "#8cc4ff" ("exe" "msi")) (dired-rainbow-define compressed "#ad7fa8" ("zip" "bz2" "tgz" "txz" "gz" "xz" "z" "Z" "jar" "war" "ear" "rar" "sar" "xpi" "apk" "xz" "tar")) (dired-rainbow-define packaged "#e6a8df" ("deb" "rpm")) (dired-rainbow-define encrypted "LightBlue" ("gpg" "pgp")) (dired-rainbow-define-chmod executable-unix "Green" "-.*x.*")) (use-package dired-collapse :bind (:map dired-mode-map (")" . dired-collapse-mode)) :hook (dired-mode . dired-collapse-mode)) #+END_SRC Browse compressed archives in dired (requires `avfs' to be installed) Run `mountavfs' to start `avfsd' which is needed for it to work. #+BEGIN_SRC txt :tangle arch-pkglist.txt avfs #+END_SRC #+BEGIN_SRC emacs-lisp (use-package dired-avfs :after dired :config ;; Don't warn about opening archives less than 512MB (default 100) (setq dired-avfs-file-size-threshold 512)) (use-package dired-open :after dired :bind (:map dired-mode-map ("RET" . dired-open-file) ([return] . dired-open-file) ("f" . dired-open-file)) :config (setq dired-open-functions '(dired-open-by-extension dired-open-guess-shell-alist dired-open-subdir))) (use-package dired-ranger :after dired :init (bind-keys :map dired-mode-map :prefix "c" :prefix-map dired-ranger-map :prefix-docstring "Map for ranger operations." ("c" . dired-ranger-copy) ("p" . dired-ranger-paste) ("m" . dired-ranger-move)) (bind-keys :map dired-mode-map ("'" . dired-ranger-bookmark) ("`" . dired-ranger-bookmark-visit))) ;;narrow dired to match filter (use-package dired-narrow :after dired :bind (:map dired-mode-map ("/" . dired-narrow))) (use-package dired-subtree :after dired :bind (:map dired-mode-map ("i" . dired-subtree-insert) ("I" . dired-subtree-remove))) #+END_SRC ** Treemacs-icons-dired: Treemacs icons for dired #+BEGIN_SRC emacs-lisp (use-package treemacs-icons-dired :after dired :config (treemacs-icons-dired-mode)) #+END_SRC * Tramp #+BEGIN_SRC emacs-lisp (use-package tramp :defer t :config (setq tramp-default-method "ssh") ;; Only for debugging slow tramp connections ;;(setq tramp-verbose 7) ;; Skip version control for tramp files (setq vc-ignore-dir-regexp (format "\\(%s\\)\\|\\(%s\\)" vc-ignore-dir-regexp tramp-file-name-regexp)) ;; Use ControlPath from .ssh/config (setq tramp-ssh-controlmaster-options "") ;; Backup tramp files like local files and don't litter the remote ;; file system with my emacs backup files ;;(setq tramp-backup-directory-alist backup-directory-alist) ;; (add-to-list 'backup-directory-alist ;; (cons tramp-file-name-regexp ;; (no-littering-expand-var-file-name "backup/"))) ;; See https://www.gnu.org/software/tramp/#Ad_002dhoc-multi_002dhops ;; For all hosts, except my local one, first connect via ssh, and then apply sudo -u root: (dolist (tramp-proxies '((nil "\\`root\\'" "/ssh:%h:") ((regexp-quote (system-name)) nil nil) ("localhost" nil nil) ("blif\\.vpn" nil nil) ("skor-pi" nil nil) ;; Add tramp proxy for atomx user (nil "atomx" "/ssh:%h:"))) (add-to-list 'tramp-default-proxies-alist tramp-proxies))) #+END_SRC * xterm-color #+BEGIN_SRC emacs-lisp (use-package xterm-color :defer t) #+END_SRC * Eshell #+BEGIN_SRC emacs-lisp ;; Always show file size in human readable format (setq eshell-ls-initial-args "-h") ;; We're in emacs, so 'cat' is nicer there than 'less' (setenv "PAGER" "cat") ;; Fixme eshell-mode-map maps to global keybindings? Check "C-d" ;; Issue: https://github.com/jwiegley/use-package/issues/332 (use-package eshell :bind (("C-x m" . eshell) ("C-x M" . dakra-eshell-split) ;;:map eshell-mode-map ;;("M-P" . eshell-previous-prompt) ;;("C-d" . dakra-eshell-quit-or-delete-char) ;;("M-N" . eshell-next-prompt) ;;("M-R" . eshell-list-history) ) :init (setq eshell-aliases-file (no-littering-expand-etc-file-name "eshell-aliases")) :config ;; Config for xterm-color (add-to-list 'eshell-preoutput-filter-functions 'xterm-color-filter) (setq eshell-output-filter-functions (remove 'eshell-handle-ansi-color eshell-output-filter-functions)) (setenv "TERM" "xterm-256color") ;; Synchronize eshell buffer name on directory change. ;; From https://github.com/manateelazycat/aweshell (defun eshell-sync-dir-buffer-name () "Change eshell buffer name by directory change." (when (equal major-mode 'eshell-mode) (rename-buffer (format "*eshell: %s*" (epe-fish-path default-directory)) t))) (add-hook 'eshell-directory-change-hook #'eshell-sync-dir-buffer-name) (add-hook 'eshell-mode-hook #'eshell-sync-dir-buffer-name) (defun dakra-eshell-split (&optional arg) "Like eshell but use pop-to-buffer to display." (interactive "P") (let ((cur-buf (buffer-name)) (eshell-buf (eshell arg))) (pop-to-buffer-same-window cur-buf) (pop-to-buffer eshell-buf))) ;; Don't print the welcome banner and ;; use native 'sudo', system sudo asks for password every time. (require 'em-tramp) (setq eshell-modules-list '(eshell-alias eshell-basic eshell-cmpl eshell-dirs eshell-glob eshell-hist eshell-ls eshell-pred eshell-prompt eshell-script eshell-term eshell-tramp eshell-unix)) (require 'em-smart) (setq-default eshell-where-to-jump 'begin) (setq-default eshell-review-quick-commands nil) (setq-default eshell-smart-space-goes-to-end t) (require 'em-hist) ;; Some ideas from https://github.com/howardabrams/dot-files/blob/master/emacs-eshell.org (setq-default eshell-scroll-to-bottom-on-input 'all eshell-error-if-no-glob t eshell-hist-ignoredups t eshell-visual-commands '("ptpython" "ipython" "pshell" "tail" "vi" "vim" "watch" "nmtui" "dstat" "mycli" "pgcli" "vue" "ngrok" "castnow" "mitmproxy" "rmapi" "tmux" "screen" "top" "htop" "less" "more" "ncftp") eshell-prefer-lisp-functions nil) ;; Fix eshell overwriting history. ;; From https://emacs.stackexchange.com/a/18569/15023. (setq eshell-save-history-on-exit nil) (defun eshell-append-history () "Call `eshell-write-history' with the `append' parameter set to `t'." (when eshell-history-ring (let ((newest-cmd-ring (make-ring 1))) (ring-insert newest-cmd-ring (car (ring-elements eshell-history-ring))) (let ((eshell-history-ring newest-cmd-ring)) (eshell-write-history eshell-history-file-name t))))) (add-hook 'eshell-pre-command-hook #'eshell-append-history) ;; Increase eshell history size from default of only 128 (setq eshell-history-size 8192) ;; Used to C-d exiting from a shell? Want it to keep working, but still allow deleting a character? ;; We can have it both (require 'em-prompt) (defun dakra-eshell-quit-or-delete-char (arg) (interactive "p") ;; Somehow eshell-finge-status-mode adds an additional (not visible?) ;; character to eol since Emacs 30. ;; Just quit eshell if point is at the last or last but one position. (if (and (>= (point) (1- (point-max))) (looking-back eshell-prompt-regexp nil)) (progn (eshell-life-is-too-much) ; Why not? (eshell/exit) (ignore-errors (when (= arg 4) ; With prefix argument, also remove eshell frame/window (progn ;; Remove frame if eshell is only window (otherwise just close window) (if (one-window-p) (delete-frame) (delete-window)))))) (delete-char arg))) (defun eshell-delete-backward-char (n) "Only call `(delete-backward-char N)' when not at beginning of prompt." (interactive "p") (if (looking-back eshell-prompt-regexp nil) (message "Beginning of prompt") (delete-char (- n)))) ;; Fixme eshell-mode-map maps to global keybindings? Check "C-d" ;; Issue: https://github.com/jwiegley/use-package/issues/332 (add-hook 'eshell-mode-hook (lambda () (local-set-key (kbd "M-P") 'eshell-previous-prompt) (local-set-key (kbd "M-N") 'eshell-next-prompt) (local-set-key (kbd "M-R") 'eshell-list-history) (local-set-key (kbd "C-r") 'consult-history) (local-set-key (kbd "C-d") 'dakra-eshell-quit-or-delete-char) (local-set-key (kbd "DEL") 'eshell-delete-backward-char) ;; Use helm as completion menu ;;(local-set-key [remap eshell-pcomplete] 'helm-esh-pcomplete) ;; or ivy (local-set-key [remap eshell-pcomplete] 'completion-at-point) ;; Support imenu (setq-local imenu-generic-expression `(("Prompt" ,eshell-prompt-regexp 1))) ;; Support outline (e.g. consult-outline) (setq-local outline-regexp eshell-prompt-regexp) ;;(eshell-smart-initialize) ;; Emacs bug where * gets removed ;; See https://github.com/company-mode/company-mode/issues/218 ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=18951 ;;(require 'company) ;;(setq-local company-idle-delay 0.1) ;;(setq-local company-backends '(company-capf company-eshell-autosuggest)) ;; (setq-local company-backends '(company-capf)) ;; (setq-local company-frontends '(company-preview-frontend)) ;; Disable overwriting the history on eshell exit ;; See eshell-append-history above (remove-hook 'eshell-exit-hook #'eshell-write-history t) (setq xterm-color-preserve-properties t) (setq-local company-backends '((company-eshell-history company-shell company-shell-env company-files company-dabbrev-code company-capf company-yasnippet))) )) ;; Functions starting with `eshell/' can be called directly from eshell ;; with only the last part. E.g. (eshell/foo) will call `$ foo' (defun eshell/d (&optional dir) "Open dired in current directory." (dired (or dir "."))) (defun eshell/ccat (file) "Like `cat' but output with Emacs syntax highlighting." (with-temp-buffer (insert-file-contents file) (let ((buffer-file-name file)) (delay-mode-hooks (set-auto-mode) (if (fboundp 'font-lock-ensure) (font-lock-ensure) (with-no-warnings (font-lock-fontify-buffer))))) (buffer-string))) (defun eshell/lcd (&optional directory) "Like regular 'cd' but don't jump out of a tramp directory. When on a remote directory with tramp don't jump 'out' of the server. So if we're connected with sudo to 'remotehost' '$ lcd /etc' would go to '/sudo:remotehost:/etc' instead of just '/etc' on localhost." (setq directory (or directory "~/")) (unless (file-remote-p directory) (setq directory (concat (file-remote-p default-directory) directory))) (eshell/cd directory)) (defun eshell/gst (&rest args) (magit-status-setup-buffer (or (pop args) default-directory)) (eshell/echo)) ;; The echo command suppresses output (defun eshell/f (filename &optional dir try-count) "Searches for files matching FILENAME in either DIR or the current directory. Just a typical wrapper around the standard `find' executable. Since any wildcards in FILENAME need to be escaped, this wraps the shell command. If not results were found, it calls the `find' executable up to two more times, wrapping the FILENAME pattern in wildcard matches. This seems to be more helpful to me." (let* ((cmd (concat (executable-find "find") " " (or dir ".") " -not -path '*/.git*'" " -and -not -path '*node_modules*'" " -and -not -path '*classes*'" " -and " " -type f -and " "-iname '" filename "'")) (results (shell-command-to-string cmd))) (if (not (s-blank-str? results)) results (cond ((or (null try-count) (= 0 try-count)) (eshell/f (concat filename "*") dir 1)) ((or (null try-count) (= 1 try-count)) (eshell/f (concat "*" filename) dir 2)) (t ""))))) (defun eshell/ef (filename &optional dir) "Searches for the first matching filename and loads it into a file to edit." (let* ((files (eshell/f filename dir)) (file (car (s-split "\n" files)))) (find-file file))) (defun eshell/find (&rest args) "Wrapper around the ‘find’ executable." (let ((cmd (concat "find " (string-join args " ")))) (shell-command-to-string cmd))) (defun execute-command-on-file-buffer (cmd) "Execute command on current buffer file." (interactive "sCommand to execute: ") (let* ((file-name (buffer-file-name)) (full-cmd (concat cmd " " file-name))) (shell-command full-cmd))) (defun execute-command-on-file-directory (cmd) "Execute command on current buffer directory." (interactive "sCommand to execute: ") (let* ((dir-name (file-name-directory (buffer-file-name))) (full-cmd (concat "cd " dir-name "; " cmd))) (shell-command full-cmd)))) (use-package eshell-bookmark :hook (eshell-mode . eshell-bookmark-setup) :after eshell) #+END_SRC ** Prompt #+BEGIN_SRC emacs-lisp (use-package eshell-prompt-extras :after esh-opt :config (require 'virtualenvwrapper) ; We want python venv support (autoload 'epe-theme-dakrone "eshell-prompt-extras") (setq eshell-highlight-prompt nil eshell-prompt-function 'epe-theme-dakrone)) #+END_SRC ** Z: cd to frequent directory #+BEGIN_SRC emacs-lisp (use-package eshell-z :after eshell) #+END_SRC ** Eshel-up: Quickly go to a specific parent directory in eshell #+BEGIN_SRC emacs-lisp (use-package eshell-up :after eshell) #+END_SRC ** Eshell-fringe-status: Show last status in fringe #+BEGIN_SRC emacs-lisp (use-package eshell-fringe-status :disabled t ;; Broken with latest Emacs? Sometimes adds invisible space at the end of the prompt :hook (eshell-mode . eshell-fringe-status-mode) :config (define-fringe-bitmap 'efs-line-bitmap [#b1111 #b1111 #b1111 #b1111 #b1111 #b1111 #b1111 #b1111 #b1111 #b1111 #b1111 #b1111 #b1111 #b1111 #b1111 #b1111 #b1111 #b1111 ] 18 4 'center) (setq eshell-fringe-status-success-bitmap 'efs-line-bitmap) (setq eshell-fringe-status-failure-bitmap 'efs-line-bitmap)) #+END_SRC ** Autocomplete #+BEGIN_SRC emacs-lisp (use-package fish-completion :if (executable-find "fish") :after eshell :config (global-fish-completion-mode)) ;; Autocomplete for git commands in shell and ;; the git command from magit ('!') (use-package pcmpl-git :after pcomplete) (use-package pcmpl-pip :after pcomplete) #+END_SRC ** company-eshell-history: Company eshell history completion backend From https://gist.github.com/gregsexton/dd2d6c304d06fc3e6833 Set as backend in =eshell-mode-hook= in eshell section above. #+BEGIN_SRC emacs-lisp (defun company-eshell-history (command &optional arg &rest ignored) (interactive (list 'interactive)) (cl-case command (interactive (company-begin-backend 'company-eshell-history)) (prefix (and (eq major-mode 'eshell-mode) (let ((word (company-grab-word))) (save-excursion (beginning-of-line) (and (looking-at-p (s-concat word "$")) word))))) (candidates (cl-remove-duplicates (->> (ring-elements eshell-history-ring) (cl-remove-if-not (lambda (item) (s-prefix-p arg item))) (mapcar 's-trim)) :test 'string=)) (sorted t))) #+END_SRC * vterm For prompt tracking in the fish shell, add the following line somewhere in your fish config =functions/fish_prompt.fish=: #+BEGIN_SRC fish :tangle no echo -e -n -s "\e]51;A" (pwd) "\e\\" #+END_SRC For =bash= add this to your =.bashrc=: #+BEGIN_SRC bash :tangle no if [[ "$INSIDE_EMACS" = 'vterm' ]]; then vterm_prompt_end(){ printf "\e]51;A$(whoami)@$(hostname):$(pwd)\e\\" } PS1=$PS1'$(vterm_prompt_end)' function clear(){ printf "\e]51;Evterm-clear-scrollback\e\\"; tput clear; } function ee(){ printf "\e]51;Efind-file \"$@\"\e\\" } function e(){ printf "\e]51;Efind-file-other-window \"$@\"\e\\" } function dow(){ printf "\e]51;Edired-other-window \"$@\"\e\\" } function gst(){ printf "\e]51;Emagit-status-setup-buffer $(pwd)\e\\" } fi #+END_SRC #+BEGIN_SRC emacs-lisp (use-package vterm :defer t :bind (:map vterm-mode-map ("C-y" . vterm-yank) ("M-y" . vterm-yank-pop) ("C-k" . vterm-send-C-k-and-kill) ;; I'm used to go up/down the shell history with M-n/p from eshell ;; Simulate this behavior in vterm ("M-p" . vterm-send-C-p) ("M-n" . vterm-send-C-n)) ;; Disable whole-line-or-region otherwise I can't bind "C-y" :hook (vterm-mode . (lambda () (whole-line-or-region-local-mode -1))) :init (setq vterm-shell "/usr/bin/bash") :config (defun vterm-send-C-p () "Sends C-p to the libvterm." (interactive) (vterm-send-key "p" nil nil t)) (defun vterm-send-C-n () "Sends C-n to the libvterm." (interactive) (vterm-send-key "n" nil nil t)) ;; Kill dead vterm buffers (setq vterm-kill-buffer-on-exit t) ;; Run a shell command in vterm with compilation-minor-mode (defun vterm-compile (command &optional name) (interactive (list (let ((command (eval compile-command))) (if (or compilation-read-command current-prefix-arg) (compilation-read-command command) command)) (consp current-prefix-arg))) (let ((buffer (generate-new-buffer (or name "*vterm*")))) (with-current-buffer buffer (let ((vterm-shell command)) (vterm-mode) (compilation-minor-mode)) (pop-to-buffer buffer)))) ;; Like normal `C-k'. Send `C-k' to libvterm but also put content in kill-ring (defun vterm-send-C-k-and-kill () "Send `C-k' to libvterm." (interactive) (kill-ring-save (point) (vterm-end-of-line)) (vterm-send-key "k" nil nil t)) ;; Allow vterm to invoke some elisp functions (setq vterm-eval-cmds '(("dired-other-window" dired-other-window) ("find-file" find-file) ("find-file-other-window" find-file-other-window) ("magit-status-setup-buffer" magit-status-setup-buffer) ("message" message) ("vterm-clear-scrollback" vterm-clear-scrollback)))) #+END_SRC * Version Control ** Emacs vc settings #+BEGIN_SRC emacs-lisp (use-package vc-git :config ;; Follow renames of a single file (setq vc-git-print-log-follow t) ;; Nicer diff (should be taken from global .config/git/config) (setq vc-git-diff-switches '("--indent-heuristic"))) (use-package diff-mode :config ;; Shorten file headers like Magit's diff format. (setq diff-font-lock-prettify t)) (use-package ediff :defer t :config ;; Always expand files before diffing (especially org files) (add-hook 'ediff-prepare-buffer-hook #'outline-show-all) ;; Do everything in one frame (setq ediff-window-setup-function 'ediff-setup-windows-plain) ;; Split ediff windows horizontally by default (setq ediff-split-window-function 'split-window-horizontally)) ;; Highlight and link issue IDs to website ;; bug-reference-url-format has to be set in dir-locals (S-p E) ;; E.g. for github: (bug-reference-url-format . "https://github.com/atomx/api/issues/%s") (use-package bug-reference :hook (prog-mode . bug-reference-prog-mode) :config ;; (setq bug-reference-bug-regexp "#\\(?2:[0-9]+\\)") (setq bug-reference-bug-regexp "\\(\\b\\(?:[Bb]ug ?#?\\|[Ii]ssue ?#\\|[Pp]atch ?#\\|RFE ?#\\|PR [a-z+-]+/\\)\\([0-9]+\\(?:#[0-9]+\\)?\\)\\)")) #+END_SRC ** Diff-hl: Show git status in fringe #+BEGIN_SRC emacs-lisp (use-package diff-hl :hook (((prog-mode conf-mode vc-dir-mode ledger-mode) . turn-on-diff-hl-mode) (magit-pre-refresh . diff-hl-magit-pre-refresh) (magit-post-refresh . diff-hl-magit-post-refresh)) :config ;; Disable diff-hl in Tramp (setq diff-hl-disable-on-remote t) ;; XXX: maybe set draw-borders to nil and set background color like ;; `(diff-added ((,class (:foreground ,green-4 :background ,green-00 :bold t)))) ;; `(diff-changed ((,class (:foreground ,yellow-4 :background ,yellow-00 :bold t)))) ;; `(diff-removed ((,class (:foreground ,red-3 :background ,red-00 :bold t)))) (setq diff-hl-draw-borders nil)) (use-package diff-hl-dired ;; in diff-hl package :after dired :hook (dired-mode . diff-hl-dired-mode)) #+END_SRC ** Gitpatch: Easily send patches from dired/magit/ibuffer Move point to patch and call `M-x gitpatch-mail` #+BEGIN_SRC emacs-lisp (use-package gitpatch :defer t) #+END_SRC ** Magit If you want to use =magit-commit-absorb= you need =git-absorb= installed. #+BEGIN_SRC txt :tangle arch-pkglist.txt git-absorb #+END_SRC #+BEGIN_SRC emacs-lisp (use-package libgit :defer t) (use-package gitconfig-mode :mode ("/\\.gitconfig\\'" "/\\.git/config\\'" "/modules/.*/config\\'" "/git/config\\'" "/\\.gitmodules\\'" "/etc/gitconfig\\'")) (use-package gitignore-mode :mode ("/\\.gitignore\\'" "gitignore_global\\'" "/info/exclude\\'" "/git/ignore\\'")) (use-package git-commit ;; Highlight issue ids in commit messages and spellcheck :hook (git-commit-setup . git-commit-turn-on-flyspell) :init (define-derived-mode git-commit-gfm-mode markdown-mode "GIT-GFM" "Major mode for editing commit messages in GitHub Flavored Markdown. Like the official markdown gfm-mode but `#' at the beginning of a line is not a title but instead is displayed as a comment." (setq markdown-link-space-sub-char "-") (setq-local markdown-regex-comment-start "^#") (setq-local markdown-regex-comment-end "$") (setq-local markdown-table-at-point-p-function 'gfm--table-at-point-p) (markdown-gfm-parse-buffer-for-languages)) ;; Mark a few major modes as safe (put 'git-commit-major-mode 'safe-local-variable (lambda (m) (or (eq m 'git-commit-gfm-mode) (eq m 'gfm-mode) (eq m 'text-mode) (eq m 'git-commit-elisp-text-mode)))) :config (setq git-commit-major-mode 'git-commit-gfm-mode)) (use-package transient :defer t :config ;; Display transient buffer below current window ;; and not bottom of the complete frame (minibuffer like) (setq transient-display-buffer-action '(display-buffer-below-selected))) (use-package magit :bind (("C-x g" . magit-status) ("C-x G" . magit-dispatch) ("C-x M-g" . magit-dispatch) ("s-m p" . magit-list-repositories) ("s-m m" . magit-status) ("s-m f" . magit-file-dispatch) ("s-m l" . magit-log) ("s-m L" . magit-log-buffer-file) ("s-m b" . magit-blame-addition) ("s-m B" . magit-blame) :map magit-process-mode-map ("k" . magit-process-kill)) :hook (after-save . magit-after-save-refresh-status) :defines (magit-ediff-dwim-show-on-hunks) :init (defcustom magit-push-protected-branch nil "When set, ask for confirmation before pushing to this branch (e.g. master)." :type 'string :safe #'stringp :group 'magit) :config (require 'magit-popup) ;; Some libs don't have explicit dependency listed (defun magit-push--protected-branch (magit-push-fun &rest args) "Ask for confirmation before pushing a protected branch." (if (equal magit-push-protected-branch (magit-get-current-branch)) ;; Arglist is (BRANCH TARGET ARGS) (if (yes-or-no-p (format "Push branch %s? " (magit-get-current-branch))) (apply magit-push-fun args) (error "Push aborted by user")) (apply magit-push-fun args))) (advice-add 'magit-push-current-to-pushremote :around #'magit-push--protected-branch) (advice-add 'magit-push-current-to-upstream :around #'magit-push--protected-branch) ;; Add switch to invert the filter e.g. show all authors but `--author=foo' ;; (magit-define-popup-switch 'magit-log-popup ;; ?i "Invert filter" "--invert-grep") ;; Show gravatars ;; (setq magit-revision-show-gravatars '("^Author: " . "^Commit: ")) ;; Don't show fringe indicators (Not really visible with our small exwm fringe) ;;(setq magit-section-visibility-indicator nil) ;; Always show recent/unpushed/unpulled commits (setq magit-section-initial-visibility-alist '((unpushed . show) (unpulled . show))) (setq magit-repository-directories '(("~/atomx" . 5) ("~/e5" . 5) ("~/projects" . 5))) ;; "b b" is only for checkout and doesn't automatically create a new branch ;; remap to `magit-branch-or-checkout' that checks out an existing branch ;; or asks to create a new one if it doesn't exist ;; (magit-remove-popup-key 'magit-branch-popup :action ?b) ;; (magit-define-popup-action 'magit-branch-popup ;; ?b "Checkout or create" 'magit-branch-or-checkout ;; 'magit-branch t) ;; Show submodules section to magit status (magit-add-section-hook 'magit-status-sections-hook 'magit-insert-modules 'magit-insert-stashes 'append) ;; Show ignored files section to magit status (magit-add-section-hook 'magit-status-sections-hook 'magit-insert-ignored-files 'magit-insert-untracked-files nil) ;; Disable safety nets (setq magit-commit-squash-confirm nil) (setq magit-save-repository-buffers 'dontask) (setf (nth 2 (assq 'magit-stash-pop magit-dwim-selection)) t) ;;(setf (nth 2 (assq 'magit-stash-drop magit-dwim-selection)) t) (add-to-list 'magit-no-confirm 'rename t) (add-to-list 'magit-no-confirm 'resurrect t) (add-to-list 'magit-no-confirm 'trash t) ;; Don't override date for extend or reword (setq magit-commit-extend-override-date nil) (setq magit-commit-reword-override-date nil) ;; Set remote.pushDefault (setq magit-remote-set-if-missing 'default) ;; When showing refs (In magit status press `y y') show only merged into master by default (setq magit-show-refs-arguments '("--merged=master")) ;; Show color and graph in magit-log. Since color makes it a bit slow, only show the last 128 commits (setq magit-log-arguments '("--graph" "--color" "--decorate" "-n128")) ;; Always highlight word differences in diff (setq magit-diff-refine-hunk 'all) ;; Show margin with author and date in the status buffer ;; NOTE: This also cut's off the right side in the diffs which is the reason I disabled it by default. ;; (setq magit-status-margin '(t age magit-log-margin-width t 18)) ;; Only show 2 ediff panes (setq magit-ediff-dwim-show-on-hunks t) ;; Don't change my window layout after quitting magit ;; Ofter I invoke magit and then do a lot of things in other windows ;; On quitting, magit would then "restore" the window layout like it was ;; when I first invoked magit. Don't do that! (setq magit-bury-buffer-function 'magit-mode-quit-window) ;; Show magit status in the same window (setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)) (use-package magit-wip :after magit :config ;; Disable more safety nets that can be reverted with WIP mode (add-to-list 'magit-no-confirm 'safe-with-wip t) (magit-wip-mode)) #+END_SRC ** Smerge This configuration automatically activates a helpful smerge-mode hydra when a file containing merge conflicts is visited from a Magit diff section. (From https://github.com/alphapapa/unpackaged.el#smerge-mode) #+BEGIN_SRC emacs-lisp (use-package smerge-mode :hook (magit-diff-visit-file . (lambda () (when smerge-mode (hydra-smerge/body)))) :config (require 'hydra) (defhydra hydra-smerge (:color pink :hint nil :post (smerge-auto-leave)) " ^Move^ ^Keep^ ^Diff^ ^Other^ ^^-----------^^-------------------^^---------------------^^------- _n_ext _b_ase _<_: upper/base _C_ombine _p_rev _u_pper _=_: upper/lower _r_esolve ^^ _l_ower _>_: base/lower _k_ill current ^^ _a_ll _R_efine ^^ _RET_: current _E_diff " ("n" smerge-next) ("p" smerge-prev) ("b" smerge-keep-base) ("u" smerge-keep-upper) ("l" smerge-keep-lower) ("a" smerge-keep-all) ("RET" smerge-keep-current) ("\C-m" smerge-keep-current) ("<" smerge-diff-base-upper) ("=" smerge-diff-upper-lower) (">" smerge-diff-base-lower) ("R" smerge-refine) ("E" smerge-ediff) ("C" smerge-combine-with-next) ("r" smerge-resolve) ("k" smerge-kill-current) ("q" nil "cancel" :color blue))) #+END_SRC ** Forge: Work with Git forges from the comfort of Magit #+BEGIN_SRC emacs-lisp (use-package forge :after magit ;; :hook ((prog-mode log-view-mode git-commit-setup) . forge-bug-reference-setup) :init (setq forge-database-connector 'sqlite-builtin) :config ;; My GitHub accounts / organizations (setq forge-owned-accounts '(("dakra") ("pythonic-emacs") ("atomx") ("restalchemy"))) ;; Don't pull notifications as it blocks Emacs for a long time (setq forge-pull-notifications nil) ;; Add private GitLab servers (add-to-list 'forge-alist '("gitlab.paesslergmbh.de" "gitlab.paesslergmbh.de/api/v4" "gitlab.paesslergmbh.de" forge-gitlab-repository)) (add-to-list 'forge-alist '("gitlab.sovendus.com" "gitlab.sovendus.com/api/v4" "gitlab.sovendus.com" forge-gitlab-repository))) #+END_SRC XXX: Only use =magit-gerrit= until functionality is implemented in =forge=. #+BEGIN_SRC emacs-lisp (use-package magit-gerrit :after magit :config (setq magit-gerrit-signed-push-p t)) #+END_SRC ** code-review #+begin_src emacs-lisp (use-package code-review :after forge :config ;; Add to forge-dispatch transient menu (transient-append-suffix 'forge-dispatch "c f" '("c r" "Code Review" code-review-forge-pr-at-point)) ;; Delete local comment with `k' (define-key code-review-feedback-section-map (kbd "k") 'code-review-section-delete-comment) (define-key code-review-local-comment-section-map (kbd "k") 'code-review-section-delete-comment) (define-key code-review-reply-comment-section-map (kbd "k") 'code-review-section-delete-comment) ;; Move between comments using `C-c C-n` and `C-c C-p` (define-key code-review-mode-map (kbd "C-c C-n") 'code-review-comment-jump-next) (define-key code-review-mode-map (kbd "C-c C-p") 'code-review-comment-jump-previous)) #+end_src ** Annotate: Annotation any file and export as commented unified diff or as code comment Activate =annotate-mode= and then you can add, edit, delete annotations (use regions) with =C-c C-a=. Then export them as diff with =annotate-export-annotations= or as inline code comments =annotate-integrate-annotations=. #+BEGIN_SRC emacs-lisp (use-package annotate :defer t) #+END_SRC ** Browse-at-remote: Open website (github/gitlab) for current buffer/line/log #+BEGIN_SRC emacs-lisp ;; open current line/region/dired/commit in github (use-package browse-at-remote :bind (("C-c G" . dakra-browse-at-remote)) :config (defun dakra-browse-at-remote (p) "Like browse-at-remote but will also copy the url in the kill ring. When called with one prefix argument only copy the url in the kill ring and don't open in the browser. When called with 2 prefix arguments only open in browser and don't copy." (interactive "p") (cl-case p (4 (browse-at-remote-kill)) (16 (browse-at-remote)) (t (browse-at-remote-kill) (browse-at-remote)))) (setq browse-at-remote-use-http '("gitlab.test.bigdata.sovendus.aws")) (add-to-list 'browse-at-remote-remote-type-regexps '("^git\\.wdf\\.sap\\.corp\\'" . "gitiles")) (add-to-list 'browse-at-remote-remote-type-regexps '(".*gitlab.*" . "gitlab")) (setq browse-at-remote-prefer-symbolic nil)) #+END_SRC * Programming ** General setup #+BEGIN_SRC emacs-lisp ;; Only auto-fill inside comments (setq comment-auto-fill-only-comments t) #+END_SRC *** Apheleia: Auto formatting Instead of calling =apheleia-format-buffer= for all projects, rather put this in your =.dir-locals.el= where you want the prettier after save hook activated. E.g.: #+BEGIN_SRC emacs-lisp :tangle no ((js-mode . ((eval . (apheleia-mode))))) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package apheleia :defer t :config ;; For Python we want to format with isort and black (setf (alist-get 'isort apheleia-formatters) '("isort" "--stdout" "-")) (setf (alist-get 'python-mode apheleia-mode-alist) '(isort black))) #+END_SRC *** Flycheck Some external extra checkers like shellcheck, markdownlint, proselint (for writing). #+BEGIN_SRC txt :tangle arch-pkglist.txt shellcheck ruby-mdl proselint #+END_SRC #+BEGIN_SRC emacs-lisp ;; activate virtualenv for flycheck ;; (from https://github.com/lunaryorn/.emacs.d/blob/master/lisp/flycheck-virtualenv.el) (use-package flycheck :hook (((prog-mode ledger-mode systemd-mode mu4e-compose-mode markdown-mode rst-mode) . flycheck-mode) (flycheck-mode . mp-flycheck-prefer-eldoc)) :config ;; Don't initialize packages (as we don't use package.el) (setq flycheck-emacs-lisp-initialize-packages nil) ;; Use the load-path from running Emacs when checking elisp files (setq flycheck-emacs-lisp-load-path 'inherit) ;; Only do flycheck when I actually safe the buffer (setq flycheck-check-syntax-automatically '(save mode-enable)) ;; Work with `eldoc-documentation-functions' ;; From https://www.masteringemacs.org/article/seamlessly-merge-multiple-documentation-sources-eldoc (defun mp-flycheck-eldoc (callback &rest _ignored) "Print flycheck messages at point by calling CALLBACK." (when-let ((flycheck-errors (and flycheck-mode (flycheck-overlay-errors-at (point))))) (mapc (lambda (err) (funcall callback (format "%s: %s" (let ((level (flycheck-error-level err))) (pcase level ('info (propertize "I" 'face 'flycheck-error-list-info)) ('error (propertize "E" 'face 'flycheck-error-list-error)) ('warning (propertize "W" 'face 'flycheck-error-list-warning)) (_ level))) (flycheck-error-message err)) :thing (or (flycheck-error-id err) (flycheck-error-group err)) :face 'font-lock-doc-face)) flycheck-errors))) (defun mp-flycheck-prefer-eldoc () (add-hook 'eldoc-documentation-functions #'mp-flycheck-eldoc nil t) (setq eldoc-documentation-strategy 'eldoc-documentation-compose-eagerly) (setq flycheck-display-errors-function nil) (setq flycheck-help-echo-function nil)) ;; Python setup (declare-function python-shell-calculate-exec-path "python") (defun flycheck-virtualenv-executable-find (executable) "Find an EXECUTABLE in the current virtualenv if any." (if (bound-and-true-p python-shell-virtualenv-root) (let ((exec-path (python-shell-calculate-exec-path))) (executable-find executable)) (executable-find executable))) (defun flycheck-virtualenv-setup () "Setup Flycheck for the current virtualenv." (setq-local flycheck-executable-find #'flycheck-virtualenv-executable-find)) (add-hook 'python-mode-hook #'flycheck-virtualenv-setup) (setq flycheck-python-mypy-cache-dir "/home/daniel/.mypy-cache") (setq flycheck-flake8-maximum-line-length 110)) #+END_SRC ** Eglot: Emacs builtin LSP client #+BEGIN_SRC emacs-lisp (use-package eglot :defer t :config (setq eglot-extend-to-xref t) (setq eglot-autoshutdown t)) #+END_SRC ** LSP: Language Server Protocol For C/C++/Objective C support install ~ccls~ and =lldb= for debugging. #+BEGIN_SRC txt :tangle arch-pkglist.txt ccls lldb #+END_SRC #+BEGIN_SRC emacs-lisp (use-package lsp-mode :bind (:map lsp-mode-map ("C-c C-a" . lsp-execute-code-action) ("M-." . lsp-find-definition-other) ("M-," . lsp-find-references-other)) :init (setq lsp-keymap-prefix nil) ; Don't map the lsp keymap to any key :config ;; Shutdown lsp-server when all buffers associated with that server are closed (setq lsp-keep-workspace-alive nil) ;; Increase lsp file watch threshold when lsp shows a warning (setq lsp-file-watch-threshold 1500) (setq lsp-enable-on-type-formatting nil) (setq lsp-enable-indentation nil) (defun lsp-find-definition-other (other?) "Like `lsp-find-definition' but open in other window when called with prefix arg." (interactive "P") (back-button-push-mark-local-and-global) (if other? (lsp-find-definition :display-action 'window) (lsp-find-definition))) (defun lsp-find-references-other (other?) "Like `lsp-find-references' but open in other window when called with prefix arg." (interactive "P") (back-button-push-mark-local-and-global) (if other? (lsp-find-references :display-action 'window) (lsp-find-references))) ;; Don't watch `build' and `.gradle' directories for file changes (add-to-list 'lsp-file-watch-ignored "[/\\\\]build$") (add-to-list 'lsp-file-watch-ignored "[/\\\\]\\.gradle$") ;; (require 'yasnippet) ;; We use yasnippet for lsp snippet support (setq-default flycheck-disabled-checkers '(c/c++-clang c/c++-cppcheck c/c++-gcc))) (use-package lsp-ui :bind (:map lsp-mode-map ("M-?" . lsp-ui-doc-toggle)) :config (defun lsp-ui-doc-toggle () "Shows or hides lsp-ui-doc popup." (interactive) (if lsp-ui-doc--bounds (lsp-ui-doc-hide) (lsp-ui-doc-show))) ;; Deactivate most of the annoying "fancy features" (setq lsp-headerline-breadcrumb-enable nil) (setq lsp-ui-doc-enable nil) (setq lsp-ui-doc-use-childframe t) (setq lsp-ui-doc-include-signature t) (setq lsp-ui-doc-position 'at-point) (setq lsp-lens-enable nil) ;; "1 reference" etc at the end of the line (setq lsp-ui-sideline-enable nil) (setq lsp-ui-sideline-show-hover nil) (setq lsp-ui-sideline-show-symbol nil)) (use-package lsp-treemacs :after lsp-mode :config ;; Enable bidirectional synchronization of lsp workspace folders and treemacs (lsp-treemacs-sync-mode)) (use-package dap-mode :after lsp-mode :bind (:map dap-server-log-mode-map ("g" . recompile) :map dap-mode-map ([f9] . dap-continue) ([S-f9] . dap-disconnect) ([f10] . dap-next) ([f11] . dap-step-in) ([S-f11] . dap-step-out) ([f12] . dap-hide/show-ui)) :config ;; FIXME: Create nice solution instead of a hack (defvar dap-hide/show-ui-hidden? t) (defun dap-hide/show-ui () "Hide/show dap ui. FIXME" (interactive) (if dap-hide/show-ui-hidden? (progn (setq dap-hide/show-ui-hidden? nil) (dap-ui-locals) (dap-ui-repl)) (dolist (buf '("*dap-ui-inspect*" "*dap-ui-locals*" "*dap-ui-repl*" "*dap-ui-sessions*")) (when (get-buffer buf) (kill-buffer buf))) (setq dap-hide/show-ui-hidden? t))) (dap-mode) ;; displays floating panel with debug buttons (dap-ui-controls-mode) ;; Displaying DAP visuals (dap-ui-mode)) (use-package ccls :hook ((c++-mode c-mode objc-mode) . ccls-lsp-init) :config (defun ccls-lsp-init () "We need to require ccls before loading lsp in a C buffer. Otherwise lsp would use the default clangd backend. use-package will load ccls for us simply by calling this function." (lsp-deferred))) (use-package dap-lldb :after ccls) #+END_SRC ** Java *** lsp-java #+BEGIN_SRC emacs-lisp (use-package lsp-java :hook ((java-mode java-ts-mode) . java-lsp-init) :config ;; Use Google style formatting by default (setq lsp-java-format-settings-url "https://raw.githubusercontent.com/google/styleguide/gh-pages/eclipse-java-google-style.xml") (setq lsp-java-format-settings-profile "GoogleStyle") ;; For projects using lombok (setq lsp-java-vmargs-for-lombok '("-noverify" "-Xmx1G" "-XX:+UseG1GC" "-XX:+UseStringDeduplication" "-javaagent:/home/daniel/.m2/repository/org/projectlombok/lombok/1.18.12/lombok-1.18.12.jar" "-Xbootclasspath/a:/home/daniel/.m2/repository/org/projectlombok/lombok/1.18.12/lombok-1.18.12.jar")) ;; Use 3rd party decompiler (setq lsp-java-content-provider-preferred "fernflower") (defun java-lsp-init () "We need to require java-lsp before loading lsp in a Java buffer. use-package will load java-lsp for us simply by calling this function." (setq electric-indent-inhibit nil) ; Auto-indent code after e.g. {} (setq company-lsp-cache-candidates nil) ; Company cache should be disabled for lsp-java (lsp-deferred))) (use-package dap-java :after lsp-java) #+END_SRC *** Groovy For groovy and gradle support #+BEGIN_SRC emacs-lisp (use-package groovy-mode :defer t) #+END_SRC *** [[https://nullprogram.com/blog/2012/08/01/][Viewing Java Class Files in Emacs]] #+BEGIN_SRC emacs-lisp (defun javap-handler-real (operation args) "Run the real handler without the javap handler installed." (let ((inhibit-file-name-handlers (cons 'javap-handler (and (eq inhibit-file-name-operation operation) inhibit-file-name-handlers))) (inhibit-file-name-operation operation)) (apply operation args))) (defun javap-handler (op &rest args) "Handle .class files by putting the output of javap in the buffer." (cond ((eq op 'get-file-buffer) (let ((file (car args))) (with-current-buffer (create-file-buffer file) (call-process "javap" nil (current-buffer) nil "-verbose" "-classpath" (file-name-directory file) (file-name-sans-extension (file-name-nondirectory file))) (setq buffer-file-name file) (setq buffer-read-only t) (set-buffer-modified-p nil) (goto-char (point-min)) (java-ts-mode) (current-buffer)))) ((javap-handler-real op args)))) (add-to-list 'file-name-handler-alist '("\\.class$" . javap-handler)) #+END_SRC *** Jarchive #+BEGIN_SRC emacs-lisp (use-package jarchive :hook (after-init . jarchive-setup)) #+END_SRC ** ABAP #+BEGIN_SRC emacs-lisp (use-package abap-mode :mode ("\\.abap\\'")) #+END_SRC ** Crontab #+BEGIN_SRC emacs-lisp (use-package crontab-mode :defer t) #+END_SRC ** C/C++ #+BEGIN_SRC emacs-lisp (use-package cc-mode :bind (:map c-mode-base-map ("C-c C-a" . nil) ;; I want to use smartparens for () and {} instead of c-electric ("(" . nil) (")" . nil) ("{" . nil) ("}" . nil) (";" . nil) ("," . nil))) (use-package cmake-font-lock :hook (cmake-mode . cmake-font-lock-activate)) (use-package cmake-mode :mode ("CMakeLists.txt" "\\.cmake\\'")) (use-package irony :disabled t :hook (((c++-mode c-mode objc-mode) . irony-mode-on-maybe) (irony-mode . irony-cdb-autosetup-compile-options)) :config (defun irony-mode-on-maybe () ;; avoid enabling irony-mode in modes that inherits c-mode, e.g: solidity-mode (when (member major-mode irony-supported-major-modes) (irony-mode 1)))) (use-package company-irony :after irony :config (add-to-list 'company-backends 'company-irony)) (use-package irony-eldoc :hook (irony-mode)) #+END_SRC ** GLSL: OpenGLSL shader #+BEGIN_SRC emacs-lisp (use-package glsl-mode :mode ("\\.vert\\'" "\\.frag\\'" "\\.glsl\\'" "\\.geom\\'")) #+END_SRC The company backend need the =glslang= package installed for =glslangValidator=. #+BEGIN_SRC txt :tangle arch-pkglist.txt glslang #+END_SRC #+BEGIN_SRC emacs-lisp (use-package company-glsl :after glsl-mode :config (add-to-list 'company-backends 'company-glsl)) #+END_SRC ** Graphviz #+BEGIN_SRC txt :tangle arch-pkglist.txt graphviz #+END_SRC #+BEGIN_SRC emacs-lisp (use-package graphviz-dot-mode :mode ("\\.dot\\'") :config (setq graphviz-dot-indent-width 4)) #+END_SRC ** PlantUML #+BEGIN_SRC txt :tangle arch-pkglist.txt plantuml #+END_SRC #+BEGIN_SRC emacs-lisp (use-package plantuml-mode :mode ("\\.plantuml\\'") :config (setq plantuml-jar-path "/usr/share/java/plantuml/plantuml.jar")) #+END_SRC ** Configs (yaml/toml/ini/.conf/etc) #+BEGIN_SRC emacs-lisp ;; Associate more files with conf-mode (use-package conf-mode :mode ("mbsyncrc\\'" "msmtprc\\'" "pylintrc\\'" "\\.cnf\\'" "\\.env\\'" "\\.ini\\.\\(tmpl\\|sample\\|j2\\)\\'" "\\.service\\'")) #+END_SRC *** Arch PKGBUILD #+BEGIN_SRC emacs-lisp (use-package pkgbuild-mode :mode "PKGBUILD\\'") #+END_SRC *** PO: Edit GNU gettext PO files #+BEGIN_SRC emacs-lisp (use-package po-mode :mode ("\\.po\\'" "\\.po\\.")) #+END_SRC *** JSON #+BEGIN_SRC emacs-lisp (use-package json-ts-mode :mode ("\\.json\\'" "\\.avsc\\'")) (use-package json-mode :disabled t ;; jsonian is a better and way faster alternative :mode ("\\.json\\'" "\\.avsc\\'")) (use-package jsonian :disabled t ;; Try json-ts-mode :mode ("\\.avsc\\'")) ;; Helper function to decode and display JWT payload (defun jwt-payload (token) "Return JWT JSON payload from TOKEN." (json-parse-string (base64-decode-string (cadr (split-string token "\\.")) t))) (defun jwt-decode (beg end) "Print JWT payload from symbol at point or region (BEG to END)." (interactive (if (or (use-region-p) (not transient-mark-mode)) (prog1 (list (region-beginning) (region-end)) (deactivate-mark)) (if (nth 3 (syntax-ppss)) (list (beginning-of-thing 'symbol) (end-of-thing 'symbol)) (user-error "No region marked and not inside a string.")))) (let ((json-encoding-pretty-print t)) (message (json-encode (jwt-payload (buffer-substring beg end)))))) #+END_SRC *** Parquet Maybe create package for this? Needs ~parquet-cli~ AUR package. #+BEGIN_SRC emacs-lisp ;; Code adapted from https://nullprogram.com/blog/2012/08/01/ (defun parquet-handler-real (operation args) "Run the real handler without the parquet handler installed." (let ((inhibit-file-name-handlers (cons 'parquet-handler (and (eq inhibit-file-name-operation operation) inhibit-file-name-handlers))) (inhibit-file-name-operation operation)) (apply operation args))) (defun parquet-handler (op &rest args) "Handle .parquet files by calling `parquet-cli' tool." (cond ((eq op 'get-file-buffer) (let ((file (car args))) (message "file: %S" file) (with-current-buffer (create-file-buffer file) (call-process "parquet-cli" nil t nil "cat" file) (setq buffer-file-name file) ;; Remove reflection warnings from parquet-cli tool (goto-char (point-min)) (delete-region (point) (1- (search-forward "{" nil t 1))) (setq buffer-read-only t) (set-buffer-modified-p nil) (goto-char (point-min)) (jsonian-mode) (current-buffer)))) ((parquet-handler-real op args)))) (add-to-list 'file-name-handler-alist '("\\.parquet$" . parquet-handler)) #+END_SRC *** YAML #+BEGIN_SRC emacs-lisp (use-package yaml-ts-mode :mode ("\\.yaml\\'" "\\.yml\\'") :config ;; Not the perfect outline regexp but better than no folding support (setq outline-regexp "\\([[:space:]]\\{0,2\\}[a-zA-Z_-]+\\):$")) #+END_SRC *** TOML #+BEGIN_SRC emacs-lisp (use-package toml-mode :mode ("\\.toml\\'" "Cargo.lock\\'")) #+END_SRC *** CSV #+BEGIN_SRC emacs-lisp (use-package csv-mode :mode "\\.csv\\'" :hook ((csv-mode . csv-align-mode) (csv-mode . csv-header-line)) :init ;; Don't font-lock as comment when there's a `##' ;; string somewhere inside a CSV line. (setq csv-comment-start-default nil) ;; Add more separators. ;; This variable has to be set *before* ;; loading csv-mode (i.e. the use-pacakge :init block) (setq csv-separators '("," " " ";" "|"))) #+END_SRC *** CDS Core Data Services #+BEGIN_SRC emacs-lisp (use-package cds-mode :defer t) #+END_SRC *** Systemd #+BEGIN_SRC emacs-lisp (use-package systemd :mode ("\\.\\(service\\|timer\\)\\'" . systemd-mode)) #+END_SRC *** Nginx #+BEGIN_SRC emacs-lisp (use-package nginx-mode :mode ("/etc/nginx/conf.d/.*" "/etc/nginx/.*\\.conf\\'")) #+END_SRC *** Apache #+BEGIN_SRC emacs-lisp (use-package apache-mode :mode ("\\.htaccess\\'" "httpd\\.conf\\'" "srm\\.conf\\'" "access\\.conf\\'")) #+END_SRC *** ssh-config #+BEGIN_SRC emacs-lisp (use-package ssh-config-mode :mode ("\\/\\.ssh/config\\'")) #+END_SRC *** HCL (Hashicorp Configuration Language) #+BEGIN_SRC emacs-lisp (use-package hcl-mode :defer t) #+END_SRC *** Terraform #+BEGIN_SRC emacs-lisp (use-package terraform-mode :defer t) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package company-terraform :after company :config (company-terraform-init)) #+END_SRC ** Docker #+BEGIN_SRC emacs-lisp (use-package docker :bind-keymap ("C-c d" . docker-command-map) :init ;; Mark all docker-compose-arguments as safe for dir local usage (put 'docker-compose-arguments 'safe-local-variable 'listp) :config ;; Use normal shell-mode instead of vterm for docker buffers ;; (setq docker-run-async-with-buffer-function 'docker-run-async-with-buffer-shell) (setq docker-images-default-sort-key '("Created" . t)) (setq docker-containers-default-sort-key '("Status" . t))) (use-package dockerfile-mode :defer t) (use-package docker-compose-mode :mode ("docker-compose[^/]*\\.ya?ml\\'")) #+END_SRC ** Kubel (Kubernetes interface) #+BEGIN_SRC emacs-lisp (use-package kubel :defer t) #+END_SRC ** Elixir #+BEGIN_SRC emacs-lisp (use-package elixir-mode :mode ("\\.ex\\'" "\\.exs\\'" "\\.elixir\\'") :config (require 'smartparens) (sp-with-modes '(elixir-mode) (sp-local-pair "fn" "end" :when '(("SPC" "RET")) :actions '(insert navigate)) (sp-local-pair "do" "end" :when '(("SPC" "RET")) :post-handlers '(sp-ruby-def-post-handler) :actions '(insert navigate))) (use-package alchemist)) #+END_SRC ** Fish #+BEGIN_SRC emacs-lisp (use-package fish-mode :mode "\\.fish\\'" :hook (fish-mode . fish-mode-init) :config (defun fish-mode-init () (setq-local company-backends '((company-fish-shell company-shell company-shell-env company-files company-dabbrev-code))))) #+END_SRC ** Scala We need scala, sbt and metals installed #+BEGIN_SRC txt :tangle arch-pkglist.txt scala sbt metals #+END_SRC https://scalameta.org/metals/docs/editors/emacs.html #+BEGIN_SRC emacs-lisp (use-package scala-mode :mode "\\.s\\(cala\\|bt\\)$") ;; Enable sbt mode for executing sbt commands (use-package sbt-mode :defer t :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) ;; sbt-supershell kills sbt-mode: https://github.com/hvesalai/emacs-sbt-mode/issues/152 (setq sbt:program-options '("-Dsbt.supershell=false"))) (use-package lsp-metals :hook (scala-mode . lsp) :config ;; Metals claims to support range formatting by default but it supports range ;; formatting of multiline strings only. You might want to disable it so that ;; emacs can use indentation provided by scala-mode. (setq lsp-metals-server-args '("-J-Dmetals.allow-multiline-string-formatting=off"))) #+END_SRC ** Scarlet Slang #+begin_src emacs-lisp (use-package slang-mode :mode "/scarlet-os/docs/.*\\.md\\'") (use-package lsp-slang :hook (slang-mode . slang-lsp-init) :config (defun slang-lsp-init () "Re-enable all fancy lsp features that I have normally disabled." (setq-local lsp-enable-indentation t) (setq-local lsp-headerline-breadcrumb-enable t) (setq-local lsp-ui-doc-enable t) (setq-local lsp-lens-enable t) (setq-local lsp-ui-sideline-enable t) (setq-local lsp-ui-sideline-show-hover t) (setq-local lsp-ui-sideline-show-symbol t) (lsp-deferred)) ;; Set to your slang installation (setq lsp-slang-dir "~/scarlet/slang")) #+end_src ** Go For better support install: arch package `go-tools' for goimports, guru and godoc `gocode' (arch gocode-git package) for autocomplete `godef' (arch godef-git package) for godoc-at-point `golint' (arch go-lint-git) XXX: `errcheck' (go get -u github.com/kisielk/errcheck) to check for missing error checks #+BEGIN_SRC txt :tangle arch-pkglist.txt go-tools gocode-git godef-git go-lint-git #+END_SRC #+BEGIN_SRC emacs-lisp (use-package go-ts-mode :hook (go-ts-mode . apheleia-mode) :bind (:map go-ts-mode-map ("M-?" . godoc-at-point) ("M-." . godef-jump) ("M-*" . pop-tag-mark) ;; Jump back after godef-jump ("C-c m r" . go-run)) :config (setq go-ts-mode-indent-offset 4)) (use-package go-mode :mode "\\.go\\'" :hook (go-mode . go-setup) :bind (:map go-mode-map ("M-?" . godoc-at-point) ("M-." . godef-jump) ("M-*" . pop-tag-mark) ;; Jump back after godef-jump ("C-c m r" . go-run)) :config ;; Prefer goimports to gofmt if installed (let ((goimports (executable-find "goimports"))) (when goimports (setq gofmt-command goimports))) ;; For autocompeltion in the `godoc' command we need 'godoc' and not 'go doc' ;;(setq godoc-command "go doc") (setq godoc-use-completing-read t) ;; Syntax highlighting for code in godoc ;; https://github.com/dominikh/go-mode.el/pull/88#issuecomment-98448284 (defun godoc-highlight () (let ((st (make-syntax-table go-mode-syntax-table))) (modify-syntax-entry ?\' "w" st) (set-syntax-table st)) (set (make-local-variable 'font-lock-defaults) '(go--build-font-lock-keywords))) (add-hook 'godoc-mode-hook 'godoc-highlight) ;; Helper function to automatically insert commit message for updated ;; vendored submodules (defun go-submodule-states () (let ((default-directory (projectile-project-root))) (mapcar (lambda (line) (pcase-let* ((`(,state ,module) (split-string line "\t"))) (list module state (and (member state '("A" "M")) (message module) (let ((default-directory (expand-file-name module))) (message default-directory) (if (file-directory-p default-directory) (car (process-lines "git" "describe" "--tags" "--always")) "REMOVED")))))) (process-lines "git" "diff-index" "--name-status" "--cached" "HEAD" "--" "vendor/")))) (defun go-submodule-insert-update-message () "Insert information about vendor packages that are changed in the index. Formatting is according to the commit message conventions." (interactive) (let ((alist (go-submodule-states))) (when alist (let ((width (apply #'max (mapcar (lambda (e) (length (car e))) alist))) (align (cl-member-if (pcase-lambda (`(,_ ,_ ,version)) (and version (string-match-p "\\`v[0-9]" version))) alist))) (when (> (length alist) 1) (let ((a 0) (m 0) (d 0)) (pcase-dolist (`(,_ ,state ,_) alist) (pcase state ("A" (cl-incf a)) ("M" (cl-incf m)) ("D" (cl-incf d)))) (insert (format "%s %-s vendor packages\n\n" (pcase (list a m d) (`(,_ 0 0) "Install") (`(0 ,_ 0) "Update") (`(0 0 ,_) "Remove") (_ "Change")) (length alist))))) (pcase-dolist (`(,drone ,state ,version) alist) (insert (format (pcase state ("A" (format "Install %%-%is %%s%%s\n" width)) ("M" (format "Update %%-%is to %%s%%s\n" width)) ("D" "Remove %s\n")) drone (if (and align version (string-match-p "\\`\\([0-9]\\|[0-9a-f]\\{7\\}\\)" version)) " " "") version))))))) ;; Some go buffer local setup (defun go-setup () (setq tab-width 4) ;; Set compile command by default to 'go run ....' (setq-local compile-command (concat "go run " (shell-quote-argument buffer-file-name))) ;; gofmt on save (apheleia-mode))) (use-package gotest :after go-mode :bind (:map go-mode-map ("C-x t" . go-test-current-test))) (use-package go-eldoc :hook (go-mode . go-eldoc-setup)) (use-package go-projectile :after (projectile go-mode)) #+END_SRC ** Haskell #+BEGIN_SRC emacs-lisp (use-package haskell-mode :hook (haskell-mode . haskell-indentation-mode)) (use-package intero :hook (haskell-mode . intero-mode)) #+END_SRC ** Javascript Instead of calling =prettier-js= for all projects, rather put this in your =.dir-locals.el= where you want the prettier after save hook activated: #+BEGIN_SRC emacs-lisp :tangle no ((js-mode . ((eval . (prettier-js-mode))))) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package js :defer t :config (setq js-indent-level 2)) (use-package js2-mode :hook (js-mode . js2-minor-mode) :config ;; Don't warn about trailing commas (setq js2-strict-trailing-comma-warning nil)) (use-package js2-imenu-extras :hook (js2-minor-mode . js2-imenu-extras-mode)) ;; Connect to chrome ;; chromium --remote-debugging-port=9222 https://localhost:3000 ;; then in emacs ;; M-x indium-connect-to-chrome ;; or node ;; node --inspect myfile.js ;; node with breakpoint at first line ;; node --inspect --debug-brk myfile.js ;; then open the url that node prints: ;; chrome-devtools://inspector.html?...&ws=127.0.0.1:PORT/PATH ;; then in emacs: ;; M-x indium-connect-to-nodejs RET 127.0.0.1 RET PORT RET PATH, PORT, PATH ;; place `.indium' file in static root folder. (use-package indium :hook (js-mode . indium-interaction-mode) :config (setq indium-update-script-on-save t) (setq indium-chrome-executable "google-chrome-stable")) (use-package js2-refactor :hook (js2-minor-mode . js2-refactor-mode) :config (define-key js2-mode-map (kbd "C-k") #'js2r-kill) (define-key js2-refactor-mode-map (kbd "C-c r") (defhydra js2-refactor-hydra (:color blue :hint nil) " ^Functions^ ^Variables^ ^Buffer^ ^sexp^ ^Debugging^ ------------------------------------------------------------------------------------------------------------------------------ [_lp_] Localize Parameter [_ev_] Extract variable [_wi_] Wrap buffer in IIFE [_k_] js2 kill [_lt_] log this [_ef_] Extract function [_iv_] Inline variable [_ig_] Inject global in IIFE [_ss_] split string [_dt_] debug this [_ip_] Introduce parameter [_rv_] Rename variable [_ee_] Expand node at point [_sl_] forward slurp [_em_] Extract method [_vt_] Var to this [_cc_] Contract node at point [_ba_] forward barf [_ao_] Arguments to object [_sv_] Split var decl. [_uw_] unwrap [_tf_] Toggle fun exp and decl [_ag_] Add var to globals [_ta_] Toggle fun expr and => [_ti_] Ternary to if [_q_] quit" ("ee" js2r-expand-node-at-point) ("cc" js2r-contract-node-at-point) ("ef" js2r-extract-function) ("em" js2r-extract-method) ("tf" js2r-toggle-function-expression-and-declaration) ("ta" js2r-toggle-arrow-function-and-expression) ("ip" js2r-introduce-parameter) ("lp" js2r-localize-parameter) ("wi" js2r-wrap-buffer-in-iife) ("ig" js2r-inject-global-in-iife) ("ag" js2r-add-to-globals-annotation) ("ev" js2r-extract-var) ("iv" js2r-inline-var) ("rv" js2r-rename-var) ("vt" js2r-var-to-this) ("ao" js2r-arguments-to-object) ("ti" js2r-ternary-to-if) ("sv" js2r-split-var-declaration) ("ss" js2r-split-string) ("uw" js2r-unwrap) ("lt" js2r-log-this) ("dt" js2r-debug-this) ("sl" js2r-forward-slurp) ("ba" js2r-forward-barf) ("k" js2r-kill) ("q" nil) ))) (use-package skewer-mode :disabled t ; Use indium :commands skewer-mode :init (setq httpd-port 8079) ; set port for simple-httpd used by skewer (add-hook 'js-mode-hook 'skewer-mode) (add-hook 'css-mode-hook 'skewer-css-mode) (add-hook 'html-mode-hook 'skewer-html-mode)) ;; Adds the node_modules/.bin directory to the buffer exec_path. ;; E.g. support project local eslint installations. ;; XXX: Maybe add autoload for web and js2 mode? ;; (eval-after-load 'js ;; '(add-hook 'js-mode-hook #'add-node-modules-path)) (use-package add-node-modules-path :defer t) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package ng2-mode :defer t) #+END_SRC Testing with mocha #+BEGIN_SRC emacs-lisp (use-package mocha :defer t) #+END_SRC ** Lisps #+BEGIN_SRC emacs-lisp ;; Nicer elisp regex syntax highlighting (use-package easy-escape :hook ((emacs-lisp-mode lisp-mode) . easy-escape-minor-mode)) ;; From: https://github.com/Fuco1/.emacs.d/blob/af82072196564fa57726bdbabf97f1d35c43b7f7/site-lisp/redef.el#L20-L94 ;; redefines the silly indent of keyword lists ;; before ;; (:foo bar ;; :baz qux) ;; after ;; (:foo bar ;; :baz qux) (eval-after-load "lisp-mode" '(defun lisp-indent-function (indent-point state) "This function is the normal value of the variable `lisp-indent-function'. The function `calculate-lisp-indent' calls this to determine if the arguments of a Lisp function call should be indented specially. INDENT-POINT is the position at which the line being indented begins. Point is located at the point to indent under (for default indentation); STATE is the `parse-partial-sexp' state for that position. If the current line is in a call to a Lisp function that has a non-nil property `lisp-indent-function' (or the deprecated `lisp-indent-hook'), it specifies how to indent. The property value can be: - `defun', meaning indent `defun'-style \(this is also the case if there is no property and the function has a name that begins with \"def\", and three or more arguments); - an integer N, meaning indent the first N arguments specially (like ordinary function arguments), and then indent any further arguments like a body; - a function to call that returns the indentation (or nil). `lisp-indent-function' calls this function with the same two arguments that it itself received. This function returns either the indentation to use, or nil if the Lisp function does not specify a special indentation." (let ((normal-indent (current-column)) (orig-point (point))) (goto-char (1+ (elt state 1))) (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t) (cond ;; car of form doesn't seem to be a symbol, or is a keyword ((and (elt state 2) (or (not (looking-at "\\sw\\|\\s_")) (looking-at ":"))) (if (not (> (save-excursion (forward-line 1) (point)) calculate-lisp-indent-last-sexp)) (progn (goto-char calculate-lisp-indent-last-sexp) (beginning-of-line) (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t))) ;; Indent under the list or under the first sexp on the same ;; line as calculate-lisp-indent-last-sexp. Note that first ;; thing on that line has to be complete sexp since we are ;; inside the innermost containing sexp. (backward-prefix-chars) (current-column)) ((and (save-excursion (goto-char indent-point) (skip-syntax-forward " ") (not (looking-at ":"))) (save-excursion (goto-char orig-point) (looking-at ":"))) (save-excursion (goto-char (+ 2 (elt state 1))) (current-column))) (t (let ((function (buffer-substring (point) (progn (forward-sexp 1) (point)))) method) (setq method (or (function-get (intern-soft function) 'lisp-indent-function) (get (intern-soft function) 'lisp-indent-hook))) (cond ((or (eq method 'defun) (and (null method) (> (length function) 3) (string-match "\\`def" function))) (lisp-indent-defform state indent-point)) ((integerp method) (lisp-indent-specform method state indent-point normal-indent)) (method (funcall method indent-point state))))))))) #+END_SRC *** Elisp #+BEGIN_SRC emacs-lisp (use-package subr-x :defer t :config (put 'if-let 'byte-obsolete-info nil) (put 'when-let 'byte-obsolete-info nil)) (use-package elisp-mode :bind (:map emacs-lisp-mode-map ("C-c C-c" . eval-defun) ("C-c C-b" . eval-buffer) ("C-c C-k" . eval-buffer) ("C-c ;" . eval-print-as-comment) :map lisp-interaction-mode-map ; Scratch buffer ("C-c C-c" . eval-defun) ("C-c C-b" . eval-buffer) ("C-c C-k" . eval-buffer) ("C-c ;" . eval-print-as-comment)) :config (defvar eval-print-as-comment-prefix ";;=> ") (defun eval-print-as-comment (&optional arg) (interactive "P") (let ((start (point))) (eval-print-last-sexp arg) (save-excursion (goto-char start) (save-match-data (re-search-forward "[[:space:]\n]*" nil t) (insert eval-print-as-comment-prefix))))) (define-minor-mode elisp-indent-docstrings-mode "If non-nil, docstrings are displayed with extra indentation." :global t (funcall (if elisp-indent-docstrings-mode #'add-hook #'remove-hook) 'emacs-lisp-mode-hook #'elisp--add-indent-docstring-font-lock-rule) (when elisp-indent-docstrings-mode (dolist (buf (buffer-list)) (with-current-buffer buf (when (derived-mode-p 'emacs-lisp-mode) (elisp--add-indent-docstring-font-lock-rule)))))) (defun elisp--add-indent-docstring-font-lock-rule () (font-lock-add-keywords nil '((elisp--indent-docstrings)) 'append) (font-lock-flush) (push 'line-prefix font-lock-extra-managed-props)) (defun elisp--indent-docstrings (limit) (when elisp-indent-docstrings-mode (let ((pos nil)) (while (and (< (point) limit) (setq pos (text-property-any (point) limit 'face 'font-lock-doc-face))) (goto-char pos) (let* ((ppss (syntax-ppss)) (start (or (nth 8 ppss) pos)) (indent (save-excursion (goto-char start) (when (and (eq (char-after) ?\") (not (eq (char-after (1+ (point))) ?\\))) (1+ (current-column))))) (display (when indent (concat ;; "\n" (make-string indent ?\s)))) (end (or (text-property-not-all (point) limit 'face 'font-lock-doc-face) limit))) (if (not display) (goto-char end) (while (re-search-forward "^." end 'move) (put-text-property (match-beginning 0) (1+ (match-beginning 0)) 'line-prefix display))))))) nil) ;; Virtually indent multiline Elisp docstrings without changing the buffer (elisp-indent-docstrings-mode) (add-hook 'emacs-lisp-mode-hook (lambda () (setq mode-name "EL")))) #+END_SRC **** Shortdoc: Quick documentation #+BEGIN_SRC emacs-lisp (use-package shortdoc :defer t :config (define-short-documentation-group datetime "Querying current time" (current-time :eval (current-time)) (float-time :eval (float-time)) "Time formats" (time-convert :eval (time-convert (current-time)) :eval (time-convert (current-time) 'list) :eval (time-convert (current-time) 100000) :eval (time-convert (current-time) 200000) :eval (time-convert (current-time) t) :eval (time-convert (current-time) 'integer)) (float-time :eval (float-time (current-time))))) #+END_SRC **** Helper libraries (dash, s, marshal) #+BEGIN_SRC emacs-lisp (use-package dash :defer t :config (global-dash-fontify-mode)) (use-package s :defer t) (use-package f :defer t) #+END_SRC ***** Request #+BEGIN_SRC emacs-lisp (use-package request :defer t) #+END_SRC **** Auto-compile #+BEGIN_SRC emacs-lisp (use-package auto-compile :disabled t ; I rather trigger a new compile by hand :defer 10 :config (auto-compile-on-load-mode) (auto-compile-on-save-mode) (setq auto-compile-display-buffer nil) (setq auto-compile-mode-line-counter t) (setq auto-compile-source-recreate-deletes-dest t) (setq auto-compile-toggle-deletes-nonlib-dest t) (setq auto-compile-update-autoloads t) (add-hook 'auto-compile-inhibit-compile-hook 'auto-compile-inhibit-compile-detached-git-head)) #+END_SRC **** Litable: Live preview for elisp #+BEGIN_SRC emacs-lisp (use-package litable :defer t) #+END_SRC **** Package helpers #+BEGIN_SRC emacs-lisp (use-package package-lint :defer t) (use-package flycheck-package :after flycheck :config (flycheck-package-setup)) #+END_SRC **** El2markdown: Convert package commentary to markdown #+BEGIN_SRC emacs-lisp (use-package el2markdown :defer t) #+END_SRC *** Common Lisp From the sly/slime manual for faster sly/slime startup: For SBCL, we recommend that you create a custom core file with socket support and POSIX bindings included because those modules take the most time to load. To create such a core, execute the following steps: #+BEGIN_SRC sh :dir ~/.emacs.d/var :tangle no $ cd ~/.emacs.d/var/ $ sbcl (mapc 'require '(sb-bsd-sockets sb-posix sb-introspect sb-cltl2 asdf)) (save-lisp-and-die "sbcl.core-for-sly") #+END_SRC #+BEGIN_SRC emacs-lisp (use-package sly :defer t :config (setq inferior-lisp-program "sbcl") (setq sly-lisp-implementations `((sbcl ("sbcl" "--core" ,(no-littering-expand-var-file-name "sbcl.core-for-sly")) :init (lambda (port-file _) (format "(slynk:start-server %S)\n" port-file)))))) #+END_SRC *** Clojure #+BEGIN_SRC emacs-lisp (use-package clojure-mode :hook ((clojure-mode . clojure-init)) :bind (:map clojure-mode-map ("C-M-;" . clojure-toggle-ignore)) :config (defun clojure-init () "Start lsp and add edn imenu regexp." (clojure-mode-add-edn-imenu-regexp) (setq-local lsp-completion-enable nil) (lsp)) ;; For large edn files I want to navigate with imenu to root level keywords. ;; For a similar hack with outline you can: `(setq-local outline-regexp "^.?.?:[^ ]+")' (defun clojure-mode-add-edn-imenu-regexp () "Hacky way to get imenu for root-level keywords. Useful in edn files." (add-to-list 'imenu-generic-expression '(nil "^.?.?\\(:[^ ]+\\).*$" 1) t)) ;; Highlight strings after a :class keyword differently ;; This makes it easier to differentiate actual text from ;; Tailwind classes in hiccup forms. (font-lock-add-keywords 'clojurescript-mode '((":class[ \\r\\n\\t]*\\((str[ \\r\\n\\t]*\\)?\\\"\\(.+?\\)\\\"" 2 font-lock-variable-name-face t))) ;; use project-root to find the root for a Clojure project (setq clojure-project-root-function (lambda (&optional path) (let ((root-path (project-root (project-current)))) (if (string-match-p "vsce" path) (concat root-path "projects/vsce") root-path)))) ;; Eval top level forms inside comment forms instead of the comment form itself (setq clojure-toplevel-inside-comment-form t) ;; Don't align the body of clojure.core/match with the first argument (put-clojure-indent 'match 1) ;; Indent fn-traced and defn-traced the same as a regular defn. ;; The macros are for re-frame-10x tracing. (put-clojure-indent 'fn-traced :defn) (put-clojure-indent 'defn-traced :defn)) #+END_SRC **** CIDER Install openjdk sources so you can jump to Java symbols in cider. #+BEGIN_SRC txt :tangle arch-pkglist.txt openjdk-src openjdk11-src #+END_SRC #+BEGIN_SRC emacs-lisp (use-package cider :bind (:map cider-mode-map ("" . cider-scratch-project) ("M-?" . cider-maybe-clojuredocs) ("C-c C-t C-c" . -cider-test-run-rich-comment-tests) ("C-c C-o" . -cider-find-and-clear-repl-and-result-output) :map cider-repl-mode-map ("M-?" . cider-doc)) :hook (((cider-mode cider-repl-mode) . cider-company-enable-fuzzy-completion) (cider-mode . eldoc-mode)) :config ;; By default prefer clojure-cli build-tool when jacking in (setq cider-preferred-build-tool 'clojure-cli) ;; and set the :dev and :licp alias (setq cider-clojure-cli-aliases ":dev:licp") ;; Always reuse a dead REPS without prompt when it's the only option (setq cider-reuse-dead-repls 'auto) ;; Only show cider eval results as overlay and not in the minibuffer (setq cider-use-overlays t) ;; Use enrich-classpath for better Java lib completions/docs (setq cider-enrich-classpath t) ;; Use `moon' spinner that looks nice and doesn't take as much space as the progress bar (setq cider-eval-spinner-type 'moon) ;; I basically never connect to a remote host nrepl, so skip the host question on connect (defun cider--completing-read-host (hosts) '("localhost")) ;; Display cider-scratch buffer in the same window (add-to-list 'display-buffer-alist '("*cider-scratch.*" (display-buffer-reuse-window display-buffer-same-window))) ;; Output to the cider-result buffer ;; This needs my personal WIP fork: https://github.com/dakra/cider/tree/wip ;; (setq cider-interactive-eval-output-destination 'cider-result-buffer) (defun -cider-find-and-clear-repl-and-result-output (&optional clear-repl) "Like `cider-find-and-clear-repl-output' but additionally clear the *cider-result* buffer." (interactive "P") (cider-find-and-clear-repl-output clear-repl) (let ((buf (get-buffer cider-result-buffer))) (when (and buf (> (buffer-size buf) 0)) ;; Only clear when buffer exists and is not empty (save-excursion (with-current-buffer buf (let ((inhibit-read-only t)) (if clear-repl ;; Remove all output when called with prefix (delete-region (point-min) (point-max)) ;; Only remove output of the "cell" where the cursor currently is (goto-char (or (search-backward "\f" nil t) (point-min))) ;; Search the beginning (forward-sexp) ;; Skip input sexp (forward-line 2) ;; Skip the =*ns* :line-ns=> info arrow (beginning-of-line) (let ((start (point))) (search-forward "\f" nil t) (backward-char) (delete-region start (point))) (insert-before-markers (propertize ";; output cleared\n" 'font-lock-face 'font-lock-comment-face))))))))) (defun cider-maybe-clojuredocs (&optional arg) "Like `cider-doc' but call `cider-clojuredocs' when invoked with prefix arg in `clojure-mode'." (interactive "P") (if (and arg (or (eq major-mode 'clojure-mode) (eq major-mode 'clojurec-mode) (eq major-mode 'cider-clojure-interaction-mode))) (cider-clojuredocs) (cider-doc))) ;; Location of the jdk sources. In Arch Linux package `openjdk-src' (setq cider-jdk-src-paths "/usr/lib/jvm/java-19-openjdk/lib/src.zip") (require 's) (defmacro -cider-check-alias-fn (alias) "Return predicate function that check if cider contains alias string ALIAS." `(lambda (&rest _) (or (and cider-clojure-cli-aliases (s-contains? ,alias cider-clojure-cli-aliases)) (and cider-clojure-cli-global-options (s-contains? ,alias cider-clojure-cli-global-options))))) ;; Inject flow-storm middleware in cider-jack-in when the `:flow-storm' alias is set (add-to-list 'cider-jack-in-nrepl-middlewares `("flow-storm.nrepl.middleware/wrap-flow-storm" :predicate ,(-cider-check-alias-fn ":flow-storm"))) ;; Inject portal middleware in cider-jack-in when the `:portal' alias is set (add-to-list 'cider-jack-in-nrepl-middlewares `("portal.nrepl/wrap-portal" :predicate ,(-cider-check-alias-fn ":portal"))) ;; Inject reveal middleware in cider-jack-in when the `:reveal' alias is set (add-to-list 'cider-jack-in-nrepl-middlewares `("vlaaad.reveal.nrepl/middleware" :predicate ,(-cider-check-alias-fn ":reveal"))) ;; Inject shadowcljs nrepl middleware in cider-jack-in when the `:cljs' alias is set (add-to-list 'cider-jack-in-nrepl-middlewares `("shadow.cljs.devtools.server.nrepl/middleware" :predicate ,(-cider-check-alias-fn ":cljs"))) (defun -cider-test-run-rich-comment-tests () "Run rich comment tests in current namespace." (interactive) (save-buffer) (cider-interactive-eval "((requiring-resolve 'com.mjdowney.rich-comment-tests/run-ns-tests!) *ns*)")) ;; Update classpath without restarting the repl ;; Requires lambdaisland.classpath which is under :licp alias in my global deps.edn (defun cider-update-deps-classpath () "Update classpath from deps.edn with lambdaisland.classpath." (interactive) ;; FIXME: Catch cider error and just display `user-error' when licp not found (cider-interactive-eval "(require 'lambdaisland.classpath)") (let* ((deps-path (concat (project-root (project-current t)) "deps.edn")) (deps-str (with-temp-buffer (insert-file-contents deps-path) (buffer-string)))) (cider-interactive-eval (concat "(lambdaisland.classpath/update-classpath! '{:extra " deps-str "})")))) ;; Shortcut for clerk/show (defun clerk-serve () "Serve clerk notebooks." (interactive) (cider-interactive-eval "(nextjournal.clerk/serve! {:browse? true})")) (defun clerk-tap-inspector () "Open tap inspector notebook to let Clerk show a tap> stream." (interactive) (message "Show tap> stream in clerk.") (cider-interactive-eval "(nextjournal.clerk/show! 'nextjournal.clerk.tap)")) (defun clerk-tap-table (&optional full-p) "Evaluate and tap the expression preceding point as a clerk table." (interactive "P") (let ((tapped-form (concat "(clojure.core/doto " (cider-last-sexp) " (->> " "(nextjournal.clerk/table " (if full-p "{:nextjournal.clerk/width :full}" "") ") clojure.core/tap>))"))) (cider-interactive-eval tapped-form nil nil (cider--nrepl-pr-request-map)))) (defun clerk-tap-vega-lite (&optional wide-p) "Evaluate and tap the expression preceding point as a vega lite chart. If invoked with WIDE-P, make the chart ::clerk/width :wide" (interactive "P") (let ((tapped-form (concat "(clojure.core/doto " (cider-last-sexp) " (->> " "(nextjournal.clerk/vl " (if wide-p "{:nextjournal.clerk/width :wide}" "") ") clojure.core/tap>))"))) (cider-interactive-eval tapped-form nil nil (cider--nrepl-pr-request-map)))) (defun clerk-build () "Build static html for the current clerk notebook." (interactive) (message "Building static page") (when-let ((filename (buffer-file-name))) (let ((root (project-root (project-current t)))) (cider-interactive-eval (concat "(nextjournal.clerk/build! {:paths [\"" (file-relative-name filename root) "\"]})"))))) (defun clerk-show () "Show buffer in clerk." (interactive) (message "Show buffer in clerk.") (when-let ((filename (buffer-file-name))) (cider-interactive-eval (concat "(nextjournal.clerk/show! \"" filename "\")")))) (defun clerk-save-and-show () "Save buffer and show in clerk." (interactive) (save-buffer) (clerk-show)) (define-minor-mode clerk-mode "A mode that just binds `' to `clerk-show'." :lighter " clerk" :keymap `((,(kbd "") . clerk-save-and-show) (,(kbd "") . clerk-tap-table)) (if clerk-mode (add-hook 'after-save-hook #'clerk-show 100 t) (remove-hook 'after-save-hook #'clerk-show t))) (defun portal-open () "Open portal, define dev/portal and add tap." (interactive) (cider-nrepl-sync-request:eval "(do (ns dev) (def portal ((requiring-resolve 'portal.api/open) {#_#_:launcher :emacs :theme :portal.colors/material-ui})) (add-tap (requiring-resolve 'portal.api/submit)))")) (defun portal-clear () "Clear portal" (interactive) (cider-nrepl-sync-request:eval "(portal.api/clear)")) (defun portal-close () "Close portal" (interactive) (cider-nrepl-sync-request:eval "(portal.api/close)")) ;; jack-in for babashka (defun cider-jack-in-babashka-old () "Start an babashka nREPL server for the current project and connect to it." (interactive) (let* ((default-directory (project-root (project-current t))) (process-filter (lambda (proc string) "Run cider-connect once babashka nrepl server is ready." (when (string-match "Started nREPL server at .+:\\([0-9]+\\)" string) (cider-connect-clj (list :host "localhost" :port (match-string 1 string) :project-dir default-directory))) ;; Default behavior: write to process buffer (internal-default-process-filter proc string)))) (set-process-filter (start-file-process "babashka" "*babashka*" "bb" "--nrepl-server" "0") process-filter))) ;; jack-in for babashka from corgi ;; Code mostly from corgi: https://github.com/lambdaisland/corgi-packages/blob/main/corgi-clojure/corgi-clojure.el#L192-L211 (defun cider-jack-in-babashka (&optional project-dir) "Start a utility CIDER REPL backed by Babashka, not related to a specific project." (interactive) (let ((project-dir (or project-dir (project-root (project-current t))))) (nrepl-start-server-process project-dir "bb --nrepl-server 0" (lambda (server-buffer) (cider-nrepl-connect (list :repl-buffer server-buffer :repl-type 'clj :host (plist-get nrepl-endpoint :host) :port (plist-get nrepl-endpoint :port) :project-dir project-dir :session-name "babashka" :repl-init-function (lambda () (setq-local cljr-suppress-no-project-warning t cljr-suppress-middleware-warnings t) (rename-buffer "*babashka-repl*")))))))) ;; Store more items in repl history (default 500) (setq cider-repl-history-size 2000) ;; When loading the buffer (C-c C-k) save first without asking (setq cider-save-file-on-load t) ;; Don't show cider help text in repl after jack-in (setq cider-repl-display-help-banner nil) ;; Don't focus repl after sending somehint to there from another buffer (setq cider-switch-to-repl-on-insert nil) ;; Eval automatically when insreting in the repl (e..g. C-c C-j d/e) (unless called with prefix) (setq cider-invert-insert-eval-p t) ;; Show error as overlay instead of the buffer (buffer is generated anyway in case it's needed) (setq cider-show-error-buffer 'except-in-repl) ;; If we set `cider-show-error-buffer' to non-nil, ;; don't focus error buffer when error is thrown (setq cider-auto-select-error-buffer nil) ;; Don't focus inspector after evaluating something (setq cider-inspector-auto-select-buffer nil) ;; Don't show tooltip with mouse hover (setq cider-use-tooltips nil) ;; Display context dependent info in the eldoc where possible. (setq cider-eldoc-display-context-dependent-info t) ;; Don't pop to the REPL buffer on connect ;; Create and display the buffer, but don't focus it. (setq cider-repl-pop-to-buffer-on-connect 'display-only) ;; Just use symbol under point and don't prompt for symbol in e.g. cider-doc. (setq cider-prompt-for-symbol nil)) #+END_SRC **** clj-refactor #+BEGIN_SRC emacs-lisp (use-package clj-refactor :hook (clojure-mode . clj-refactor-mode) :bind (:map cider-mode-map ("C-c C-r n a" . cljr-add-missing-libspec)) :config ;; Allow a few more chars each row in namespace (default 72) (setq cljr-print-right-margin 90) (dolist (magic-require '(("aero" . "aero.core") ("clerk" . "nextjournal.clerk") ("csv" . "clojure.data.csv") ("edn" . "clojure.edn") ("http" . "babashka.http-client") ("jdbc" . "next.jdbc") ("transit" . "cognitect.transit") ("walk" . "clojure.walk") ("pprint" . "clojure.pprint") ("http" . "babashka.http-client") ("reagent" . "reagent.core") ("re-frame" . "re-frame.core") ("tick" . "tick.core"))) (add-to-list 'cljr-magic-require-namespaces magic-require))) #+END_SRC **** clj-kondo Flycheck integration with the clj-kondo linter You need the =clj-kondo= tool installed. #+BEGIN_SRC txt :tangle arch-pkglist.txt clj-kondo-bin #+END_SRC #+BEGIN_SRC emacs-lisp (use-package flycheck-clj-kondo :after (flycheck clojure-mode)) #+END_SRC **** ob-clojure #+BEGIN_SRC emacs-lisp (use-package ob-clojure :after ob :config (setq org-babel-clojure-backend 'babashka)) #+END_SRC **** Reference for "Clojure, The Essential Reference" #+BEGIN_SRC emacs-lisp (use-package clojure-essential-ref-nov :defer t ;; :bind (:map cider-mode-map ;; ("M-???" . clojure-essential-ref) ;; :map cider-repl-mode-map ;; ("M-?" . clojure-essential-ref)) :config (setq clojure-essential-ref-nov-epub-path "~/books/Clojure_The_Essential_Reference_v31.epub") (setq clojure-essential-ref-default-browse-fn #'clojure-essential-ref-nov-browse)) #+END_SRC **** Neil #+BEGIN_SRC emacs-lisp (use-package neil :defer t :config (setq neil-prompt-for-version-p nil) ; Always use latest version (setq neil-inject-dep-to-project-p nil) (setq neil-executable-path (-> "neil" locate-library file-name-directory (concat "neil")))) #+END_SRC **** Convert HTML to Hiccup syntax #+BEGIN_SRC emacs-lisp (use-package html-to-hiccup :commands (html-to-hiccup-yank-formatted) :config (defun html-to-hiccup-yank-formatted () (interactive) (let ((beg (point))) (html-to-hiccup-yank) (cider-format-edn-region beg (point)))) (setq html-to-hiccup-use-shorthand-p nil)) #+END_SRC **** Datomic yasnippets Yasnippets for Datomic. *Usage* - =dsa= Insert a datomic schema attribute - =dsf= Insert a datomic schema function - =dst= List all datomic schema types - =:db= List additional attribute attributes. #+BEGIN_SRC emacs-lisp (use-package datomic-snippets :disabled t :after yasnippet) #+END_SRC **** Datomic utility functions #+BEGIN_SRC emacs-lisp (use-package datomic :defer t :config ;; Don't query user on auto deploy when there is a dependency conflict. (setq datomic-confirm-deploy-with-conflicts nil) (setq datomic-access-program (expand-file-name "~/clojure/datomic-aws/datomic-cli/datomic-access")) (setq datomic-systems '("pm-mvp"))) #+END_SRC **** Clomacs: Call Clojure code from Emacs lisp and vice versa #+BEGIN_SRC emacs-lisp (use-package clomacs :defer t) #+END_SRC *** Hy #+BEGIN_SRC emacs-lisp ;;; Lisp in python vm (use-package hy-mode :mode "\\.hy\\'") #+END_SRC ** Lua #+BEGIN_SRC emacs-lisp (use-package lua-mode :mode "\\.lua\\'" :interpreter ("lua" . lua-mode) :hook (lua-mode . lua-outline-mode) :bind (:map lua-mode-map ("M-?" . lua-search-documentation) ("M-." . xref-find-definitions)) :config ;; Use eww to browse Lua documentation (setq lua-documentation-function 'eww) (defun lua-outline-mode () (setq-local outline-regexp "function"))) (use-package company-lua :hook (lua-mode . my-lua-mode-company-init) :config (defun my-lua-mode-company-init () (setq-local company-backends '((company-lua company-etags company-dabbrev-code))))) #+END_SRC ** Markup Languages #+BEGIN_SRC emacs-lisp (use-package adoc-mode :mode ("\\.adoc\\'")) (use-package jira-markup-mode :mode ("\\.confluence\\'" "\\.jira\\'")) (use-package markdown-mode :mode (("/itsalltext/.*\\(gitlab\\|github\\).*\\.txt$" . gfm-mode) ("\\.markdown\\'" . gfm-mode) ("README\\.md\\'" . gfm-mode)) :bind (:map markdown-mode-map ("C-c =" . markdown-insert-header-dwim)) :config ;; Display remote images (setq markdown-display-remote-images t) ;; Enable fontification for code blocks (setq markdown-fontify-code-blocks-natively t) ;; Add some more languages (dolist (x '(("ini" . conf-mode) ("clj" . clojure-mode) ("cljs" . clojure-mode) ("cljc" . clojure-mode))) (add-to-list 'markdown-code-lang-modes x)) ;; use pandoc with source code syntax highlighting to preview markdown (C-c C-c p) (setq markdown-command "pandoc -s --highlight-style pygments -f markdown_github -t html5")) #+END_SRC ** Nim You need to install the =nim= package, plus =nimble= and =nimsuggest=. #+BEGIN_SRC txt :tangle arch-pkglist.txt nim nimble nimsuggest #+END_SRC #+BEGIN_SRC emacs-lisp (use-package nim-mode :hook (nim-mode . nimsuggest-mode)) #+END_SRC ** Octave #+BEGIN_SRC txt :tangle arch-pkglist.txt octave #+END_SRC #+BEGIN_SRC emacs-lisp (use-package octave :mode ("\\.m\\'" . octave-mode) :interpreter ("octave" . octave-mode) :bind (:map octave-mode-map ("C-x C-e" . octave-send-region-or-line)) :config (setq octave-block-offset 4) (defun octave-send-region-or-line () (interactive) (if (region-active-p) (octave-send-region (region-beginning) (region-end)) (octave-send-line)))) #+END_SRC ** PHP #+BEGIN_SRC emacs-lisp (use-package php-mode :defer t) #+END_SRC ** Python *** Cython #+BEGIN_SRC emacs-lisp (use-package cython-mode :mode ("\\.pyd\\'" "\\.pyi\\'" "\\.pyx\\'")) (use-package flycheck-cython :after (cython-mode flycheck)) #+END_SRC *** Jupyter #+BEGIN_SRC emacs-lisp (use-package zmq :disabled t :defer t) (use-package jupyter :disabled t :after ob ;; Load after org-babel :config ;; Set a default session and make execution async by default (setq org-babel-default-header-args:jupyter-python '((:async . "yes") (:kernel . "python") (:session . "py-default")))) (use-package jupyter-repl :disabled t :after jupyter ;;:hook (jupyter-repl-mode . show-smartparens-mode) :config ;; Always show output from eval in a python buffer also in the repl (setq jupyter-repl-echo-eval-p t) ;; Allow to press RET even when the kernel is busy (setq jupyter-repl-allow-RET-when-busy t) ;; Display more in the repl before it get's truncated (setq jupyter-repl-maximum-size 16384)) #+END_SRC *** Code-cells: Edit Jupyter notebooks as Python files Tools needed to convert to and from notebook files: #+BEGIN_SRC txt :tangle arch-pkglist.txt python-jupytext pandoc-bin #+END_SRC #+BEGIN_SRC emacs-lisp (use-package code-cells :bind (:map code-cells-mode-map ("C-M-p" . code-cells-backward-cell) ("C-M-n" . code-cells-forward-cell) ("C-c C-SPC" . code-cells-mark-cell) ([remap python-shell-send-whole-line-or-region] . code-cells-eval) ([remap python-shell-send-region] . code-cells-eval) ([remap jupyter-eval-line-or-region] . code-cells-eval) ([remap cider-eval-region] . code-cells-eval)) :config (define-key code-cells-mode-map (kbd "C-c C-w") (code-cells-command 'kill-region :use-region)) (define-key code-cells-mode-map (kbd "C-c M-w") (code-cells-command 'kill-ring-save :use-region))) #+END_SRC *** Main Python setup #+BEGIN_SRC emacs-lisp (use-package python :mode (("\\.py\\'" . python-ts-mode) ("\\.xsh\\'" . python-ts-mode)) ; Xonsh script files :interpreter ("python" . python-ts-mode) :bind (:map python-ts-mode-map ("C-x C-e" . python-shell-send-whole-line-or-region) ("C-c C-c" . python-shell-send-whole-line-or-region) ("C-c C-k" . python-shell-send-buffer) ("C-c C-d" . python-shell-send-defun) ("C-c C-p" . hydra-python/body) ("C-c C-t" . hydra-python/body) :map python-mode-map ("C-x C-e" . python-shell-send-whole-line-or-region) ("C-c C-c" . python-shell-send-whole-line-or-region) ("C-c C-k" . python-shell-send-buffer) ("C-c C-d" . python-shell-send-defun) ("C-c C-p" . hydra-python/body) ("C-c C-t" . hydra-python/body)) :hook (python-mode . python-flat-imenu-index) :init ;; Allow setting some python variables via dir-locals. ;; This can be dangerous if someone makes you open an untrusted ;; file with a malicious `.dir-locals' and execute some more ;; malicious python code. But I'm not too worried ;; and I change these often enough that I don't want to save ;; for each variable I allow. ;; TODO: Make the check for extra-pythonpaths more strict. (put 'python-shell-extra-pythonpaths 'safe-local-variable 'listp) ;; Only mark virtualenvs safe that are my home folder (put 'python-shell-virtualenv-root 'safe-local-variable (lambda (p) (s-starts-with-p (expand-file-name "~/.virtualenvs/") (expand-file-name p)))) (put 'python-shell-process-environment 'safe-local-variable (create-safe-env-p "DJANGO_SETTINGS_MODULE" "ENV_INI_PATH")) :config (defun run-pyspark () (interactive) (run-python "pyspark --packages io.delta:delta-core_2.12:1.0.0 --conf \"spark.sql.extensions=io.delta.sql.DeltaSparkSessionExtension\" --conf \"spark.sql.catalog.spark_catalog=org.apache.spark.sql.delta.catalog.DeltaCatalog\"" nil t)) ;; ipython5 uses prompt_toolkit which doesn't play nice with emacs ;; when setting interpreter to 'ipython', you need additional '--simple-prompt' arg ;; (setq python-shell-interpreter "python") ;; (setq python-shell-interpreter-args "-i") ;; FIXME: run new python interpreter on projectile-switch-project? ;; and only run pshell when it's a pyramid project. ;;(setq python-shell-interpreter "python" ;; python-shell-interpreter-args "--simple-prompt -i /home/daniel/.virtualenvs/atomx/lib/python3.5/site-packages/pyramid/scripts/pshell.py /home/daniel/atomx/api/development.ini") (setq python-shell-interpreter "ipython") (setq python-shell-interpreter-args "-i --simple-prompt") ;; Don't spam message buffer when python-mode can't guess indent-offset (setq python-indent-guess-indent-offset-verbose nil) (defun python-shell-send-whole-line-or-region (prefix) "Send whole line or region to inferior Python process." (interactive "*p") (whole-line-or-region-wrap-beg-end 'python-shell-send-region prefix) (deactivate-mark)) (defhydra hydra-python-test (python-mode-map "C-c C-t" :color blue) "Run Python Tests" ("f" python-test-function "Function") ("m" python-test-method "Method") ("c" python-test-class "Class") ("F" python-test-file "File") ("p" python-test-project "Project") ("q" nil "Cancel")) (defun py-isort-add-import-whole-line-or-region (prefix) "Import module(s) from region or whole line." (interactive "*p") (whole-line-or-region-wrap-beg-end 'py-isort-add-import-region prefix)) (defun python-run-server () "Start pyramid pserve or django runserver." (interactive) (if (pyramid-project-root) (pyramid-serve) (djangonaut-run-management-command "runserver"))) (defhydra hydra-python (python-mode-map "C-c C-p" :color blue :hint nil) " ^Tests^ ^Import^ ^Other^ ------------------------------------------------------------------- [_f_] Function [_a_] From ... import [_P_] Run Python in project [_m_] Method [_i_] Import [_I_] Pippel [_c_] Class [_l_] Import line/region [_R_] Runserver [_F_] File [_r_] Remove imports [_!_] Run Python in cwd [_p_] Project [_s_] Sort imports [_q_] Cancel " ("a" py-isort-add-from-import) ("i" py-isort-add-import) ("l" py-isort-add-import-whole-line-or-region) ("r" py-isort-remove-import) ("s" py-isort-buffer) ("f" python-test-function) ("m" python-test-method) ("c" python-test-class) ("F" python-test-file) ("p" python-test-project) ("P" project-run-python) ("I" pippel-list-packages) ("R" python-run-server) ("!" run-python) ("q" nil)) ;; Convert python syntax to json (require 'pythonic) (defun py2json (start end) "Read region in Python syntax and convert to json." (interactive (if (or (use-region-p) (not transient-mark-mode)) (prog1 (list (region-beginning) (region-end)) (deactivate-mark)) (user-error "No region"))) (let* ((exit-code nil) (code "import json; print(json.dumps(%s))") (code (format code (buffer-substring start end))) (output (with-output-to-string (with-current-buffer standard-output (setq exit-code (pythonic-call-process :buffer standard-output :args (append (list "-c" code)))))))) (when (not (zerop exit-code)) (error (format "Python exit with status code %d" exit-code))) (kill-region start end) (insert output) (json-reformat-region (- (point) (length output)) (point)))) (defun python-pprint (start end) "Read region in Python syntax and pretty print it." (interactive (if (or (use-region-p) (not transient-mark-mode)) (prog1 (list (region-beginning) (region-end)) (deactivate-mark)) (user-error "No region"))) (let* ((exit-code nil) (code "import pprint; pprint.pprint(%s)") (code (format code (buffer-substring start end))) (output (with-output-to-string (with-current-buffer standard-output (setq exit-code (pythonic-call-process :buffer standard-output :args (append (list "-c" code)))))))) (when (not (zerop exit-code)) (error (format "Python exit with status code %d" exit-code))) (kill-region start end) (insert output))) (require 'thingatpt) (require 'projectile) (defun projectile-find-sql-file () "Find sql file for symbol at point." (interactive) (let* ((project-files (projectile-current-project-files)) (file (if (region-active-p) (format "%s.sql" (buffer-substring (region-beginning) (region-end))) (when (thing-at-point 'symbol) (format "%s.sql" (thing-at-point 'symbol))))) (candidates (cl-remove-if-not (lambda (f) (let ((name (file-name-nondirectory f))) (string-equal name file))) project-files))) (when candidates ;; Just take the first candidate (find-file (expand-file-name (car candidates) (projectile-project-root))) (run-hooks 'projectile-find-file-hook) t))) (defun python-flat-imenu-index () (setq-local imenu-create-index-function #'python-imenu-create-flat-index))) #+END_SRC *** Anaconda: Code navigation, documentation lookup and completion for Python #+BEGIN_SRC emacs-lisp (use-package anaconda-mode :bind (:map anaconda-mode-map ("M-." . python-goto-sql-file-or-definition) ("M-," . anaconda-mode-find-assignments) ("M-?" . anaconda-mode-show-doc)) :hook (((python-mode python-ts-mode) . anaconda-mode) ((python-mode python-ts-mode) . anaconda-eldoc-mode)) :config (defun python-goto-sql-file-or-definition (&optional arg) "Call anaconda find-definitions or with prefix ARG find sql file." (interactive "P") (back-button-push-mark-local-and-global) (if arg (projectile-find-sql-file) (anaconda-mode-find-definitions) (recenter)))) (use-package company-anaconda :after anaconda-mode :config (add-to-list 'company-backends 'company-anaconda)) #+END_SRC *** Pippel: List, install, upgrade packages with pip #+BEGIN_SRC emacs-lisp ;; package-list-packages like interface for python packages (use-package pippel :defer t) #+END_SRC *** Pip requirements #+BEGIN_SRC emacs-lisp ;; Syntax highlighting for requirements.txt files (use-package pip-requirements :defer t) #+END_SRC *** Sphinx This adds a few sphinx features and fontification for rst buffers. You can do `sphinx-compile` (`C-c C-x C-c`) to compile the sphinx docs or `sphinx-compile-and-view` (`C-c C-x C-v`) to compile and view. #+BEGIN_SRC emacs-lisp (use-package sphinx-mode :hook (rst-mode . sphinx-mode)) #+END_SRC *** Sphinx-doc: Auto-generate docstrings #+BEGIN_SRC emacs-lisp (use-package sphinx-doc :hook (((python-mode python-ts-mode) . sphinx-doc-mode)) :config (setq sphinx-doc-include-types t)) #+END_SRC *** Python-test: Run python tests with unittest, pytest, django #+BEGIN_SRC emacs-lisp (use-package python-test :defer t :config ;; Set default test backend to pytest (setq python-test-backend 'pytest)) #+END_SRC *** Pyramid #+BEGIN_SRC emacs-lisp (use-package pyramid :defer t) #+END_SRC *** Django #+BEGIN_SRC emacs-lisp (use-package djangonaut :defer t :config (setq djangonaut-run-shell-plus-arguments '("--plain")) (defun djangonaut-run-shell-plus-inferior-python () (message "test hook")) (setq djangonaut-run-shell-plus-hook '(djangonaut-run-shell-plus-inferior-python)) (defun djangonaut-shell-plus () (interactive) (with-current-buffer (djangonaut-run-management-command "shell_plus" "--plain") (let ((python-shell--interpreter nil) (python-shell--interpreter-args nil)) (inferior-python-mode)))) ) #+END_SRC *** Pydoc: Nicer documentation view #+BEGIN_SRC emacs-lisp ;; Enable (restructured) syntax highlighting for python docstrings (use-package python-docstring :hook (((python-mode python-ts-mode) . python-docstring-mode))) (use-package pydoc :disabled t ; FIXME: Stopped working with newer jedi versions :after anaconda-mode :bind (:map anaconda-mode-map ("M-?" . pydoc-at-point))) #+END_SRC *** Isort I have a =.isort.cfg= in my home folder that contains a config to match the =black= style: #+BEGIN_SRC conf :tangle ~/.config/_isort.cfg # Auto-generated from emacs config # isort config to match `black` style [settings] line_length=99 known_third_party=rethinkdb,pydruid,redis,publicsuffix,rapidjson,restalchemy multi_line_output=3 include_trailing_comma=True force_grid_wrap=0 combine_as_imports=True indent=4 #+END_SRC #+BEGIN_SRC emacs-lisp ;; Automatically sort and format python imports (use-package py-isort :defer t) #+END_SRC *** Blacken: Auto format Python buffer with black If you want =blacken= called as before-save-hook in your project put something like this in your =.dir-locals.el=: #+BEGIN_SRC emacs-lisp :tangle no ((python-mode . ((flycheck-checker . python-mypy) (eval . (blacken-mode))))) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package blacken :disabled t ; FIXME: Obsolete since Apheleia :defer t :config ;; Allow using Python 3.6-only syntax (setq blacken-allow-py36 t) ;; Skips temporary sanity checks (setq blacken-fast-unsafe t) ;; Use fill-column line-length (setq blacken-line-length 'fill)) #+END_SRC *** Virtualenvwrapper: Automatically switch virtualenvs on projectile switch project #+BEGIN_SRC emacs-lisp (use-package virtualenvwrapper ;; Automatically switch python venv :hook ((projectile-after-switch-project . venv-projectile-auto-workon) (venv-postmkvirtualenv . venv-install-dev-deps)) :config (defun venv-install-dev-deps () (venv-with-virtualenv venv-current-name (compile "pip install -U pip readline pylint flake8 mypy black isort requests types-requests ipython"))) (venv-initialize-interactive-shells) ;; if you want interactive shell support (venv-initialize-eshell) ;; if you want eshell support (setq venv-virtualenv-command "python -m venv") (setq venv-location "/home/daniel/.virtualenvs/")) #+END_SRC ** Prolog #+BEGIN_SRC emacs-lisp (use-package prolog :mode (("\\.\\(pl\\|pro\\|lgt\\)\\'" . prolog-mode) ("\\.m\\'" . mercury-mode)) :interpreter ("prolog" . prolog-mode) :config (setq-default prolog-system 'swi)) #+END_SRC ** Redis #+BEGIN_SRC emacs-lisp ;; Commint for redis (use-package redis :defer t) #+END_SRC ** Ruby #+BEGIN_SRC emacs-lisp (use-package ruby-mode :defer t) (use-package inf-ruby :hook (ruby-mode-hook . inf-ruby-minor-mode)) #+END_SRC ** Rust #+BEGIN_SRC emacs-lisp ;; You may need installing the following packages on your system: ;; * rustc (Rust Compiler) ;; * cargo (Rust Package Manager) ;; * racer (Rust Completion Tool) ;; * rustfmt (Rust Tool for formatting code) (use-package rust-mode :mode "\\.rs\\'" :config (use-package flycheck-rust :after flycheck :commands flycheck-rust-setup :init (add-hook 'flycheck-mode-hook #'flycheck-rust-setup)) (use-package cargo :commands cargo-minor-mode :init (add-hook 'rust-mode-hook #'cargo-minor-mode)) (use-package racer :commands racer-mode :hook (rust-mode . racer-mode) :config (define-key rust-mode-map (kbd "TAB") #'company-indent-or-complete-common))) #+END_SRC ** Solidity (Ethereum) Solidity with flycheck integration requires `solc` and `soldium`. So for Arch you need to install `solidity-git` and `sodium` from AUR. #+BEGIN_SRC emacs-lisp (use-package solidity-mode :mode "\\.sol\\'" :init (setq solidity-flycheck-solc-checker-active t) (setq solidity-flycheck-solium-checker-active t)) #+END_SRC ** SQL #+BEGIN_SRC emacs-lisp (use-package sql :mode (("\\.\\(m\\|my\\)?sql\\'" . sql-mode)) ; msql = Mako template sql :hook (sql-interactive-mode . toggle-truncate-lines) :commands sql-init-passwords :bind (:map sql-interactive-mode-map ("M-p" . comint-previous-prompt) ("M-n" . comint-next-prompt) ([return] . dakra/add-semicolon-and-comint-send-input) ("DEL" . sql-delete-backward-char) ("C-d" . dakra/sql-quit-or-delete-char)) :init ;; Persist sqli history across multiple sessions (setq-default sql-input-ring-file-name (no-littering-expand-var-file-name "sql-input-ring")) :config ;; Don't display the SQLi buffer after `sql-send-*' commands. ;; Often I have the SQL buffer open on a different frame (/monitor) and ;; can check the result there (setq sql-display-sqli-buffer-function nil) ;; Fix prompt detection for mariadb (sql-set-product-feature 'mysql :prompt-regexp "^.*\\(MariaDB\\|MySQL\\|mysql\\) ?\\[?[_a-zA-Z0-9()]*\\]?> ") (sql-set-product-feature 'mariadb :prompt-regexp "^\\(MariaDB\\|MySQL\\|mysql\\) ?\\[?[_a-zA-Z0-9()]*\\]?> ") ;; I never use multiline in the comint mode. So auto add ";" at the end (defun dakra/add-semicolon-and-comint-send-input () "Adds semicolon at the end of the line and runs comint-send-input." (interactive) (beginning-of-line) (if (looking-at "\\\\") ;; Don't append ";" if we use a postgres special command (comint-send-input) (move-end-of-line nil) ;;(delete-horizontal-space) ; Remove all trailing whitespace (skip-syntax-backward " \n") (backward-char) ;; Only add semicolon if there is non already (unless (looking-at ";") (forward-char) (insert ";")) (comint-send-input))) (defun dakra/sql-quit-or-delete-char (arg) (interactive "p") (if (and (eolp) (looking-back sql-prompt-regexp nil)) (progn (comint-quit-subjob) (sleep-for 0.4) ; Wait 400ms for the process to quit (kill-buffer (current-buffer)) (ignore-errors (when (= arg 4) ; With prefix argument, also remove sql buffer frame/window (progn ;; Remove frame if sql buffer is only window (otherwise just close window) (if (one-window-p) (delete-frame) (delete-window)))))) (delete-char arg))) (defun sql-delete-backward-char (n) "Only call (delete-backward-char N) when not at beginning of prompt." (interactive "p") (if (looking-back sql-prompt-regexp nil) (message "Beginning of prompt") (delete-char (- n)))) (setq sql-product 'postgres) (defun dakra-sql-connect () "Ensure that sql-connection-alist is populated including passwords." (interactive) (unless sql-connection-alist (sql-init-passwords)) (call-interactively #'sql-connect)) ;; FIXME: Use advice (defun sql-connect--ensure-passwords (&optional _connection _buf-name) "Ensure that sql-connection-alist is populated including passwords." (interactive (progn (unless sql-connection-alist (sql-init-passwords)) (list (sql-read-connection "Connection: ") current-prefix-arg)))) ;;(advice-add 'sql-connect :before #'sql-connect--ensure-passwords) (defun sql-init-passwords () "Fill sql-connection-alist with passwords from =~/.authinfo.gpg=." (interactive) (setq sql-connection-alist '((mysql-root (sql-product 'mariadb) (sql-user "root") (sql-database "mysql") (sql-mysql-options '("-A"))) (spicy-local (sql-product 'mysql) (sql-user "spicy") (sql-port 3306) (sql-server "127.0.0.1") (sql-database "booking_2spicy") (sql-mysql-options '("-A"))) (spicy-prod (sql-product 'mariadb) (sql-port 3307) ;; (sql-default-directory "/ssh:booking-2spicy:") (sql-user "booking_2spicy") (sql-database "booking_2spicy")) (postgres-root (sql-product 'postgres) (sql-user "postgres") (sql-port 5432) (sql-database "skor")) (skor-local (sql-product 'postgres) (sql-user "skor") (sql-port 5433) (sql-database "skor")) (skor-remote (sql-product 'postgres) (sql-port 5433) (sql-user "skor") (sql-database "skor")))) (auth-source-forget-all-cached) ;; FIXME (dolist (conn sql-connection-alist) (let* ((conn-name (car conn)) (conn-details (cdr conn)) (host (or (cadr (assoc 'sql-server conn-details)) "127.0.0.1")) (user (symbol-name conn-name)) (password (auth-source-pick-first-password :host host :user user))) (unless password (message "No password set for sql connection %s in authinfo." conn-name)) ;; Add password to each antry from .authinfo.gpg (nconc conn-details `((sql-password ,password))) ;; When there is no sql-server set, set it to "127.0.0.1" (unless (assoc 'sql-server conn-details) (nconc conn-details '((sql-server "127.0.0.1"))))))) (setq sql-mysql-login-params (append sql-mysql-login-params '(port))) (setq sql-mysql-login-params '((user :default "daniel") (database :default "api") (server :default "localhost")))) #+END_SRC *** sql-indent: Smart indentation for SQL files #+BEGIN_SRC emacs-lisp (use-package sql-indent :hook ((sql-mode sql-interactive-mode) . sqlind-minor-mode)) ;;:config (setq-default sqlind-basic-offset 4) #+END_SRC *** sqlup-mode: Capitalize keywords in SQL mode #+BEGIN_SRC emacs-lisp (use-package sqlup-mode :hook (sql-mode sql-interactive-mode redis-mode) :config ;; Don't capitalize keywords that are quoted ;; https://github.com/Trevoke/sqlup-mode.el/issues/69 (modify-syntax-entry ?\" "\"" sql-mode-syntax-table) (modify-syntax-entry ?` "\"" sql-mode-syntax-table) ;; Don't capitalize some SQL keywords that I also use as column names (setq sqlup-blacklist '("id" "name" "names" "type"))) #+END_SRC *** elc-sql: Use a JDBC connection for a better SQL client To use elc for AWS Athena (idea from [[https://deepumohan.com/tech/query-aws-athena-with-emacs-using-jdbc/][here]]): Download athena-jdbc jar from [[https://docs.aws.amazon.com/athena/latest/ug/connect-with-jdbc.html][AWS site]] and install locally with #+BEGIN_SRC shell :dir ~/Downloads/ :tangle no mvn install:install-file -Dfile="./AthenaJDBC42.jar" -DgroupId=com.simba.athena -DartifactId=athena-jdbc -Dversion=2.0.16 -Dpackaging=jar -DgeneratePom=true #+END_SRC Then add athena jdbc driver to the `ejc-jdbc-drivers` (see config below). To activate =ejc= in =org-mode= use M-x =ejc-connect=. #+BEGIN_SRC emacs-lisp (use-package ejc-sql :defer t :hook ((ejc-sql-minor-mode . ejc-eldoc-setup) (ejc-sql-connected . dakra/ejc-sql-connected)) :config (plist-put ejc-jdbc-drivers "awsathena" [com.simba.athena/athena-jdbc "2.0.16"]) (setq ejc-result-table-impl 'ejc-result-mode) ;; 'orgtbl-mode ;; Store the last 50 query results (setq ejc-ring-length 50) ;; and store them in the Emacs var folder (setq ejc-results-path (no-littering-expand-var-file-name "ejc-results")) ;; Give Athena a bit more time to respond (default 10s) (setq nrepl-sync-request-timeout 120) (require 'ejc-company) ;; Use company for completion (push 'ejc-company-backend company-backends) ;; Use standard selection instead of ido so we can make use of ivy (setq ejc-completion-system 'standard) ;; There's only one dateformat (setq ejc-date-output-format "%Y-%m-%d %H:%M:%S") (defun dakra/ejc-sql-connected () ;; (ejc-set-use-unicode t) ;; Increase max rows (ejc-set-max-rows 500) (ejc-set-fetch-size 500)) (ejc-create-connection "sov-dbs01-dwh-prod-ro" :dependencies [[mysql/mysql-connector-java "8.0.22"]] ;;:classname "com.mysql.cj.jdbc.Driver" :connection-uri "jdbc:mysql://dbs01-dwh-prod.sdc.adacor.host:3306/gc_datawarehouse?serverTimezone=Europe/Berlin&useSSL=False" :user "dkraus_ro" :password "lJsxxgiBAo742wgob9fM") (ejc-create-connection "athena-datalake-prod" :dbtype "awsathena" :classpath "~/.m2/repository/com/simba/athena/athena-jdbc/2.0.16/athena-jdbc-2.0.16.jar" :classname "com.simba.athena.jdbc.Driver" :subprotocol "awsathena" :connection-uri (concat "jdbc:awsathena://AwsRegion=eu-central-1;" "LogLevel=0;" ;; turn logging off "Schema=datalake;" "S3OutputLocation=s3://sov-data-prod-athena-results/;" "AwsCredentialsProviderClass=com.simba.athena.amazonaws.auth.profile.ProfileCredentialsProvider;" "AwsCredentialsProviderArguments=technical_prod;")) (ejc-create-connection "athena-datalake-test" :dbtype "awsathena" :classpath "~/.m2/repository/com/simba/athena/athena-jdbc/2.0.16/athena-jdbc-2.0.16.jar" :classname "com.simba.athena.jdbc.Driver" :subprotocol "awsathena" :connection-uri (concat "jdbc:awsathena://AwsRegion=eu-central-1;" "LogLevel=0;" ;; turn logging off "Schema=datalake;" "S3OutputLocation=s3://sov-data-test-athena-results/;" "AwsCredentialsProviderClass=com.simba.athena.amazonaws.auth.profile.ProfileCredentialsProvider;" "AwsCredentialsProviderArguments=technical_test;")) (ejc-create-connection "athena-datalake-old" :dbtype "awsathena" :classpath "~/.m2/repository/com/simba/athena/athena-jdbc/2.0.16/athena-jdbc-2.0.16.jar" :classname "com.simba.athena.jdbc.Driver" :subprotocol "awsathena" :connection-uri (concat "jdbc:awsathena://AwsRegion=eu-central-1;" "LogLevel=0;" ;; turn logging off "Schema=datalake;" "S3OutputLocation=s3://aws-athena-query-results-sovendus/athena/;" "AwsCredentialsProviderClass=com.simba.athena.amazonaws.auth.profile.ProfileCredentialsProvider;" "AwsCredentialsProviderArguments=simba_session;")) ;; (ejc-create-connection "athena-datalake-no-mfa" ;; :dbtype "awsathena" ;; :classpath "~/.m2/repository/com/simba/athena/athena-jdbc/2.0.9/athena-jdbc-2.0.9.jar" ;; :classname "com.simba.athena.jdbc.Driver" ;; :subprotocol "awsathena" ;; :subname (concat "//athena.eu-central-1.amazonaws.com:443/datalake;" ;; "S3OutputLocation=s3://aws-athena-query-results-sovendus/athena/") ;; :user (getenv "AWS_ACCESS_KEY_ID") ;; :password (getenv "AWS_SECRET_ACCESS_KEY")) ) #+END_SRC ** MongoDB For org-babel support #+BEGIN_SRC emacs-lisp (use-package ob-mongo :defer t) #+END_SRC Start inferior mongodb shell #+BEGIN_SRC emacs-lisp (use-package inf-mongo :defer t) #+END_SRC ** Tailwind #+BEGIN_SRC emacs-lisp (defun tailwind-sort (beg end) "Sort a tailwind class string with rustywind. Sort either the marked region or if no region is active and point is inside a string, sort the string." (interactive (if (or (use-region-p) (not transient-mark-mode)) (prog1 (list (region-beginning) (region-end)) (deactivate-mark)) (if (nth 3 (syntax-ppss)) (list (beginning-of-thing 'edit-indirect-string) (end-of-thing 'edit-indirect-string)) (user-error "No region marked and not inside a string.")))) (let* ((buf (current-buffer)) (orig-str (buffer-substring-no-properties beg end)) (class-str (concat "class=\"" orig-str "\"")) (proc (make-process :name "tailwind-sort" :command '("rustywind" "--stdin") :filter (lambda (p output) (process-put p :output (concat (process-get p :output) output))) :connection-type 'pipe))) (process-send-string proc class-str) (process-send-eof proc) (accept-process-output proc) (with-current-buffer buf (save-excursion (delete-region beg end) (goto-char beg) (insert (substring (process-get proc :output) 7 -1)))))) #+END_SRC ** Tex Some useful packages and necessary for `org-timesheet`. #+BEGIN_SRC txt :tangle arch-pkglist.txt rubber texlive-bin texlive-fontsextra texlive-latexextra #+END_SRC You also need to configure and install auctex in the system. Borg does only build the elisp part. #+BEGIN_SRC sh :tangle no :var auctexdir=(expand-file-name "auctex" borg-drones-directory) cd $auctexdir ./autogen.sh ./configure make sudo make install #+END_SRC #+BEGIN_SRC emacs-lisp (use-package auctex :defer t) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package company-auctex :after auctex :config (company-auctex-init)) #+END_SRC ** Typescript Format options can be specified in the ~tide-format-options~ variable or via tsfmt.json (present in the root folder along with tsconfig.json): #+BEGIN_SRC json :tangle no { "indentSize": 4, "tabSize": 4, "insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": false, "placeOpenBraceOnNewLineForFunctions": false, "placeOpenBraceOnNewLineForControlBlocks": false } #+END_SRC See https://github.com/Microsoft/TypeScript/blob/cc58e2d7eb144f0b2ff89e6a6685fb4deaa24fde/src/server/protocol.d.ts#L421-473 for the full list available options To edit javascript with typescript create `jsconfig.json` in the root folder of your project. `jsconfig.json` is `tsconfig.json` with `allowJs` attribute set to true. #+BEGIN_SRC json :tangle no { "compilerOptions": { "target": "es2017", "allowSyntheticDefaultImports": true, "noEmit": true, "checkJs": true, "jsx": "react", "lib": [ "dom", "es2017" ] } } #+END_SRC #+BEGIN_SRC emacs-lisp ;; TypeScript (use-package typescript-mode :config (setq typescript-indent-level 2)) (use-package tide :hook ((typescript-mode) . tide-setup) :config ;; Configure javascript-tide checker to run after your default javascript checker (flycheck-add-next-checker 'javascript-eslint 'javascript-tide 'append) ;; Format the buffer before saving ;; FIXME: auto indent doesn't respect editorconfig ;;(add-hook 'before-save-hook 'tide-format-before-save) (setq tide-format-options '(:insertSpaceAfterFunctionKeywordForAnonymousFunctions t :placeOpenBraceOnNewLineForFunctions nil))) #+END_SRC ** Vega-view: Data visualization using vega, vega-lite Needs the Vega command line tools installed: #+BEGIN_SRC shell :tangle no npm install -g vega vega-lite vega-cli canvas #+END_SRC #+BEGIN_SRC emacs-lisp (use-package vega-view :defer t) #+END_SRC ** Web Helper function for URL encode / decode #+BEGIN_SRC emacs-lisp (defun url-encode-region (start end) "URL encode region." (interactive (if (or (use-region-p) (not transient-mark-mode)) (prog1 (list (region-beginning) (region-end)) (deactivate-mark)) (user-error "No region"))) (let ((encoded (url-hexify-string (buffer-substring-no-properties start end)))) (delete-region start end) (insert encoded))) (defun url-decode-region (start end) "URL decode region." (interactive (if (or (use-region-p) (not transient-mark-mode)) (prog1 (list (region-beginning) (region-end)) (deactivate-mark)) (user-error "No region"))) (let ((decoded (url-unhex-string (buffer-substring-no-properties start end)))) (delete-region start end) (insert decoded))) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package emmet-mode :hook (web-mode sgml-mode css-mode) :bind (:map emmet-mode-keymap ("" . emmet-expand-line) ("\C-c TAB" . emmet-expand-line) ("C-M-p" . emmet-prev-edit-point) ("C-M-n" . emmet-next-edit-point)) :config (setq emmet-move-cursor-between-quotes t) (setq emmet-move-cursor-after-expanding t)) (use-package rainbow-mode :hook (css-mode scss-mode sass-mode emacs-lisp-mode)) (use-package scss-mode :defer t :config ;;(setq css-indent-offset 2) ;; turn off annoying auto-compile on save (setq scss-compile-at-save nil)) (use-package sass-mode :mode ("\\.sass\\'")) #+END_SRC #+BEGIN_SRC emacs-lisp ;; FIXME: add flycheck support? Only for .vue files? ;; (flycheck-add-mode 'javascript-eslint 'web-mode) (use-package web-mode :mode ("\\.phtml\\'" "\\.tpl\\.php\\'" "\\.tpl\\'" "\\.blade\\.php\\'" "\\.jsp\\'" "\\.as[cp]x\\'" "\\.erb\\'" "\\.html.?\\'" "/\\(views\\|html\\|theme\\|templates\\)/.*\\.php\\'" "\\.jinja2?\\'" "\\.mako\\'" "\\.vue\\'" "_template\\.txt" "\\.ftl\\'") :init (add-to-list 'safe-local-eval-forms '(web-mode-set-engine "django")) :config ;;(setq web-mode-engines-alist '(("django" . "/templates/.*\\.html\\'"))) (setq web-mode-engines-alist '(("django" . "\\.jinja2?\\'"))) ;; make web-mode play nice with smartparens (setq web-mode-enable-auto-pairing nil) (require 'smartparens) (sp-with-modes '(web-mode) (sp-local-pair "%" "%" :unless '(sp-in-string-p) :post-handlers '(((lambda (&rest _ignored) (just-one-space) (save-excursion (insert " "))) "SPC" "=" "#"))) (sp-local-tag "%" "<% " " %>") (sp-local-tag "=" "<%= " " %>") (sp-local-tag "#" "<%# " " %>")) ;; Flyspell setup ;;http://blog.binchen.org/posts/effective-spell-check-in-emacs.html ;; {{ flyspell setup for web-mode (defun web-mode-flyspell-verify () (let* ((f (get-text-property (- (point) 1) 'face)) rlt) (cond ;; Check the words with these font faces, possibly. ;; this *blacklist* will be tweaked in next condition ((not (memq f '(web-mode-html-attr-value-face web-mode-html-tag-face web-mode-html-attr-name-face web-mode-constant-face web-mode-doctype-face web-mode-keyword-face web-mode-comment-face ;; focus on get html label right web-mode-function-name-face web-mode-variable-name-face web-mode-css-property-name-face web-mode-css-selector-face web-mode-css-color-face web-mode-type-face web-mode-block-control-face))) (setq rlt t)) ;; check attribute value under certain conditions ((memq f '(web-mode-html-attr-value-face)) (save-excursion (search-backward-regexp "=['\"]" (line-beginning-position) t) (backward-char) (setq rlt (string-match "^\\(value\\|class\\|ng[A-Za-z0-9-]*\\)$" (thing-at-point 'symbol))))) ;; finalize the blacklist (t (setq rlt nil))) rlt)) (put 'web-mode 'flyspell-mode-predicate 'web-mode-flyspell-verify) ;; Don't display doublon (double word) as error (defvar flyspell-check-doublon t "Check doublon (double word) when calling `flyspell-highlight-incorrect-region'.") (make-variable-buffer-local 'flyspell-check-doublon) (defadvice flyspell-highlight-incorrect-region (around flyspell-highlight-incorrect-region-hack activate) (if (or flyspell-check-doublon (not (eq 'doublon (ad-get-arg 2)))) ad-do-it)) (defun web-mode-hook-setup () ;;(flyspell-mode 1) (setq flyspell-check-doublon nil)) (add-hook 'web-mode-hook 'web-mode-hook-setup) ;; } flyspell setup ;; Enable current element highlight (setq web-mode-enable-current-element-highlight t) ;; Show column for current element ;; Like highlight-indent-guide but only one line for current element (setq web-mode-enable-current-column-highlight t) ;; Don't indent directly after a etc. (defadvice company-tide (before web-mode-set-up-ac-sources activate) "Set `tide-mode' based on current language before running company-tide." (if (equal major-mode 'web-mode) (let ((web-mode-cur-language (web-mode-language-at-pos))) (if (or (string= web-mode-cur-language "javascript") (string= web-mode-cur-language "jsx") ) (unless tide-mode (tide-mode)) (if tide-mode (tide-mode -1))))))) #+END_SRC * personal.el #+BEGIN_SRC emacs-lisp ;; auto kill buffer when closing window (defun maybe-delete-frame-buffer (frame) "When a dedicated FRAME is deleted, also kill its buffer. A dedicated frame contains a single window whose buffer is not displayed anywhere else." (let ((windows (window-list frame))) (when (eq 1 (length windows)) (let ((buffer (window-buffer (car windows)))) (when (eq 1 (length (get-buffer-window-list buffer nil t))) (kill-buffer buffer)))))) ;;(add-to-list 'delete-frame-functions #'maybe-delete-frame-buffer) (use-package restclient :mode ("\\.rest\\'" . restclient-mode) :hook (restclient-mode . restclient-outline-mode) :config ;; Open application/edn responses in clojure mode (add-to-list 'restclient-content-type-modes '("application/edn" . clojure-mode)) (defun restclient-outline-mode () (outline-minor-mode) (setq-local outline-regexp "##+"))) (use-package company-restclient :after (restclient company) :config (add-to-list 'company-backends 'company-restclient)) ;; Useful if you're *not* using exwm: ;; More useful frame title, that show either a file or a ;; buffer name (if the buffer isn't visiting a file) ;; (setq frame-title-format ;; '("" invocation-name " " (:eval (if (buffer-file-name) ;; (abbreviate-file-name (buffer-file-name)) ;; "%b")))) ;; change `find-file` so all files that belong to root are opened as root ;; too often unintentional changes. just use 'M-x crux-sudo-edit' when needed ;;(crux-reopen-as-root-mode) ;; Operate on system processes like dired (use-package proced :bind ("C-x p" . proced) :config (setq-default proced-filter 'all) (setq proced-format 'medium) (setq proced-auto-update-flag t) (setq proced-auto-update-interval 1)) ;; scroll 4 lines up/down w/o moving pointer ;;(global-set-key "\M-n" (lambda () (interactive) (scroll-up 1)) ) ;;(global-set-key "\M-p" (lambda () (interactive) (scroll-down 1)) ) (use-package iedit :init (setq iedit-toggle-key-default nil) :bind ("C-c ;" . iedit-mode)) (use-package yasnippet :disabled t :defer 10 :mode (("\\.yasnippet\\'" . snippet-mode)) :bind (:map yas-minor-mode-map ;; Complete yasnippets with company. No need for extra bindings ;;("TAB" . nil) ; Remove Yasnippet's default tab key binding ;;([tab] . nil) ;; Set Yasnippet's key binding to C-tab ("\C-c TAB" . yas-expand)) :config (yas-global-mode 1)) (use-package shrink-whitespace :bind ("M-SPC" . shrink-whitespace)) ;; backup (setq create-lockfiles nil) ; disable lock file symlinks ;;(setq backup-directory-alist `((".*" . "~/.emacs.d/.backups"))) (setq make-backup-files t ; backup of a file the first time it is saved. backup-by-copying t ; don't clobber symlinks version-control t ; version numbers for backup files delete-old-versions t ; delete excess backup files silently kept-old-versions 6 ; oldest versions to keep when a new numbered backup is made (default: 2) kept-new-versions 9 ; newest versions to keep when a new numbered backup is made (default: 2) ) (use-package keychain-environment :disabled t :if (daemonp) ;; Load ssh/gpg agent environment after 2 minutes. If the agent isn't started yet (not entered password), ;; we have to call (keychain-refresh-environment) interactively later :defer 120 :commands keychain-refresh-environment :config (keychain-refresh-environment)) #+END_SRC * Org A lot of functions and inspiration from http://doc.norang.ca/org-mode.html #+BEGIN_SRC emacs-lisp (use-package org :mode ("\\.\\(org\\|org_archive\\|txt\\)\\'" . org-mode) :hook (org-mode . org-mode--init) :bind (("C-c a" . org-agenda) ("" . org-agenda) ("" . org-clock-goto) ("C-c l" . org-store-link) ("C-c o c" . org-clock-goto) ("C-c o i" . org-clock-in-or-list) ("C-c C-x C-j" . org-clock-goto) ("C-c C-x C-i" . org-clock-in-or-list) ("C-c C-x C-o" . org-clock-out) ("C-c o O" . org-clock-out) ("C-c o l" . org-store-link) ("C-c o a" . org-agenda) ("C-c o b" . org-switchb) ("C-c o d" . org-hide-all-drawers) :map org-mode-map ([(shift return)] . crux-smart-open-line) ([(control shift return)] . crux-smart-open-line-above) ("" . org-insert-todo-heading-respect-content) ("" . org-meta-return) ("M-." . org-open-at-point) ; So M-. behaves like in source code. ("M-," . org-mark-ring-goto) ("M-;" . org-comment-dwim-2) ("M-i" . consult-org-heading) ("C-c C-x C-i" . org-clock-in-or-list) ;; Disable adding and removing org-agenda files via keybinding. ;; I explicitly specify agenda file directories in the config. ("C-c [" . nil) ("C-c ]" . nil) ("\C-c TAB" . nil) ;; Remove for tempel-expand ("C-a" . org-beginning-of-line) ; Overwrite crux-beginning-of-line ("M-o" . ace-link-org) ("M-p" . org-previous-visible-heading) ("M-n" . org-next-visible-heading) ("" . org-metaup) ("" . org-metadown) :map org-src-mode-map ("C-x n" . org-edit-src-exit)) :init (defun org-mode--init () ;; Allow posting images in org-mode via yank-media (yank-media-handler "image/.*" #'org-mode--image-yank-handler) ;; Automatic line-wrapping in org-mode ;;(auto-fill-mode 1) (setq completion-at-point-functions '(org-completion-symbols org-cap-filesystem))) :config ;; FIXME: Can be removed once the default goes down (setq org-element--cache-self-verify-frequency 0.001) ;; Mostly from sgml-mode.el html-mode--image-yank-handler (defun org-mode--image-yank-handler (type image) (let ((file (read-file-name (format "Save %s image to: " type) (expand-file-name "images/" default-directory)))) (when (file-directory-p file) (user-error "%s is a directory")) (when (and (file-exists-p file) (not (yes-or-no-p (format "%s exists; overwrite?" file)))) (user-error "%s exists")) (with-temp-buffer (set-buffer-multibyte nil) (insert image) (write-region (point-min) (point-max) file)) (insert (format "[[file:%s]]\n" (file-relative-name file))) (org-display-inline-images))) ;; Put a real space in front of the content for each level ;; (setq org-adapt-indentation t) ;; Insead of "..." show "…" when there's hidden folded content ;; Some characters to choose from: …, ⤵, ▼, ↴, ⬎, ⤷, and ⋱ (setq org-ellipsis "…") (defun org-clock-in-or-list (&optional select start-time) "Like org-clock-in but show list of recent clocks when not in org buffer. Show clock history when not in org buffer or when called with prefix argument." (interactive "P") (if (and (eq major-mode 'org-mode) (not (equal select '(4)))) (org-clock-in select start-time) ;; FIXME: add functionality of (counsel-org-clock-history) to consult ;; Use variable org-clock-history with list of markers (org-clock-goto))) ;; Fold everything when opening an org file. ;; Set it to 'overview to onnly show headings up to level 2. (setq org-startup-folded t) ;; Show inline images by default (setq org-startup-with-inline-images t) ;; Add more levels to headlines that get displayed with imenu (setq org-imenu-depth 5) ;; Enter key follows links (= C-c C-o) (setq org-return-follows-link t) ;; When enabling the cache I get errors on latest Emacs 29.1 ;; (wrong-type-argument avl-tree- nil) ;; So disable it (temporarily) ;; (setq org-element-use-cache nil) ;; Never show 'days' in clocksum (e.g. in report clocktable) ;; format string used when creating CLOCKSUM lines and when generating a ;; time duration (avoid showing days) (setq org-duration-format '((special . h:mm))) ;; Set to (("d" . nil) (special . h:mm)) if you want to show days ;; Set default column view headings: Task Effort Clock_Summary ;;(setq org-columns-default-format "%80ITEM(Task) %10Effort(Effort){:} %10CLOCKSUM") ;; Set default column view headings: Task Total-Time Time-Stamp (setq org-columns-default-format "%75ITEM(Task) %10CLOCKSUM %16TIMESTAMP_IA") ;; global Effort estimate values ;; global STYLE property values for completion (setq org-global-properties (quote (("Effort_ALL" . "0:15 0:30 0:45 1:00 2:00 3:00 4:00 5:00 6:00 0:00") ("STYLE_ALL" . "habit")))) ;; Tags with fast selection keys (setq org-tag-alist (quote ((:startgroup) ("WAITING" . ?w) ("HOLD" . ?h) ("MEETING" . ?m) ("REVIEW" . ?r) ("NOTE" . ?n) (:endgroup) ("PERSONAL" . ?P) ("WORK" . ?W) ("crypt" . ?c) ("FLAGGED" . ??)))) ;; Allow setting single tags without the menu (setq org-fast-tag-selection-single-key (quote expert)) (setq org-archive-mark-done nil) (setq org-archive-location "%s_archive::* Archived Tasks") ;; C-RET, C-S-RET insert new heading after current task content (setq org-insert-heading-respect-content nil) ;; Show a little bit more when using sparse-trees (setq org-show-following-heading t) (setq org-show-hierarchy-above t) (setq org-show-siblings (quote ((default)))) ;; don't show * / = etc (setq org-hide-emphasis-markers t) ;; leave highlights in sparse tree after edit. C-c C-c removes highlights (setq org-remove-highlights-with-change nil) ;; M-RET should not split the lines (setq org-M-RET-may-split-line '((default . nil))) (setq org-special-ctrl-a/e t) (setq org-special-ctrl-k t) (setq org-yank-adjusted-subtrees t) ;; Export latex with vertical space between paragraphs ;; instead of the default which is just a slight horizontal indent. (add-to-list 'org-latex-default-packages-alist '("" "parskip" nil)) (require 'smartparens-org) ;; Additional org sp-local-pairs (setq org-directory "~/org/") ;; Log time when we re-schedule a task (setq org-log-reschedule 'time) ;; Always take note when marking task as done (setq org-log-done 'note) ;; and take note when re-scheduling a deadline (setq org-log-redeadline 'note) ;; Show org entities as UTF-8 characters (e.g. \sum as ∑) (setq org-pretty-entities t) ;; But Don't print "bar" as subscript in "foo_bar" (setq org-pretty-entities-include-sub-superscripts nil) ;; And also don't display ^ or _ as super/subscripts (setq org-use-sub-superscripts nil) ;; Undone TODO entries will block switching the parent to DONE (setq org-enforce-todo-dependencies t) (setq org-use-fast-todo-selection t) ;; This allows changing todo states with S-left and S-right skipping all of the normal processing ;; when entering or leaving a todo state. ;; This cycles through the todo states but skips setting timestamps and entering notes which ;; is very convenient when all you want to do is fix up the status of an entry. (setq org-treat-S-cursor-todo-selection-as-state-change nil) (setq org-default-notes-file (concat org-directory "inbox.org")) ;; FIXME: Merge with org mode org-cycle-hide-drawers ;; Using only this would always show all drawers expanded by default ;; From: https://stackoverflow.com/questions/17478260/completely-hide-the-properties-drawer-in-org-mode (defun org-cycle-hide-drawers-all (state) "Re-hide all drawers after a visibility state change." (when (and (derived-mode-p 'org-mode) (not (memq state '(overview folded contents)))) (save-excursion (let* ((globalp (memq state '(contents all))) (beg (if globalp (point-min) (point))) (end (if globalp (point-max) (if (eq state 'children) (save-excursion (outline-next-heading) (point)) (org-end-of-subtree t))))) (goto-char beg) (while (re-search-forward org-drawer-regexp (max end (point)) t) (save-excursion (beginning-of-line 1) (when (looking-at org-drawer-regexp) (let* ((start (1- (match-beginning 0))) (limit (save-excursion (outline-next-heading) (point))) (msg (format (concat "org-cycle-hide-drawers: " "`:END:`" " line missing at position %s") (1+ start)))) (if (re-search-forward "^[ \t]*:END:" limit t) (outline-flag-region start (line-end-position) t) (user-error msg)))))))))) (defun org-hide-all-drawers () "Hide all drawers" (interactive) (org-cycle-hide-drawers-all 'all)) ;; FIXME: Create minor mode that always hides drawers and adds/removes advice instead of temp var (setq org-always-hide-all-drawers-p nil) (defun org-always-hide-all-drawers (state) (when (and (not (eq state 'folded)) org-always-hide-all-drawers-p) (org-cycle-hide-drawers-all 'all))) (add-hook 'org-cycle-hook #'org-always-hide-all-drawers) (defun org-always-hide-all-drawers-toggle () "Toggle always hidden drawers." (interactive) (setq org-always-hide-all-drawers-p (not org-always-hide-all-drawers-p)) (org-cycle-hide-drawers-all t)) ;; Targets include this file and any file contributing to the agenda - up to 9 levels deep (setq org-refile-targets '((nil :maxlevel . 9) (org-agenda-files :maxlevel . 9))) ;; Allow refile to create parent tasks with confirmation (setq org-refile-allow-creating-parent-nodes (quote confirm)) (setq org-refile-use-outline-path 'file) ; Show filename for refiling (setq org-outline-path-complete-in-steps nil) ; Refile in a single go ;; Exclude DONE state tasks from refile targets (defun org-refile-verify-refile-target () "Exclude todo keywords with a done state from refile targets." (not (member (nth 2 (org-heading-components)) org-done-keywords))) (setq org-refile-target-verify-function #'org-refile-verify-refile-target) ;; Automatically change the list bullets when you change list levels (setq org-list-demote-modify-bullet '(("+" . "-") ("*" . "-") ("1." . "-") ("1)" . "-") ("A)" . "-") ("B)" . "-") ("a)" . "-") ("b)" . "-") ("A." . "-") ("B." . "-") ("a." . "-") ("b." . "-"))) (setq org-todo-keywords (quote ((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d)") (sequence "WAITING(w@/!)" "HOLD(h@/!)" "|" "CANCELLED(c@/!)" "MEETING")))) (setq org-todo-keyword-faces (quote (("TODO" :foreground "red" :weight bold) ("NEXT" :foreground "blue" :weight bold) ("DONE" :foreground "forest green" :weight bold) ("WAITING" :foreground "orange" :weight bold) ("HOLD" :foreground "magenta" :weight bold) ("CANCELLED" :foreground "forest green" :weight bold) ("MEETING" :foreground "yellow" :weight bold)))) ;; Auto completion for symbols in org-mode ;; https://oremacs.com/2017/10/04/completion-at-point/ (defun org-completion-symbols () (when (looking-back "[`~=][a-zA-Z]+" nil) (let (cands) (save-match-data (save-excursion (goto-char (point-min)) (while (re-search-forward "[`~=]\\([a-zA-Z.\\-_]+\\)[`~=]" nil t) (cl-pushnew (match-string-no-properties 0) cands :test 'equal)) cands)) (when cands (list (match-beginning 0) (match-end 0) cands))))) (require 'ffap) (defun org-cap-filesystem () (when-let ((path (ffap-string-at-point))) (when (string-match "\\`file:\\(.*\\)\\'" path) (setq path (match-string 1 path))) (when-let ((compl (all-completions path #'read-file-name-internal))) (let* ((str (car compl)) (offset (let ((i 0) (len (length str))) (while (and (< i len) (equal (get-text-property i 'face str) 'completions-common-part)) (cl-incf i)) i))) (list (- (point) offset) (point) compl))))) ;; Custom org-sort to sort by TODO and then by priority ;; See: https://emacs.stackexchange.com/a/9588/12559 (defun org-sort-todo-to-int (todo) (first (-non-nil (mapcar (lambda (keywords) (let ((todo-seq (-map (lambda (x) (first (split-string x "("))) (rest keywords)))) (cl-position-if (lambda (x) (string= x todo)) todo-seq))) org-todo-keywords)))) (defun org-sort-by-todo-and-prio-key () (let* ((todo-max (apply #'max (mapcar #'length org-todo-keywords))) (todo (org-entry-get (point) "TODO")) (todo-int (if todo (org-sort-todo-to-int todo) todo-max)) (priority (org-entry-get (point) "PRIORITY")) (priority-int (if priority (string-to-char priority) org-default-priority))) (format "%03d %03d" todo-int priority-int))) (defun org-sort-entries-by-todo-and-prio () "Sort org entries first by TODO keyword and then priority." (interactive) (org-sort-entries nil ?f #'org-sort-by-todo-and-prio-key))) #+END_SRC ** Org Packages *** Org-appear: Make invisible parts of Org elements appear visible #+BEGIN_SRC emacs-lisp (use-package org-appear :hook (org-mode . org-appear-mode) :config (setq org-appear-autolinks nil)) #+END_SRC *** Org-agenda #+BEGIN_SRC emacs-lisp (use-package appt :defer t :config (require 'notifications) (defvar appt-org-notification-critical-threshold 5) (defun appt-org-notification-single (min _time text) (notifications-notify :title (format "%s in %s minutes." text min) :timeout 10000 ;; Close notification after 10s :urgency (if (< (string-to-number min) appt-org-notification-critical-threshold) 'critical 'normal))) (defun appt-org-notification (min time text) (if (listp min) (mapc #'appt-org-notification-single (-interleave min time text)) (appt-org-notification-single min time text))) (setq appt-display-format 'window) (setq appt-disp-window-function #'appt-org-notification) (setq appt-message-warning-time 6)) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package org-agenda :defer t :config (setq org-agenda-files '("~/org")) (defun org-agenda-custom-command-for (file) "Return list usable in org-agenda-custom-commands with agenda and all TODOs for FILE and inbox." (list '((agenda "") (alltodo "")) `((org-agenda-files '(,(expand-file-name "inbox.org" org-directory) ,(expand-file-name "caldav-inbox.org" org-directory) ,(expand-file-name (concat file ".org") org-directory)))))) (setq org-clocked-in-project "sovendus") (setq org-agenda-custom-commands `(("A" "Agenda and all TODOs" ((agenda "") (alltodo ""))) (" " "Current Project Agenda and TODOs" ,@(org-agenda-custom-command-for org-clocked-in-project)) ;;("s" "Sovendus Agenda and TODOs" ,@(org-agenda-custom-command-for "sovendus")) ("s" "Sovendus Agenda and TODOs" ((agenda "") (alltodo "") (org-agenda-files '(,(expand-file-name "inbox.org" org-directory) ,(expand-file-name "caldav-inbox.org" org-directory) ,(expand-file-name "sovendus.org" org-directory))))) ("e" "E5 Agenda and TODOs" ,@(org-agenda-custom-command-for "e5")) ("p" "Personal Agenda and TODOs" ,@(org-agenda-custom-command-for "personal")) ("t" "PTapp Agenda and TODOs" ,@(org-agenda-custom-command-for "ptapp")) ("S" "Skor Agenda and TODOs" ,@(org-agenda-custom-command-for "skor")) ("W" "Weekly Review" ((agenda "" ((org-agenda-span 7))); review upcoming deadlines and appointments ; type "l" in the agenda to review logged items (stuck "") ; review stuck projects as designated by org-stuck-projects (todo "PROJECT") ; review all projects (assuming you use todo keywords to designate projects) (todo "MAYBE") ; review someday/maybe items (todo "WAITING"))) ; review waiting items ;; ...other commands here )) ;; Overwrite the current window with the agenda (setq org-agenda-window-setup 'current-window) ;; Do not dim blocked tasks (setq org-agenda-dim-blocked-tasks nil) ;; Compact the block agenda view (setq org-agenda-compact-blocks nil) ;; Agenda clock report parameters (setq org-agenda-clockreport-parameter-plist (quote (:link t :maxlevel 5 :fileskip0 t :stepskip0 t :compact nil :narrow 80))) ;; Agenda log mode items to display (closed and state changes by default) (setq org-agenda-log-mode-items (quote (closed state clock))) ;; Keep tasks with dates on the global todo lists (setq org-agenda-todo-ignore-with-date nil) ;; Keep tasks with deadlines on the global todo lists (setq org-agenda-todo-ignore-deadlines nil) ;; Keep tasks with scheduled dates on the global todo lists (setq org-agenda-todo-ignore-scheduled nil) ;; Keep tasks with timestamps on the global todo lists (setq org-agenda-todo-ignore-timestamp nil) ;; Remove completed deadline tasks from the agenda view (setq org-agenda-skip-deadline-if-done t) ;; Remove completed scheduled tasks from the agenda view ;; (setq org-agenda-skip-scheduled-if-done t) ;; Remove completed items from search results ;; (setq org-agenda-skip-timestamp-if-done t) ;; Include agenda archive files when searching for things (setq org-agenda-text-search-extra-files (quote (agenda-archives))) ;; Show all future entries for repeating tasks (setq org-agenda-repeating-timestamp-show-all t) ;; Show all agenda dates - even if they are empty (setq org-agenda-show-all-dates t) ;; Start the weekly agenda on Monday (setq org-agenda-start-on-weekday 1) ;; For 0 to 4am show yesterday in the agenda view "toady" (setq org-extend-today-until 4) ;; Agenda styling (setq org-agenda-block-separator ?─ org-agenda-time-grid '((daily today require-timed) (800 1000 1200 1400 1600 1800 2000) " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄") org-agenda-current-time-string "⭠ now ─────────────────────────────────────────────────") ;; Use sticky agenda's so they persist ;;(setq org-agenda-sticky t) ;; Limit restriction lock highlighting to the headline only (setq org-agenda-restriction-lock-highlight-subtree nil) ;; Sorting order for tasks on the agenda (setq org-agenda-sorting-strategy (quote ((agenda habit-down time-up user-defined-up effort-up category-keep) (todo category-up effort-up) (tags category-up effort-up) (search category-up)))) ;; Enable display of the time grid so we can see the marker for the current time ;;(setq org-agenda-time-grid (quote ((daily today remove-match) ;; #("----------------" 0 16 (org-heading t)) ;; (0900 1100 1300 1500 1700)))) ;; Update appt list on agenda view (add-hook 'org-agenda-finalize-hook 'org-agenda-to-appt) (appt-activate) (org-agenda-to-appt) (setq org-fold-catch-invisible-edits 'show-and-error) (setq org-auto-align-tags t) ;; Display tags farther right ;; (setq org-agenda-tags-column 0) (setq org-agenda-tags-column -102) ) #+END_SRC *** Org-super-agenda #+BEGIN_SRC emacs-lisp (use-package org-super-agenda :after org-agenda :config (setq org-super-agenda-groups '(;; Each group has an implicit boolean OR operator between its selectors. (:name "Today" ; Optionally specify section name ;;:time-grid t ; Items that appear on the time grid :todo "NEXT") ; Items that have this TODO keyword (:name "Important" ;; Single arguments given alone ;;:tag "bills" :priority "A" ) ;; Set order of multiple groups at once (:order-multi (2 (:name "Shopping in town" ;; Boolean AND group matches items that match all subgroups :and (:tag "shopping" :tag "@town")) (:name "Food-related" ;; Multiple args given in list with implicit OR :tag ("food" "dinner")) (:name "Personal" :habit t :tag "personal") (:name "Space-related (non-moon-or-planet-related)" ;; Regexps match case-insensitively on the entire entry :and (:regexp ("space" "NASA") ;; Boolean NOT also has implicit OR between selectors :not (:regexp "moon" :tag "planet"))))) ;; Groups supply their own section names when none are given (:name "Waiting for" :todo "WAITING" :order 8) ; Set order of this section (:todo ("SOMEDAY" "TO-READ" "CHECK" "TO-WATCH" "WATCHING") ;; Show this group at the end of the agenda (since it has the ;; highest number). If you specified this group last, items ;; with these todo keywords that e.g. have priority A would be ;; displayed in that group instead, because items are grouped ;; out in the order the groups are listed. :order 9) (:priority<= "B" ;; Show this section after "Today" and "Important", because ;; their order is unspecified, defaulting to 0. Sections ;; are displayed lowest-number-first. :order 1) ;; After the last group, the agenda will display items that didn't ;; match any of these groups, with the default order position of 99 )) ;;(org-super-agenda-mode) ) #+END_SRC *** Org-caldav: Sync your calendars with your agenda / org tasks FIXME: automatically call =(org-caldav-sync)= before showing agenda. #+BEGIN_SRC emacs-lisp (use-package org-caldav :defer t ; Only autoloaded command is org-caldav-sync :config (setq org-caldav-debug-level 2) ;; Skip all entries without a scheduled or deadline property (setq org-caldav-skip-conditions '(nottimestamp)) ;; Mailbox.org (doesn't work with office 365 ical links and doesn't sync very old items) ;; https://userforum-en.mailbox.org/topic/office365-and-subscribe-via-ics ;; (setq org-caldav-url "https://dav.mailbox.org/caldav") ;; (setq org-caldav-calendars ;; `(;; All personal stuff goes in the shared family calendar ;; (:calendar-id "Y2FsOi8vMC84MA" ; family calendar id ;; :inbox ,(expand-file-name "caldav-inbox-family.org" org-directory) ;; :files (,(expand-file-name "personal.org" org-directory))) ;; ;; Everything but personal.org is work related and goes in my peronsal calendar ;; (:calendar-id "Y2FsOi8vMC84MQ" ; work calendar id ;; :inbox ,(expand-file-name "caldav-inbox-personal.org" org-directory) ;; :files ,(delete (expand-file-name "caldav-inbox-family.org" org-directory) ;; (delete (expand-file-name "personal.org" org-directory) (org-agenda-files)))))) ;; Nextcloud (setq org-caldav-url "https://nx599.your-storageshare.de/remote.php/dav/calendars/dakra") (setq org-caldav-calendars `(;; All personal stuff goes in the shared family calendar (:calendar-id "family" ; family calendar id :inbox ,(expand-file-name "caldav-inbox-family.org" org-directory) :files (,(expand-file-name "personal.org" org-directory))) ;; Everything but personal.org is work related and goes in my peronsal calendar (:calendar-id "work" ; work calendar id :inbox ,(expand-file-name "caldav-inbox-work.org" org-directory) :files ,(delete (expand-file-name "caldav-inbox-family.org" org-directory) (delete (expand-file-name "personal.org" org-directory) (org-agenda-files))))))) #+END_SRC *** Org-babel #+BEGIN_SRC emacs-lisp (use-package ob :defer t :init ;; display/update images in the buffer after I evaluate (add-hook 'org-babel-after-execute-hook 'org-display-inline-images 'append) :config (setq org-confirm-babel-evaluate nil) ; don't prompt me to confirm every time I want to evaluate a block (defun org-babel-restart-session-to-point (&optional arg) "Restart session up to the src-block in the current point. Goes to beginning of buffer and executes each code block with `org-babel-execute-src-block' that has the same language and session as the current block. ARG has same meaning as in `org-babel-execute-src-block'." (interactive "P") (unless (org-in-src-block-p) (error "You must be in a src-block to run this command")) (let* ((current-point (point-marker)) (info (org-babel-get-src-block-info)) (lang (nth 0 info)) (params (nth 2 info)) (session (cdr (assoc :session params)))) (save-excursion (goto-char (point-min)) (while (re-search-forward org-babel-src-block-regexp nil t) ;; goto start of block (goto-char (match-beginning 0)) (let* ((this-info (org-babel-get-src-block-info)) (this-lang (nth 0 this-info)) (this-params (nth 2 this-info)) (this-session (cdr (assoc :session this-params)))) (when (and (< (point) (marker-position current-point)) (string= lang this-lang) (src-block-in-session-p session)) (org-babel-execute-src-block arg))) ;; move forward so we can find the next block (forward-line))))) (defun org-babel-kill-session () "Kill session for current code block." (interactive) (unless (org-in-src-block-p) (error "You must be in a src-block to run this command")) (save-window-excursion (org-babel-switch-to-session) (kill-buffer))) (defun org-babel-remove-result-buffer () "Remove results from every code block in buffer." (interactive) (save-excursion (goto-char (point-min)) (while (re-search-forward org-babel-src-block-regexp nil t) (org-babel-remove-result)))) ;; this adds a "new language" in babel that gets exported as js in html ;; https://www.reddit.com/r/orgmode/comments/5bi6ku/tip_for_exporting_javascript_source_block_to/ (add-to-list 'org-src-lang-modes '("inline-js" . javascript)) (defvar org-babel-default-header-args:inline-js '((:results . "html") (:exports . "results"))) (defun org-babel-execute:inline-js (body _params) (format "" body)) ;; Path when plantuml is installed from AUR (package `plantuml') (setq org-plantuml-jar-path "/usr/share/java/plantuml/plantuml.jar") ;; add all languages to org mode (org-babel-do-load-languages 'org-babel-load-languages '((C . t) ;;(R . t) (abap) (asymptote) (awk) (calc . t) (clojure . t) (comint) (css) (ditaa . t) (dot . t) (emacs-lisp . t) (fortran) (gnuplot . t) (haskell) (io) (java) (js . t) (latex) ;; (ledger . t) (lilypond) (lisp) (lua . t) (matlab) (maxima) (mscgen) (ocaml) (octave . t) (org . t) (perl) (picolisp) (plantuml . t) (python . t) (restclient . t) (ref) (ruby) (sass) (scala) (scheme) (screen) (shell . t) (shen) (snippet) (sql . t) (sqlite . t) ;; jupyter should be the last element as it depends on other lang-modes. ;; See jupyter readme for more info. ;; (jupyter . t) )) ;; Load personal library of babel (org-babel-lob-ingest (no-littering-expand-etc-file-name "library-of-babel.org"))) (use-package ob-restclient :after ob) (use-package ob-async :after ob :config (setq ob-async-inject-variables "\\(\\borg-babel.+\\|sql-connection-alist\\)")) #+END_SRC *** Org-src #+BEGIN_SRC emacs-lisp (use-package org-src :defer t :init (put 'org-src-preserve-indentation 'safe-local-variable 'booleanp) :config ;; Always split babel source window below. ;; Alternative is `current-window' to don't mess with window layout at all (setq org-src-window-setup 'split-window-below) (setq org-src-fontify-natively t) ; syntax highlighting for source code blocks ;; Tab should do indent in code blocks (setq org-src-tab-acts-natively t) ;; Don't remove (or add) any extra whitespace (setq org-src-preserve-indentation nil) (setq org-edit-src-content-indentation 0) ;;; Some helper function to manage org-babel sessions (defun src-block-in-session-p (&optional name) "Return if src-block is in a session of NAME. NAME may be nil for unnamed sessions." (let* ((info (org-babel-get-src-block-info)) ;;(lang (nth 0 info)) ;;(body (nth 1 info)) (params (nth 2 info)) (session (cdr (assoc :session params)))) (cond ;; unnamed session, both name and session are nil ((and (null session) (null name)) t) ;; Matching name and session ((and (stringp name) (stringp session) (string= name session)) t) ;; no match (t nil)))) ;; dot == graphviz-dot (add-to-list 'org-src-lang-modes '("dot" . graphviz-dot)) ;; Add 'conf-mode' to org-babel (add-to-list 'org-src-lang-modes '("ini" . conf)) (add-to-list 'org-src-lang-modes '("conf" . conf)) (add-to-list 'org-src-lang-modes '("web" . web)) (define-derived-mode web-django-mode web-mode "WebDjango" "Major mode for editing web-mode django templates." (web-mode) (web-mode-set-engine "django"))) #+END_SRC *** Org-indent: Indent text according to outline structure. #+BEGIN_SRC emacs-lisp (use-package org-indent :hook (org-mode . org-indent-mode)) #+END_SRC *** org-modern #+BEGIN_SRC emacs-lisp (use-package org-modern :hook ((org-mode . org-modern-mode) (org-agenda-finalize . org-modern-agenda)) :config (setq org-modern-hide-stars nil) (modify-all-frames-parameters '((right-divider-width . 2) (internal-border-width . 0))) ;; (setq org-modern-star ["①" "②" "③" "④" "⑤" "⑥"]) ;; (setq org-modern-star ["◍" "●" "⊗" "○" "⊙" "◌" "▷" "✸"]) (setq org-modern-star ["❶" "❷" "❸" "❹" "❺" "❻" "❼"])) #+END_SRC *** org-moden-indent #+BEGIN_SRC emacs-lisp (use-package org-modern-indent :defer t :init ;; Add late to hook (add-hook 'org-mode-hook #'org-modern-indent-mode)) #+END_SRC *** Org-capture #+BEGIN_SRC emacs-lisp (use-package org-protocol :after org) ;; org-capture chrome plugin: https://chrome.google.com/webstore/detail/org-capture/kkkjlfejijcjgjllecmnejhogpbcigdc?hl=en (use-package org-capture :bind ("C-c c" . org-capture) :config ;; I don't want that org-capture rearanges the windows for me. ;; From https://stackoverflow.com/questions/54192239/open-org-capture-buffer-in-specific-window/54251825#54251825 (defun org-capture-place-template-dont-delete-windows (oldfun args) (cl-letf (((symbol-function 'delete-other-windows) 'ignore)) (apply oldfun args))) (advice-add 'org-capture-place-template :around 'org-capture-place-template-dont-delete-windows) ;; Do *NOT* bookmark to the last location when capturing (setq org-capture-bookmark nil) ;; Capture/refile new items to the top of the list (setq org-reverse-note-order t) ;; Capture templates for: TODO tasks, Notes, appointments, phone calls, meetings, and org-protocol (setq org-capture-templates `(("t" "todo" entry (file ,(concat org-directory "refile.org")) "* TODO %?\n" :clock-in t :clock-resume t) ("T" "todo with link" entry (file ,(concat org-directory "refile.org")) "* TODO %?\n%a\n" :clock-in t :clock-resume t) ("e" "email" entry (file ,(concat org-directory "refile.org")) "* TODO %? Email: %:from on %:subject\nSCHEDULED: %t\n%U\n%a\n" :clock-in t :clock-resume t :immediate-finish nil) ("j" "Journal entry" entry (file+olp+datetree ,(concat org-directory "journal.org")) "* %?\n") ("s" "Sovendus journal entry" entry (file+olp+datetree ,(concat org-directory "sovendus.org") "Journal") "* %?\n") ("J" "Journal with link" entry (file+olp+datetree ,(concat org-directory "journal.org")) "* %?\n%a\n") ("r" "respond" entry (file ,(concat org-directory "refile.org")) "* TODO Respond to %:from on %:subject\nSCHEDULED: %t\n%U\n%a\n" :clock-in t :clock-resume t :immediate-finish t) ("n" "note" entry (file ,(concat org-directory "refile.org")) "* %? :NOTE:\n%a\n" :clock-in t :clock-resume t) ("w" "org-protocol" entry (file ,(concat org-directory "refile.org")) "* TODO Review %c\n%U\n" :immediate-finish t) ("p" "Protocol" entry (file ,(concat org-directory "refile.org")) "* %^{Title}\nSource: %u, %c\n #+BEGIN_QUOTE\n%i\n#+END_QUOTE\n\n\n%?") ("L" "Protocol Link" entry (file ,(concat org-directory "refile.org")) "* %?\n[[%:link][%:description]]\n") ("w" "Web site" entry (file "") "* %a :website:\n\n%U %?\n\n%:initial")))) (use-package noflet :defer t) ; let you locally overwrite functions ;; FIXME: install bookmarklet and shell script (integrate with org-capture plugin?!) (use-package org-protocol-capture-html :disabled t ; Useful but never used since bookmarklet not configured yet :after org-capture) #+END_SRC *** Org-clock Install =xprintidle= to get idle time over all X11. Otherwise it's only Emacs idle time. #+BEGIN_SRC txt :tangle arch-pkglist.txt xprintidle #+END_SRC #+BEGIN_SRC emacs-lisp ;;; Clock Setup (use-package org-clock :after org :config ;; FIXME: remove unused bh functions? (setq bh/keep-clock-running nil) (defun bh/clock-in-last-task (arg) "Clock in the interrupted task if there is one Skip the default task and get the next one. A prefix arg forces clock in of the default task." (interactive "p") (let ((clock-in-to-task (cond ((eq arg 4) org-clock-default-task) ((and (org-clock-is-active) (equal org-clock-default-task (cadr org-clock-history))) (caddr org-clock-history)) ((org-clock-is-active) (cadr org-clock-history)) ((equal org-clock-default-task (car org-clock-history)) (cadr org-clock-history)) (t (car org-clock-history))))) (widen) (org-with-point-at clock-in-to-task (org-clock-in nil)))) (defun bh/clock-in-to-next (kw) "Switch a task from TODO to NEXT when clocking in. Skips capture tasks, projects, and subprojects. Switch projects and subprojects from NEXT back to TODO" (when (not (and (boundp 'org-capture-mode) org-capture-mode)) (cond ((and (member (org-get-todo-state) (list "TODO")) (bh/is-task-p)) "NEXT") ((and (member (org-get-todo-state) (list "NEXT")) (bh/is-project-p)) "TODO")))) (defun bh/find-project-task () "Move point to the parent (project) task if any" (save-restriction (widen) (let ((parent-task (save-excursion (org-back-to-heading 'invisible-ok) (point)))) (while (org-up-heading-safe) (when (member (nth 2 (org-heading-components)) org-todo-keywords-1) (setq parent-task (point)))) (goto-char parent-task) parent-task))) (defun bh/punch-in (arg) "Start continuous clocking and set the default task to the selected task. If no task is selected set the Organization task as the default task." (interactive "p") (setq bh/keep-clock-running t) (if (equal major-mode 'org-agenda-mode) ;; ;; We're in the agenda ;; (let* ((marker (org-get-at-bol 'org-hd-marker)) (tags (org-with-point-at marker (org-get-tags)))) (if (and (eq arg 4) tags) (org-agenda-clock-in '(16)) (bh/clock-in-organization-task-as-default))) ;; ;; We are not in the agenda ;; (save-restriction (widen) ; Find the tags on the current task (if (and (equal major-mode 'org-mode) (not (org-before-first-heading-p)) (eq arg 4)) (org-clock-in '(16)) (bh/clock-in-organization-task-as-default))))) (defun bh/punch-out () (interactive) (setq bh/keep-clock-running nil) (when (org-clock-is-active) (org-clock-out)) (org-agenda-remove-restriction-lock)) (defun bh/clock-in-default-task () (save-excursion (org-with-point-at org-clock-default-task (org-clock-in)))) (defun bh/clock-in-parent-task () "Move point to the parent (project) task if any and clock in" (let ((parent-task)) (save-excursion (save-restriction (widen) (while (and (not parent-task) (org-up-heading-safe)) (when (member (nth 2 (org-heading-components)) org-todo-keywords-1) (setq parent-task (point)))) (if parent-task (org-with-point-at parent-task (org-clock-in)) (when bh/keep-clock-running (bh/clock-in-default-task))))))) (defvar bh/organization-task-id "f2088c3f-8452-4221-b63e-fbd9fb83089f") (defun bh/clock-in-organization-task-as-default () (interactive) (org-with-point-at (org-id-find bh/organization-task-id 'marker) (org-clock-in '(16)))) (defun bh/clock-out-maybe () (when (and bh/keep-clock-running (not org-clock-clocking-in) (marker-buffer org-clock-default-task) (not org-clock-resolving-clocks-due-to-idleness)) (bh/clock-in-parent-task))) (add-hook 'org-clock-out-hook 'bh/clock-out-maybe 'append) :config ;; Install `xprintidle' to get idle time over all X11. Otherwise it's only Emacs idle time. ;; (setq org-clock-idle-time 35) ; idle after 35 minutes ;;(setq org-clock-continuously t) ; Start clocking from the last clock-out time, if any. ;; Show lot of clocking history so it's easy to pick items off the C-F11 list (setq org-clock-history-length 30) ;; Save the running clock and all clock history when exiting Emacs, load it on startup (setq org-clock-persist t) (org-clock-persistence-insinuate) ;; org-clock-display (C-c C-x C-d) shows times for this month by default (setq org-clock-display-default-range 'thismonth) ;; Only show the current clocked time in mode line (not all) (setq org-clock-mode-line-total 'current) ;; Don't show modeline - display in i3status bar instead (setq org-clock-clocked-in-display nil) ;; Clocktable (C-c C-x C-r) defaults ;; Use fixed month instead of (current-month) because I want to keep a table for each month (setq org-clock-clocktable-default-properties `(:block ,(format-time-string "%Y-%m") :scope file-with-archives)) ;; Clocktable (reporting: r) in the agenda (setq org-clocktable-defaults '(:maxlevel 3 :lang "en" :scope file-with-archives :wstart 1 :mstart 1 :tstart nil :tend nil :step nil :stepskip0 t :fileskip0 t :tags nil :emphasize nil :link t :narrow 70! :indent t :formula nil :timestamp nil :level nil :tcolumns nil :formatter nil)) ;; Resume clocking task on clock-in if the clock is open (setq org-clock-in-resume t) ;; Change tasks to NEXT when clocking in ;;(setq org-clock-in-switch-to-state 'bh/clock-in-to-next) ;; Separate drawers for clocking and logs ;;(setq org-drawers (quote ("PROPERTIES" "LOGBOOK"))) ;; Save clock data and state changes and notes in the LOGBOOK drawer (setq org-clock-into-drawer t) ;; Log all State changes to drawer (setq org-log-into-drawer t) ;; make time editing use discrete minute intervals (no rounding) increments (setq org-time-stamp-rounding-minutes (quote (1 1))) ;; Sometimes I change tasks I'm clocking quickly - this removes clocked tasks with 0:00 duration (setq org-clock-out-remove-zero-time-clocks t) ;; Don't clock out when moving task to a done state (setq org-clock-out-when-done nil) ;; Enable auto clock resolution for finding open clocks (setq org-clock-auto-clock-resolution (quote when-no-clock-is-running)) ;; Include current clocking task in clock reports (setq org-clock-report-include-clocking-task t)) #+END_SRC *** Org-crypt: Encrypt parts in org file tagged with ~CRYPT~ #+BEGIN_SRC emacs-lisp (use-package org-crypt :defer t :config ;; Encrypt all entries before saving (org-crypt-use-before-save-magic) (setq org-tags-exclude-from-inheritance (quote ("crypt"))) ;; GPG key to use for encryption (setq org-crypt-key "C1C8D63F884EF9C9") ;; don't ask to disable auto-save (setq org-crypt-disable-auto-save nil)) #+END_SRC *** Org-export #+BEGIN_SRC emacs-lisp (use-package ox :commands org-formatted-copy ;;:bind ("C-c e" . org-formatted-copy) :config ;; By default only export subtree instead of whole buffer (setq org-export-initial-scope 'subtree) ;; Mark setting org-export scope as safe for usage in .dir-locals (put 'org-export-initial-scope 'safe-local-variable (lambda (x) (or (eq x 'buffer) (eq x 'subtree)))) ;; Don't evaluate babel source blocks during export (setq org-export-use-babel nil) ;; Preserve linebreaks. Otherwise every paragraph is exported as one line. ;; (setq org-export-preserve-breaks t) ;; copy org text as rich text (defun org-formatted-copy () "Export region to HTML, and copy it to the clipboard." (interactive) (save-window-excursion (let* ((buf (org-export-to-buffer 'html "*Formatted Copy*" nil nil t t)) (_html (with-current-buffer buf (buffer-string)))) (with-current-buffer buf (shell-command-on-region (point-min) (point-max) "xclip -selection clipboard -t 'text/html' -i")) (kill-buffer buf)))) ;; FIXME: This is only a hack as I do NOT want the tags INSIDE the h3 title tag (defun my-hack-org-html-format-headline-function (todo _todo-type priority text tags info) "Default format function for a headline. See `org-html-format-headline-function' for details." (let ((todo (org-html--todo todo info)) (priority (org-html--priority priority info)) (tags (org-html--tags tags info))) (concat todo (and todo " ") priority (and priority " ") text (and tags "   

") tags (and tags "

")))) (setq org-html-format-headline-function #'my-hack-org-html-format-headline-function) ;; Use html5 as org export and use new tags (I don't care about browsers <=IE8) (setq org-html-doctype "html5") (setq org-html-html5-fancy t) ;; Don't add html footer to export (setq org-html-postamble nil) ;; Don't export ^ or _ as super/subscripts (setq org-export-with-sub-superscripts nil)) ;; Import Excel as org table (use-package ox-odt :after ox :commands org-odt-import-xlsx :config (defun org-odt-import-xlsx (xlsx-file) "Import XLSX-FILE as org table." (interactive "fImport file:") (let ((csv-file (concat (file-name-sans-extension xlsx-file) ".csv"))) (org-odt-convert xlsx-file "csv") (org-table-import csv-file nil) (delete-file csv-file)))) ;; Export tasks to ical (used for org-vcal) (use-package ox-icalendar :after ox :config (setq org-icalendar-timezone "Europe/Berlin") ;; Include maximum of 2048 characters in calendar description (setq org-icalendar-include-body 2048)) ;; Export blog posts to hugo (use-package ox-hugo :after ox) ;; Jira export (then copy&paste to ticket) (use-package ox-jira :after ox) ;; Confluence (use-package ox-confluence :after ox) ;; Github markdown (use-package ox-gfm :after ox) ;; reStructuredText (use-package ox-rst :after ox) #+END_SRC *** Org-habit: Track habits #+BEGIN_SRC emacs-lisp (use-package org-habit :after org) #+END_SRC *** Org-man: Make org-links work with man pages Install Linux man pages #+BEGIN_SRC txt :tangle arch-pkglist.txt man-pages #+END_SRC #+BEGIN_SRC emacs-lisp (use-package ol-man :after org :config (setq org-man-command 'woman)) ; open org-link man pages with woman #+END_SRC *** Org-expiry: Automatically add a CREATED property when inserting a new headline #+BEGIN_SRC emacs-lisp (use-package org-expiry :after org :config (setq org-expiry-inactive-timestamps t) (org-expiry-insinuate)) #+END_SRC *** Org-id: Create ID property with new task #+BEGIN_SRC emacs-lisp (use-package org-id :after org :config (setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)) #+END_SRC *** Org-table #+BEGIN_SRC emacs-lisp (use-package org-table :after org :config ;; FIXME: Maybe just bind key to mark cell and M-f M-b to cell forward/backwards. ;; no hydra needed ;; Nice org table navigation (and easy copy etc from cells) ;; https://github.com/kaushalmodi/.emacs.d/blob/ea60f986d58b27f45d510cde1148bf6d52e10dda/setup-files/setup-org.el#L1041-L1080 ;;;; Table Field Marking (defun org-table-mark-field () "Mark the current table field." (interactive) ;; Do not try to jump to the beginning of field if the point is already there (when (not (looking-back "|[[:blank:]]?" nil)) (org-table-beginning-of-field 1)) (set-mark-command nil) (org-table-end-of-field 1)) (defhydra hydra-org-table-mark-field (:body-pre (org-table-mark-field) :color red :hint nil) " ^^ ^🠙^ ^^ ^^ _p_ ^^ 🠘 _b_ selection _f_ 🠚 | Org table mark ▯field▮ | ^^ _n_ ^^ ^^ ^🠛^ ^^ " ("x" exchange-point-and-mark "exchange point/mark") ("f" (lambda (arg) (interactive "p") (when (eq 1 arg) (setq arg 2)) (org-table-end-of-field arg))) ("b" (lambda (arg) (interactive "p") (when (eq 1 arg) (setq arg 2)) (org-table-beginning-of-field arg))) ("n" next-line) ("p" previous-line) ("q" nil "cancel" :color blue)) (bind-keys :map org-mode-map :filter (org-at-table-p) ("S-SPC" . hydra-org-table-mark-field/body))) #+END_SRC *** Org-toc: Create table of content in org files #+BEGIN_SRC emacs-lisp (use-package org-make-toc :defer t :config (setq org-make-toc-filename-prefix t)) #+END_SRC *** Org-pomodoro #+BEGIN_SRC emacs-lisp (use-package org-pomodoro :commands (dakra/org-pomodoro-i3-bar-time) :config ;; called with i3status-rs in ~/.config/i3/status.toml with ;; command = "emacsclient --eval '(dakra/org-pomodoro-i3-bar-time)' || echo 'Emacs daemon not started'" (defun dakra/org-pomodoro-i3-bar-time () "Display remaining pomodoro time in i3 status bar." (if (org-pomodoro-active-p) (let* ((time-left (time-subtract org-pomodoro-end-time (current-time))) (minutes-left (/ (time-convert time-left 'integer) 60))) (if (or (eq org-pomodoro-state :short-break) (eq org-pomodoro-state :long-break)) (format "🍅(break) %d min left - %s" minutes-left org-clock-heading) (format "🍅(%d/%d) %d min left - %s" org-pomodoro-count org-pomodoro-long-break-frequency minutes-left org-clock-heading))) (if (org-clock-is-active) (org-no-properties (org-clock-get-clock-string)) "No active pomodoro or task"))) ;; Don't delete already clocked time when killing a running pomodoro (setq org-pomodoro-keep-killed-pomodoro-time t) ;; Never clock-out automatically (setq org-pomodoro-clock-break t)) #+END_SRC *** Org-jira: Sync issues with Jira #+BEGIN_SRC emacs-lisp (use-package org-jira :defer t :config (setq jiralib-url "https://jira.paesslergmbh.de") ;;(setq jiralib-url "https://sapjira.wdf.sap.corp") ;; Don't sync anything back to jira (setq org-jira-deadline-duedate-sync-p nil) (setq org-jira-worklog-sync-p nil)) #+END_SRC *** Org-github: Sync issues with GitHub #+BEGIN_SRC emacs-lisp (use-package org-github :defer t :config (setq org-github-default-owner "scarletcomply") (setq org-github-default-name "slang")) #+END_SRC *** Org-link #+BEGIN_SRC emacs-lisp (use-package ol :bind (:map org-mode-map ("C-c )" . hydra-org-link-edit/body)) :config ;; Don't remove links after inserting (setq org-link-keep-stored-after-insertion t) ;; (setq org-link-abbrev-alist '(("gh" . "https://www.github.com/%s"))) (require 'org-link-edit) (defun org-link-unlinkify () "Replace an org-link with the description, or if this is absent, the path." (interactive) (let ((eop (org-element-context))) (when (eq 'link (car eop)) (message "%s" eop) (let* ((start (org-element-property :begin eop)) (end (org-element-property :end eop)) (contents-begin (org-element-property :contents-begin eop)) (contents-end (org-element-property :contents-end eop)) (path (org-element-property :path eop)) (desc (and contents-begin contents-end (buffer-substring contents-begin contents-end)))) (setf (buffer-substring start end) (concat (or desc path) (make-string (org-element-property :post-blank eop) ?\s))))))) (defhydra hydra-org-link-edit (:color red) "Org Link Edit" (")" org-link-edit-forward-slurp "Forward slurp") ("}" org-link-edit-forward-barf "Forward barf") ("(" org-link-edit-backward-slurp "Backward slurp") ("{" org-link-edit-backward-barf "Backward barf") ("t" org-toggle-link-display "Toggle link display") ("r" org-link-unlinkify "Remove link") ("q" nil "Cancel" :color blue))) #+END_SRC *** Orgit: org-link support for magit buffers #+BEGIN_SRC emacs-lisp (use-package orgit ;; Automatically copy orgit link to last commit after commit :hook (git-commit-post-finish . orgit-store-after-commit) :config (defun orgit-store-after-commit () "Store orgit-link for latest commit after commit message editor is finished." (let* ((repo (abbreviate-file-name default-directory)) (rev (magit-git-string "rev-parse" "HEAD")) (link (format "orgit-rev:%s::%s" repo rev)) (summary (substring-no-properties (magit-format-rev-summary rev))) (desc (format "%s (%s)" summary repo))) (push (list link desc) org-stored-links)))) #+END_SRC Forge support for orgit #+BEGIN_SRC emacs-lisp (use-package orgit-forge :after orgit) #+END_SRC *** valign: Pretty org-tables #+BEGIN_SRC emacs-lisp (use-package valign :hook (org-mode . valign-mode) :disabled t :config (setq valign-max-table-size 6000) (setq valign-fancy-bar t)) #+END_SRC * Erc (Emacs IRC client) #+BEGIN_SRC emacs-lisp (use-package erc :config (setq erc-lurker-hide-list '("PART" "QUIT" "JOIN")) (setq erc-autojoin-channels-alist '(("Libera.Chat" "#emacs" "#clojure"))) (setq erc-server "chat.sr.ht") (setq erc-email-userid "dakra/irc.libera.chat") (setq erc-nick "dakra") (setq erc-user-full-name user-full-name) (setq erc-prompt-for-password nil) (setq erc-interpret-mirc-color t) (add-to-list 'erc-modules 'notifications) (add-to-list 'erc-modules 'spelling) (erc-update-modules) (erc-track-minor-mode 1) (erc-track-mode 1)) (use-package erc-hl-nicks :after erc) (use-package erc-track :after erc :config (setq erc-track-exclude-types '("JOIN" "NICK" "QUIT" "MODE" "333" "353"))) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package erc-services :after erc :config (setq erc-prompt-for-nickserv-password nil) (setq erc-nickserv-passwords `((irc.libera.chat (("dakra" . ,(auth-source-pick-first-password :host "irc.libera.chat" :login erc-nick))))))) #+END_SRC * Email ** Mu4e You also need to configure and install mu in the system. Borg does only build mu4e as building mu every time is unnecessary. #+BEGIN_SRC sh :tangle no :var mudir=(expand-file-name "mu4e" borg-drones-directory) :results none # Install mu for mu4e cd $mudir # Maybe you have to run distclean first # make distclean ./autogen.sh make sudo make install #+END_SRC Packages to compile the mu binary: #+BEGIN_SRC txt :tangle arch-pkglist.txt gmime3 xapian-core cld2-git #+END_SRC For =mu= to recognize personal email addresses you have to pass them to ~mu init~ before indexing with: #+BEGIN_SRC sh :tangle no mu init --maildir=/home/daniel/Maildir \ --ignored-address=/.*noreply*/ \ --my-address=daniel@kraus.my \ --my-address=daniel.kraus@gmail.com \ --my-address=d@niel-kraus.de \ --my-address=/dakra-.*@tr0ll\.net/ \ --my-address=daniel@scarletcomply.com \ --my-address=daniel@skor.buzz \ --my-address=daniel@price-monitor.ai \ --my-address=support@price-monitor.ai \ --my-address=info@price-monitor.ai \ --my-address=dakra@2spicy.de \ --my-address=daniel.kraus@paessler.com \ --my-address=daniel.kraus@ebenefuenf.de \ --my-address=daniel@restalchemy.org \ --my-address=extern.daniel.kraus@sovendus.de \ --my-address=daniel@price-monitor.info \ --my-address=daniel@hogaso.com #+END_SRC #+BEGIN_SRC emacs-lisp (use-package mu4e ;; Open mu4e with the 'Mail' key (if your keyboard has one) :bind (("" . mu4e) :map mu4e-main-mode-map ("U" . mu4e-update-mail-and-index-background) :map mu4e-headers-mode-map ("TAB" . mu4e-headers-next-unread) ("J" . mu4e-move-to-junk) ("d" . my-move-to-trash) ("D" . my-move-to-trash) ("M" . mu4e-headers-mark-all-unread-read) ; Mark all as read :map mu4e-search-minor-mode-map ("P" . mu4e-view-headers-prev) :map mu4e-view-mode-map ("A" . mu4e-view-attachment-action) ("M-o" . ace-link-mu4e) ("o" . ace-link-mu4e) ("n" . mu4e-scroll-up) ("p" . mu4e-scroll-down) ("N" . mu4e-view-headers-next) ("P" . mu4e-view-headers-prev) ("J" . mu4e-move-to-junk) ("d" . my-move-to-trash) ("D" . my-move-to-trash)) :hook (message-send . message-warn-if-no-attachments) :init ;; Prefer text over html/ritchtext (setq mm-discouraged-alternatives '("text/html" "text/richtext")) ;; Use completing-read (vertico) instead of ido or mu4e's own version (setq mu4e-read-option-use-builtin nil) (setq mu4e-completing-read-function 'completing-read) ;; set mu4e as default mail client (setq mail-user-agent 'mu4e-user-agent) ;; Always use local smtp server (msmtp in my case) to send mails (setq send-mail-function 'sendmail-send-it sendmail-program "~/bin/msmtp-enqueue.sh" mail-specify-envelope-from t message-sendmail-f-is-evil nil mail-envelope-from 'header message-sendmail-envelope-from 'header) :config ;; Display the main window in the current window instead of making it full screen (add-to-list 'display-buffer-alist '("*mu4e-main*" (display-buffer-same-window))) (defun mu4e-update-mail-and-index-background () "Call `mu4e-update-mail-and-index' to run in background." (interactive) (mu4e-update-mail-and-index t)) ;; gmail delete == move mail to trash folder (fset 'my-move-to-trash "mt") ;; Move mails to spam/junk folder (fset 'mu4e-move-to-junk "mj") ;; Fix mu4e highlighting in moe-dark theme (set-face-attribute 'mu4e-header-highlight-face nil :background "#626262" :foreground "#eeeeee") ;;; Save attachment (this can also be a function) (setq mu4e-attachment-dir "~/Downloads") ;; When saving multiple attachments (C-u prefix) save all in same directory ;; without asking for the location of every attachment (setq mu4e-save-multiple-attachments-without-asking t) (setq mu4e-msg2pdf "/usr/bin/msg2pdf") ; to display html messages as pdf ;; Show additional user-agent header (setq-default mu4e-view-fields '(:from :to :cc :subject :flags :date :maildir :user-agent :mailing-list :tags :attachments :signature :decryption)) ;; Attach file with helm-locate ;;(helm-add-action-to-source "Attach to Email" #'mml-attach-file helm-source-locate) ;; default (setq-default mu4e-maildir "~/Maildir") (setq-default mu4e-drafts-folder "/private/Drafts") (setq-default mu4e-sent-folder "/private/Sent") (setq-default mu4e-trash-folder "/private/Trash") ;; Setup some handy shortcuts ;; you can quickly switch to your Inbox -- press ``ji'' ;; then, when you want archive some messages, move them to ;; the 'All Mail' folder by pressing ``ma''. (setq mu4e-maildir-shortcuts '(("/private/Inbox" . ?i) ("/private/Sent" . ?s) ("/private/Trash" . ?t) ("/private/Drafts" . ?d) ("/private/Junk" . ?j) ("/private/Archive" . ?a))) ;; Dynamically refile ;; See: https://www.djcbsoftware.nl/code/mu/mu4e/Smart-refiling.html#Smart-refiling (defun dakra-mu4e-private-refile (msg) (cond ;; refile all messages from Uber to the 'uber' folder ((mu4e-message-contact-field-matches msg :from "@uber\\.com") "/private/uber") ;; and from Kindergarden to 'loewen' ((mu4e-message-contact-field-matches msg '(:to :cc :bcc) "loewenkindergarten@googlegroups\\.com") "/private/loewen") ;; and from Grundschule to 'grundschule' ((mu4e-message-contact-field-matches msg '(:from :to :cc :bcc) "grundschule-wolfartsweier-ka\\.schule\\.bwl\\.de") "/private/grundschule") ;; any to price-monitor ((mu4e-message-contact-field-matches msg '(:to :cc :bcc) "price-monitor") "/private/price-monitor") ;; important to have a catch-all at the end! (t "/private/Archive"))) (setq mu4e-refile-folder 'dakra-mu4e-private-refile) ;; Don't show duplicate mails when searching (setq mu4e-search-skip-duplicates t) ;; Don't show related messages by default. ;; Activate with 'W' on demand (setq mu4e-search-include-related nil) ;; Don't ask to quit (setq mu4e-confirm-quit nil) ;; Don't spam the minibuffer with 'Indexing...' messages (setq mu4e-hide-index-messages t) ;; Always update in background otherwise mu4e manipulates the window layout ;; when the update is finished but this breaks when we switch exwm workspaces ;; and the current focused window just gets hidden. (setq mu4e-index-update-in-background t) ;; Allow using temp-files for optimizing mu <-> mu4e communication. (setq mu4e-mu-allow-temp-file t) ;; Add some mailing lists (dolist (mailing-list '(("intern.lists.entropia.de" . "Entropia") ("intern.lists.ccc.de" . "CCC") ("pylons-discuss.googlegroups.com" . "PyrUsr") ("pylons-devel.googlegroups.com" . "PyrDev") ("loewenkindergarten.googlegroups.com" . "Loewen") ("sqlalchemy.googlegroups.com" . "SQLA"))) (add-to-list 'mu4e-mailing-lists mailing-list)) (setq mu4e-bookmarks `((,(concat "maildir:/private/Inbox OR " ;; "maildir:/paessler/Inbox OR " "maildir:/sovendus/Inbox OR " "maildir:/sap/Inbox OR " "maildir:/gmail/inbox OR " "maildir:/scarlet/inbox OR " "maildir:/spicy/inbox OR " "maildir:/hogaso/inbox OR " "maildir:/e5/Inbox") "All inboxes" ?i) ("flag:flagged" "Flagged messages" ?f) (,(concat "flag:unread AND " "NOT flag:trashed AND " "NOT flag:seen AND " "NOT list:emacs-devel.gnu.org AND " "NOT list:emacs-orgmode.gnu.org AND " "NOT maildir:/private/Junk AND " "NOT maildir:/private/Trash AND " "NOT maildir:/scarlet/spam AND " "NOT maildir:/scarlet/trash AND " "NOT maildir:/spicy/spam AND " "NOT maildir:/spicy/trash AND " "NOT maildir:/paessler/Inbox AND " "NOT maildir:/paessler/Deleted\\ Items AND " "NOT maildir:/sovendus/Trash AND " "NOT maildir:/sovendus/Junk AND " "NOT maildir:/sovendus/Alerts AND " "NOT maildir:/sap/Deleted\\ Items AND " "NOT maildir:/e5/Spam AND " "NOT maildir:/gmail/spam AND " "NOT maildir:/gmail/trash") "Unread messages" ?a) (,(concat "flag:unread AND " "NOT flag:trashed AND " "NOT flag:seen AND " "NOT maildir:/private/Junk AND " "NOT maildir:/private/Trash AND " "NOT maildir:/scarlet/spam AND " "NOT maildir:/scarlet/trash AND " "NOT maildir:/spicy/spam AND " "NOT maildir:/spicy/trash AND " "NOT maildir:/paessler/Inbox AND " "NOT maildir:/paessler/Deleted\\ Items AND " "NOT maildir:/sovendus/Trash AND " "NOT maildir:/sovendus/Junk AND " "NOT maildir:/sovendus/Alerts AND " "NOT maildir:/sap/Deleted\\ Items AND " "NOT maildir:/e5/Spam AND " "NOT maildir:/gmail/spam AND " "NOT maildir:/gmail/trash") "All Unread messages" ?A) ("list:emacs-devel.gnu.org" "Emacs dev" ?d) ("list:emacs-orgmode.gnu.org" "Emacs orgmode" ?o) ("list:magit.googlegroups.com OR list:mu-discuss.googlegroups.com" "Elisp" ?e) ("list:pylons-discuss.googlegroups.com OR list:pylons-devel.googlegroups.com OR list:sqlalchemy.googlegroups.com" "Python" ?p) ("maildir:/private/github" "GitHub" ?g) ("maildir:/private/scarlet-github" "Scarlet GitHub" ?s) ("list:intern.lists.entropia.de" "Entropia Intern" ?k) ("list:loewenkindergarten.googlegroups.com" "Löwen" ?l))) ;; (add-hook 'mu4e-mark-execute-pre-hook ;; (lambda (mark msg) ;; (cond ((member mark '(refile trash)) (mu4e-action-retag-message msg "-\\Inbox")) ;; ((equal mark 'flag) (mu4e-action-retag-message msg "\\Starred")) ;; ((equal mark 'unflag) (mu4e-action-retag-message msg "-\\Starred"))))) ;; allow for updating mail using 'U' in the main view: ;; (only update inboxes) ;; (setq mu4e-get-mail-command "mbsync private:Inbox sovendus:Inbox e5:Inbox gmail-inbox spicy-inbox hogaso-inbox") (setq mu4e-get-mail-command "mbsync private:INBOX gmail-inbox scarlet-inbox") ;; sovendus:INBOX ;; for update all: ;;(setq mu4e-get-mail-command "mbsync -a") ;; update database every ten minutes ;; (setq mu4e-update-interval (* 60 10)) (setq mu4e-update-interval nil) ;; We do a full index (that verify integrity) with a systemd job ;; Go fast inside emacs ;; (setq mu4e-index-cleanup nil) ;; don't do a full cleanup check ;; (setq mu4e-index-lazy-check t) ;; don't consider up-to-date dirs ;; Set to 't to use 'fancy' non-ascii characters in various places in mu4e (setq mu4e-use-fancy-chars nil) ;; And change default threading characters to some "nicer" looking chars (setq mu4e-headers-thread-child-prefix '("├>" . "├→ ")) (setq mu4e-headers-thread-last-child-prefix '("└>" . "└→ ")) (setq mu4e-headers-thread-connection-prefix '("│" . "│ ")) (setq mu4e-headers-thread-orphan-prefix '("┬>" . "┬→ ")) (setq mu4e-headers-thread-single-orphan-prefix '("─>" . "─→ ")) ;; Also change to some nicer characters for marks (setq mu4e-headers-new-mark '("N" . "📨")) (setq mu4e-headers-passed-mark '("P" . "›")) (setq mu4e-headers-replied-mark '("R" . "‹")) (setq mu4e-headers-seen-mark '("S" . "")) (setq mu4e-headers-attach-mark '("a" . "📎")) (setq mu4e-headers-unread-mark '("u" . "📫")) ;; I want my format=flowed thank you very much ;; mu4e sets up visual-line-mode and also fill (M-q) to do the right thing ;; each paragraph is a single long line; at sending, emacs will add the ;; special line continuation characters. (setq mu4e-compose-format-flowed nil) ;; Don't open new frame for composing mails (setq mu4e-compose-in-new-frame nil) ;; Don't reply to self ;; personal addresses need to be specified with `mu init' before indexing. (setq mu4e-compose-dont-reply-to-self t) ;; Extract name from email for yasnippet template ;; http://pragmaticemacs.com/emacs/email-templates-in-mu4e-with-yasnippet/ (defun bjm/mu4e-get-names-for-yasnippet () "Return comma separated string of names for an email" (interactive) (let ((email-name "") str email-string email-list email-name2 tmpname) (save-excursion (goto-char (point-min)) ;; first line in email could be some hidden line containing NO to field (setq str (buffer-substring-no-properties (point-min) (point-max)))) ;; take name from TO field - match series of names (when (string-match "^To: \"?\\(.+\\)" str) (setq email-string (match-string 1 str))) ;;split to list by comma (setq email-list (split-string email-string " *, *")) ;;loop over emails (dolist (tmpstr email-list) ;;get first word of email string (setq tmpname (car (split-string tmpstr " "))) ;;remove whitespace or "" (setq tmpname (replace-regexp-in-string "[ \"]" "" tmpname)) ;;join to string (setq email-name (concat email-name ", " tmpname))) ;;remove initial comma (setq email-name (replace-regexp-in-string "^, " "" email-name)) ;;see if we want to use the name in the FROM field ;;get name in FROM field if available, but only if there is only ;;one name in TO field (if (< (length email-list) 2) (when (string-match "^\\([^ ,\n]+\\).+writes:$" str) (progn (setq email-name2 (match-string 1 str)) ;;prefer name in FROM field if TO field has "@" (when (string-match "@" email-name) (setq email-name email-name2)) ))) email-name)) ;; Always store contacts as first last ;; https://martinralbrecht.wordpress.com/2016/05/30/handling-email-with-emacs/ (defun malb/canonicalise-contact-name (name) (let ((case-fold-search nil)) (setq name (or name "")) (if (string-match-p "^[^ ]+@[^ ]+\.[^ ]" name) "" (progn ;; drop email address (setq name (replace-regexp-in-string "^\\(.*\\) [^ ]+@[^ ]+\.[^ ]" "\\1" name)) ;; strip quotes (setq name (replace-regexp-in-string "^\"\\(.*\\)\"" "\\1" name)) ;; deal with YELL’d last names (setq name (replace-regexp-in-string "^\\(\\<[[:upper:]]+\\>\\) \\(.*\\)" "\\2 \\1" name)) ;; Foo, Bar becomes Bar Foo (setq name (replace-regexp-in-string "^\\(.*\\), \\([^ ]+\\).*" "\\2 \\1" name)))))) (defun malb/mu4e-contact-process-function (contact) (let* ((name (or (plist-get contact :name) "")) ;; (mail (plist-get contact :mail)) (case-fold-search nil)) (plist-put contact :name (malb/canonicalise-contact-name name)) contact)) ;; (setq mu4e-contact-process-function #'malb/mu4e-contact-process-function) (defun mu4e-action-view-in-firefox (msg) "View the body of the message in a new Firefox window." (let ((browse-url-browser-function 'browse-url-firefox) (browse-url-new-window-flag t)) (mu4e-action-view-in-browser msg))) ;; View mail in browser with "a V" (add-to-list 'mu4e-view-actions '("ViewInBrowser" . mu4e-action-view-in-browser) t) (add-to-list 'mu4e-view-actions '("fViewInFirefox" . mu4e-action-view-in-firefox) t) (add-to-list 'mu4e-view-actions '("xViewXWidget" . mu4e-action-view-with-xwidget) t) ;; use imagemagick, if available (when (fboundp 'imagemagick-register-types) (imagemagick-register-types)) ;;rename files when moving ;;NEEDED FOR MBSYNC (setq mu4e-change-filenames-when-moving t) (defun mu4e-message-maildir-matches (msg rx) "Match message MSG with regex RX based on maildir." (when rx (if (listp rx) ;; if rx is a list, try each one for a match (or (mu4e-message-maildir-matches msg (car rx)) (mu4e-message-maildir-matches msg (cdr rx))) ;; not a list, check rx (string-match rx (mu4e-message-field msg :maildir))))) (defmacro mu4e-context-match-fun (maildir) "Return lambda for context switching which checks if a message is in MAILDIR." `(lambda (msg) (when msg (mu4e-message-maildir-matches msg ,maildir)))) (setq mu4e-contexts `( ,(make-mu4e-context :name "private" :enter-func (lambda () (mu4e-message "Switch to the Private context")) :match-func (mu4e-context-match-fun "^/private") :vars '((gnus-icalendar-org-capture-file "~/org/personal.org") ( user-mail-address . "daniel@kraus.my" ) ( mu4e-maildir-shortcuts . (("/private/Inbox" . ?i) ("/private/Archive" . ?a) ("/private/Sent" . ?s) ("/private/Trash" . ?t) ("/private/Drafts" . ?d) ("/private/Junk" . ?j))) ( mu4e-drafts-folder . "/private/Drafts" ) ( mu4e-sent-folder . "/private/Sent" ) ( mu4e-trash-folder . "/private/Trash" ) ( mu4e-refile-folder . dakra-mu4e-private-refile))) ,(make-mu4e-context :name "gmail" :enter-func (lambda () (mu4e-message "Switch to the gmail context")) :match-func (mu4e-context-match-fun "^/gmail") :vars '((gnus-icalendar-org-capture-file "~/org/personal.org") ( user-mail-address . "daniel.kraus@gmail.com" ) ( mu4e-maildir-shortcuts . (("/gmail/inbox" . ?i) ("/gmail/all_mail" . ?a) ("/gmail/sent_mail" . ?s) ("/gmail/trash" . ?t) ("/gmail/drafts" . ?d) ("/gmail/spam" . ?j) )) ( mu4e-drafts-folder . "/gmail/drafts" ) ( mu4e-sent-folder . "/gmail/sent_mail" ) ( mu4e-trash-folder . "/gmail/trash" ) ( mu4e-refile-folder . "/gmail/all_mail" ) ;; don't save message to Sent Messages, Gmail/IMAP takes care of this ( mu4e-sent-messages-behavior . delete))) ,(make-mu4e-context :name "scarlet" :enter-func (lambda () (mu4e-message "Switch to the Scarlet context")) :match-func (mu4e-context-match-fun "^/scarlet") :vars '(( user-mail-address . "daniel@scarletcomply.com" ) ( mu4e-maildir-shortcuts . (("/scarlet/inbox" . ?i) ("/scarlet/sent_mail" . ?s) ("/scarlet/trash" . ?t) ("/scarlet/drafts" . ?d) ("/scarlet/all_mail" . ?a) ("/private/scarlet-github" . ?g))) ( mu4e-drafts-folder . "/scarlet/drafts" ) ( mu4e-sent-folder . "/scarlet/sent_mail" ) ( mu4e-trash-folder . "/scarlet/trash" ) ( mu4e-refile-folder . "/scarlet/all_mail" ) ;; don't save message to Sent Messages, Gmail/IMAP takes care of this ( mu4e-sent-messages-behavior . delete))) ,(make-mu4e-context :name "2spicy" :enter-func (lambda () (mu4e-message "Switch to the 2Spicy context")) :match-func (mu4e-context-match-fun "^/spicy") :vars '(( user-mail-address . "daniel@spicy.com" ) ( mu4e-maildir-shortcuts . (("/spicy/inbox" . ?i) ("/spicy/sent_mail" . ?s) ("/spicy/trash" . ?t) ("/spicy/drafts" . ?d) ("/spicy/all_mail" . ?a))) ( mu4e-drafts-folder . "/spicy/drafts" ) ( mu4e-sent-folder . "/spicy/sent_mail" ) ( mu4e-trash-folder . "/spicy/trash" ) ( mu4e-refile-folder . "/spicy/all_mail" ) ;; don't save message to Sent Messages, Gmail/IMAP takes care of this ( mu4e-sent-messages-behavior . delete))) ,(make-mu4e-context :name "e5" :enter-func (lambda () (mu4e-message "Switch to the e5 context")) :match-func (mu4e-context-match-fun "^/e5") :vars '((gnus-icalendar-org-capture-file "~/org/e5.org") ( user-mail-address . "daniel.kraus@ebenefuenf.de" ) ( mu4e-maildir-shortcuts . (("/e5/Inbox" . ?i) ("/e5/Sent" . ?s) ("/e5/Trash" . ?t) ("/e5/Drafts" . ?d) ("/e5/Archive" . ?a))) ( mu4e-drafts-folder . "/e5/Drafts" ) ( mu4e-sent-folder . "/e5/Sent" ) ( mu4e-trash-folder . "/e5/Trash" ) ( mu4e-refile-folder . "/e5/Archive" ))) ,(make-mu4e-context :name "Sovendus" :enter-func (lambda () (mu4e-message "Switch to the Sovendus context")) :match-func (mu4e-context-match-fun "^/sovendus") :vars '((gnus-icalendar-org-capture-file "~/org/sovendus.org") ( user-mail-address . "extern.daniel.kraus@sovendus.de" ) ( mu4e-maildir-shortcuts . (("/sovendus/Inbox" . ?i) ("/sovendus/Archive" . ?a) ("/sovendus/Outbox" . ?s) ("/sovendus/Trash" . ?t) ("/sovendus/Spam" . ?j) ("/sovendus/Drafts" . ?d) )) ( mu4e-drafts-folder . "/sovendus/Drafts" ) ( mu4e-sent-folder . "/sovendus/Sent" ) ( mu4e-trash-folder . "/sovendus/Trash" ) ( mu4e-refile-folder . "/sovendus/Archive" ))) ,(make-mu4e-context :name "Paessler" :enter-func (lambda () (mu4e-message "Switch to the paessler context")) :match-func (mu4e-context-match-fun "^/paessler") :vars '((gnus-icalendar-org-capture-file "~/org/e5.org") ( user-mail-address . "daniel.kraus@paessler.com" ) ( mu4e-maildir-shortcuts . (("/paessler/Inbox" . ?i) ("/paessler/Outbox" . ?s) ("/paessler/Deleted Items" . ?t) ("/paessler/Drafts" . ?d) ("/paessler/Archive" . ?a))) ( mu4e-drafts-folder . "/paessler/Drafts" ) ( mu4e-sent-folder . "/paessler/Sent" ) ( mu4e-trash-folder . "/paessler/Deleted Items" ) ( mu4e-refile-folder . "/paessler/Archive" ))) ,(make-mu4e-context :name "hogaso" :enter-func (lambda () (mu4e-message "Switch to the Hogaso context")) :match-func (mu4e-context-match-fun "^/hogaso") :vars '(( user-mail-address . "daniel@hogaso.com" ) ( mu4e-maildir-shortcuts . (("/hogaso/inbox" . ?i) ("/hogaso/all_mail" . ?a) ("/hogaso/sent_mail" . ?s) ("/hogaso/trash" . ?t) ("/hogaso/drafts" . ?d) ("/hogaso/spam" . ?j))) ( mu4e-drafts-folder . "/hogaso/drafts" ) ( mu4e-sent-folder . "/hogaso/sent_mail" ) ( mu4e-trash-folder . "/hogaso/trash" ) ( mu4e-refile-folder . "/hogaso/all_mail" ) ;; don't save message to Sent Messages, Gmail/IMAP takes care of this ( mu4e-sent-messages-behavior . delete) ( mu4e-compose-signature . (concat "Daniel Kraus\n" "Hogaso | https://hogaso.com\n")))))) ;; start with the first (default) context; ;; default is to ask-if-none (ask when there's no context yet, and none match) (setq mu4e-context-policy 'pick-first) ;; compose with the current context is no context matches; ;; default is to ask '(setq mu4e-compose-context-policy nil) ;; don't keep message buffers around (setq message-kill-buffer-on-exit t) ;; something about ourselves ;; (setq ;; user-mail-address "daniel@kraus.my" ;; user-full-name "Daniel Kraus" ;; mu4e-compose-signature ;; (concat ;; "regards,\n" ;; " Daniel\n")) ;; If there's 'attach' 'file' 'pdf' in the message warn when sending w/o attachment ;; From http://mbork.pl/2016-02-06_An_attachment_reminder_in_mu4e (defun message-attachment-present-p () "Return t if an attachment is found in the current message." (save-excursion (save-restriction (widen) (goto-char (point-min)) (when (search-forward "<#part" nil t) t)))) (defcustom message-attachment-intent-re (regexp-opt '("attach" "pdf" "anhang" "angehängt" "angehaengt")) "A regex which - if found in the message, and if there is no attachment - should launch the no-attachment warning." :type '(sexp) :group 'mu4e) (defcustom message-attachment-reminder "Are you sure you want to send this message without any attachment? " "The default question asked when trying to send a message containing `message-attachment-intent-re' without an actual attachment." :type '(string) :group 'mu4e) (defun message-warn-if-no-attachments () "Ask the user if he wants to send the message even though there are no attachments. Should be added to `message-send-hook'." (when (and (save-excursion (save-restriction (widen) (goto-char (point-min)) (re-search-forward message-attachment-intent-re nil t))) (not (message-attachment-present-p))) (unless (y-or-n-p message-attachment-reminder) (keyboard-quit))))) #+END_SRC ** Gnus I don't use gnus but ~debbugs~, ~mu4e~ (the viewer) and ~mu4e-icalendar~ use it. #+BEGIN_SRC emacs-lisp (use-package gnus :after mu4e :config ;; Don't go fullscreen startin gnus (e.g. from debbugs) (setq gnus-use-full-window nil) ;; Always just delete temp html files without asking (setq gnus-article-browse-delete-temp t)) (use-package gnus-icalendar :after gnus :config ;; Setup Email ical to org sync (setq gnus-icalendar-org-capture-file "~/org/inbox.org") (setq gnus-icalendar-org-capture-headline '("Calendar")) (gnus-icalendar-org-setup)) #+END_SRC ** Mail (mu4e/gnus) icalendar / org integration #+BEGIN_SRC emacs-lisp (use-package mu4e-icalendar :after mu4e :config ;; Setup Email ical to org sync - need gnus-icalendar setup from above (setq mu4e-icalendar-trash-after-reply nil) (mu4e-icalendar-setup)) #+END_SRC ** Org and mails #+BEGIN_SRC emacs-lisp ;; for org capture (use-package org-mu4e :disabled t ;; obsolete :after (:any org mu4e) :config ;; when mail is sent, automatically convert org body to HTML (setq-default org-mu4e-convert-to-html t) (defalias 'org-mail 'org-mu4e-compose-org-mode) ;; FIXME: only set this during mu4e usage (setq-default org-export-with-toc nil) ; turn off table of contents ;; Store link to message if in header view, not to header query (setq-default org-mu4e-link-query-in-headers-mode nil)) #+END_SRC Send emails with =org-msg=. Some keybindings: - ~C-c C-e~ -- ~org-msg-preview~ - ~C-c C-s~ -- ~message-goto-subject~ (same as in [[https://www.gnu.org/software/emacs/manual/html_mono/message.html][Message mode]]) - ~C-c C-b~ -- ~org-msg-goto-body~ (similar to ~message-goto-body~ in [[https://www.gnu.org/software/emacs/manual/html_mono/message.html][Message mode]]) - ~C-c C-a~ -- ~org-msg-attach~ - ~C-c C-c~ -- ~org-ctrl-c-ctrl-c~. *OrgMsg* configures ~org-msg-ctrl-c-ctrl-c~ as a final hook of [[https://orgmode.org/][Org mode]]. When =C-c C-c= is called in a *OrgMsg* buffer it generates the MIME message and sends it. #+BEGIN_SRC emacs-lisp (use-package org-msg :defer t :config (setq org-msg-options "html-postamble:nil H:5 num:nil ^:{} toc:nil author:nil email:nil \\n:t" org-msg-startup "hidestars indent inlineimages" org-msg-greeting-name-limit 3 org-msg-convert-citation t org-msg-default-alternatives '((new . (text html)) (reply-to-html . (text html)) (reply-to-text . (text))))) #+END_SRC ** mml-sec: Auto sign mails Encrypt mails by calling (mml-secure-message-encrypt-pgpmime) #+BEGIN_SRC emacs-lisp (use-package mml-sec ;; Use this hook if you *always* want to sign your emails ;; :hook (mu4e-compose-mode . mml-secure-message-sign-pgpmime) :defer t) #+END_SRC ** Gnus-dired: Attach files from dired (C-c RET C-a) #+BEGIN_SRC emacs-lisp (use-package gnus-dired :after mu4e :hook (dired-mode . turn-on-gnus-dired-mode) :config ;; From the mu4e manual https://www.djcbsoftware.nl/code/mu/mu4e/Attaching-files-with-dired.html ;; make the `gnus-dired-mail-buffers' function also work on ;; message-mode derived modes, such as mu4e-compose-mode (defun gnus-dired-mail-buffers () "Return a list of active message buffers." (let (buffers) (save-current-buffer (dolist (buffer (buffer-list t)) (set-buffer buffer) (when (and (derived-mode-p 'message-mode) (null message-sent-message-via)) (push (buffer-name buffer) buffers)))) (nreverse buffers))) (setq gnus-dired-mail-mode 'mu4e-user-agent)) #+END_SRC ** mu4e-contrib: Display html messages #+BEGIN_SRC emacs-lisp (use-package mu4e-contrib :after mu4e :config ;;(require 'mu4e-message) ;;(setq mu4e-html2text-command 'mu4e-shr2text) (add-hook 'mu4e-view-mode-hook (lambda() ;; try to emulate some of the eww key-bindings (local-set-key (kbd "") 'shr-next-link) (local-set-key (kbd "") 'shr-previous-link)))) #+END_SRC ** message-view-patch: Colorize patch-based emails #+BEGIN_SRC emacs-lisp (use-package message-view-patch :hook (gnus-part-display . message-view-patch-highlight)) #+END_SRC ** mm-*: View MIME objects #+BEGIN_SRC emacs-lisp (use-package mm-decode :defer t :config ;; View JSON file attachements inline in mu4e/gnus (defun mm-display-json-inline (handle) "Show a JSON file from HANDLE inline." (mm-display-inline-fontify handle 'json-ts-mode)) (add-to-list 'mm-inline-media-tests '("application/json" mm-display-json-inline identity)) (add-to-list 'mm-inlined-types "application/json")) #+END_SRC * Window Manager ** Emacs transparent frames / alpha-background For transparency to work you need a composite manager like =picom= running. #+BEGIN_SRC txt :tangle arch-pkglist.txt picom #+END_SRC #+BEGIN_SRC emacs-lisp (defun system-process-running? (process-name) "Checks if process with PROCESS-NAME is running on the system." (->> (list-system-processes) (mapcar (lambda (pid) (string-match-p process-name (alist-get 'args (process-attributes pid))))) (-any? #'identity))) (setq alpha-background-default 75) (defun alpha-background-frame-opaque-p () "Return if current frame is opaque." (let ((current-alpha (frame-parameter nil 'alpha-background))) (or (not current-alpha) (= current-alpha 100)))) (defun alpha-background-frame-toggle (&optional alpha-background) "Toggle transparency for the current frame. Toggle alpha-background to `alpha-background' or `alpha-background-default' when not specified." (interactive "P") ;; Start picom if it's not running already (unless (system-process-running? "picom") (start-process-shell-command "picom" nil "picom")) (let ((alpha-bkg (when (alpha-background-frame-opaque-p) (or alpha-background alpha-background-default)))) (set-frame-parameter nil 'alpha-background alpha-bkg))) #+END_SRC ** i3 #+BEGIN_SRC txt :tangle arch-pkglist.txt i3status-rust #+END_SRC To get the current clocked-in org task displayed in the mode line, I use ~i3status-rust~ and have the following block in it's ~config.yml~: #+BEGIN_SRC yaml :tangle no [[block]] block = "custom" interval = 20 command = "emacsclient --eval '(if (org-clock-is-active) (org-no-properties (org-clock-get-clock-string)) \"No active task\")' | cut -d '\"' -f 2 || echo 'Emacs daemon not started'" #+END_SRC #+BEGIN_SRC emacs-lisp (use-package i3 :bind (("s-j" . i3-windmove-left) ("s-l" . i3-windmove-right) ("s-i" . i3-windmove-up) ("s-k" . i3-windmove-down) ("s-J" . i3-windmove-swap-states-left) ("s-L" . i3-windmove-swap-states-right)) :config (require 'windmove) (defun i3-windmove-left (&optional arg) "Like windmove-left but call i3 command `focus left' if there is no window on the left." (interactive "P") (if (windmove-find-other-window 'left arg) (windmove-do-window-select 'left arg) ;; No window to the left (i3-command 0 "focus left"))) (defun i3-windmove-right (&optional arg) "Like windmove-right but call i3 command `focus right' if there is no window on the right." (interactive "P") (if (windmove-find-other-window 'right arg) (windmove-do-window-select 'right arg) ;; No window to the right (i3-command 0 "focus right") ;; (i3-command 0 "mode 'default'") )) (defun i3-windmove-up (&optional arg) "Like windmove-up but call i3 command `focus up' if there is no window on the up." (interactive "P") (if (windmove-find-other-window 'up arg) (windmove-do-window-select 'up arg) ;; No window to the up (i3-command 0 "focus up"))) (defun i3-windmove-down (&optional arg) "Like windmove-down but call i3 command `focus down' if there is no window on the down." (interactive "P") (let ((other-window (windmove-find-other-window 'down arg))) (if (or (and other-window (not (window-minibuffer-p other-window))) (and (window-minibuffer-p other-window) (minibuffer-window-active-p other-window))) (windmove-do-window-select 'down arg) ;; No window to the down (i3-command 0 "focus down")))) (defun i3-windmove-swap-states-left (&optional arg) "Like windmove-swap-states-left but call i3 command `move left' if there is no window on the left." (interactive "P") (if (windmove-find-other-window 'left arg) (windmove-swap-states-in-direction 'left) ;; No window to the left (i3-command 0 "move left"))) (defun i3-windmove-swap-states-right (&optional arg) "Like windmove-swap-states-right but call i3 command `move right' if there is no window on the right." (interactive "P") (if (windmove-find-other-window 'right arg) (windmove-swap-states-in-direction 'right) ;; No window to the right (i3-command 0 "move right")))) #+END_SRC Better syntax highlighting for =.config/i3/config= #+BEGIN_SRC emacs-lisp (use-package i3wm-config-mode :defer t) #+END_SRC ** emacs-everywhere: Edit non-emacs input fields with Emacs #+begin_src emacs-lisp (use-package emacs-everywhere :if (daemonp) :defer 20 :config ;; Don't insert the selection-buffer content on start (setq emacs-everywhere-init-hooks '(emacs-everywhere-set-frame-name emacs-everywhere-set-frame-position emacs-everywhere-apply-major-mode))) #+end_src ** Exwm #+BEGIN_SRC emacs-lisp (use-package xelb :disabled t :if (daemonp) :config (setq xcb:connection-timeout 5)) (use-package exwm :disabled t :if (daemonp) :demand t :hook ((exwm-init . exwm-startup-apps) (exwm-update-title . exwm-rename-buffer-to-class+title)) :bind (:map exwm-mode-map ;; The following can only apply to EXWM buffers, else it could have unexpected effects. ("s-SPC" . exwm-floating-toggle-floating) ("s-q" . exwm-input-send-next-key) ; Shorter than the default C-c C-q ("s-t" . exwm-input-toggle-keyboard) ("s-F" . exwm-layout-toggle-fullscreen) ("M-y" . exwm-counsel-yank-pop)) :config (defun exwm-rename-buffer-to-class+title () "Update exwm buffer name with the X class name and the actual X window name." (let ((title (concat exwm-class-name " - " exwm-title))) (exwm-workspace-rename-buffer (if (< (length title) 42) title (concat (substring title 0 42) "..."))))) (defun exwm-counsel-yank-pop () "Same as `counsel-yank-pop' but also works for exwm buffers. It copies the selected entry to the clipboard and then sends `C-v' to the X11 Application. Sometimes this doesn't work. Then you can call this method with a prefix argument and each character from the copied entry will be send separately." (interactive) (if (not (derived-mode-p 'exwm-mode)) (call-interactively #'counsel-yank-pop) (let ((inhibit-read-only t) ;; Make sure we send selected yank-pop candidate to the clipboard (yank-pop-change-selection t)) (exwm-input--set-focus (exwm--buffer->id (window-buffer (selected-window)))) (if current-prefix-arg (mapc #'exwm-input--fake-key (string-to-list (call-interactively #'counsel-yank-pop))) (call-interactively #'counsel-yank-pop) (exwm-input--fake-key ?\C-v))))) ;; Set the initial workspace number. (setq exwm-workspace-number 4) ;; Make class name the buffer name (add-hook 'exwm-update-class-hook (lambda () (exwm-workspace-rename-buffer exwm-class-name))) (add-hook 'exwm-floating-setup-hook 'exwm-layout-hide-mode-line) (add-hook 'exwm-floating-exit-hook 'exwm-layout-show-mode-line) ;; XXX: Make macro (defun exwm-bind-keys (&rest bindings) "Like exwm-input-set-key but syntax similar to bind-keys. Define keybindings that work in exwm and non-exwm buffers. Only works *before* exwm in initialized." (pcase-dolist (`(,key . ,fun) bindings) (add-to-list 'exwm-input-global-keys `(,(kbd key) . ,fun)))) (exwm-bind-keys ;; General exwm commands '("s-R" . exwm-reset) '("s-w" . exwm-workspace-switch) ;; Moving/editing windows '("s-h" . winner-wrong-window) '("s-j" . exwm-windmove-left) '("s-k" . exwm-windmove-down) '("s-i" . exwm-windmove-up) '("s-l" . exwm-windmove-right) '("" . exwm-windmove-left) '("" . exwm-windmove-down) '("" . exwm-windmove-up) '("" . exwm-windmove-right) '("s-\\" . toggle-window-split) '("s-J" . windmove-swap-states-left) '("s-K" . windmove-swap-states-down) '("s-I" . windmove-swap-states-up) '("s-L" . windmove-swap-states-right) ;; XXX: switch to winner-mode (C-X 1 and the C-c ) '("s-f" . toggle-single-window) ;; Workspaces '("s-u" . exwm-workspace-switch-previous) '("s-o" . exwm-workspace-switch-next) ;; Launch apps '("s-b" . ivy-switch-buffer) '("s-d" . counsel-linux-app) '("s-D" . exwm-launch-shell-command)) ;; XXX: There is `framemove` in elpa which does something similar and ;; maybe better as it checks the shortest distance to the next frame ;; to decide where to jump so you don't have to hard code your monitor ;; setup in like here. But I don't switch my monitor layout often ;; and `framemove` doesn't "switch" exwm workspaces. ;; TODO Maybe create a `exwm-winmove` package that combines ;; winmove, framemove and some of this code. ;; We start workspaces at 1 instead of 0 (defun exwm-workspace-number-to-string (number) (number-to-string (1+ number))) (setq exwm-workspace-index-map #'exwm-workspace-number-to-string) ;; Switching workspaces (defun exwm-workspace-switch-previous (p) "Switch to previous workspace" (interactive "p") (if (< (- exwm-workspace-current-index p) 0) (exwm-workspace-switch (1- (length exwm-workspace--list))) (exwm-workspace-switch (- exwm-workspace-current-index p)))) (defun exwm-workspace-switch-next (p) "Switch to next workspace" (interactive "p") (if (> (+ exwm-workspace-current-index p) (1- (length exwm-workspace--list))) (exwm-workspace-switch 0) (exwm-workspace-switch (+ exwm-workspace-current-index p)))) (require 'windmove) (defun exwm-windmove-left (&optional arg) "Like windmove-left but go to previous workspace if there is no window on the left." (interactive "P") (if (or (<= exwm-connected-displays 1) (windmove-find-other-window 'left arg)) (windmove-do-window-select 'left arg) ;; No window to the left ;; Switch to previous workspace and select rightmost window (exwm-workspace-switch-previous 1) (while (windmove-find-other-window 'right arg) (windmove-do-window-select 'right arg)))) (defun exwm-windmove-right (&optional arg) "Like windmove-right but go to previous workspace if there is no window on the right." (interactive "P") (if (or (<= exwm-connected-displays 1) (windmove-find-other-window 'right arg)) (windmove-do-window-select 'right arg) ;; No window to the left ;; Switch to next workspace and select leftmost window (exwm-workspace-switch-next 1) (while (windmove-find-other-window 'left arg) (windmove-do-window-select 'left arg)) (windmove-do-window-select 'left arg))) (setq exwm-windmove-workspace-1-below-p t) ;; FIXME: Automatically get displayed workspace on top monitor (setq exwm-windmove-last-workspace-top 1) (defun exwm-windmove-down (&optional arg) "Like windmove-down but go to workspace 1 if there is no window or active minibuffer below and `exwm-windmove-workspace-1-below-p' is non-NIL." (interactive "P") (let ((active-minibuffer-below-p (and (minibuffer-window-active-p (minibuffer-window)) (eq (minibuffer-window) (windmove-find-other-window 'down arg))))) (if (or (<= exwm-connected-displays 1) active-minibuffer-below-p (= exwm-workspace-current-index 0) (not (eq (minibuffer-window) (windmove-find-other-window 'down arg)))) (windmove-do-window-select 'down arg) ;; No window below (when exwm-windmove-workspace-1-below-p ;; Switch to workspace 0 and select top window (setq exwm-windmove-last-workspace-top exwm-workspace-current-index) (exwm-workspace-switch 0) (while (windmove-find-other-window 'up arg) (windmove-do-window-select 'up arg)))))) (defun exwm-windmove-up (&optional arg) "Like windmove-up but go to workspace 1 if there is no window below and `exwm-windmove-workspace-1-below-p' is non-NIL." (interactive "P") (if (or (<= exwm-connected-displays 1) (windmove-find-other-window 'up arg)) (windmove-do-window-select 'up arg) ;; No window below (when exwm-windmove-workspace-1-below-p ;; Switch to workspace 1 and select bottom window (exwm-workspace-switch exwm-windmove-last-workspace-top) (while (windmove-find-other-window 'down arg) (windmove-do-window-select 'down arg))))) ;; 's-N': Switch to certain workspace (dotimes (i 4) (exwm-input-set-key (kbd (format "s-%d" (+ 1 i))) `(lambda () (interactive) (exwm-workspace-switch-create ,i)))) ;; 's-D': Launch application (defun exwm-launch-shell-command (command) (interactive (list (read-shell-command "$ "))) (start-process-shell-command command nil command)) ;; Line-editing shortcuts (setq exwm-input-simulation-keys '(([?\C-b] . [left]) ([?\C-f] . [right]) ([?\C-p] . [up]) ([?\C-n] . [down]) ([?\C-a] . [home]) ([?\C-e] . [end]) ([?\M-v] . [prior]) ([?\C-v] . [next]) ([?\C-y] . [?\C-v]) ;;([?\C-k] . [S-end delete]) ([?\C-d] . [delete]))) (setq exwm-workspace-show-all-buffers t) (setq exwm-layout-show-all-buffers t) ;; This setup needs `autorandr' installed. ;; AUR package `autorandr' and enable with `systemctl enable autorandr` ;; Autorandr uses udev rules to pick and choose the correct xrandr layout so here ;; in exwm we only need to set dynamically which workspaces map to which output. (require 'exwm-randr) ;; Dynamic xrandr config ideas from https://github.com/ch11ng/exwm/issues/202 (defvar exwm-connected-displays 1 "Number of connected displays.") ;; Update exwm-randr-workspace-output-plist with 2 or 3 outputs named ;; 'primary' and 'other-1'/'other-2'. ;; With 3 outputs connected the first workspace will be primary, ;; second workspace goes to 'other-2' and all others to 'other-1'. ;; With 2 outputs, first workspace is 'primary' display and rest 'other-1'. ;; And with only one connected output, primary has all workspaces. (defun dakra-exwm-randr-screen-change () (let* ((connected-cmd "xrandr -q|awk '/ connected/ {print $1}'") (connected (process-lines "bash" "-lc" connected-cmd)) (primary (car connected)) ; Primary display is always first in list (other-1 (cadr connected)) (other-2 (caddr connected))) (setq exwm-connected-displays (length connected)) (setq exwm-randr-workspace-monitor-plist (append (list 0 primary) (list 1 (or other-2 other-1 primary)) (mapcan (lambda (i) (list i (or other-1 other-2 primary))) (number-sequence 2 exwm-workspace-number)))) (exwm-randr-refresh) (message "Randr: %s monitors refreshed." (string-join connected ", ")))) (add-hook 'exwm-randr-screen-change-hook #'dakra-exwm-randr-screen-change) (exwm-randr-enable) ;; Warp cursor automatically after workspace switch (setq exwm-workspace-warp-cursor t) (require 'exwm-systemtray) ;; Pick some height for the system tray. Some applet icons don't appear otherwise. (setq exwm-systemtray-height 32) (exwm-systemtray-enable) (setq exwm-manage-configurations '(((string= exwm-instance-name "emacs") ;; Emacs is better off being started in char-mode. char-mode t) ;; ((equal exwm-class-name "KeePassXC") ;; floating t ;; floating-mode-line nil ;; width 0.6 ;; height 0.8) ;; ((equal exwm-class-name "zoom") ;; floating t ;; floating-mode-line nil ;; width 0.6 ;; height 0.8) ((equal exwm-class-name "rdesktop") floating nil) ((equal exwm-class-name "firefoxdeveloperedition") simulation-keys (([?\C-q] . [?\C-w]) ; close tab instead of quitting Firefox ([?\C-b] . [left]) ([?\C-f] . [right]) ([?\C-p] . [up]) ([?\C-n] . [down]) ([?\C-a] . [home]) ([?\C-e] . [end]) ([?\M-v] . [prior]) ([?\C-v] . [next]) ([?\C-d] . [delete]))) ((equal exwm-class-name "Alacritty") simulation-keys (([?\C-c ?\C-c] . [?\C-c]) ;; Send C-c with C-c C-c ([?\C-f] . [right]) ([?\C-p] . [up]) ([?\C-n] . [down]) ([?\C-a] . [home]) ([?\C-e] . [end]) ([?\M-v] . [prior]) ([?\C-v] . [next]))) ((equal exwm-class-name "Termite") simulation-keys (([?\C-c ?\C-c] . [?\C-c]) ;; Send C-c with C-c C-c ([?\C-f] . [right]) ([?\C-p] . [up]) ([?\C-n] . [down]) ([?\C-a] . [home]) ([?\C-e] . [end]) ([?\M-v] . [prior]) ([?\C-v] . [next]))))) (defun dakra/emacs-teardown () "This saves all buffers and runs `kill-emacs-hook' without killing exwm or Emacs." (save-some-buffers t) ;; `run-hooks' doesn't work with let binding. (setq dakra-kill-hook (thread-last kill-emacs-hook (remove 'exwm--server-stop) (remove 'server-force-stop))) (run-hooks 'dakra-kill-hook)) (defun dakra/poweroff () "Clock out, save all Emacs buffers and shut computer down." (interactive) (when (y-or-n-p "Really want to shut down?") (when (org-clock-is-active) (org-clock-out)) (mapc #'systemctl-stop '("postgresql" "mysqld" "redis" "rethinkdb@default.service" "docker" "org.cups.cupsd" "confluent-zookeeper" "confluent-server" "confluent-schema-registry" "confluent-kafka-connect" "confluent-kafka-rest" "confluent-control-center")) (ovpn-mode-stop-all t) (dakra/emacs-teardown) (start-process-shell-command "poweroff" nil "poweroff"))) (defun dakra/reboot () "Save all Emacs buffers and reboot." (interactive) (when (y-or-n-p "Really want to reboot?") (dakra/emacs-teardown) (start-process-shell-command "reboot" nil "reboot"))) ;; Start some apps (defun exwm-startup-apps () "Start some applications after exwm init." ;; Start some always used Emacs apps (org-agenda nil "a") ;; (pop-to-buffer (eshell)) ;; (pop-to-buffer (find-file "~/.emacs.d/init.org")) ;; Start some external apps (start-process-shell-command "pidgin" nil "pidgin") (start-process-shell-command "firefox" nil "env GTK_THEME=Arc firefox") ;; (start-process-shell-command "syncthing-gtk" nil "syncthing-gtk --minimized") (start-process-shell-command "indicator-kdeconnect" nil "indicator-kdeconnect") (start-process-shell-command "keepassxc" nil "keepassxc") ;; Toggle battery display ;; (auto-display-battery-mode) ;; (start-process-shell-command "cbatticon" nil "cbatticon") ;; Set face attribute for new (child-)frames to our current font ;; (set-face-attribute 'default nil :font "Iosevka-9:slant=normal:weight=regular:width=normal") (set-face-attribute 'default nil :font "Operator Mono-9:weight=light:width=normal") ) ;; Enable EXWM (exwm-enable)) #+END_SRC ** Statusbar #+BEGIN_SRC emacs-lisp ;;(use-package statusbar ;; :defer 5 ;; :hook (exwm-init . statusbar-mode)) #+END_SRC ** PulseAudio #+BEGIN_SRC emacs-lisp (use-package pulseaudio-control :bind (("" . pulseaudio-control-increase-volume) ("" . pulseaudio-control-decrease-volume) ("" . pulseaudio-control-toggle-current-sink-mute) ("C-c v" . hydra-pulseaudio-control/body) ;; :map exwm-mode-map ;; ("" . pulseaudio-control-increase-volume) ;; ("" . pulseaudio-control-decrease-volume) ;; ("" . pulseaudio-control-toggle-current-sink-mute) ) ;;:bind-keymap ("C-c v" . pulseaudio-control-map) :config ;; XXX: Maybe -set-volume (1-9 keys sets 10%, 20% etc)? ;; Maybe show selected sink and volume (require 'hydra) (defhydra hydra-pulseaudio-control (:hint nil) "Pulseaudio Control" ("+" pulseaudio-control-increase-volume "Increase Volume") ("i" pulseaudio-control-increase-volume "Increase Volume") ("-" pulseaudio-control-decrease-volume "Decrease Volume") ("d" pulseaudio-control-decrease-volume "Decrease Volume") ("m" pulseaudio-control-toggle-current-sink-mute "Toggle Mute") ("s" pulseaudio-control-select-sink-by-name "Select Sink") ("S" pulseaudio-control-select-source-by-name "Select Source") ("q" nil "quit")) (setq pulseaudio-control-volume-step "5%")) #+END_SRC ** Xbacklight: Adjust screen brightness #+BEGIN_SRC emacs-lisp (use-package xbacklight :bind (("" . xbacklight-increase) ("" . xbacklight-decrease) ;; :map exwm-mode-map ;; ("" . xbacklight-increase) ;; ("" . xbacklight-decrease) )) #+END_SRC ** Network Control network manager from Emacs FIXME: replace with consult #+BEGIN_SRC emacs-lisp :tangle no (defvar counsel-network-manager-history nil "Network manager history.") (defun counsel-network-manager (&optional initial-input) "Connect to wifi network." (interactive) (shell-command "nmcli device wifi rescan") (let ((networks-list (s-split "\n" (shell-command-to-string "nmcli device wifi list")))) (ivy-read "Select network" networks-list :initial-input initial-input :require-match t :history counsel-network-manager-history :sort nil :caller 'counsel-network-manager :action (lambda (line) (let ((network (car (s-split " " (s-trim (s-chop-prefix "*" line)) t)))) (message "Connecting to \"%s\".." network) (async-shell-command (format "nmcli device wifi connect %s" (shell-quote-argument network)))))))) #+END_SRC ** Navigation #+BEGIN_SRC emacs-lisp ;; Focus follows mouse for Emacs windows and frames (setq mouse-autoselect-window t) (setq focus-follows-mouse t) (defvar single-window--last-configuration nil "Last window configuration before calling `delete-other-windows'.") (defun toggle-single-window () "Un-maximize current window. If multiple windows are active, save window configuration and delete other windows. If only one window is active and a window configuration was previously save, restore that configuration." (interactive) (if (= (count-windows) 1) (when single-window--last-configuration (set-window-configuration single-window--last-configuration)) (setq single-window--last-configuration (current-window-configuration)) (delete-other-windows))) (defun toggle-window-split () "Switch between vertical and horizontal split. It only works for frames with exactly two windows." (interactive) (if (= (count-windows) 2) (let* ((this-win-buffer (window-buffer)) (next-win-buffer (window-buffer (next-window))) (this-win-edges (window-edges (selected-window))) (next-win-edges (window-edges (next-window))) (this-win-2nd (not (and (<= (car this-win-edges) (car next-win-edges)) (<= (cadr this-win-edges) (cadr next-win-edges))))) (splitter (if (= (car this-win-edges) (car (window-edges (next-window)))) 'split-window-horizontally 'split-window-vertically))) (delete-other-windows) (let ((first-win (selected-window))) (funcall splitter) (if this-win-2nd (other-window 1)) (set-window-buffer (selected-window) this-win-buffer) (set-window-buffer (next-window) next-win-buffer) (select-window first-win) (if this-win-2nd (other-window 1)))))) (global-set-key (kbd "C-x C-\\") 'toggle-window-split) #+END_SRC *** windmove: Easily focus and swap windows #+BEGIN_SRC emacs-lisp (use-package windmove :bind (("s-i" . windmove-up) ("s-k" . windmove-down) ("s-J" . windmove-swap-states-left) ("s-K" . windmove-swap-states-down) ("s-I" . windmove-swap-states-up) ("s-L" . windmove-swap-states-right))) #+END_SRC *** winner-mode: undo/redo window configurations #+BEGIN_SRC emacs-lisp (use-package winner :defer 3 :bind (("s-h" . winner-wrong-window)) :config (defun winner-wrong-window () "Open the last opened buffer in the other window." (interactive) (let* ((current (window-list)) (previous (save-window-excursion (winner-undo) (window-list))) (window (seq-some (lambda (w) (not (memq w previous))) current)) (buffer (window-buffer window))) (winner-undo) (other-window 1) (switch-to-buffer buffer))) (winner-mode 1)) #+END_SRC * Transmission: BitTorrent #+BEGIN_SRC txt :tangle arch-pkglist.txt transmission-cli #+END_SRC #+BEGIN_SRC emacs-lisp (use-package transmission :defer t :config ;; Suggest the latest torrent in my Download folder when calling `transmission-add' (setq transmission-default-torrent-dir (expand-file-name "~/Download")) (defun transmission-find-last-torrent-file () "Return latest torrent file from folder `transmission-default-torrent-dir'." (car (sort (directory-files (expand-file-name "~/Downloads") t "\\.torrent\\'" t) #'file-newer-than-file-p))) (add-to-list 'transmission-torrent-functions #'transmission-find-last-torrent-file) ;; Auto refresh for all transmission buffers (setq transmission-refresh-modes '(transmission-mode transmission-files-mode transmission-info-mode transmission-peers-mode))) #+END_SRC * Multimedia ** Brain-fm: Stream music from brain.fm #+BEGIN_SRC emacs-lisp (use-package brain-fm :defer t :config ;; Station 35 is "Focus" (setq brain-fm-default-station-id 35)) #+END_SRC ** Emms #+BEGIN_SRC emacs-lisp (use-package emms :defer t) (use-package emms-player-mpv :after (:any emms dired) :config (setq emms-player-list '(emms-player-mpv)) (setq emms-player-mpv-parameters '("--no-terminal" "--force-window=no" "--audio-display=no"))) #+END_SRC ** YouTube Download #+BEGIN_SRC txt :tangle arch-pkglist.txt yt-dlp python-pycryptodome atomicparsley mpv #+END_SRC #+BEGIN_SRC emacs-lisp (use-package youtube-dl :defer t :init (setq youtube-dl-directory "~/videos/youtube") :config (setq youtube-dl-program "yt-dlp") (setq youtube-dl-arguments '("--no-mtime" "--restrict-filenames" "--format" "best" "--mark-watched"))) #+END_SRC ** Image (magick) This needs imagemagick support compiled in Emacs to work. #+BEGIN_SRC emacs-lisp (use-package image :defer t :config ;; always loop GIF images (setq image-animate-loop t)) #+END_SRC ** Scrot: Screenshot utility using scrot #+BEGIN_SRC emacs-lisp (use-package scrot :defer t) #+END_SRC * Misc ** Atomx #+BEGIN_SRC emacs-lisp (use-package atomx :defer t) #+END_SRC ** AWS #+BEGIN_SRC emacs-lisp (use-package aws :defer t :config (setq aws-serial-number "arn:aws:iam::123456:mfa/daniel.kraus")) #+END_SRC ** Auto-display-battery-mode Automatically show battery info when laptop is not connected to line power. #+BEGIN_SRC emacs-lisp (use-package auto-display-battery :defer t) #+END_SRC ** Aurel: Search, vote for and download AUR packages #+BEGIN_SRC emacs-lisp (use-package aurel :defer t :config ;; Add "machine aur.archlinux.org login password " line to .authinfo (setq aurel-aur-user-package-info-check t)) #+END_SRC ** Debbugs: GNU Bug tracker #+BEGIN_SRC emacs-lisp (use-package debbugs :defer t) #+END_SRC ** ChatGPT #+BEGIN_SRC emacs-lisp (use-package chatgpt-shell :config (setq chatgpt-shell-openai-key (lambda () (auth-source-pick-first-password :host "openai.com" :user "apikey"))) (setq chatgpt-shell-model-version "gpt-4") (setq chatgpt-shell-system-prompt "You are a helpful programmer with AWS knowledge who's preferred programming language is Clojure and ClojureScript. Always show code snippets in markdown blocks with language labels.") (add-to-list 'chatgpt-shell-system-prompts `("Clojure programmer" ,chatgpt-shell-system-prompt))) (use-package dall-e-shell :config (setq dall-e-shell-openai-key (auth-source-pick-first-password :host "openai.com" :user "apikey"))) #+END_SRC ** Gif-Screencasts: One-frame-per-action GIF recording You need to install ~scrot~, ImageMagick (~convert~) and optional ~Gifsicle~. XXX: Use emacs ~keycast~ package to display keys in modeline. #+BEGIN_SRC emacs-lisp (use-package gif-screencast :bind (:map gif-screencast-mode-map ("" . gif-screencast-toggle-pause) ("" . gif-screencast-stop) ("" . gif-screencast-stop)) :config (setq gif-screencast-output-directory (expand-file-name "videos/emacs/" "~"))) #+END_SRC ** IPInfo: Get IP info from ipinfo.io #+BEGIN_SRC emacs-lisp (use-package ipinfo :defer t) #+END_SRC ** Speed-type: Type a text and measure your speed #+BEGIN_SRC emacs-lisp (use-package speed-type :defer t) #+END_SRC ** Disk-usage: File system analyzer. Tabulated view of file listings sorted by size #+BEGIN_SRC emacs-lisp (use-package disk-usage :defer t) #+END_SRC ** Systemctl #+BEGIN_SRC emacs-lisp (use-package systemctl :commands (hydra-systemctl/body services-run) :config (defun forme-backend-start () (interactive) (let ((default-directory (projectile-project-root))) (compilation-start "./gradlew dev" nil (lambda (_mode) "*4me - dev")) (sleep-for 2) (compilation-start "./gradlew -x pmdMain -x spotbugsMain -x spotlessJava run --args='-cwd .'" nil (lambda (_mode) "*4me - run")))) ;; FIXME: Loot at https://github.com/rejeep/prodigy.el (setq services-list '((:project-root "/home/daniel/atomx/api/" :services ("mysqld" "redis") :fun pyramid-serve) (:project-root "/home/daniel/skor/buzzer/src/github.com/skor/buzzer/" :services () :fun compilation-start :fun-args ("make" t ; Use compilation-shell-minor-mode under comint-mode (lambda (_mode) "*skor buzzer*"))) (:project-root "/home/daniel/skor/back-end/" :services ("postgresql" "redis") :fun compilation-start :fun-args ("uvicorn --debug --host 0.0.0.0 skor.app:app" t ; Use compilation-shell-minor-mode under comint-mode (lambda (_mode) "*skor back-end*"))) (:project-root "/home/daniel/skor/front-end/" :services () :fun compilation-start :fun-args ("yarn serve" t ; Use compilation-shell-minor-mode under comint-mode (lambda (_mode) "*skor front-end*"))) (:project-root "/home/daniel/sap/forme-backend/" :services () :fun forme-backend-start :fun-args ()) (:project-root "/home/daniel/sap/forme-frontend/" :services () :fun compilation-start :fun-args ("gulp dev" nil (lambda (_mode) "*forme frontend*"))) (:project-root "/home/daniel/projects/bush-kennels.uk/" :services () :fun compilation-start :fun-args ("yarn serve" t ; Use compilation-shell-minor-mode under comint-mode (lambda (_mode) "*yarn serve bush-kennels.uk*"))) (:project-root "/home/daniel/e5/blif/blif-frontend/" :services () :fun compilation-start :fun-args ("gulp dev" t ; Use compilation-shell-minor-mode under comint-mode (lambda (_mode) "*blif frontend*"))) (:project-root "/home/daniel/e5/paessler/website/" :services ("mysqld" "redis") :fun djangonaut-run-management-command :fun-args ("runserver" "8001")) (:project-root "/home/daniel/e5/paessler/shop/" :services ("mysqld" "redis") :fun djangonaut-run-management-command :fun-args ("runserver")) ;; (:project-root "/home/daniel/e5/paessler/shop/" ;; :services ("docker") ;; :fun compilation-start ;; :fun-args ("docker-compose -p bis-trunk -f docker/docker-compose.yml -f docker/shop-admin.yml up" ;; t ; Use compilation-shell-minor-mode under comint-mode ;; (lambda (_mode) "*docker compose paessler*"))) (:project-root "/home/daniel/e5/neorent/neorent/" :services ("postgresql" "redis") :fun pyramid-serve :fun-args (t)))) ;; FIXME: Add process environment (defun services-run (&optional project-root) "Start running server for project NAME." (interactive) (let* ((project-root (or project-root (projectile-project-root))) (project (-first (lambda (p) (equal (plist-get p :project-root) project-root)) services-list)) (services (plist-get project :services)) (fun (plist-get project :fun)) (fun-args (plist-get project :fun-args))) (unless project (error "Project not found in services-list.")) (message "Starting services for project %s" project-root) (dolist (service services) (systemctl-start service)) (let ((default-directory project-root)) (apply fun fun-args)))) (defun systemctl-hydra-status (unit) "Return a checkbox indicating the status of UNIT." (if (equal (type-of unit) 'string) (if (systemctl-is-active-p unit) "[x]" "[ ]") (if (-all-p 'systemctl-is-active-p unit) "[x]" "[ ]"))) (defhydra hydra-systemctl (:hint none) " Presets Services Kafka ------- -------- ----- _1_: ?1? postgres/redis ?p? _p_ostgres ?z? _z_ookeeper _2_: ?2? mysql/redis ?r? _r_edis ?k? _k_afka _3_: ?3? mysql/rdb/redis ?m? _m_ysql _4_: ?4? confluent kafka ?t? re_t_hinkdb ?d? _d_ocker ?a? _a_ws-vpn _o_: offline (stop all) ?c? _c_ups _g_: Refresh Hydra _q_: quit" ;; Environments ("1" (mapc #'systemctl-start '("postgresql" "redis")) (systemctl-hydra-status '("postgresql" "redis"))) ("2" (mapc #'systemctl-start '("mysqld" "redis")) (systemctl-hydra-status '("mysqld" "redis"))) ("3" (mapc #'systemctl-start '("mysqld" "redis" "rethinkdb@default.service")) (systemctl-hydra-status '("mysqld" "redis" "rethinkdb@default.service"))) ("4" (mapc #'systemctl-start '("confluent-zookeeper" "confluent-server" "confluent-schema-registry" "confluent-kafka-rest")) ; "confluent-kafka-connect" "confluent-control-center" (systemctl-hydra-status '("confluent-zookeeper" "confluent-server" "confluent-schema-registry" "confluent-kafka-rest"))) ;; Stop all ("o" (mapc #'systemctl-stop' ("postgresql" "mysqld" "redis" "rethinkdb@default.service" "docker" "systemd-resolved" "awsvpnclient" "org.cups.cupsd" "confluent-zookeeper" "confluent-server" "confluent-schema-registry" "confluent-kafka-rest"))) ;; Services ("p" (systemctl-toggle "postgresql") (systemctl-hydra-status "postgresql")) ("r" (systemctl-toggle "redis") (systemctl-hydra-status "redis")) ("m" (systemctl-toggle "mysqld") (systemctl-hydra-status "mysqld")) ("t" (systemctl-toggle "rethinkdb@default.service") (systemctl-hydra-status "rethinkdb@default.service")) ("d" (systemctl-toggle "docker") (systemctl-hydra-status "docker")) ("a" (mapc #'systemctl-toggle '("systemd-resolved" "awsvpnclient")) (systemctl-hydra-status "awsvpnclient")) ("c" (mapc #'systemctl-toggle '("org.cups.cupsd" "cups-browsed" "avahi-daemon")) (systemctl-hydra-status "org.cups.cupsd")) ("z" (systemctl-toggle "confluent-zookeeper") (systemctl-hydra-status "confluent-zookeeper")) ("k" (systemctl-toggle "confluent-server") (systemctl-hydra-status "confluent-server")) ("g" (message "Hydra refreshed")) ("q" (message "Abort") :exit t))) #+END_SRC ** OVPN: OpenVPN management mode Place all your openvpn (~.ovpn~) in =~/vpn= and then select a config and start a vpn with ~s~ or start in a separate namespace without altering the main system route with ~n~ then you can spawn a command (like ~transmission~) in that namespace with ~x~ #+BEGIN_SRC emacs-lisp (use-package ovpn-mode :defer t :config (setq ovpn-mode-preferred-browser "google-chrome-stable") (setq ovpn-mode-ipv6-auto-toggle t) ; Always turn off ipv6 when starting vpn (setq ovpn-mode-base-directory "~/vpn")) #+END_SRC ** F5 VPN #+BEGIN_SRC emacs-lisp (use-package f5vpn :defer t :config (setq f5vpn-host "connectwdf.sap.com")) #+END_SRC ** Ledger: Accounting #+BEGIN_SRC emacs-lisp ;; ledger-mode for bookkeeping (defun ledger-mode-outline-hook () (outline-minor-mode) (setq-local outline-regexp "[#;]+")) (use-package hledger-mode :disabled t ;; Think ledger-mode is better.. needs more experimenting ;;:mode "\\.ledger\\'" :commands (hledger-mode hledger-jentry hledger-run-command) :bind (:map hledger-mode-map ("C-c e" . hledger-jentry) ("C-c j" . hledger-run-command) ("M-p" . hledger/prev-entry) ("M-n" . hledger/next-entry)) :init (add-hook 'hledger-mode-hook 'ledger-mode-outline-hook) :config (setq hledger-jfile "/home/daniel/cepheus/finances.ledger") ;; Auto-completion for account names (add-to-list 'company-backends 'hledger-company) (defun hledger/next-entry () "Move to next entry and pulse." (interactive) (hledger-next-or-new-entry) (hledger-pulse-momentary-current-entry)) (defun hledger/prev-entry () "Move to last entry and pulse." (interactive) (hledger-backward-entry) (hledger-pulse-momentary-current-entry))) (use-package ledger-mode ;;:disabled t ;; try hledger :mode "\\.ledger\\'" :init ;; http://unconj.ca/blog/using-hledger-with-ledger-mode.html ;; Required to use hledger instead of ledger itself. ;;(setq ledger-mode-should-check-version nil ;; ledger-report-links-in-register nil ;; ledger-binary-path "hledger") (add-hook 'ledger-mode-hook 'ledger-mode-outline-hook) :config (setq ledger-reports '(("Balance (this year)" "%(binary) -f %(ledger-file) bal -p 'this year'") ("Balance (last year)" "%(binary) -f %(ledger-file) bal -p 'last year'") ("Balance (all time)" "%(binary) -f %(ledger-file) bal") ("Register (this year)" "%(binary) -f %(ledger-file) reg -p 'this year'") ("Register (last year)" "%(binary) -f %(ledger-file) reg -p 'last year'") ("Account (this year)" "%(binary) -f %(ledger-file) reg %(account) -p 'this year'") ("Account (last year)" "%(binary) -f %(ledger-file) reg %(account) -p 'last year'") ("Account (all time)" "%(binary) -f %(ledger-file) reg %(account)") ("Payee" "%(binary) -f %(ledger-file) reg @%(payee)"))) (setq ledger-use-iso-dates t) ; Use YYYY-MM-DD format ;;(add-to-list 'ledger-reports ;; (list "monthly expenses" ;; (concat "%(binary) -f %(ledger-file) balance expenses " ;; "--tree --no-total --row-total --average --monthly"))) (setq ledger-post-amount-alignment-column 60)) (use-package flycheck-ledger :after (flycheck ledger-mode)) #+END_SRC ** Elfeed: Atomx/RSS news reader #+BEGIN_SRC python :tangle ~/bin/read_url.py :shebang #!/usr/bin/python3 """Script to get the readable text from a website. Like in Firefox article view. Needs `requests` and `readability-lxml` installed. """ import sys import os import requests from readability import Document def usage(argv): cmd = os.path.basename(argv[0]) print('Usage: %s \n' '(Example: "%s https://domain.com/article.html")' % (cmd, cmd)) sys.exit(1) if __name__ == '__main__': if len(sys.argv) != 2: usage(sys.argv) url = sys.argv[1] response = requests.get(url) if response.ok: doc = Document(response.text) print(doc.summary()) else: print('Error fetching {}: {}'.format(url, response.reason)) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package elfeed :defer t :bind (:map elfeed-show-mode-map ("d" . elfeed-search-youtube-dl) ;; Make n/p scroll and N/P switch articles ("N" . elfeed-show-next) ("P" . elfeed-show-prev) ("n" . scroll-up-line) ("p" . scroll-down-line) :map elfeed-search-mode-map ("RET" . elfeed-readability-show-entry) ;; M for unread like in mu4e where R is for reply ("m" . elfeed-search-untag-all-unread) ("M" . elfeed-mark-all-as-read) ("N" . elfeed-mark-all-read-and-next-tag) ("U" . elfeed-search-fetch) ("R" . elfeed-mark-all-as-read) ("x" . elfeed-reset-filter) ("t" . elfeed-toggle-tags) ("y" . elfeed-toggle-youtube) ("l" . elfeed-show-log) ("d" . elfeed-search-youtube-dl) ("D" . elfeed-search-youtube-dl-slow) ("L" . youtube-dl-list)) :config ;; Config from https://github.com/skeeto/.emacs.d/blob/master/etc/feed-setup.el (setq-default elfeed-search-filter "@1-week-ago -youtube +unread") (defun elfeed-mark-all-as-read () "Mark all as read." (interactive) (call-interactively 'mark-whole-buffer) (elfeed-search-untag-all-unread)) (defun elfeed-reset-filter () "Reset filter." (interactive) (elfeed-search-set-filter (default-value 'elfeed-search-filter))) (defun elfeed-show-log () "Show elfeed log buffer." (interactive) (switch-to-buffer (elfeed-log-buffer))) ;; Download the readable part of the original website ;; like in Firefox's article view ;; XXX: Instead of external python script use `eww-readable' (setq elfeed-readability-script "~/bin/read_url.py") (setq elfeed-readability-url-regex "www\\.\\(heise\\|tagesschau\\)\\.de") (defun elfeed-readability-content (entry) "Replace entry content with readability article. Some feeds (like heise.de) only provide a summary and not the full article. This uses a python script to fetch the readable part of the original article content. Like in Firefox article view." (unless (elfeed-meta entry :readability) (let ((url (elfeed-entry-link entry))) (message "Downloading article content for: %s" url) (setf (elfeed-entry-content entry) (elfeed-ref (shell-command-to-string (format "%s %s" elfeed-readability-script url)))) (setf (elfeed-meta entry :readability) t)))) ;; Uncomment this if you always want to fetch the readability article content. ;; As the python script runs synchronously it makes Emacs "hang" while downloading content. ;; (add-hook 'elfeed-new-entry-hook ;; (elfeed-make-tagger :feed-url elfeed-readability-url-regex ;; :callback #'elfeed-readability-content)) (defun elfeed-readability-show-entry (entry) "Download readable content from website and show entry in a buffer. This command is like `elfeed-search-show-entry' but it first downloads the readable website content if the entry url matches `elfeed-readability-url-regex'." (interactive (list (elfeed-search-selected :ignore-region))) (when (string-match-p elfeed-readability-url-regex (elfeed-entry-link entry)) (elfeed-readability-content entry)) (elfeed-search-show-entry entry)) (defun elfeed-mark-all-read-and-next-tag () "Marks all as read and filters by another tag." (interactive) ;; Only toggle all as read if we're not in the overview (unless (eq elfeed-search-filter (default-value 'elfeed-search-filter)) (elfeed-mark-all-as-read)) (elfeed-toggle-tags)) (defun elfeed-toggle-tags () "Iterate over taglist and set filter for each tag." (interactive) ;; FIXME: simplify list (cl-macrolet ((re (re rep str) `(replace-regexp-in-string ,re ,rep ,str))) (elfeed-search-set-filter (cond ((string-match-p "-youtube" elfeed-search-filter) (re " *-youtube" " +emacs" elfeed-search-filter)) ((string-match-p "\\+emacs" elfeed-search-filter) (re " *\\+emacs" " +clojure" elfeed-search-filter)) ((string-match-p "\\+clojure" elfeed-search-filter) (re " *\\+clojure" " +python" elfeed-search-filter)) ((string-match-p "\\+python" elfeed-search-filter) (re " *\\+python" " +dev" elfeed-search-filter)) ((string-match-p "\\+dev" elfeed-search-filter) (re " *\\+dev" " +fefe" elfeed-search-filter)) ((string-match-p "\\+fefe" elfeed-search-filter) (re " *\\+fefe" " +mma" elfeed-search-filter)) ((string-match-p "\\+mma" elfeed-search-filter) (re " *\\+mma" " +chess" elfeed-search-filter)) ((string-match-p "\\+chess" elfeed-search-filter) (re " *\\+chess" " +poker" elfeed-search-filter)) ((string-match-p "\\+poker" elfeed-search-filter) (re " *\\+poker" " +health" elfeed-search-filter)) ((string-match-p "\\+health" elfeed-search-filter) (re " *\\+health" " +news" elfeed-search-filter)) ((string-match-p "\\+news" elfeed-search-filter) (re " *\\+news" " -youtube" elfeed-search-filter)) ((concat elfeed-search-filter " -youtube"))))) ;; Skip tags when there's no result (unless (or (string-match-p "-youtube" elfeed-search-filter) (elfeed-search-selected t)) (elfeed-toggle-tags))) (defun elfeed-toggle-unread () "Toggle unread filter" (interactive) (cl-macrolet ((re (re rep str) `(replace-regexp-in-string ,re ,rep ,str))) (elfeed-search-set-filter (cond ((string-match-p "-unread" elfeed-search-filter) (re " *-unread" " +unread" elfeed-search-filter)) ((string-match-p "\\+unread" elfeed-search-filter) (re " *\\+unread" " -unread" elfeed-search-filter)) ((concat elfeed-search-filter " -unread")))))) ;; Some youtube helpers (defun elfeed-toggle-youtube () "Toggle youtube filter" (interactive) (cl-macrolet ((re (re rep str) `(replace-regexp-in-string ,re ,rep ,str))) (elfeed-search-set-filter (cond ((string-match-p "-youtube" elfeed-search-filter) (re " *-youtube" " +youtube" elfeed-search-filter)) ((string-match-p "\\+youtube" elfeed-search-filter) (re " *\\+youtube" " -youtube" elfeed-search-filter)) ((concat elfeed-search-filter " -youtube")))))) (defun elfeed-show-youtube-dl () "Download the current entry with youtube-dl." (interactive) (pop-to-buffer (youtube-dl (elfeed-entry-link elfeed-show-entry)))) (cl-defun elfeed-search-youtube-dl (&key slow) "Download the current entry with youtube-dl." (interactive) (let ((entries (elfeed-search-selected))) (dolist (entry entries) (if (null (youtube-dl (elfeed-entry-link entry) :title (elfeed-entry-title entry) :slow slow)) (message "Entry is not a YouTube link!") (message "Downloading %s" (elfeed-entry-title entry))) (elfeed-untag entry 'unread) (elfeed-search-update-entry entry) (unless (use-region-p) (forward-line))))) (defalias 'elfeed-search-youtube-dl-slow (elfeed-expose #'elfeed-search-youtube-dl :slow t) "Slowly download the current entry with youtube-dl.") ;; Custom faces (defface elfeed-comic '((t :foreground "#BFF")) "Marks comics in Elfeed." :group 'elfeed) (push '(comic elfeed-comic) elfeed-search-face-alist) (defface elfeed-youtube '((t :foreground "#f9f")) "Marks YouTube videos in Elfeed." :group 'elfeed) (push '(youtube elfeed-youtube) elfeed-search-face-alist) (defface elfeed-important '((t :foreground "#E33")) "Marks important entries in Elfeed." :group 'elfeed) (push '(important elfeed-important) elfeed-search-face-alist) ;; Special filters (add-hook 'elfeed-new-entry-hook (elfeed-make-tagger :before "7 days ago" :remove 'unread)) ;; The actual feeds listing (defvar youtube-feed-format '(("^UC" . "https://www.youtube.com/feeds/videos.xml?channel_id=%s") ("^PL" . "https://www.youtube.com/feeds/videos.xml?playlist_id=%s") ("" . "https://www.youtube.com/feeds/videos.xml?user=%s"))) (defun elfeed--expand (listing) "Expand feed URLs depending on their tags." (cl-destructuring-bind (url . tags) listing (cond ((member 'youtube tags) (let* ((case-fold-search nil) (test (lambda (s r) (string-match-p r s))) (format (cl-assoc url youtube-feed-format :test test))) (cons (format (cdr format) url) tags))) (listing)))) (defmacro elfeed-config (&rest feeds) "Minimizes feed listing indentation without being weird about it." (declare (indent 0)) `(setf elfeed-feeds (mapcar #'elfeed--expand ',feeds))) (elfeed-config ("http://blog.atomx.com/rss" atomx) ("http://www.bildblog.de/wp-rss2.php" news german) ("http://blog.fefe.de/rss.xml?html" news german fefe) ("http://www.gruenderszene.de/feed" news german) ("http://www.tagesschau.de/xml/rss2" news german) ("http://blog.chromium.org/feeds/posts/default" dev web) ("http://blog.angularjs.org/feeds/posts/default" dev web js) ("https://mungingdata.com/rss" dev spark) ("http://www.archlinux.org/feeds/news/" dev important) ("http://www.heise.de/newsticker/heise-atom.xml" dev) ("http://feeds.feedburner.com/codinghorror/" dev) ("http://googledevelopers.blogspot.com/atom.xml" dev google) ("https://cloudblog.withgoogle.com/rss/" dev google) ("https://blogs.msdn.microsoft.com/oldnewthing/feed" dev) ("http://blog.dubbelboer.com/atom.xml" dev friends) ("https://blog.golang.org/feeds/posts/default" dev go) ("http://codelike.com/blog/feed" dev friends python) ("http://planet.python.org/rss20.xml" dev python) ("http://planet.scipy.org/rss20.xml" dev python) ("http://feeds.doughellmann.com/PyMOTW" dev python) ("https://blogs.msdn.microsoft.com/pythonengineering/feed/" dev python) ("https://scripter.co/posts/index.xml" dev emacs) ("http://www.holgerschurig.de/topics/emacs/index.xml" dev emacs) ("http://planet.emacsen.org/atom.xml" dev emacs) ("http://feeds.feedburner.com/XahsEmacsBlog" dev emacs) ("https://oremacs.com/atom.xml" dev emacs) ("http://sachachua.com/blog/category/emacs/feed/" dev emacs) ("http://emacsredux.com/atom.xml" dev emacs) ("http://kitchingroup.cheme.cmu.edu/blog/feed" dev emacs) ("http://lisperator.net/atom" dev emacs) ("http://planet.lisp.org/rss20.xml" dev lisp) ("https://emacs.stackexchange.com/feeds" dev stackexchange emacs) ("http://planet.clojure.in/atom.xml" dev clojure) ("https://code.thheller.com/feed.xml" dev clojure) ("http://nutrientjournal.com/feed/" health) ("http://suppversity.blogspot.com/feeds/posts/default" health) ("http://mountaindogdiet.com/feed/" health) ;; ("https://examine.com/nutrition/rss/" health) ;; "https://en.chessbase.com/feed" is broken: elfeed says "Unknown feed type" ("http://feeds.feedburner.com/chessbase/mNmu" chess) ("http://www.twoplustwo.com/two-plus-two-magazine-rss.xml" poker) ("http://www.highstakesdb.com/rss.aspx" poker) ("http://www.mmafighting.com/rss.xml" mma) ("http://www.terrencechanpoker.com/feeds/posts/default" mma) ("http://xkcd.com/rss.xml" comic) ("http://googleblog.blogspot.com/atom.xml" news google) ("1veritasium" youtube education) ("UCsXVk37bltHxD1rDPwtNM8Q" youtube education) ; Kurzgesagt – In a Nutshell ("Wendoverproductions" youtube education) ("minutephysics" youtube education) ("SciShow" youtube education) ("AsapSCIENCE" youtube education) ("UCAuUUnT6oDeKwE6v1NGQxug" youtube education) ; TED ("UCsooa4yRKGN_zEE8iknghZA" youtube education) ; TED-Ed ("Vsauce" youtube education) ("PowerPlayChess" youtube chess) ("UC2TXq_t06Hjdr2g_KdKpHQg" youtube dev) ; media.ccc.de ("BroScienceLife" youtube comedy) ("cgpgrey" youtube education) ("ufc" youtube mma) ("MMAFightingonSBN" youtube mma))) #+END_SRC ** Rdesktop #+BEGIN_SRC emacs-lisp ;; Set rdesktop-host to SAP VDI (persistent WTS) host (setq rdesktop-host "10.76.99.117") ;; Set resolution to laptop display minus Emacs modeline and map home dir (setq rdesktop-options "-P -x l -g 1920x1040 -r disk:sap=/home/daniel/sap") (defun rdesktop () "Start rdesktop session." (interactive) (if-let ((plist (car (auth-source-search :host rdesktop-host :max 1))) (user (plist-get plist :user)) (pass (funcall (plist-get plist :secret)))) (start-process-shell-command "SAP Desktop" nil (format "rdesktop %s -u %s -p %s %s" rdesktop-options user pass rdesktop-host)) (error "No user / password found in authinfo"))) #+END_SRC ** Info-beamer #+BEGIN_SRC emacs-lisp (use-package info-beamer :hook (lua-mode . info-beamer-mode)) #+END_SRC ** KDEConnect #+BEGIN_SRC emacs-lisp (use-package kdeconnect :defer t) #+END_SRC ** KeePassXC #+BEGIN_SRC emacs-lisp (use-package keepassxc :defer t :config (setq keepassxc-database-file "/home/daniel/Nextcloud/keepass.kdbx")) #+END_SRC ** Nov: Read EPUBs #+BEGIN_SRC emacs-lisp (use-package nov :mode ("\\.epub\\'" . nov-mode) :bind (:map nov-mode-map ;; In non editing buffers I like to move around with just n/p ;; and go to the next/previous chapter with N/P ("n" . next-line) ("p" . previous-line) ("N" . nov-next-document) ("P" . nov-previous-document) ("b" . nov-history-back) ;; Make it a bit easier to adjust font size for reading ("+" . text-scale-increase) ("-" . text-scale-decrease)) :config (setq nov-text-width 78)) #+END_SRC ** Piper: Shell scripting with Emacs #+BEGIN_SRC emacs-lisp (use-package piper :defer t) #+END_SRC ** Pocket reader #+BEGIN_SRC emacs-lisp ;; Read and manage your pocket (getpocket.com) list (use-package pocket-reader :defer t) ;; Tag articles with 'capture' in pocket and then call ;; org-pocket-capture-items to save all tagged articles in an org file (use-package org-pocket :after (pocket-reader org) :config (setq org-pocket-capture-file "org/pocket.org")) #+END_SRC ** Eww #+BEGIN_SRC emacs-lisp ;; Use 'C-c S' or 'M-s M-w' for 'eww-search-words' current region ;;(define-key prelude-mode-map (kbd "C-c S") nil) ; remove default crux find-shell-init keybinding (global-set-key (kbd "C-c S") 'eww-search-words) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package browse-url :bind (("C-c u" . browse-url-at-point)) :init (defun dakra-toggle-browser () "Toggle browser function between eww and Firefox." (interactive) (if (eq browse-url-browser-function 'eww-browse-url) (progn (setq browse-url-browser-function 'browse-url-firefox) (message "Setting browser to Firefox")) (setq browse-url-browser-function 'eww-browse-url) (message "Setting browser to eww"))) :config (setq browse-url-secondary-browser-function 'eww-browse-url) (setq browse-url-firefox-program "firefox") (setq browse-url-browser-function 'browse-url-firefox)) (use-package eww :defer t :config ;; (setq eww-search-prefix "https://google.com/search?q=") ) #+END_SRC ** Wolfram alpha #+BEGIN_SRC emacs-lisp ;; wolfram alpha queries (M-x wolfram-alpha) (use-package wolfram :defer t :config (setq wolfram-alpha-app-id "KTKV36-2LRW2LELV8")) #+END_SRC ** Tea timer #+BEGIN_SRC emacs-lisp (use-package tea-timer :defer t) #+END_SRC ** Web Server: A web server running handlers written in Emacs Lisp Create a simple "file server" as a fast replacement to something like ~python -m http.server~ From https://gist.github.com/TeMPOraL/f6f5333ae93de4ce9b5bd82cdad87d32 and http://eschulte.github.io/emacs-web-server/File-Server.html#File-Server #+BEGIN_SRC emacs-lisp (use-package web-server :config (defvar web-server-file-server nil "Is the file server running? Holds an instance if so.") (defvar web-server-file-server-default-port 8888 "Default port the file web server listens to when not calls with prefix argument.") (defvar web-server-old-global-mode-string nil) ; XXX: Make nicer solution to display in mode line (defun web-server-file-server-toggle () "Toggle file-server start/stop." (interactive) (if web-server-file-server (web-server-file-server-stop) (web-server-file-server-start web-server-file-server-default-port))) (defun web-server-file-server-start (&optional port) "Start a file server on a `PORT', serving the content of directory associated with the current buffer's file." (interactive "p") (if web-server-file-server (message "File server is already running!") (when (= port 1) (setq port web-server-file-server-default-port)) (lexical-let ((docroot (if (buffer-file-name) (file-name-directory (buffer-file-name)) (expand-file-name default-directory)))) (setf web-server-file-server (ws-start (lambda (request) (with-slots (process headers) request (let ((path (substring (cdr (assoc :GET headers)) 1))) (if (ws-in-directory-p docroot path) (if (file-directory-p path) (ws-send-directory-list process (expand-file-name path docroot) "^[^\.]") (ws-send-file process (expand-file-name path docroot))) (ws-send-404 process))))) port nil ; no log buffer :host "0.0.0.0")) (setq web-server-old-global-mode-string global-mode-string) (add-to-list 'global-mode-string (format " fs:%d" port) t) (message "Serving files from %s on port %d" docroot port)))) (defun web-server-file-server-stop () "Stop the file server if running." (interactive) (if web-server-file-server (progn (ws-stop web-server-file-server) (setf web-server-file-server nil) (setq global-mode-string web-server-old-global-mode-string) (message "File server stopped.")) (message "No file server is running.")))) #+END_SRC ** Debug emacs init startup time Run ~M-x esup~ to see where the time is spend during emacs startup. #+BEGIN_SRC emacs-lisp (use-package esup :defer t :config (setq esup-user-init-file "~/.emacs.d/emacs.el")) #+END_SRC ** Umlaut mode: A mode for conveniently inserting Umlauts #+BEGIN_SRC emacs-lisp (use-package umlaut :defer t :hook (message-mode . umlaut-mode)) #+END_SRC ** Unsortet stuff in no packages Probably create a `dakra` package (again) to lazy load all those commands. From https://github.com/emacscollective/borg/wiki/Utilities#borg-sync-drone-urls #+BEGIN_SRC emacs-lisp (defun borg-sync-drone-urls () "Offer to update outdated upstream urls of all drones." (interactive) (let (moved) (dolist (drone (borg-clones)) (let ((a (borg-get drone "url")) (b (ignore-errors (oref (epkg drone) url)))) (when (and a b (not (forge--url-equal a b)) (yes-or-no-p (format "Move %s: %s => %s" drone a b))) (push (list drone a b) moved)))) (when (and moved (yes-or-no-p (concat (mapconcat (pcase-lambda (`(,drone ,a ,b)) (format "%s: %s => %s" drone a b)) moved "\n") "\n\nThese upstream repositories appear to have moved." "\s\sUpdate local configuration accordingly? "))) (let ((default-directory borg-user-emacs-directory)) (pcase-dolist (`(,drone ,_ ,b) moved) (process-file "git" nil nil nil "config" "-f" ".gitmodules" (format "submodule.%s.url" drone) b)) (process-file "git" nil nil nil "submodule" "sync"))))) #+END_SRC * Libraries Here are helper functions and utility libraries for other elisp packages to use and not for actual direct user usage. ** jiralib2: Provide connectivity to JIRA REST services. #+BEGIN_SRC emacs-lisp (use-package jiralib2 :commands jira->org :config (setq jiralib2-host "sovendus.atlassian.net" jiralib2-url (concat "https://" jiralib2-host) jiralib2-auth 'token jiralib2-user-login-name "extern.daniel.kraus@sovendus.de") ;; Get jira API token from auth-source (setq jiralib2-token (auth-source-pick-first-password :host jiralib2-host :user "jira-token@sovendus.de")) (require 'ejira-parser) (defun jira->org (issue-num) (interactive "sIssue ID:") ;; If auth method is cookie, maybe we need to login first (unless (and (eq jiralib2-auth 'cookie) (bound-and-true-p jiralib2--session)) (jiralib2-session-login "extern.daniel.kraus@sovendus.de" (auth-source-pick-first-password :host jiralib2-host :user "extern.daniel.kraus@sovendus.de"))) (let* ((issue-id (if (string-match "\\`[0-9]+\\'" issue-num) (concat "SDP-" issue-num) (upcase issue-num))) (issue (jiralib2-get-issue issue-id)) (fields (alist-get 'fields issue)) (summary (alist-get 'summary fields)) (description (alist-get 'description fields)) (acceptance-criteria (alist-get 'customfield_10600 fields)) (acceptance-criteria-p (and acceptance-criteria (not (string-empty-p acceptance-criteria))))) (org-insert-todo-heading-respect-content) (insert (org-link-make-string (concat "sdp:" issue-num) (concat "SDP-" issue-num)) " " summary) (org-insert-subheading nil) (when acceptance-criteria-p (insert "Acceptance criteria\n" (ejira-parser-jira-to-org acceptance-criteria)) (org-insert-heading '(4))) (insert "Description\n" (ejira-parser-jira-to-org description))))) #+END_SRC ** ejira-parser: Parsing to and from JIRA markup. #+BEGIN_SRC emacs-lisp (use-package ejira-parser :defer t) #+END_SRC ** language-detection: Detect programming language in a buffer #+BEGIN_SRC emacs-lisp (use-package language-detection :defer t) #+END_SRC ** Oauth2: OAuth 2.0 Authorization Protocol #+BEGIN_SRC emacs-lisp (use-package oauth2 :defer t) #+END_SRC ** Posframe: Pop a child frame at point #+BEGIN_SRC emacs-lisp (use-package posframe :defer t :config ;; Don't reset the mouse cursor when showing a posframe (setq posframe-mouse-banish nil)) #+END_SRC * Hydras #+BEGIN_SRC emacs-lisp (use-package hydra :bind (("C-c S" . hydra-scratchpad/body) ("C-x t" . hydra-toggle-stuff/body) ("C-x 9" . hydra-unicode/body) ("C-x l" . hydra-emacs-launcher/body) ("C-x C-l" . hydra-emacs-launcher/body) ("C-x L" . hydra-external-launcher/body)) :config (hydra-add-font-lock) (defhydra hydra-scratchpad (:hint nil) " _p_ython _e_lisp _s_ql _g_o _j_avascript _t_ypescript _r_ust _R_est-client _h_tml _o_rg-mode _T_ext _m_arkdown " ("p" (switch-to-buffer "*python*scratchpad.py")) ("e" (switch-to-buffer "*elisp*scratchpad.el")) ("s" (switch-to-buffer "*sql*scratchpad.sql")) ("g" (switch-to-buffer "*go*scratchpad.go")) ("j" (switch-to-buffer "*js*scratchpad.js")) ("t" (switch-to-buffer "*ts*scratchpad.ts")) ("r" (switch-to-buffer "*rust*scratchpad.rs")) ("R" (switch-to-buffer "*rest*scratchpad.rest")) ("h" (switch-to-buffer "*html*scratchpad.html")) ("o" (switch-to-buffer "*org*scratchpad.org")) ("T" (switch-to-buffer "*text*scratchpad.txt")) ("m" (switch-to-buffer "*markdown*scratchpad.md"))) (eval-and-compile (defmacro hydra-help-toggle (text toggle) `(if (bound-and-true-p ,toggle) (format "[x] %s" ,text) (format "[ ] %s" ,text)))) (defhydra hydra-toggle-stuff (:color blue :hint nil) "Toggle" ("a" abbrev-mode (hydra-help-toggle "abbrev" abbrev-mode) :column "Misc") ("b" dakra-toggle-browser (format "[%s] toggle eww/firefox" (if (eq browse-url-browser-function 'browse-url-firefox) "Firefox" "eww"))) ("d" toggle-debug-on-error (hydra-help-toggle "debug-on-error" debug-on-error)) ("s" sticky-buffer-mode (hydra-help-toggle "Sticky buffer mode" sticky-buffer-mode)) ("t" alpha-background-frame-toggle (format "[%s] toggle transparency" (if (alpha-background-frame-opaque-p) " " "x"))) ("c" column-number-mode (hydra-help-toggle "column-number-mode" column-number-mode) :column "Text") ("f" auto-fill-mode (hydra-help-toggle "fill-mode" auto-fill-function)) ("F" web-server-file-server-toggle (hydra-help-toggle "Toggle file file-server" web-server-file-server)) ("w" whitespace-mode (hydra-help-toggle "whitespace-mode" whitespace-mode)) ("l" toggle-truncate-lines (hydra-help-toggle "truncate-lines" truncate-lines)) ("ol" org-toggle-link-display (hydra-help-toggle "org link-display" org-descriptive-links) :column "Org") ("op" org-toggle-pretty-entities (hydra-help-toggle "org pretty-entities" org-pretty-entities)) ("oi" org-toggle-inline-images (hydra-help-toggle "org inline-images" org-inline-image-overlays))) (defun ansi-term-bash () "Start ansi-term with bash." (interactive) (ansi-term "/bin/bash")) ;; Start different emacs packages (like elfeed or mu4e) (defhydra hydra-emacs-launcher (:color blue :hint nil) "Launch emacs package" ("e" elfeed "Elfeed - RSS/Atom Newsreader" :column "Apps") ("t" transmission "Transmission - Torrent") ("m" mu4e "mu4e - Mail") ("p" proced "proced") ("v" ovpn "VPN") ("c" quick-calc "calc - Quick calc" :column "Utils") ("d" docker "docker") ("C" calendar "calendar") ("T" world-clock "time - Display world time") ("s" hydra-systemctl/body "Systemctl") ("a" ansi-term-bash "Ansi Terminal" :column "Misc") ("b" brain-fm-play "brain.fm - Stream music") ("E" elisp-index-search "elisp-index-search") ("S" scrot "Screenshot with scrot") ("w" woman "woman - Man page viewer") ("y" (dired youtube-dl-directory) "YouTube - Open dired buffer with youtube downloads") ("z" zone "Zone - Screensaver")) ;; Start different external programs (like Termite or Firefox). (defhydra hydra-external-launcher (:color blue :hint nil) "Start external program" ("p" (start-process-shell-command "pavucontrol" nil "pavucontrol") "pavucontrol - sound settings") ("f" (start-process-shell-command "firefox" nil "env GTK_THEME=Arc firefox") "Firefox") ("F" (start-process-shell-command "firefox-developer-edition" nil "env GTK_THEME=Arc firefox-developer-edition") "Firefox Developer Edition") ("k" (start-process-shell-command "keepassxc" nil "keepassxc") "keepassxc - Password Manager") ("l" (start-process-shell-command "i3lockr" nil "i3lockr --blur 25 -- --nofork --ignore-empty-password") "Lock screen") ("n" (start-process-shell-command "networkmanager_dmenu" nil "networkmanager_dmenu") "Networkmanager") ("s" (start-process-shell-command "shutter" nil "shutter") "shutter - Screenshot") ("t" (start-process-shell-command "alacritty" nil "alacritty") "alacritty - Terminal" )) (defhydra hydra-diff-hl (:color red) "diff-hl" ("=" diff-hl-diff-goto-hunk "goto hunk") ("" diff-hl-diff-goto-hunk "goto hunk") ("u" diff-hl-revert-hunk "revert hunk") ("[" diff-hl-previous-hunk "prev hunk") ("p" diff-hl-previous-hunk "prev hunk") ("]" diff-hl-next-hunk "next hunk") ("n" diff-hl-next-hunk "next hunk") ("q" nil "cancel")) (defun dakra/insert-unicode (unicode-name) "Same as C-x 8 enter UNICODE-NAME." (insert-char (gethash unicode-name (ucs-names)))) (defhydra hydra-unicode (:color blue :hint nil) " Unicode _c_ € _a_ ä _A_ Ä _d_ ° _o_ ö _O_ Ö _e_ € _u_ Ü _U_ Ü _p_ £ _s_ ß _m_ µ _r_ → " ("a" (dakra/insert-unicode "LATIN SMALL LETTER A WITH DIAERESIS")) ("A" (dakra/insert-unicode "LATIN CAPITAL LETTER A WITH DIAERESIS")) ("o" (dakra/insert-unicode "LATIN SMALL LETTER O WITH DIAERESIS")) ;; ("O" (dakra/insert-unicode "LATIN CAPITAL LETTER O WITH DIAERESIS")) ("u" (dakra/insert-unicode "LATIN SMALL LETTER U WITH DIAERESIS")) ;; ("U" (dakra/insert-unicode "LATIN CAPITAL LETTER U WITH DIAERESIS")) ("s" (dakra/insert-unicode "LATIN SMALL LETTER SHARP S")) ("c" (dakra/insert-unicode "COPYRIGHT SIGN")) ("d" (dakra/insert-unicode "DEGREE SIGN")) ("e" (dakra/insert-unicode "EURO SIGN")) ("p" (dakra/insert-unicode "POUND SIGN")) ("r" (dakra/insert-unicode "RIGHTWARDS ARROW")) ("m" (dakra/insert-unicode "MICRO SIGN")))) #+END_SRC * Post Initialization #+BEGIN_SRC emacs-lisp (message "Loading %s...done (%.3fs)" user-init-file (float-time (time-subtract (current-time) before-user-init-time))) (add-hook 'after-init-hook (lambda () (message "Loading %s...done (%.3fs) [after-init]" user-init-file (float-time (time-subtract (current-time) before-user-init-time))) ;; Restore original file name handlers (setq file-name-handler-alist file-name-handler-alist-old) ;; Let's lower our GC thresholds back down to 256MB. (setq gc-cons-threshold (* 256 1024 1024))) t) #+END_SRC