Skip to content
This repository has been archived by the owner on Apr 13, 2019. It is now read-only.

Latest commit

 

History

History
3638 lines (3428 loc) · 134 KB

config.org

File metadata and controls

3638 lines (3428 loc) · 134 KB

Wiedzmin’s literate Emacs configuration

bootstrapping

basic utils

It’s good to have centralized working datasets storage, to prevent pollution of Emacs config directory.
(use-package no-littering
  :ensure t
  :custom
  (no-littering-var-directory (expand-file-name "data/" user-emacs-directory)))
(use-package subr-x)

(setq home-directory (getenv "HOME"))
(setq config-basedir
      (file-name-directory
       (or (buffer-file-name) load-file-name)))

(defun concat-normalize-slashes (prefix suffix)
  (concat "/"
          (string-join
           (split-string
            (string-join (list prefix suffix) "/") "/" t) "/")))

(defun at-homedir (&optional suffix)
  (concat-normalize-slashes home-directory suffix))

(defun at-org-dir (&optional suffix)
  (concat-normalize-slashes (at-homedir "/docs/org")
                            suffix))

(defun at-org-kb-dir (&optional suffix)
  (concat-normalize-slashes (at-homedir "/docs/org-kb")
                            suffix))

(defun at-config-basedir (&optional suffix)
  (concat-normalize-slashes config-basedir suffix))

(defun at-user-data-dir (&optional suffix)
  (concat-normalize-slashes no-littering-var-directory suffix))

(defun at-workspace-dir (&optional suffix)
  (concat-normalize-slashes (at-homedir "/workspace") suffix))

(defvar enable-experimental-packages nil)

essential packages and tweaks

(load (at-config-basedir "credentials.el.gpg"))

(use-package auto-compile
  :ensure t
  :config
  (auto-compile-on-load-mode 1)
  (auto-compile-on-save-mode 1)
  :custom
  (auto-compile-display-buffer nil)
  (auto-compile-mode-line-counter t))

(use-package f
  :ensure t
  :after (s dash))

(use-package names :ensure t)
(use-package anaphora :ensure t)

(use-package delight :ensure t)

(use-package notifications)

(use-package cus-edit
  :hook (kill-emacs-hook . (lambda () (delete-file custom-file)))
  :custom
  (custom-file (at-config-basedir "customizations.el")))

(use-package emacs
  :general
  ("M-\"" 'eval-region)
  ([remap kill-buffer] 'kill-this-buffer)
  :config
  (fset 'yes-or-no-p 'y-or-n-p)
  (setq scalable-fonts-allowed t)
  (setq use-dialog-box nil)
  (setq enable-recursive-minibuffers t)
  ;; don't let the cursor go into minibuffer prompt
  (setq minibuffer-prompt-properties
        '(read-only t point-entered minibuffer-avoid-prompt face minibuffer-prompt))
  (when (eq system-type 'gnu-linux)
    (setq x-alt-keysym 'meta))
  (put 'downcase-region 'disabled nil)
  (put 'erase-buffer 'disabled nil)
  (put 'narrow-to-region 'disabled nil)
  (put 'scroll-left 'disabled nil)
  (put 'scroll-right 'disabled nil)
  (put 'upcase-region 'disabled nil)
  (setq scroll-preserve-screen-position 'always)
  ;; reduce point movement lag, see https://emacs.stackexchange.com/questions/28736/emacs-pointcursor-movement-lag/28746
  (setq auto-window-vscroll nil)
  (setq undo-limit 1000000)
  (advice-add 'undo-auto--last-boundary-amalgamating-number :override #'ignore) ;; https://stackoverflow.com/a/41560712/2112489
  (setq indent-tabs-mode nil)
  (set-default 'indent-tabs-mode nil);; Never insert tabs, !!!DO NOT REMOVE!!!
  (setq-default tab-width 4)
  (setq mark-even-if-inactive nil)
  (setq-default fill-column 200)
  (setq-default indicate-empty-lines t)
  (setq-default truncate-lines t)
  (setq x-stretch-cursor t)
  (setq user-full-name (capitalize private/real-name))
  ;; print symbols
  (setq print-circle t)
  (setq print-gensym t)
  ;; encodings
  (setq locale-coding-system 'utf-8)
  (define-coding-system-alias 'UTF-8 'utf-8)
  (define-coding-system-alias 'utf-8-emacs 'utf-8) ; needed by bbdb...
  (define-coding-system-alias 'utf_8 'utf-8)
  (set-default buffer-file-coding-system 'utf-8-unix)
  (setq sentence-end-double-space nil)
  (setq tab-always-indent t)
  (setq frame-inhibit-implied-resize nil)
  (setq split-width-threshold nil)
  (setq split-height-threshold nil)
  (setq same-window-buffer-names
        '("*Help*")))

input methods

Set default input method
(setq default-input-method 'russian-computer)

Reverse input method makes Emacs with non-English system keyboard layout to behave correctly with keybindings. It definitely makes sense while using Emacs in such X WMs when there is no chance to hook into keyboard layouts switching (those are probably all, except StumpWM/XMonad, AFAIK)

(use-package reverse-im
  :ensure t
  :if (not (member (getenv "CURRENT_WM") '("stumpwm" "xmonad")))
  :config
  (reverse-im-activate "russian-computer"))

encryption and security

(use-package auth-source
  :custom
  ;;TODO: investigate and setup ghub according to https://github.com/magit/ghub/blob/master/ghub.org#user-content-manually-creating-and-storing-a-token
  ;;TODO: check if it needed and resurrect .authinfo.gpg
  (auth-sources '("~/.authinfo.gpg")))

(use-package epa
  :after (epg)
  :config
  (epa-file-enable)
  :custom
  (epa-pinentry-mode 'loopback))

(use-package epg-config
  :after (epg)
  :custom
  (epg-gpg-program "gpg2")
  (epg-gpg-home-directory "~/.gnupg"))

(use-package password-cache
  :custom
  (password-cache-expiry nil)
  (password-cache t))

(use-package keychain-environment
  :ensure t
  :config
  (keychain-refresh-environment))

GC tweaks

(setq gc-cons-percentage 0.3)

(setq gc-cons-threshold most-positive-fixnum)
(add-hook 'after-init-hook #'(lambda ()
                               (setq gc-cons-threshold 800000)))

(add-hook 'minibuffer-setup-hook (lambda () (setq gc-cons-threshold most-positive-fixnum)))
(add-hook 'minibuffer-exit-hook (lambda () (setq gc-cons-threshold 800000)))

(add-hook 'focus-out-hook #'garbage-collect)

encodings

(use-package mule
  :config
  (prefer-coding-system 'utf-8)
  (set-default-coding-systems 'utf-8)
  (set-buffer-file-coding-system 'utf-8 'utf-8-unix)
  (set-selection-coding-system 'utf-8)
  (set-terminal-coding-system 'utf-8)
  (set-clipboard-coding-system 'utf-8)
  (set-keyboard-coding-system 'utf-8))

appearance

fonts

(use-package font-core
  :config
  (global-font-lock-mode 1))

(use-package font-lock
  :preface
  (defun custom/highlight-keywords ()
    ;; highlight additional keywords
    (font-lock-add-keywords nil '(("\\<\\(FIXME\\|FIX_ME\\|FIX ME\\):" 1 font-lock-warning-face t)))
    (font-lock-add-keywords nil '(("\\<\\(BUG\\|BUGS\\):" 1 font-lock-warning-face t)))
    (font-lock-add-keywords nil '(("\\<\\(TODO\\|TO DO\\NOTE\\|TBD\\):" 1 font-lock-warning-face t)))
    (font-lock-add-keywords nil '(("\\<\\(DONE\\|HACK\\):" 1 font-lock-doc-face t)))
    ;; highlight too long lines
    (font-lock-add-keywords nil '(("^[^\n]\\{120\\}\\(.*\\)$" 1 font-lock-warning-face t))))
  :hook ((emacs-lisp-mode-hook lisp-mode-hook python-mode-hook) . custom/highlight-keywords)
  :config
  (setq font-lock-maximum-decoration t))

(use-package face-remap
  :general
  ("C-=" 'text-scale-increase)
  ("C--" 'text-scale-decrease))

(use-package unicode-fonts
  :ensure t
  :after (persistent-soft)
  :config
  (unicode-fonts-setup))

setup smart modeline

(use-package doom-modeline
  :ensure t
  :custom
  (doom-modeline-height 25)
  (doom-modeline-icon t)
  (doom-modeline-major-mode-icon nil)
  (doom-modeline-minor-modes nil)
  :config
  (doom-modeline 1))

load themes and choose one

Also some other good-looking theme is “material-theme”
(use-package nord-theme :ensure t :config (load-theme 'nord t) :disabled)
(use-package kaolin-themes :ensure t :config (load-theme 'kaolin-dark t) :disabled)
(use-package hc-zenburn-theme :ensure t :config (load-theme 'hc-zenburn t))
(use-package darkburn-theme :ensure t :config (load-theme 'darkburn t) :disabled)
(use-package solarized-theme :ensure t :config (load-theme 'solarized-dark t) :disabled)

;; Providing dark enough colors, unless we are using an appropriate theme, Darkburn, for example
(when (boundp 'zenburn-colors-alist)
  (set-face-attribute 'default nil :background "#1A1A1A")
  (set-face-attribute 'region nil :background (cdr (assoc "zenburn-bg-2" zenburn-colors-alist))))

frames

(use-package tooltip
  :config
  (tooltip-mode 0))

(use-package avoid
  :config
  (mouse-avoidance-mode 'jump))

(use-package frame
  :preface
  (defvar opacity-percent 75 "Opacity percent")
  (defun custom/toggle-transparency ()
    (interactive)
    (let ((alpha (frame-parameter nil 'alpha)))
      (set-frame-parameter
       nil 'alpha
       (if (eql (cond ((numberp alpha) alpha)
                      ((numberp (cdr alpha))
                       (cdr alpha))
                      ;; Also handle undocumented (<active> <inactive>) form.
                      ((numberp (cadr alpha)) (cadr alpha)))
                100)
           `(,opacity-percent . 50) '(100 . 100)))))
  :general
  ("M-o" 'ace-window)
  (:prefix "<f2>"
           "n" 'make-frame-command
           "k" 'delete-frame
           "s" 'delete-other-frames
           "v" 'custom/toggle-transparency)
  :config
  (general-unbind 'global "M-o")
  (add-to-list 'default-frame-alist `(alpha . (100 . 100)))
  (blink-cursor-mode 0)
  (set-frame-parameter (selected-frame) 'alpha '(100 . 100))
  (setq frame-title-format "emacs - %b %f") ;; for various external tools
  (setq opacity-percent 75)
  (setq truncate-partial-width-windows nil))

(use-package tool-bar
  :config
  (tool-bar-mode -1))

(use-package scroll-bar
  :config
  (scroll-bar-mode -1)
  (when (>= emacs-major-version 25)
    (horizontal-scroll-bar-mode -1)))

(use-package menu-bar
  :general
  (:keymaps 'mode-specific-map
            "b" 'toggle-debug-on-error
            "q" 'toggle-debug-on-quit)
  :config
  (menu-bar-mode -1))

(use-package popwin :ensure t)

(use-package hl-line
  :config
  (global-hl-line-mode 1))

(use-package time
  :config
  (display-time)
  :custom
  (display-time-day-and-date t)
  ;; (display-time-form-list (list 'time 'load))
  (display-time-world-list
   '(("Europe/Moscow" "Moscow")))
  (display-time-mail-file t)
  (display-time-default-load-average nil)
  (display-time-24hr-format t)
  (display-time-string-forms '( day " " monthname " (" dayname ") " 24-hours ":" minutes)))

uniquify buffer names

(use-package uniquify
  :custom
  (uniquify-buffer-name-style 'post-forward)
  (uniquify-separator ":")
  (uniquify-ignore-buffers-re "^\\*")
  (uniquify-strip-common-suffix nil))

persistence

backups, history and timely saving
(use-package savehist
  :config
  (savehist-mode t)
  :custom
  (savehist-save-minibuffer-history t)
  (savehist-autosave-interval 60)
  (history-length 10000)
  (history-delete-duplicates t)
  (savehist-additional-variables
        '(kill-ring
          search-ring
          regexp-search-ring)))

(use-package backup-each-save
  :ensure t
  :hook (after-save-hook . backup-each-save))

(use-package super-save
  :ensure t
  :delight super-save-mode
  :custom
  (super-save-remote-files nil)
  :config
  (super-save-mode 1))

maintain recent files

(use-package recentf
  :no-require t
  :defer 1
  :config
  (use-package recentf-ext :ensure t)
  (add-to-list 'recentf-exclude no-littering-var-directory)
  (add-to-list 'recentf-exclude no-littering-etc-directory)
  (recentf-mode t)
  :custom
  (recentf-max-saved-items 250)
  (recentf-max-menu-items 15))

Simultaneous edits still will be detected when saving is made. But disabling lock files prevents our working dirs from being clobbered with.

(setf create-lockfiles nil)

common

emacs server

(use-package server
  :defer 2
  :preface
  (defun custom/server-save-edit ()
    (interactive)
    (save-buffer)
    (server-edit))
  (defun custom/save-buffer-clients-on-exit ()
    (interactive)
    (if (and (boundp 'server-buffer-clients) server-buffer-clients)
        (server-save-edit)
      (save-buffers-kill-emacs t)))
  :hook (server-visit-hook . (lambda () (local-set-key (kbd "C-c C-c") 'custom/server-save-edit)))
  :config
  (unless (and (string-equal "root" (getenv "USER"))
               (server-running-p))
    (require 'server)
    (server-start))
  (advice-add 'save-buffers-kill-terminal :before 'custom/save-buffer-clients-on-exit))

misc

(use-package amx
  :ensure t
  :general ("M-x" 'amx)
  :custom
  (amx-backend 'ivy)
  (amx-save-file (at-user-data-dir "amx-items")))

(use-package tramp
  :config
  (setq tramp-default-method "ssh")
  (setq tramp-ssh-controlmaster-options "")
  (setq tramp-default-proxies-alist nil))

(use-package paradox
  :ensure t
  :after (seq let-alist spinner)
  :commands paradox-list-packages
  :custom
  (paradox-execute-asynchronously t)
  (paradox-column-width-package 27)
  (paradox-column-width-version 13)
  (paradox-github-token private/paradox-github-token)
  :config
  (remove-hook 'paradox-after-execute-functions #'paradox--report-buffer-print))

;; for some reason feature 'files' provided with use-package
;; brings more headache than it deserves, so a little bit of
;; dirty imperative style below (still hope on fixing it later)
(defun custom/untabify-buffer ()
  (when (member major-mode '(haskell-mode
                             emacs-lisp-mode
                             lisp-mode
                             python-mode))
    (untabify (point-min) (point-max))))
(add-hook 'before-save-hook #'delete-trailing-whitespace)
(add-hook 'before-save-hook #'custom/untabify-buffer)
(when (> emacs-major-version 25) (auto-save-visited-mode 1))
(setq require-final-newline t)
(setq enable-local-variables nil)
;; backup settings
(setq auto-save-default nil)
(setq backup-by-copying t)
(setq backup-by-copying-when-linked t)
(setq backup-directory-alist '(("." . "~/.cache/emacs/backups")))
(setq delete-old-versions -1)
(setq kept-new-versions 6)
(setq kept-old-versions 2)
(setq version-control t)
(setq save-abbrevs 'silently)
(global-set-key (kbd "C-x f") 'find-file) ; I never use set-fill-column and I hate hitting it by accident.

(use-package novice
  :config
  (setq disabled-command-function nil))

(use-package which-key
  :ensure t
  :config
  (which-key-setup-side-window-right)
  (which-key-mode))

(use-package ibuffer
  :commands ibuffer
  :general
  ([remap list-buffers] 'ibuffer)
  (:keymaps 'ibuffer-mode-map
            "/ ." '(lambda (qualifier)
                     (interactive "sFilter by extname: ")
                     (ibuffer-filter-by-filename (concat "\\." qualifier "$")))
            "M-o" 'other-window) ; was ibuffer-visit-buffer-1-window
  :hook (ibuffer-mode-hook . (lambda ()
                               ;; Make sure we're always using our buffer groups
                               (ibuffer-switch-to-saved-filter-groups "default")))
  :custom
  (ibuffer-default-sorting-mode 'major-mode) ;recency
  (ibuffer-always-show-last-buffer :nomini)
  (ibuffer-default-shrink-to-minimum-size t)
  (ibuffer-jump-offer-only-visible-buffers t)
  (ibuffer-saved-filters
   '(("dired" ((mode . dired-mode)))
     ("foss" ((filename . "foss")))
     ("pets" ((filename . "pets")))
     ("jabberchat" ((mode . jabber-chat-mode)))
     ("orgmode" ((mode . org-mode)))
     ("elisp" ((mode . emacs-lisp-mode)))
     ("fundamental" ((mode . fundamental-mode)))
     ("haskell" ((mode . haskell-mode)))))
  (ibuffer-saved-filter-groups custom/ibuffer-saved-filter-groups))

(use-package ibuffer-vc
  :ensure t
  :disabled
  :hook (ibuffer-hook . (lambda ()
                          (ibuffer-vc-set-filter-groups-by-vc-root)
                          (unless (eq ibuffer-sorting-mode 'alphabetic)
                            (ibuffer-do-sort-by-alphabetic))))
  :custom
  (ibuffer-formats
   '((mark modified read-only vc-status-mini " "
           (name 18 18 :left :elide)
           " "
           (size 9 -1 :right)
           " "
           (mode 16 16 :left :elide)
           " "
           filename-and-process)) "include vc status info"))

(use-package ibuffer-project
  :ensure t
  :hook ((ibuffer-hook . (lambda () (setq ibuffer-filter-groups (ibuffer-project-generate-filter-groups))))
         (ibuffer-hook . (lambda ()
                           (setq ibuffer-filter-groups (ibuffer-project-generate-filter-groups))
                           (unless (eq ibuffer-sorting-mode 'project-file-relative)
                             (ibuffer-do-sort-by-project-file-relative)))))
  :custom
  (ibuffer-formats
   '((mark modified read-only locked " "
           (name 18 18 :left :elide)
           " "
           (size 9 -1 :right)
           " "
           (mode 16 16 :left :elide)
           " " project-file-relative))))

navigate

Ivy

(use-package ivy
  :ensure t
  :delight ivy-mode
  :general
  ("M-<f12>" 'ivy-switch-buffer)
  ("<f10>" 'ivy-resume)
  (:keymaps 'ctl-x-map
            "b" 'ivy-switch-buffer)
  (:keymaps 'mode-specific-map
            "v" 'ivy-push-view
            "V" 'ivy-pop-view)
  (:keymaps 'ivy-minibuffer-map
            "C-j" 'ivy-immediate-done)
  :config
  (general-unbind 'global "C-x C-b")
  (ivy-mode 1)
  :custom-face
  (ivy-current-match ((t (:background "gray1"))))
  :custom
  (ivy-display-style 'fancy)
  (ivy-use-selectable-prompt t "Make the prompt line selectable")
  (ivy-use-virtual-buffers t) ;; add 'recentf-mode’and bookmarks to 'ivy-switch-buffer'.
  (ivy-height 20) ;; number of result lines to display
  (ivy-count-format "%d/%d ")
  (ivy-initial-inputs-alist nil) ;; no regexp by default
  (ivy-re-builders-alist
   ;; allow input not in order
   '((read-file-name-internal . ivy--regex-fuzzy)
     (t . ivy--regex-ignore-order))))

(use-package counsel
  :ensure t
  :defer 2
  :after (swiper)
  :delight counsel-mode
  :preface
  (defun custom/open-org-file ()
    (interactive)
    (ivy-read "Org files: "
              (funcall #'(lambda () (f-files (at-org-dir) nil t)))
              :action #'(lambda (candidate)
                          (find-file candidate))
              :require-match t
              :caller 'custom/open-org-file))
  (defun custom/open-org-kb-file ()
    (interactive)
    (ivy-read "Org files: "
              (funcall #'(lambda () (f-files (at-org-kb-dir) nil t)))
              :action #'(lambda (candidate)
                          (find-file candidate))
              :require-match t
              :caller 'custom/open-org-kb-file))
  :init
  (require 'iso-transl)
  :general
  ([remap menu-bar-open] 'counsel-tmm)
  ([remap insert-char] 'counsel-unicode-char)
  ([remap isearch-forward] 'counsel-grep-or-swiper)
  (:keymaps 'mode-specific-map
            "C-SPC" 'counsel-mark-ring
            "C-." 'counsel-fzf
            "w" 'counsel-wmctrl
            "O" 'custom/open-org-file
            "K" 'custom/open-org-kb-file)
  (:keymaps 'ctl-x-map
            "C-r" 'counsel-recentf)
  ("C-h L" 'counsel-locate)
  (:prefix "<f9>"
           "y" 'counsel-yank-pop
           "m" 'counsel-mark-ring
           "c" 'counsel-command-history
           "e" 'counsel-expression-history
           "p" 'counsel-package
           "l" 'counsel-git-log
           "g" 'counsel-rg
           "G" '(lambda () (interactive) (counsel-rg (thing-at-point 'symbol)))
           "m" 'swiper-multi
           "I" 'ivy-imenu-anywhere)
  (:keymaps 'help-map
            "l" 'counsel-find-library)
  (:prefix "<f1>"
           "l" 'counsel-find-library
           "b" 'counsel-descbinds
           "i" 'counsel-info-lookup-symbol)
  (:keymaps 'iso-transl-ctl-x-8-map
            "RET" 'counsel-unicode-char)
  (:keymaps 'ivy-minibuffer-map
            "M-y" 'ivy-next-line)
  :custom
  (counsel-git-cmd "rg --files")
  (counsel-grep-base-command "rg -i -M 120 --no-heading --line-number --color never '%s' %s")
  (counsel-rg-base-command "rg -i -M 120 --no-heading --line-number --color never %s .")
  :config
  (counsel-mode 1))

(use-package ivy-rich
  :ensure t
  :after (ivy)
  :defines ivy-rich-abbreviate-paths ivy-rich-switch-buffer-name-max-length
  :custom
  (ivy-rich-switch-buffer-name-max-length 60 "Increase max length of buffer name.")
  :config
  (ivy-rich-mode 1))

(use-package ivy-xref
  :ensure t
  :custom
  (xref-show-xrefs-function #'ivy-xref-show-xrefs "Use Ivy to show xrefs"))

(use-package ivy-dired-history
  :ensure t
  :after (dired savehist)
  :config
  (add-to-list 'savehist-additional-variables 'ivy-dired-history-variable))

(use-package ivy-historian
  :ensure t
  :config
  (ivy-historian-mode))

URLs, links and TAPs

(use-package link-hint
  :ensure t
  :general
  (:keymaps 'mode-specific-map
            "o s" 'custom/open-url-current-buffer
            "o f" 'link-hint-open-link
            "o y" 'link-hint-copy-link
            "o F" 'link-hint-open-multiple-links
            "o Y" 'link-hint-copy-multiple-links)
  :custom
  (link-hint-avy-style 'de-bruijn))

(use-package browse-url
  :if (and (eq system-type 'gnu/linux)
           (eq window-system 'x))
  :preface
  (defun custom/buffer-urls--candidates ()
    (save-excursion
      (save-restriction
        (let ((urls))
          (goto-char (point-min))
          (while (re-search-forward org-plain-link-re nil t)
            (push (thing-at-point 'url) urls))
          (remove nil urls)))))
  (defun custom/open-url-current-buffer ()
    (interactive)
    (ivy-read "URLs: "
              (funcall #'custom/buffer-urls--candidates)
              :action #'(lambda (candidate)
                          (browse-url candidate))
              :require-match t
              :caller 'custom/open-url-current-buffer))
  (defun feh-browse (url &rest ignore)
    "Browse image using feh."
    (interactive (browse-url-interactive-arg "URL: "))
    (start-process (concat "feh " url) nil "feh" url))
  (defun mpv-browse (url &rest ignore)
    "Browse video using mpv."
    (interactive (browse-url-interactive-arg "URL: "))
    (start-process (concat "mpv --loop-file=inf" url) nil "mpv" "--loop-file=inf" url))
  :custom
  (browse-url-browser-function 'browse-url-generic)
  (browse-url-generic-program "xdg-open")
  :config
  (setq browse-url-browser-function
        (append
         (mapcar (lambda (re)
                   (cons re #'eww-browse-url))
                 private/browse-url-images-re)
         (mapcar (lambda (re)
                   (cons re #'mpv-browse))
                 private/browse-url-videos-re)
         '(("." . browse-url-xdg-open)))))

cursor positioning

;;Make cursor stay in the same column when scrolling using pgup/dn.
;;Previously pgup/dn clobbers column position, moving it to the
;;beginning of the line.
;;<http://www.dotemacs.de/dotfiles/ElijahDaniel.emacs.html>
(defadvice custom/scroll-up (around ewd-scroll-up first act)
  "Keep cursor in the same column."
  (let ((col (current-column)))
    ad-do-it
    (move-to-column col)))
(defadvice custom/scroll-down (around ewd-scroll-down first act)
  "Keep cursor in the same column."
  (let ((col (current-column)))
    ad-do-it
    (move-to-column col)))

(use-package saveplace
  :defer 1
  :config
  (save-place-mode 1))

(use-package on-screen :ensure t)

special navigation

(use-package beginend
  :ensure t
  :delight beginend-global-mode beginend-prog-mode beginend-magit-status-mode
  :config
  (beginend-global-mode))

(use-package mwim
  :ensure t
  :general
  ([remap move-beginning-of-line] 'mwim-beginning-of-code-or-line)
  ([remap move-end-of-line] 'mwim-end-of-code-or-line))

(use-package smooth-scrolling :ensure t)

(use-package bln-mode
  :ensure t
  :general
  (:keymaps 'mode-specific-map
            "h" 'bln-backward-half
            "j" 'bln-forward-half-v
            "k" 'bln-backward-half-v
            "l" 'bln-forward-half))

projects

(use-package projectile
  :ensure t
  :delight (projectile-mode " prj")
  :custom
  (projectile-enable-caching t)
  (projectile-require-project-root nil)
  (projectile-completion-system 'ivy)
  (projectile-track-known-projects-automatically t)
  :config
  (setq projectile-switch-project-action 'projectile-commander)
  (projectile-mode 1))

(use-package counsel-projectile
  :ensure t
  :after (counsel projectile)
  :preface
  (defun custom/open-project-todos ()
    (interactive)
    (let ((todos-file (expand-file-name "todo.org" (projectile-project-root))))
      (condition-case nil
          (when (file-exists-p todos-file)
            (find-file todos-file))
        (error (message "Cannot find project todos")))))
  (defun custom/ensure-project-switch-buffer (arg)
    "Custom switch to buffer.
     With universal argument ARG or when not in project, rely on
     `ivy-switch-buffer'.
     Otherwise, use `counsel-projectile-switch-to-buffer'."
    (interactive "P")
    (if (or arg
            (not (projectile-project-p)))
        (ivy-switch-buffer)
      (counsel-projectile-switch-to-buffer)))
  (defun custom/search-deadgrep ()
    (interactive)
    (let ((term (read-from-minibuffer "Search term: ")))
      (deadgrep term)))
  (defun counsel-projectile-switch-project-action-open-todos (project)
    "Open project's TODOs."
    (let ((projectile-switch-project-action #'custom/open-project-todos))
      (counsel-projectile-switch-project-by-name project)))
  (defun counsel-projectile-switch-project-action-deadgrep (project)
    "Search in project with deadgrep."
    (let ((projectile-switch-project-action #'custom/search-deadgrep))
      (counsel-projectile-switch-project-by-name project)))
  :general
  (:keymaps 'ctl-x-map
            "j j" 'counsel-projectile-switch-project
            "b" 'custom/ensure-project-switch-buffer)
  (:prefix "<f8>"
           "i" 'projectile-invalidate-cache
           "k" 'projectile-kill-buffers
           "c" 'projectile-commander
           "d" 'projectile-dired
           "f" 'projectile-recentf
           "t" 'custom/open-project-todos
           "T" 'doom/ivy-tasks
           "h" 'projectile-find-file)
  :config
  (counsel-projectile-mode 1)
  (add-to-list 'counsel-projectile-switch-project-action
               '("t" counsel-projectile-switch-project-action-open-todos "open project's todos") t)
  (add-to-list 'counsel-projectile-switch-project-action
               '("d" counsel-projectile-switch-project-action-deadgrep "search project with deadgrep") t)
  (setq projectile-switch-project-action 'counsel-projectile-switch-project))

dired

(use-package dired
  :commands dired
  :hook (dired-mode-hook . auto-revert-mode)
  :general
  ([remap list-directory] 'dired)
  (:keymaps 'dired-mode-map
            "e" '(lambda ()
                   (interactive)
                   (when (derived-mode-p 'dired-mode)
                     (if (file-directory-p (dired-get-filename))
                         (message "Directories cannot be opened in EWW")
                       (eww-open-file (dired-get-file-for-visit)))))
            "C-x C-k" 'dired-do-delete)
  :preface
  (defvar custom/large-file-ok-types
    (rx "." (or "mp4" "mkv" "pdf") string-end)
    "Regexp matching filenames which are definitely ok to visit,
     even when the file is larger than `large-file-warning-threshold'.")
  (defadvice abort-if-file-too-large (around custom/check-large-file-ok-types)
    "If FILENAME matches `custom/large-file-ok-types', do not abort."
    (unless (string-match-p custom/large-file-ok-types (ad-get-arg 2))
      ad-do-it))
  :custom
  (dired-recursive-deletes 'top) ;; Allows recursive deletes
  (dired-dwim-target t)
  (dired-listing-switches "-lah1v --group-directories-first") ;;TODO: think of using TIME_STYLE env var
  :config
  (put 'dired-find-alternate-file 'disabled nil)
  (ad-activate 'abort-if-file-too-large)
  (use-package dired-filetype-face :ensure t)
  (use-package wdired
    :general
    (:keymaps 'dired-mode-map
              "r" 'wdired-change-to-wdired-mode)
    :custom
    (wdired-allow-to-change-permissions 'advanced))
  (use-package dired-narrow
    :ensure t
    :general
    (:keymaps 'dired-mode-map
              "/" 'dired-narrow))
  (use-package dired-quick-sort
    :ensure t
    :config
    (dired-quick-sort-setup))
  (use-package diredfl
    :ensure t
    :config
    (diredfl-global-mode))
  (use-package dired-x
    :config
    ;; do not bind C-x C-j, it may be binded later
    (setq dired-bind-jump nil))
  (use-package dired-hide-dotfiles
    :ensure t
    :after (dired)
    :general
    (:keymaps 'dired-mode-map
              "." 'dired-hide-dotfiles-mode)
    :hook
    (dired-mode . dired-hide-dotfiles-mode)))

;; Reload dired after making changes
(--each '(dired-do-rename
          dired-create-directory
          wdired-abort-changes)
  (eval `(defadvice ,it (after revert-buffer activate)
           (revert-buffer))))

search

(use-package deadgrep
  :ensure t
  :commands deadgrep
  :general
  (:keymaps 'mode-specific-map
            "d" 'deadgrep))

(use-package socyl
  :ensure t
  :general
  (:keymaps 'mode-specific-map
            "r" 'socyl-search-regexp)
  :custom
  (socyl-backend 'ripgrep))

(use-package phi-search
  :ensure t
  :commands phi-search phi-search-backward
  :hook (isearch-mode-hook . phi-search-from-isearch-mc/setup-keys)
  :config
  (use-package phi-search-mc
    :ensure t
    :config
    (phi-search-mc/setup-keys)))

(use-package wgrep
  :ensure t
  :general
  (:keymaps 'grep-mode-map
            "C-x C-q" 'wgrep-change-to-wgrep-mode
            "C-c C-c" 'wgrep-finish-edit))

(defadvice occur-mode-goto-occurrence (after close-occur activate)
  (delete-other-windows))

(use-package imenu-anywhere
  :ensure t
  :commands ivy-imenu-anywhere)

;; inline tasks navigation
(use-package doom-todo-ivy
  :quelpa
  (doom-todo-ivy :repo "jsmestad/doom-todo-ivy" :fetcher github)
  :hook (after-init . doom-todo-ivy))

operations with windows/frames

(use-package winner
  :config
  (winner-mode 1))

(use-package windsize
  :ensure t
  :disabled ;;TODO: remap keys and reenable
  :general
  ("C-s-k" 'windsize-up)
  ("C-s-j" 'windsize-down)
  ("C-s-h" 'windsize-left)
  ("C-s-l" 'windsize-right))

(use-package ace-window
  :ensure t
  :after (avy)
  :commands ace-window
  :custom
  (aw-background nil)
  (aw-leading-char-style 'char)
  (aw-scope 'visible)
  :config
  (ace-window-display-mode 1)
  :custom-face (aw-leading-char-face
                ((t (:inherit ace-jump-face-foreground
                              :foreground "green"
                              :height 0.1)))))

warping

(use-package swiper
  :ensure t
  :commands swiper swiper-multi swiper-occur
  :preface
  (defun custom/swiper (&optional tap)
    (interactive "P")
    (if tap
        (swiper (thing-at-point 'symbol))
      (swiper)))
  :general
  ("C-s" 'custom/swiper)
  :config
  (general-unbind 'global "C-s")
  :custom
  (swiper-include-line-number-in-search t)
  :custom-face (swiper-match-face-1 ((t (:background "#dddddd"))))
  :custom-face (swiper-match-face-2 ((t (:background "#bbbbbb" :weight bold))))
  :custom-face (swiper-match-face-3 ((t (:background "#bbbbff" :weight bold))))
  :custom-face (swiper-match-face-4 ((t (:background "#ffbbff" :weight bold)))))

(use-package avy
  :ensure t
  :general
  ("C-:" 'avy-goto-char)
  ("M-s M-s" 'avy-goto-word-0)
  :custom
  (avy-timeout-seconds 0.5)
  (avy-keys '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9))
  :custom-face (avy-goto-char-timer-face ((nil (:foreground "green" :weight bold))))
  :config
  (avy-setup-default))

(use-package filecache)

editing

move and bend text around

(use-package ialign
  :ensure t
  :general
  (:keymaps 'ctl-x-map
            "l" 'ialign))

(use-package multiple-cursors
  :ensure t
  :general
  (:keymaps 'region-bindings-mode-map
            "C-*" 'mc/mark-all-like-this
            "C-S-<up>" 'mc/mark-previous-like-this
            "C-S-<down>" 'mc/mark-next-like-this
            "C-%" 'mc/mark-more-like-this-extended))

(use-package delsel
  :general
  (:keymaps 'mode-specific-map
            "C-g" 'minibuffer-keyboard-quit)
  :config
  (delete-selection-mode 1))

;;TODO: bind to keys more extensively
;;TODO: consolidate (un)filling functionality
(use-package unfill
  :ensure t
  :general
  ([remap fill-paragraph] 'unfill-toggle))

(use-package simple
  :hook
  (((prog-mode-hook text-mode-hook) . turn-on-auto-fill))
  :delight
  ((visual-line-mode . "")
   (auto-fill-function . ""))
  :preface
  (defun custom/newline-hook ()
    (local-set-key (kbd "C-m") 'newline-and-indent)
    (local-set-key (kbd "<return>") 'newline-and-indent))
  (defun custom/gnocchi-case (s)
    "Convert S to 'gnocchi case'."
    (declare (side-effect-free t))
    (s-join " " (mapcar 'downcase (s-split-words s))))
  (defun custom/switch-case (&optional style)
    (interactive)
    (let* ((bounds (bounds-of-thing-at-point 'symbol))
           (from (if (use-region-p) (region-beginning) (car bounds)))
           (to (if (use-region-p) (region-end) (cdr bounds)))
           (data (buffer-substring-no-properties from to))
           (result (funcall (cond ((eq style 'camel) 's-lower-camel-case)
                                  ((eq style 'camel-up) 's-upper-camel-case)
                                  ((eq style 'snake) 's-snake-case)
                                  ((eq style 'gnocchi) 'custom/gnocchi-case)
                                  ((eq style 'kebab) 's-dashed-words)
                                  (t 's-lower-camel-case))
                            data)))
      (save-excursion
        (delete-region from to)
        (goto-char from)
        (insert result))))
  :hook (((yaml-mode-hook emacs-lisp-mode-hook lisp-mode-hook python-mode-hook) . custom/newline-hook))
  :general
  ("M-g" 'goto-line)
  ("M-SPC" 'cycle-spacing)
  (:prefix "<f11>"
           "b" 'subword-mode
           "v" 'view-mode)
  (:prefix "C-z"
           "o" 'just-one-space
           "w" 'delete-trailing-whitespace
           "s" 'transpose-sexps
           "6" '(lambda () (interactive) (custom/switch-case 'camel))
           "^" '(lambda () (interactive) (custom/switch-case 'camel-up))
           "_" '(lambda () (interactive) (custom/switch-case 'snake))
           "SPC" '(lambda () (interactive) (custom/switch-case 'gnocchi))
           "-" '(lambda () (interactive) (custom/switch-case 'kebab)))
  :custom
  (bidi-display-reordering nil)
  (kill-whole-line t)
  (next-line-add-newlines nil)
  (blink-matching-paren nil)
  (set-mark-command-repeat-pop nil)
  (save-interprogram-paste-before-kill t)
  (x-gtk-use-system-tooltips nil)
  (eval-expression-print-length nil)
  (eval-expression-print-level nil)
  (kill-ring-max 1024)
  :config
  (general-unbind 'global "<f11>" "C-z")
  (column-number-mode 1)
  (line-number-mode 1)
  (size-indication-mode 1)
  (toggle-truncate-lines 1)
  (transient-mark-mode 1)
  (put 'transient-mark-mode 'permanent-local t)
  (put 'set-goal-column 'disabled nil))

(use-package tabify
  :general
  (:keymaps 'mode-specific-map
            "t SPC" 'untabify
            "t TAB" 'tabify))

(use-package sort
  :general
  (:keymaps 'mode-specific-map
            "s s" 'sort-lines
            "s u" 'delete-duplicate-lines))

scope

(use-package easy-kill
  :ensure t
  :general
  ([remap kill-ring-save] 'easy-kill)
  ([remap mark-sexp] 'easy-mark))

(use-package region-bindings-mode
  :ensure t
  :custom
  (region-bindings-mode-disable-predicates '((lambda () buffer-read-only)))
  :config
  (region-bindings-mode-enable))

(use-package recursive-narrow
  :ensure t
  :general
  (:keymaps 'mode-specific-map
            "n r" 'narrow-to-region
            "n d" 'narrow-to-defun
            "n w" 'widen
            "n N" 'recursive-narrow-or-widen-dwim
            "n D" 'recursive-widen-dwim))

(use-package highlight-indent-guides
  :ensure t
  :general
  (:keymaps 'mode-specific-map
            "g h" 'highlight-indent-guides-mode)
  :custom
  (highlight-indent-guides-method 'fill)
  (highlight-indent-guides-responsive 'stack))

commenting

(use-package comment-dwim-2
  :ensure t
  :general
  (:keymaps 'mode-specific-map
            "]" 'comment-dwim-2))

(use-package newcomment
  :general
  (:keymaps 'mode-specific-map
            "/" 'comment-box))

clipboard and killring

(use-package savekill :ensure t)

(use-package copy-as-format
  :ensure t
  :general
  (:keymaps 'mode-specific-map
            "f s" 'copy-as-format-slack
            "f g" 'copy-as-format-github
            "f o" 'copy-as-format-org-mode
            "f m" 'copy-as-format-markdown
            "f a" 'copy-as-format-asciidoc
            "f b" 'copy-as-format-bitbucket
            "f d" 'copy-as-format-disqus
            "f l" 'copy-as-format-gitlab
            "f c" 'copy-as-format-hipchat
            "f h" 'copy-as-format-html
            "f j" 'copy-as-format-jira
            "f w" 'copy-as-format-mediawiki
            "f p" 'copy-as-format-pod
            "f r" 'copy-as-format-rst
            "f f" 'copy-as-format))

(use-package select
  :custom
  (select-enable-clipboard t)
  (x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)))

undo/redo

(use-package undo-propose
  :ensure t
  :general
  (:keymaps 'ctl-x-map
            "u" 'undo-propose))

spellchecking

flycheck

(use-package flycheck
  :ensure t
  :preface
  ;; CREDITS: https://github.com/nathankot/dotemacs
  (defvar counsel-flycheck-history nil
    "History for `counsel-flycheck'")
  (defun counsel-flycheck ()
    (interactive)
    (if (not (bound-and-true-p flycheck-mode))
        (message "Flycheck mode is not available or enabled")
      (ivy-read "Error: "
                (let ((source-buffer (current-buffer)))
                  (with-current-buffer (or (get-buffer flycheck-error-list-buffer)
                                           (progn
                                             (with-current-buffer
                                                 (get-buffer-create flycheck-error-list-buffer)
                                               (flycheck-error-list-mode)
                                               (current-buffer))))
                    (flycheck-error-list-set-source source-buffer)
                    (flycheck-error-list-reset-filter)
                    (revert-buffer t t t)
                    (split-string (buffer-string) "\n" t " *")))
                :action (lambda (s &rest _)
                          (-when-let* ( (error (get-text-property 0 'tabulated-list-id s))
                                        (pos (flycheck-error-pos error)) )
                            (goto-char (flycheck-error-pos error))))
                :history 'counsel-flycheck-history)))
  :general
  (:keymaps 'mode-specific-map
            "y" 'counsel-flycheck)
  :custom-face (flycheck-warning ((t (:foreground "yellow" :background "red"))))
  :custom
  (flycheck-check-syntax-automatically '(mode-enabled save idle-change))
  (flycheck-display-errors-delay 0.4)
  (flycheck-display-errors-function #'flycheck-display-error-messages-unless-error-list)
  (flycheck-global-modes '(not emacs-lisp-mode))
  :config
  (global-flycheck-mode 1))

(use-package flycheck-pos-tip
  :ensure t
  :after (flycheck)
  :config
  (flycheck-pos-tip-mode 1))

BACKLOG review concrete checkers functionality and usage

utils

(use-package executable
  :hook (after-save-hook . executable-make-buffer-file-executable-if-script-p))

(use-package text-mode
  :hook (text-mode-hook . text-mode-hook-identify))

(use-package jka-cmpr-hook
  :config
  (auto-compression-mode 1))

(use-package electric
  :config
  (electric-indent-mode -1))

(use-package persistent-scratch
  :ensure t
  :mode ("^*scratch*$" . lisp-interaction-mode)
  :hook ((after-init-hook . persistent-scratch-restore)
         (kill-emacs-hook . persistent-scratch-save)))

(use-package yatemplate
  :ensure t
  :after (yasnippet)
  :init
  (auto-insert-mode)
  :custom
  (yatemplate-dir (at-config-basedir "resources/auto-insert"))
  :config
  (yatemplate-fill-alist))

(use-package editorconfig
  :ensure t
  :delight (editorconfig-mode " EC")
  :hook ((prog-mode-hook text-mode-hook) . editorconfig-mode))

(use-package autorevert
  :defer 2
  :custom
  (auto-revert-verbose nil)
  (global-auto-revert-non-file-buffers t)
  (auto-revert-check-vc-info t)
  ;; (vc-handled-backends (delq 'Git vc-handled-backends))
  ;; (vc-handled-backends nil)
  :config
  (global-auto-revert-mode 1))

(use-package kmacro
  :custom
  (setq kmacro-ring-max 16))

(use-package mwheel
  :custom
  (mouse-wheel-scroll-amount '(1 ((shift) . 1)))
  (mouse-wheel-progressive-speed nil))

whitespaces

(use-package whitespace
  :ensure t
  :defer 2
  :hook
  ((prog-mode-hook text-mode-hook) . whitespace-turn-on)
  :general
  (:keymaps 'mode-specific-map
            "x w" 'whitespace-mode
            "x W" 'global-whitespace-mode)
  :custom
  (whitespace-line-column 121)
  (whitespace-style '(indentation::space
                      space-after-tab
                      space-before-tab
                      trailing
                      lines-tail
                      tab-mark
                      face
                      tabs)))

;;TODO: consolidate all whitespaces utils
;;TODO: think of activating ws-butler in some modes, just for hands-on testing
(use-package ws-butler
  :ensure t
  :commands ws-butler-mode)

major (and helper) modes

(use-package rst
  :mode ("\\.rst$" . rst-mode))

(use-package vimrc-mode
  :ensure t
  :mode ((".vim\\(rc\\)?$" . vimrc-mode)
         ("*pentadactyl*" . vimrc-mode)))

(use-package sh-script
  :mode (("bashrc$" . shell-script-mode)
         ("bash_profile$" . shell-script-mode)
         ("bash_aliases$" . shell-script-mode)
         ("bash_local$" . shell-script-mode)
         ("bash_completion$" . shell-script-mode)
         (".powenv$" . shell-script-mode)
         ("\\zsh\\'" . shell-script-mode))
  :config
  ;; zsh
  (setq system-uses-terminfo nil))

(use-package nginx-mode
  :ensure t
  :mode ("nginx" . nginx-mode))

(use-package fic-mode :ensure t)

(use-package csv-mode
  :ensure t
  :mode ("\\.csv" . csv-mode)
  :config
  (setq-default csv-align-padding 2))

(use-package jinja2-mode
  :ensure t
  :mode ("\\.j2$" . jinja2-mode))

(use-package yaml-mode
  :ensure t
  :mode (("\\.yml\\'" . yaml-mode)
         ("\\.yaml\\'" . yaml-mode)))

docker

(use-package dockerfile-mode
  :ensure t
  :mode  ("\\Dockerfile" . dockerfile-mode))

(use-package docker-compose-mode
  :ensure t
  :mode ("docker-compose" . docker-compose-mode))

sexps

(use-package smartparens
  :ensure t
  :after (dash)
  :demand t
  :hook (((prog-mode-hook yaml-mode-hook) . smartparens-mode)
         ((lisp-mode-hook emacs-lisp-mode-hook markdown-mode-hook) . smartparens-strict-mode))
  :general
  (:keymaps 'smartparens-mode-map
         ;;TODO: try to make more brief keybindings
            "C-M-t" 'sp-transpose-sexp
            "M-F" nil
            "M-B" nil
            "M-<backspace>" nil
            "C-S-a" 'sp-beginning-of-sexp
            "C-S-d" 'sp-end-of-sexp
            ")" 'sp-up-sex
            "C-<left_bracket>" 'sp-select-previous-thing)
  :config
  (use-package smartparens-config)
  (show-smartparens-global-mode t)
  (sp-use-smartparens-bindings))

(use-package paren
  :defer 2
  :custom
  (show-paren-delay 0)
  :config
  (show-paren-mode t))

indentation

(use-package dtrt-indent
  :ensure t
  :config
  (dtrt-indent-mode))

(use-package aggressive-indent
  :ensure t
  :if enable-experimental-packages
  :config
  (global-aggressive-indent-mode 1)
  (add-to-list 'aggressive-indent-excluded-modes 'html-mode)
  (add-to-list 'aggressive-indent-excluded-modes 'slime-repl-mode)
  (add-to-list 'aggressive-indent-excluded-modes 'haskell-mode)
  (add-to-list 'aggressive-indent-excluded-modes 'lisp-mode)
  (add-to-list 'aggressive-indent-excluded-modes 'emacs-lisp-mode)
  (delete 'lisp-mode aggressive-indent-modes-to-prefer-defun)
  (delete 'emacs-lisp-mode aggressive-indent-modes-to-prefer-defun)
  (add-to-list 'aggressive-indent-dont-indent-if
               '(not (null (string-match (rx (zero-or-more space) (syntax comment-start) (zero-or-more anything)) (thing-at-point 'line))))))

(use-package dynamic-ruler
  :ensure t
  :general
  ("C->" 'dynamic-ruler)
  ("C-^" 'dynamic-ruler-vertical))

completion

snippets

(use-package yasnippet ;;TODO: make more declarative
  :ensure t
  :demand t
  :delight yas-minor-mode
  :mode (("yasnippet/snippets" . snippet-mode)
         ("\\.yasnippet$" . snippet-mode))
  :preface
  ;; hook for automatic reloading of changed snippets
  (defun custom/update-yasnippets-on-save ()
    (when (string-match "/resources/yasnippet" buffer-file-name)
      (yas-load-directory (at-config-basedir "resources/"))))
  ;; Inter-field navigation
  (defun custom/yas-goto-end-of-active-field ()
    (interactive)
    (let* ((snippet (car (yas--snippets-at-point)))
           (position (yas--field-end (yas--snippet-active-field snippet))))
      (if (= (point) position)
          (move-end-of-line)
        (goto-char position))))
  (defun custom/yas-goto-start-of-active-field ()
    (interactive)
    (let* ((snippet (car (yas--snippets-at-point)))
           (position (yas--field-start (yas--snippet-active-field snippet))))
      (if (= (point) position)
          (move-beginning-of-line)
        (goto-char position))))
  (defun custom/do-yas-expand ()
    (let ((yas/fallback-behavior 'return-nil))
      (yas/expand)))
  (defun custom/tab-indent-or-complete ()
    (interactive)
    (if (minibufferp)
        (minibuffer-complete)
      (if (or (not yas/minor-mode)
              (null (custom/do-yas-expand)))
          (if (check-expansion)
              (company-complete-common)
            (indent-for-tab-command)))))
  :general
  (:prefix "<f5>"
           "v" 'yas-visit-snippet-file
           "i" 'ivy-yasnippet)
  (:keymaps 'yas-keymap
            [(tab)] nil
            [(shift tab)] nil
            [backtab] nil
            "TAB" nil
            "<return>" 'yas-exit-all-snippets
            "C-e" 'custom/yas-goto-end-of-active-field
            "C-a" 'custom/yas-goto-start-of-active-field
            "C-n" 'yas-next-field-or-maybe-expand
            "C-p" 'yas-prev-field)
  (:keymaps 'yas-minor-mode-map
            [(tab)] nil
            "<tab>" nil
            "TAB" nil)
  :hook
  (hippie-expand-try-functions-list . yas-hippie-try-expand)
  (after-save-hook . custom/update-yasnippets-on-save)
  :config
  ;; snippets editing mode
  (use-package ivy-yasnippet
    :after (dash ivy yasnippet)
    :ensure t)
  (setq yas-snippet-dirs nil)
  (push yas-installed-snippets-dir yas-snippet-dirs)
  (push (at-config-basedir "resources/yasnippet/") yas-snippet-dirs)
  (push (at-config-basedir "resources/yasnippet-private/") yas-snippet-dirs)
  (setq yas-key-syntaxes '("w" "w_" "w_." "^ " "w_.()" yas-try-key-from-whitespace))
  (setq yas-expand-only-for-last-commands '(self-insert-command))
  (setq yas-prompt-functions
        '(yas-completing-prompt
          yas-x-prompt
          yas-no-prompt))
  ;; Wrap around region
  (setq yas-wrap-around-region t)
  (yas-global-mode 1))

company

(use-package company
  :ensure t
  :demand t
  :delight (company-mode " γ")
  :general
  (:keymaps 'company-active-map
            "C-n" 'company-select-next
            "C-p" 'company-select-previous
            "C-d" 'company-show-doc-buffer
            "M-." 'company-show-location)
  :custom
  (company-idle-delay 0)
  (company-minimum-prefix-length 2)
  (company-tooltip-align-annotations t)
  (company-show-numbers t)
  :config
  (use-package company-flx
    :ensure t
    :no-require t
    :after (company)
    :config
    (company-flx-mode +1))
  (use-package company-quickhelp
    :ensure t
    :no-require t
    :after (company)
    :general
    (:keymaps 'company-active-map
              "C-c h" 'company-quickhelp-manual-begin)
    :config
    (company-quickhelp-mode 1))
  (use-package company-statistics
    :ensure t
    :after (company)
    :config
    (company-statistics-mode))
  (global-company-mode))
  

specialized uses

ansible

(use-package company-ansible
  :ensure t
  :after (company)
  :config
  (add-to-list 'company-backends 'company-ansible))

(use-package poly-ansible
  :ensure t)

try 3rdparty packages

abbrevs

(use-package hippie-exp
  :general
  ("C-S-<iso-lefttab>" 'hippie-expand)
  :custom
  (setq hippie-expand-try-functions-list
        '(yas-hippie-try-expand
          try-expand-all-abbrevs
          try-complete-file-name-partially
          try-complete-file-name
          try-expand-dabbrev
          try-expand-dabbrev-from-kill
          try-expand-dabbrev-all-buffers
          try-expand-list
          try-expand-line
          try-complete-lisp-symbol-partially
          try-complete-lisp-symbol)))

(use-package abbrev
  :delight (abbrev-mode " Abv")
  :config
  (setq-default abbrev-mode t))

programming

common

virtualization

(use-package docker
  :ensure t
  :after (dash docker-tramp s tablist json-mode)
  :delight docker-mode
  :custom
  (docker-containers-show-all t)
  :config
  ;;TODO: bind keys
  ;;FIXME: epkgs.docker is something another then the original - investigate this
  (docker-global-mode 1))

(use-package docker-tramp :ensure t)

(use-package vagrant-tramp :ensure t)

;;TODO: rebind to something
(use-package counsel-tramp
  :ensure t
  :after (docker-tramp vagrant-tramp))

eldoc

(use-package eldoc
  :delight eldoc-mode
  :hook ((emacs-lisp-mode-hook lisp-interaction-mode-hook ielm-mode-hook) . turn-on-eldoc-mode)
  :custom
  (eldoc-idle-delay 0))

(use-package c-eldoc
  :ensure t
  :after (eldoc)
  :hook ((c-mode-hook c++-mode-hook) . c-turn-on-eldoc-mode))

(use-package eldoc-eval
  :ensure t
  :after (eldoc))

packages/modes

;;TODO: extend setup
(use-package compile)
(use-package multi-compile :ensure t)

(use-package regex-tool
  :ensure t
  :commands regex-tool
  :custom
  (regex-tool-backend 'perl))

(use-package xr
  :ensure t)

(use-package prog-fill
  :ensure t
  :general
  (:keymaps 'prog-mode-map
            "M-q" 'prog-fill))

(use-package ini-mode
  :ensure t
  :mode ("\\.ini\\'" . ini-mode))

(use-package po-mode
  :ensure t
  :mode ("\\.po$\\|\\.po\\." . po-mode))

(use-package diff-mode
  :mode ("diff" . diff-mode))

(use-package make-mode
  :mode ("[Mm]akefile" . makefile-mode))

;; TODO: (alex3rd) extend setup
(use-package format-all :ensure t)

(use-package skeletor
  :ensure t
  :custom
  (skeletor-project-directory (at-workspace-dir "pets")))

(use-package lsp-mode
  :ensure t
  :hook (lsp-after-open-hook . lsp-enable-imenu)
  :custom
  (lsp-message-project-root-warning t)
  (lsp-inhibit-message t)
  (lsp-prefer-flymake nil)
  :config
  (use-package lsp-clients))

(use-package lsp-ui
  :ensure t
  :after (lsp-mode)
  :hook (lsp-mode-hook . lsp-ui-mode)
  :general
  (:keymaps 'lsp-ui-mode-map
            [remap xref-find-definitions] 'lsp-ui-peek-find-definitions
            [remap xref-find-references] 'lsp-ui-peek-find-references)
  (:keymaps 'mode-specific-map
            "R" 'lsp-restart-workspace))

(use-package company-lsp
  :ensure t
  :custom
  (company-lsp-cache-candidates 'auto)
  (company-lsp-async t)
  (company-lsp-enable-recompletion t)
  :config
  (push 'company-lsp company-backends))

vcs

git

(use-package magit
  :ensure t
  :after (async dash with-editor git-commit magit-popup)
  :commands magit-status magit-blame
  :mode (("COMMIT_EDITMSG" . conf-javaprop-mode)
         ("COMMIT" . git-commit-mode))
  :general
  (:prefix "C-'"
           "s" 'magit-status
           "f" 'magit-log-buffer-file
           "c" 'magit-checkout
           "w" 'magit-diff-working-tree
           "r" 'magit-reflog
           "b" 'magit-blame-addition
           "B" 'magit-branch-manager
           "l" 'magit-log
           "l" 'open-global-repos-list)
  (:keymaps 'magit-status-mode-map
            "E" 'magit-rebase-interactive
            "q" 'custom/magit-kill-buffers)
  :preface
  (defun open-global-repos-list ()
    (interactive)
    (let ((repos-buffer (get-buffer "*Magit Repositories*")))
      (if repos-buffer
          (switch-to-buffer repos-buffer)
        (magit-list-repositories))))
  (defun custom/magit-restore-window-configuration (&optional kill-buffer)
    "Bury or kill the current buffer and restore previous window configuration."
    (let ((winconf magit-previous-window-configuration)
          (buffer (current-buffer))
          (frame (selected-frame)))
      (quit-window kill-buffer (selected-window))
      (when (and winconf (equal frame (window-configuration-frame winconf)))
        (set-window-configuration winconf)
        (when (buffer-live-p buffer)
          (with-current-buffer buffer
            (setq magit-previous-window-configuration nil))))))
  (defun custom/magit-kill-buffers ()
    "Restore window configuration and kill all Magit buffers."
    (interactive)
    (let ((buffers (magit-mode-get-buffers)))
      (magit-restore-window-configuration)
      (mapc #'kill-buffer buffers)))
  :custom
  (magit-completing-read-function 'ivy-completing-read)
  (magit-blame-heading-format "%H %-20a %C %s")
  (magit-diff-refine-hunk t)
  (magit-display-buffer-function 'magit-display-buffer-fullframe-status-topleft-v1)
  (magit-repository-directories private/magit-repositories)
  :config
  (use-package magit-filenotify
    :ensure t
    :delight (magit-filenotify-mode " FN")
    :after magit
    :hook (magit-status-mode-hook . (lambda ()
                                      (condition-case nil
                                          (magit-filenotify-mode)
                                        (error (magit-filenotify-mode -1))))))
  (use-package vdiff-magit
    :ensure t
    :general
    (:keymaps 'magit-mode-map
              "d" 'vdiff-magit-dwim
              "p" 'vdiff-magit-popup)
    :config
    (setcdr (assoc ?e (plist-get magit-dispatch-popup :actions))
            '("vdiff dwim" 'vdiff-magit-dwim))
    (setcdr (assoc ?E (plist-get magit-dispatch-popup :actions))
            '("vdiff popup" 'vdiff-magit-popup))))

(use-package magithub
  :disabled
  :ensure t
  :after (magit)
  :custom
  (magithub-clone-default-directory (at-workspace-dir "foss"))
  :config
  (magithub-feature-autoinject t))

(use-package git-timemachine
  :ensure t
  :after (ivy)
  :demand t
  :preface
  ;; credits to @binchen
  (defun custom/git-timemachine-show-selected-revision ()
    "Show last (current) revision of file."
    (interactive)
    (let* ((collection (mapcar (lambda (rev)
                                 ;; re-shape list for the ivy-read
                                 (cons (concat (substring-no-properties (nth 0 rev) 0 7) "|" (nth 5 rev) "|" (nth 6 rev)) rev))
                               (git-timemachine--revisions))))
      (ivy-read "commits:"
                collection
                :action (lambda (rev)
                          ;; compatible with ivy 9+ and ivy 8
                          (unless (string-match-p "^[a-z0-9]*$" (car rev))
                            (setq rev (cdr rev)))
                          (git-timemachine-show-revision rev))
                :unwind (lambda () (if (not (eq last-command-event 13))
                                       (git-timemachine-quit))))))
  (defun custom/git-timemachine ()
    "Open git snapshot with the selected version.  Based on ivy-mode."
    (interactive)
    (git-timemachine--start #'custom/git-timemachine-show-selected-revision))
  :general
  (:keymaps 'mode-specific-map
            ";" 'custom/git-timemachine))

(use-package gitignore-mode
  :ensure t
  :mode ("^.gitignore$" . gitignore-mode))

;; think of relocating, cause it supports not only Git
(use-package diff-hl
  :ensure t
  :hook (magit-post-refresh-hook . diff-hl-magit-post-refresh)
  :config
  (global-diff-hl-mode 1))

(use-package git-msg-prefix
  :ensure t
  :general
  (:keymaps 'git-commit-mode-map
            "C-c i" 'commit-msg-prefix)
  :custom
  (git-msg-prefix-log-flags " --since='1 week ago' ")
  (commit-msg-prefix-input-method 'ivy-read))

(use-package browse-at-remote
  :ensure t
  :general
  (:keymaps 'mode-specific-map
            "g g" 'browse-at-remote)
  (:keymaps 'magit-status-mode-map
            "o" 'browse-at-remote)
  :custom
  (browse-at-remote-prefer-symbolic nil))

BACKLOG [#A] find some way (maybe smth like spacemacs dashboard) to represent the states of repos from some list (either hardcoded or created dynamically), with unstaged/unpushed/whatever_useful info displayed

tools

(use-package smerge-mode
  :delight (smerge-mode "")
  :general
  (:keymaps 'mode-specific-map
            "g k" 'smerge-prev
            "g j" 'smerge-next)
  (:keymaps 'local
            :predicate '(bound-and-true-p smerge-mode)
            "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
            "ZZ" '(lambda ()
                    (interactive)
                    (save-buffer)
                    (bury-buffer)))
  :hook (find-file-hooks . (lambda ()
                             (save-excursion
                               (goto-char (point-min))
                               (when (re-search-forward "^<<<<<<< " nil t)
                                 (smerge-mode 1))))))

languages

common

(use-package info-look)

elisp

(use-package edebug-x :ensure t)

(use-package elisp-slime-nav
  :delight elisp-slime-nav-mode
  :ensure t
  :hook ((emacs-lisp-mode-hook ielm-mode-hook) . elisp-slime-nav-mode))

(use-package elisp-mode
  :hook ((emacs-lisp-mode-hook . (lambda ()
                                   (auto-fill-mode 1)
                                   (setq indent-tabs-mode nil)
                                   (setq comment-start ";;")
                                   (turn-on-eldoc-mode)))))

(use-package company-elisp
  :after (elisp-mode company)
  :config
  (add-to-list 'company-backends 'company-elisp))

(add-hook 'eval-expression-minibuffer-setup-hook #'eldoc-mode)
(add-hook 'eval-expression-minibuffer-setup-hook #'eldoc-mode)

(dolist (mode '(paredit-mode smartparens-mode))
  (when (fboundp mode)
    (add-hook 'eval-expression-minibuffer-setup-hook mode)))

lisp

(use-package slime
  :ensure t
  :pin melpa-stable ;; corresponds to quicklisp version
  :hook ((lisp-mode-hook . (lambda ()
                             (slime-mode t)
                             (set (make-local-variable 'slime-lisp-implementations)
                                  (list (assoc 'sbcl slime-lisp-implementations)))))
         (inferior-lisp-mode-hook . inferior-slime-mode)
         (slime-mode-hook . (lambda () (when (> emacs-major-version 25)
                                         (slime-autodoc-mode -1)))) ;; some signature down the call stack is broken in 2.20
         (lisp-mode-hook . (lambda ()
                             (auto-fill-mode 1)
                             (setq indent-tabs-mode nil))))
  :init
  (use-package slime-autoloads)
  :custom
  (slime-complete-symbol*-fancy t)
  (slime-complete-symbol-function 'slime-fuzzy-complete-symbol)
  (slime-net-coding-system 'utf-8-unix)
  :config
  (defadvice slime-documentation-lookup
      (around change-browse-url-browser-function activate)
    "Use w3m for slime documentation lookup."
    (let ((browse-url-browser-function 'w3m-browse-url))
      ad-do-it))
  (slime-setup
   '(slime-fancy-inspector slime-fancy-trace slime-fontifying-fu
     slime-hyperdoc slime-package-fu slime-references slime-trace-dialog
     slime-xref-browser slime-asdf slime-autodoc slime-banner slime-fancy
     slime-fuzzy slime-repl slime-sbcl-exts))
  (add-to-list 'slime-lisp-implementations '(sbcl ("sbcl")  :coding-system utf-8-unix)))

;;TODO: check if there is any conflict inconsistency between slime-builtin/company completion
(use-package slime-company
  :ensure t
  :after (slime company))

(setq custom/hyperspec-root "~/help/HyperSpec/")

(use-package inf-lisp
  :config
  (setq inferior-lisp-program "sbcl"))

(use-package common-lisp-snippets
  :ensure t
  :after (yasnippet))

;; lookup information in hyperspec
(info-lookup-add-help
 :mode 'lisp-mode
 :regexp "[^][()'\" \t\n]+"
 :ignore-case t
 :doc-spec '(("(ansicl)Symbol Index" nil nil nil)))

python

(use-package python
  :mode ("\\.py$" . python-mode)
  :hook
  (python-mode-hook . (lambda ()
                        (setq indent-tabs-mode nil)
                        (setq tab-width 4)
                        (setq imenu-create-index-function 'imenu-default-create-index-function)
                        (auto-fill-mode 1)))
  ;; Highlight the call to ipdb, src http://pedrokroger.com/2010/07/configuring-emacs-as-a-python-ide-2/
  (python-mode-hook . (lambda ()
                        (highlight-lines-matching-regexp "import ipdb")
                        (highlight-lines-matching-regexp "ipdb.set_trace()")
                        (highlight-lines-matching-regexp "import wdb")
                        (highlight-lines-matching-regexp "wdb.set_trace()")))
  (python-mode-hook . lsp)
  (python-mode-hook . flycheck-mode)
  :general
  (:keymaps 'python-mode-map
            "M-_" 'python-indent-shift-left
            "M-+" 'python-indent-shift-right)
  :config
  (add-function :before-until (local 'eldoc-documentation-function)
                #'(lambda () "")))

(use-package py-yapf :ensure t)

(use-package pyvenv
  :ensure t
  :after (projectile dash)
  :init
  (defun custom/switch-python-project-context ()
    (let ((project-root (projectile-project-root)))
      (when (-non-nil (mapcar (lambda (variant) (file-exists-p (concat project-root variant)))
                              '("requirements.pip" "requirements.txt")))
        (pyvenv-deactivate)
        (pyvenv-activate (format "%s/%s"
                                 (pyvenv-workon-home)
                                 (file-name-base
                                  (directory-file-name
                                   project-root)))))))
  :config
  (pyvenv-mode 1)
  :hook ((projectile-after-switch-project-hook . custom/switch-python-project-context)
         (python-mode . custom/switch-python-project-context)))

(use-package pip-requirements
  :ensure t
  :delight (pip-requirements-mode "PyPA Requirements")
  :preface
  (defun custom/pip-requirements-ignore-case ()
    (setq-local completion-ignore-case t))
  :mode ("requirements\\." . pip-requirements-mode)
  :hook (pip-requirements-mode . custom/pip-requirements-ignore-case))
BACKLOG imports/formatting automation (search elpy/standalone extensions)
isort
BACKLOG fix new setup
incorrect flake8 config (excludes)
check/add W0512
check epc/importmagic work
actualize py-isort setup
review pylint setup

golang

;;TODO: some harness either here or withoin shell to automate the burden of setting up new golang project's boilerplate

(use-package go-mode
  :ensure t
  :no-require t
  :after (multi-compile)
  :mode ("\\.go$" . go-mode)
  :hook (before-save-hook . gofmt-before-save)
  :general
  (:keymaps 'go-mode-map
            "C-c C-c" 'multi-compile-run
            "M-." 'godef-jump
            "M-," 'pop-tag-mark)
  :config
  (use-package godoctor :ensure t)
  (setq  gofmt-command "goimports")
  (add-to-list 'multi-compile-alist
               '(go-mode . (("go-build/git" "go build -v"
                             (locate-dominating-file buffer-file-name ".git")) ;;TODO: try to guess binary name from project name (investigate how this refers to libraries builds, etc.)
                            ("go-build/main" "go build -v"
                             (locate-dominating-file buffer-file-name "main.go"))
                            ("go-build-and-run/git" "go build -v && echo '########## build finished ##########' && eval ./${PWD##*/}"
                             (multi-compile-locate-file-dir ".git"))
                            ("go-build-and-run/main" "go build -v && echo '########## build finished ##########' && eval ./${PWD##*/}"
                             (multi-compile-locate-file-dir "main.go"))))))

(use-package company-go
  :ensure t
  :after (go-mode company)
  :config
  (add-to-list 'company-backends 'company-go))

(use-package go-guru
  :ensure t
  :hook (go-mode-hook . go-guru-hl-identifier-mode))

(use-package flycheck-gometalinter
  :ensure t
  :custom
  ;; only run fast linters
  (flycheck-gometalinter-fast t)
  ;; use in tests files
  (flycheck-gometalinter-test t)
  (flycheck-gometalinter-deadline "10s")
  ;; gometalinter: skips 'vendor' directories and sets GO15VENDOREXPERIMENT=1
  (flycheck-gometalinter-vendor t)
  ;; gometalinter: only enable selected linters
  (flycheck-gometalinter-disable-all t)
  (flycheck-gometalinter-enable-linters
   '("golint" "vet" "vetshadow" "golint" "ineffassign" "goconst" "errcheck" "deadcode"))
  :config
  (flycheck-gometalinter-setup))

(use-package go-eldoc
  :ensure t
  :hook (go-mode-hook . go-eldoc-setup))

(use-package gotest
  :ensure t
  :after (go-mode)
  :general
  (:keymaps 'go-mode-map
            "C-c C-x f" 'go-test-current-file
            "C-c C-x t" 'go-test-current-test
            "C-c C-x p" 'go-test-current-project
            "C-c C-x T" 'go-test-current-benchmark
            "C-c C-x F" 'go-test-current-file-benchmarks
            "C-c C-x P" 'go-test-current-project-benchmarks
            "C-c C-x x" 'go-run))

(use-package go-tag
  :ensure t
  :no-require t
  :after (go-mode)
  :general
  (:keymaps 'go-mode-map
            "C-c t" 'go-tag-add
            "C-c T" 'go-tag-remove)
  :custom
  (go-tag-args '("-transform" "camelcase")))

(use-package go-playground
  :ensure t
  :after (go-mode))

(use-package gorepl-mode
  :ensure t
  :hook (go-mode-hook . gorepl-mode))

BACKLOG try to integrate https://getgb.io/

lua

(use-package lua-mode
  :ensure t
  :preface
  (defun lua-broken-indentation-fix ()
    (save-excursion
      (lua-forward-line-skip-blanks 'back)
      (let* ((current-indentation (current-indentation))
             (line (thing-at-point 'line t))
             (busted-p (s-matches?
                        (rx (+ bol (* space)
                               (or "context" "describe" "it" "setup" "teardown")
                               "("))
                        line)))
        (when busted-p
          (+ current-indentation lua-indent-level)))))
  (defun rgc-lua-calculate-indentation-override (old-function &rest arguments)
    (or (lua-broken-indentation-fix)
        (apply old-function arguments)))
  :mode ("\\.lua$" . lua-mode)
  :hook (lua-mode-hook . (lambda ()
                           (setq flycheck-checker 'lua-luacheck)))
  :config
  (advice-add #'lua-calculate-indentation-override
              :around #'rgc-lua-calculate-indentation-override))

(use-package company-lua
  :ensure t
  :after (lua-mode company))

NixOS

(use-package nix-mode
  :ensure t
  :mode (("\\.nix$" . nix-mode)
         ((rx (eval "configuration.nix") (zero-or-more anything) eol) . nix-mode)))

(use-package company-nixos-options
  :ensure t
  :disabled
  :config
  (add-to-list 'company-backends 'company-nixos-options))

Rust

(use-package rustic
  :ensure t
  :hook (rust-mode-hook . lsp)
  :custom (rustic-rls-pkg 'lsp-mode)
  :mode ("\\.rs" . rustic-mode))

Clojure

(use-package clojure-mode
  :defer t
  :config
  (define-clojure-indent
    (alet 'defun)
    (mlet 'defun)))

(use-package clojure-snippets
  :defer t)

(use-package cider
  :defer t
  ;; :custom
  ;; (cider-repl-display-help-banner nil)
  :config
  ;; sadly, we can't use :diminish keyword here, yet
  (diminish 'cider-mode
            '(:eval (format " 🍏%s" (cider--modeline-info)))))

(use-package kibit-helper
  :disabled ;;TODO: setup kibit first
  :defer t)

other

Languages without much extra customization are going precisely here
(use-package actionscript-mode
  :ensure t
  :mode ("\\.actionscript" . actionscript-mode))

(use-package json-mode
  :after (json-reformat json-snatcher)
  :mode ("\\.json$" . json-mode))

(use-package opascal
  :quelpa
  (opascal :repo "ki11men0w/emacs-delphi-mode" :fetcher github)
  :mode ("\\.\\(pas\\|dpr\\|dpk\\)\\'" . opascal-mode)
  :preface
  (defun custom/enclose-by-spaces (left right)
    "Insert symbols LEFT and RIGHT around a region or point."
    (interactive "r")
    (if (use-region-p) ; act on region
        (let ((start (region-beginning))
              (end (region-end)))
          (save-excursion
            (goto-char end)
            (insert " ")
            (goto-char start)
            (insert " ")))
      (progn ; act around point
        (insert " " " ")
        (backward-char 1))))
  :general
  (:keymaps 'mode-specific-map
            "a" 'custom/enclose-by-spaces)
  :custom
  (opascal-indent-level 2)
  :config
  (smartparens-mode -1))

(use-package ccls
  :ensure t
  :after (lsp-mode)
  :hook ((c-mode-hook c++-mode-hook objc-mode-hook) . (lambda () (require 'ccls) (lsp)))
  :custom
  (ccls-executable "/run/current-system/sw/bin/ccls")
  :config
  (setq-default flycheck-disabled-checkers '(c/c++-clang c/c++-cppcheck c/c++-gcc)))

webdev

setup

(use-package sgml-mode
  :general
  (:keymaps 'html-mode-map
            "C-c C-w" 'html-wrap-in-tag))

(use-package markdown-mode
  :ensure t
  :mode (("\\.markdown$" . markdown-mode)
         ("\\.md$" . markdown-mode)
         ("\\.mkd$" . markdown-mode)
         ("\\.pdc$" . markdown-mode)
         ("\\.README$" . markdown-mode))
  :general
  (:keymaps 'markdown-mode-map
            "C-c C-v" 'markdown-preview
            "C-<tab>" 'yas/expand))

(use-package graphql-mode
  :ensure t
  :mode ("\\.graphql$" . graphql-mode))

(use-package web-mode
  :ensure t
  :mode (("\\.phtml\\'" . web-mode)
         ("\\.tpl\\.php\\'" . web-mode)
         ("\\.[agj]sp\\'" . web-mode)
         ("\\.as[cp]x\\'" . web-mode)
         ("\\.erb\\'" . web-mode)
         ("\\.mustache\\'" . web-mode)
         ("\\.djhtml\\'" . web-mode)
         ("\\.html?\\'" . web-mode))
  :general
  (:keymaps 'web-mode-map
            "M-SPC" 'company-complete) ;; manual autocomplete
  :hook (web-mode-hook . (lambda ()
                           (set (make-local-variable 'company-backends)
                                '(company-tern company-web-html company-yasnippet company-files))
                           (company-mode t)))
  :custom
  (web-mode-enable-current-element-highlight t)
  (web-mode-enable-auto-closing t)
  (web-mode-enable-auto-expanding t)
  (web-mode-enable-auto-pairing t)
  (web-mode-enable-auto-quoting t)
  (web-mode-enable-css-colorization t)
  (web-mode-markup-indent-offset 2)
  (web-mode-code-indent-offset 2)
  (web-mode-css-indent-offset 2)
  :config
  (use-package company-web
    :ensure t
    :after (company dash web-completion-data))
  (use-package web-mode-edit-element
    :ensure t
    :hook (web-mode-hook . web-mode-edit-element-minor-mode))
  (use-package web-narrow-mode
    :ensure t
    :hook (web-mode-hook . web-narrow-mode))
  (add-to-list 'web-mode-engines-alist '("django" . "\\.html\\'"))
  ;; Enable JavaScript completion between <script>...</script> etc.
  ;; TODO: check why company and AC are mentioned together (see below)
  (defadvice company-tern (before web-mode-set-up-ac-sources activate)
    "Set `tern-mode' based on current language before running company-tern."
    (message "advice")
    (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 tern-mode (tern-mode))
            (if tern-mode (tern-mode -1)))))))

(use-package css-mode
  :mode ("\\.scss$" . css-mode))

(use-package css-eldoc
  :ensure t
  :after (eldoc)
  :hook (css-mode-hook . turn-on-css-eldoc))

color helper

(use-package rainbow-mode
  :ensure t
  :hook (css-mode-hook . rainbow-mode))

emmet mode setup

(use-package emmet-mode
  :ensure t
  :delight emmet-mode
  :commands emmet-mode
  :general
  (:keymaps 'emmet-mode-keymap
            "C-j" nil
            "<C-return>" nil
            "C-c C-j" 'emmet-expand-line)
  :hook ((sgml-mode-hook nxml-mode-hook django-mode sgml-mode-hook css-mode-hook) . emmet-mode)
  :custom
  (emmet-move-cursor-between-quotes t)
  (emmet-indentation 2))

clients

restclient

(use-package company-restclient
  :ensure t
  :after (restclient company))

(use-package ob-restclient
  :ensure t
  :after (ob restclient)
  :commands (org-babel-execute:restclient))

(use-package httprepl :ensure t)

tmux

(use-package emamux
  :ensure t
  :general
  (:prefix "<f12>"
           "n" 'emamux:new-window
           "s" 'emamux:send-region
           "r" 'emamux:run-command))

internal browser (w3m/eww)

(use-package w3m
  :ensure t
  :commands w3m
  :hook (w3m-display-hook . (lambda (url)
                              (rename-buffer
                               (format "*w3m: %s*" (or w3m-current-title
                                                       w3m-current-url)) t)))
  :custom
  (w3m-coding-system 'utf-8)
  (w3m-file-coding-system 'utf-8)
  (w3m-file-name-coding-system 'utf-8)
  (w3m-input-coding-system 'utf-8)
  (w3m-output-coding-system 'utf-8)
  (w3m-terminal-coding-system 'utf-8)
  (w3m-use-cookies t)
  :config
  ;; special chars
  (standard-display-ascii ?\200 [15])
  (standard-display-ascii ?\201 [21])
  (standard-display-ascii ?\202 [24])
  (standard-display-ascii ?\203 [13])
  (standard-display-ascii ?\204 [22])
  (standard-display-ascii ?\205 [25])
  (standard-display-ascii ?\206 [12])
  (standard-display-ascii ?\210 [23])
  (standard-display-ascii ?\211 [14])
  (standard-display-ascii ?\212 [18])
  (standard-display-ascii ?\214 [11])
  (standard-display-ascii ?\222 [?\'])
  (standard-display-ascii ?\223 [?\"])
  (standard-display-ascii ?\224 [?\"])
  (standard-display-ascii ?\227 " -- "))

(use-package w3m-search
  :after (w3m)
  :config
  (add-to-list 'w3m-search-engine-alist
               '("emacs-wiki" "http://www.emacswiki.org/cgi-bin/wiki.pl?search=%s")))

(use-package eww
  :preface
  (defun eww-more-readable () ;;TODO: add to appropriate hook
    "Makes eww more pleasant to use. Run it after eww buffer is loaded."
    (interactive)
    (setq eww-header-line-format nil) ;; removes page title
    (setq mode-line-format nil) ;; removes mode-line
    (set-window-margins (get-buffer-window) 20 20) ;; increases size of margins
    (redraw-display) ;; apply mode-line changes
    (eww-reload 'local))) ;; apply eww-header changes

open ebooks internally

(use-package pdf-tools
  :ensure t
  :hook ((pdf-view-mode-hook . (pdf-links-minor-mode
                                pdf-outline-minor-mode))
         (after-init-hook . pdf-tools-install))
  :config
  (use-package pdf-view
    :ensure nil
    :mode ("\\.pdf$" . pdf-view-mode)
    ;; :magic ("%PDF" . pdf-view-mode)
    :preface
    (defun custom/scroll-other-window (&optional arg)
      (interactive "P")
      (awhen (ignore-errors (other-window-for-scrolling))
        (let* ((buffer (window-buffer it))
               (mode (with-current-buffer buffer major-mode)))
          (cond
           ((eq mode 'pdf-view-mode)
            (save-selected-window
              (select-window it)
              (with-current-buffer buffer
                (pdf-view-next-page (cond ((eq arg '-) -1)
                                          ((numberp arg) arg)
                                          (t 1))))))
           (t (scroll-other-window arg))))))
    :general
    ("C-M-v" 'custom/scroll-other-window)
    (:keymaps 'pdf-view-mode-map
              "C-s" 'isearch-forward
              "h" 'pdf-annot-add-highlight-markup-annotation
              "t" 'pdf-annot-add-text-annotation
              "y" 'pdf-view-kill-ring-save
              "D" 'pdf-annot-delete)
    :hook ((after-init-hook . pdf-tools-install)
           (pdf-view-mode-hook . pdf-isearch-minor-mode)
           ;; (pdf-tools-enabled-hook . pdf-view-midnight-minor-mode)
           (pdf-view-mode-hook . (lambda () (cua-mode -1)))) ;; turn off cua so copy works
    :custom
    (pdf-view-midnight-colors (quote ("white smoke" . "#002b36"))) ;; more brightness in midnight mode
    (pdf-view-resize-factor 1.1) ;; more fine-grained zooming
    (pdf-view-display-size 'fit-page))
  (use-package pdf-annot
    :ensure nil
    :general
    (:keymaps 'pdf-annot-edit-contents-minor-mode-map
              "<return>" 'pdf-annot-edit-contents-commit
              "<S-return>" 'newline)
    :custom
    (pdf-annot-activate-created-annotations t)
    :config
    (advice-add 'pdf-annot-edit-contents-commit :after 'save-buffer)))

email

common

(use-package footnote)

(use-package sendmail
  :custom
  (mail-specify-envelope-from t)
  (mail-envelope-from 'header)
  (send-mail-function 'sendmail-send-it))

(use-package message
  :hook (message-mode-hook . turn-on-orgtbl)
  :custom
  (message-sendmail-envelope-from 'header)
  (message-kill-buffer-on-exit t))

notmuch

(use-package notmuch
  :ensure t
  :no-require t
  :commands notmuch
  :general
  (:keymaps 'notmuch-search-mode-map
            "!" '(lambda ()
                   "toggle unread tag for thread"
                   (interactive)
                   (if (member "unread" (notmuch-search-get-tags))
                       (notmuch-search-tag '("-unread" "-spam"))
                     (notmuch-search-tag '("+unread"))))
            "g" 'notmuch-refresh-this-buffer)
  (:keymaps 'notmuch-message-mode-map
            "#" 'mml-attach-file)
  (:keymaps 'mode-specific-map
            "4 n" 'notmuch
            "4 N" 'counsel-notmuch)
  :hook ((notmuch-hello-refresh-hook . (lambda ()
                                         (if (and (eq (point) (point-min))
                                                  (search-forward "Saved searches:" nil t))
                                             (progn
                                               (forward-line)
                                               (widget-forward 1))
                                           (if (eq (widget-type (widget-at)) 'editable-field)
                                               (beginning-of-line)))))
         (message-setup-hook . mml-secure-message-sign-pgpmime))  ;; Crypto Settings
  :custom
  (mm-text-html-renderer 'w3m)
  (notmuch-mua-compose-in 'current-window)
  (notmuch-identities private/gmail-accounts)
  (notmuch-fcc-dirs private/notmuch-fcc-dirs)
  (notmuch-search-line-faces '(("unread" . (:foreground "white"))
                               ("deleted" . (:foreground "red" :background "blue"))))
  (notmuch-saved-searches private/notmuch-saved-searches)
  (notmuch-crypto-process-mime t) ; Automatically check signatures
  (notmuch-hello-hide-tags (quote ("killed")))
  (notmuch-address-command "notmuch-addrlookup")
  :config
  (use-package org-notmuch
    :after (org notmuch))
  (use-package counsel-notmuch
    :ensure t
    :after (counsel notmuch)
    :commands counsel-notmuch))

security

(use-package pass
  :ensure t
  :general
  (:prefix "<f6>"
           "p" 'pass
           "!" 'ivy-pass)
  :config
  (use-package ivy-pass :ensure t))

various useful packages

(imagemagick-register-types)

(use-package wttrin
  :ensure t
  :after (xterm-color)
  :custom
  (wttrin-default-cities '("Moscow")))

(use-package webpaste
  :ensure t
  :general
  (:prefix "M-p"
           "b" 'webpaste-paste-buffer
           "r" 'webpaste-paste-region)
  :custom
  (webpaste-provider-priority '("ix.io" "gist.github.com")))

(use-package atomic-chrome
  :ensure t
  :custom
  (atomic-chrome-buffer-open-style 'frame)
  (atomic-chrome-server-ghost-text-port 4001)
  :config
  ;; TODO: (alex3rd) make use of atomic-chrome-url-major-mode-alist
  (atomic-chrome-start-server))

(use-package carbon-now-sh
  :quelpa
  (carbon-now-sh :repo "wiedzmin/carbon-now-sh.el" :fetcher github))

Twitter

(use-package twittering-mode
  :ensure t
  :general
  (:keymaps 'mode-specific-map
            "5 t" 'twit)
  :init
  (setq twittering-use-master-password t)
  (setq twittering-private-info-file (expand-file-name "~/docs/enc/cred/.twittering-mode.gpg")))

Telegram

(use-package telega
  :disabled
  :quelpa
  (telega :repo "zevlg/telega.el" :fetcher github :version original)
  :custom
  (telega-completing-read-function #'ivy-completing-read)
  :hook (telega-root-mode . telega-notifications-mode)
  :config
  (use-package telega-notifications))

browser editing connectivity

(use-package edit-server
  :ensure t
  :config
  (edit-server-start))

orgmode

preface

Notes about setting up org-capture

update-desktop-database

URL: javascript:location.href=’org-protocol://capture://l/’encodeURIComponent(location.href)‘+encodeURIComponent(document.title)+’‘+encodeURIComponent(window.getSelection())

~/.local/share/applications/mimeapps.list [Default Applications] x-scheme-handler/org-protocol=org-protocol.desktop

~/.local/share/applications/org-protocol.desktop [Desktop Entry] Name=org-protocol Exec=emacsclient %u Type=Application Terminal=false Categories=System; MimeType=x-scheme-handler/org-protocol;

org-id usage

org-id usage example for the future: “* TODO___ %a\n :PROPERTIES:\n :ID: %(org-id-new)\n :END:\n %U\n\n %i”

setup

(use-package org
  :ensure org-plus-contrib
  :after (f)
  :mode (("\\.org$" . org-mode)
         ("\\.org_archive$" . org-mode))
  :preface
  (defun custom/finally-tangle-literate-config ()
    (shell-command (format "ntangle %s" (concat user-emacs-directory literate-config-filename))))
  ;; remove read-only props from yanked text (e.g. from jabber.el chat buffer)
  (defadvice org-yank (after make-yank-writeable disable)
    (let ((inhibit-read-only t))
      (remove-text-properties (region-beginning) (region-end)
                              '(read-only t))))
  (defvar custom/org-journal-file (at-org-dir "/journal.org"))
  (defvar custom/org-browser-tabs (at-org-dir "/browser-tabs.org"))
  (defun custom/verify-refile-target () ;; Exclude DONE state tasks from refile targets
    "Exclude todo keywords with a done state from refile targets"
    (not (member (nth 2 (org-heading-components)) org-done-keywords)))
  ;;TODO: customize "todo-only" parameter for "org-tags-view"
  (defun custom/follow-tag-link (tag)
    "Display a list of TODO headlines with tag TAG.
         With prefix argument, also display headlines without a TODO keyword."
    (org-tags-view nil tag))              ;nil was (null current-prefix-arg) originally
  ;; http://irreal.org/blog/?p=6166
  (defun custom/org-tags-all ()
    ;;TODO: bind some key to close buffer
    (interactive)
    (with-current-buffer (get-buffer-create "*org-tags*")
      (delete-region (point-min) (point-max))
      (org-mode)
      ;;TODO: review tag collection methods and find some truth
      ;; (sort (delete-dups (apply 'append (delete-dups (org-map-entries (lambda () org-scanner-tags) t 'agenda)))) 'string<)
      (let ((tags (sort (delete-dups
                         (cl-loop for buffer in (org-buffer-list 'agenda t)
                                  append (with-current-buffer buffer
                                           (org-with-wide-buffer
                                            (goto-char (point-min))
                                            (cl-loop while (re-search-forward org-complex-heading-regexp nil t)
                                                     when (match-string 5)
                                                     append (split-string (substring-no-properties (match-string 5))
                                                                          ":" t "[[:space:]]+"))))))
                        'string<)))
        (dolist (tag tags)
          (insert (concat "[[elisp:(org-tags-view nil \"" tag "\")][" tag "]]\n"))))
      (beginning-of-buffer)
      (switch-to-buffer (current-buffer))
      (read-only-mode)))
  ;; Remove empty CLOCK drawers on clock out
  (defun custom/remove-empty-drawer-on-clock-out ()
    (interactive)
    (save-excursion
      (beginning-of-line 0)
      (org-remove-empty-drawer-at "CLOCK" (point))))
  (defun custom/org-use-speed-commands-for-headings-and-lists ()
    "Activate speed commands on list items too."
    (or (and (looking-at org-outline-regexp) (looking-back "^\**"))
        (save-excursion (and (looking-at (org-item-re)) (looking-back "^[ \t]*")))))
  ;; TODO: bind somewhere
  (defun custom/org-capture-refile-and-jump ()
    (interactive)
    (org-capture-refile)
    (org-refile-goto-last-stored))
  ;;TODO: investigate usage, seems useful
  (defun custom/org-link-describe (link desc)
    (cond ((string-match "file:" link)
           (replace-regexp-in-string "^file:" "File link -> " (org-link-unescape link)))
          (t (or desc link))))
  ;; (SEC-HIGH SEC-LOW MICROSEC PICOSEC) current-idle-time
  (defvar custom/idle-clockout-timeout 1800
    "Perform first attempt to clock-out after this period of emacs
          inactivity. It can decide to postpone the clocking-out if it's
          only emacs that is idle, but not the computer itself.")
  (defvar custom/idle-clockout-recheck-interval 300
    "After a sufficient idle time was achieved by emacs, we'll
          periodically check current idle time of the whole OS to decide
          whether we need to clock out")
  (defvar custom/idle-clockout-repeat-timer nil
    "Timer for repeatedly (during a single idle interval) checking
          whether we need to clock-out")
  (defun custom/clockout-when-idle ()
    (awhen custom/idle-clockout-repeat-timer
      (cancel-timer it))
    (when (org-clocking-p)
      (if (> (org-user-idle-seconds)
             custom/idle-clockout-timeout)
          (let ((org-clock-out-switch-to-state "WAITING")) ;TODO: introduce variable
            (org-clock-out nil t))
        (setf custom/idle-clockout-repeat-timer
              (run-with-idle-timer
               (time-add (current-idle-time) custom/idle-clockout-recheck-interval)
               nil
               'custom/clockout-when-idle)))))
  :general
  (:prefix "<f7>"
           "g" 'org-clock-goto
           "." 'org-clock-in
           "," 'org-clock-out
           "^" 'org-mru-clock-select-recent-task
           "c" 'org-clock-cancel
           "x" 'counsel-org-clock-context
           "h" 'counsel-org-clock-history
           "d" 'org-clock-display
           "R" 'org-clock-report
           "p" 'org-pomodoro
           "s" 'org-schedule
           "|" 'org-deadline
           "t" 'org-toggle-timestamp-type
           "e" 'org-capture
           "w" 'org-store-link
           "y" 'org-insert-link-global
           "S" 'org-set-property
           "D" 'org-delete-property
           "A" 'org-footnote-action
           "r" 'org-refile
           "T" 'org-table-create
           "a" 'org-agenda
           "b" 'org-dashboard-display
           "v" 'org-reveal
           "f" 'ace-link-org
           "n" 'org-narrow-to-subtree
           "-" 'org-sparse-tree
           "l" 'counsel-org-agenda-headlines
           "H" 'org-recent-headings-ivy
           "=" 'org-show-todo-tree
           "\\" 'counsel-org-tag
           "<right>" 'outline-next-visible-heading
           "<left>" 'outline-previous-visible-heading
           "<down>" 'org-forward-heading-same-level
           "<up>" 'org-backward-heading-same-level
           "u" 'outline-up-heading
           "G" 'org-goto
           ";" 'custom/org-tags-all)
  ;; (:keymaps 'org-agenda-mode-map
  ;;           "<f7> ." 'org-agenda-clock-in org-agenda-mode-map
  ;;           "<f7> ," 'org-agenda-clock-out org-agenda-mode-map
  ;;           "<f7> o" 'ace-link-org org-agenda-mode-map)
  (:keymaps 'org-mode-map
            "C-'" nil
            "C-c [" nil
            "C-c ]" nil
            "C-c C-o" nil
            "s-j" 'org-babel-next-src-block
            "s-k" 'org-babel-previous-src-block
            "s-l" 'org-edit-src-code
            "C-c C-'" 'org-edit-src-code)
  (:keymaps 'org-src-mode-map
            "s-l" 'org-edit-src-exit
            "C-c C-'" 'org-edit-src-exit)

    :config
  (use-package org-capture-pop-frame :ensure t)
  (add-hook 'kill-emacs-hook #'custom/finally-tangle-literate-config)
  (setq org-archive-location (concat custom/org-journal-file "::datetree/"))
  (setq org-contrib-base '(org-agenda org-archive org-attach org-bbdb
                           org-bibtex org-clock org-docview org-habit
                           org-id org-info org-inlinetask org-irc
                           org-mouse org-protocol org-timer org-w3m))
  (setq org-contrib-extra '(org-bookmark org-checklist org-collector
                            org-drill org-expiry org-index org-interactive-query
                            org-man org-velocity))
  (setq org-modules `(,@org-contrib-base ,@org-contrib-extra))
  (add-to-list 'file-coding-system-alist (cons "\\.\\(org\\|org_archive\\|/TODO\\)$"  'utf-8))
  (setq org-lowest-priority 70) ;; extend priorities set (given ascii code)
  (setq org-use-speed-commands 'custom/org-use-speed-commands-for-headings-and-lists)
  (setq org-use-speed-commands t)
  (add-to-list 'org-speed-commands-user '("x" org-todo "DONE"))
  (add-to-list 'org-speed-commands-user '("y" org-todo-yesterday "DONE"))
  (add-to-list 'org-speed-commands-user '("s" call-interactively 'org-schedule))
  (add-to-list 'org-speed-commands-user '("i" call-interactively 'org-clock-in))
  (add-to-list 'org-speed-commands-user '("o" call-interactively 'org-clock-out))
  (add-to-list 'org-speed-commands-user '("$" call-interactively 'org-archive-subtree))
  (f-entries (at-org-dir)
             (lambda (entry) (when (and (f-file? entry)
                                        (s-suffix? "org" entry)
                                        (file-exists-p entry))
                               (push entry org-agenda-files)))
             t)
  (dolist (orgfile (directory-files (at-org-dir "/journals") t "journal") )
    (setq org-agenda-files
          (delete orgfile org-agenda-files)))
  (add-to-list 'org-agenda-files (at-config-basedir "config.org"))
  ;; agenda customizations
  (setq org-confirm-elisp-link-not-regexp "org-tags-view")
  (setf org-agenda-clockreport-parameter-plist '(:link t :maxlevel 2 :narrow 60))
  (setq org-agenda-dim-blocked-tasks 'invisible)
  (setq org-agenda-include-all-todo t)
  (setq org-agenda-include-diary t)
  (setq org-agenda-inhibit-startup t)
  (setq org-agenda-persistent-filter t)
  (setq org-agenda-repeating-timestamp-show-all nil)
  (setq org-agenda-restore-windows-after-quit t)
  (setq org-agenda-show-all-dates t)
  (setq org-agenda-show-inherited-tags nil)
  (setq org-agenda-show-log t)
  (setq org-agenda-skip-additional-timestamps-same-entry t)
  (setq org-agenda-skip-deadline-if-done t)
  (setq org-agenda-skip-deadline-prewarning-if-scheduled 'pre-scheduled)
  (setq org-agenda-skip-scheduled-if-done t)
  (setq org-agenda-skip-timestamp-if-done t)
  (setq org-agenda-span 'month)
  (setq org-agenda-start-on-weekday 1)
  (setq org-agenda-sticky nil) ;otherwise agenda behaves strangely on non-stuck projects
  (setq org-agenda-tags-todo-honor-ignore-options t)
  (setq org-agenda-todo-ignore-deadlines 'all)
  (setq org-agenda-todo-ignore-scheduled 'all)
  (setq org-agenda-todo-ignore-timestamp 'past)
  (setq org-agenda-todo-ignore-with-date t)
  (setq org-agenda-todo-list-sublevels nil)
  (setq org-agenda-use-tag-inheritance t)
  (setq org-agenda-window-setup 'current-window)
  (setf agenda-opts-all-with-time
        '((org-agenda-todo-ignore-scheduled nil)
          (org-agenda-todo-ignore-deadlines nil)
          (org-agenda-todo-ignore-with-date nil)))
  (setq org-agenda-time-grid
        '((daily today require-timed remove-match)
          "----------------"
          (930 1000 1200 1400 1600 1800 2000 2200 2400 2500)))
  (setq org-agenda-custom-commands
        `(("d" . "some non-straightforward TODO statuses")
          ("db" todo "BACKLOG" nil)
          ("ds" todo "SOON" nil)
          ("dc" todo "CANCELLED" nil)
          ("dw" todo "WAITING|FEEDBACK" nil)
          ("dg" todo "GOING" ,agenda-opts-all-with-time)
          ("da" tags "+actual_p")
          ("c" . "by context")
          ("cp" tags "+@personal/GOING|WAITING|BACKLOG|SOON")
          ("cr" tags "+@project/GOING|WAITING|BACKLOG|SOON")
          ("cj" tags "+@job/GOING|WAITING|FEEDBACK|BACKLOG|SOON")
          ("cw" tags "+@workplace/GOING|WAITING|BACKLOG|SOON")
          ("ct" tags "+@phonecall/WAITING|BACKLOG|SOON")
          ("cs" tags "+@someday")
          ("cq" tags "+@quicknote")
          ("e" . "by essence")
          ;;TODO: find more handy shortcuts
          ("ec" tags "+current")
          ("ef" tags "+reference")
          ("em" tags "+master")
          ("eo" tags "+ordering")
          ("er" tags "+repair")
          ("ed" tags "+develop")
          ("ei" tags "+investigate")
          ("ee" tags "+entertainment")
          ("ey" tags "+family")
          ("eH" tags-todo "+housekeeping")
          ("eC" tags-todo "+current")
          ("eF" tags-todo "+reference")
          ("eM" tags-todo "+master")
          ("eO" tags-todo "+ordering")
          ("eR" tags-todo "+repair")
          ("eD" tags-todo "+develop")
          ("eI" tags-todo "+investigate")
          ("eE" tags-todo "+entertainment")
          ("u" . "unassigned")
          ("up" alltodo "Unprioritized TODO entries"
           ((org-agenda-skip-function
             (lambda nil
               (org-agenda-skip-entry-if 'regexp "\\[#[ABC]]")))
            (org-tags-match-list-sublevels 'indented)
            (org-agenda-sorting-strategy
             '((agenda time-up tag-up) ))
            ;; '(org-agenda-sorting-strategy '((agenda time-up priority-down tag-up) (todo tag-up)))
            (org-agenda-overriding-header "Unprioritized TODO entries: ")))
          ("P" . "Prioritized tasks")
          ("Pa" "Prioritized tasks A"
           ((tags-todo "+PRIORITY=\"A\"") ))
          ("Pb" "Prioritized tasks B"
           ((tags-todo "+PRIORITY=\"B\"")))
          ("Pc" "Prioritized tasks C"
           ((tags-todo "+PRIORITY=\"C\"")))
          ("S" "Scheduled tasks" agenda ""
           ((org-agenda-time-grid nil)
            (org-deadline-warning-days 32)
            (org-agenda-entry-types '(:scheduled))
            ))
          ("p" tags "+purchase")
          ("b" . "tickets")
          ("be" tags "+ticket+emacs")
          ("bs" tags "+ticket+stumpwm")
          ("jc" tags "+@job+current/GOING|FEEDBACK")
          ))
  (setq org-agenda-prefix-format '((agenda . " %i %-12:c%?-12t% s %b")
                                   (timeline . "  % s")
                                   (todo . " %i %-12:c")
                                   (tags . " %i %-12:c")
                                   (search . " %i %-12:c")))
  ;; clocking customizations
  (setq org-clock-history-length 35)
  (setq org-clock-idle-time 3)
  (setq org-clock-in-resume t)
  (setq org-clock-in-switch-to-state "GOING")
  (setq org-clock-out-switch-to-state "HOLD")
  (setq org-clock-into-drawer "CLOCK")
  (setq org-clock-out-remove-zero-time-clocks t)
  (setq org-clock-persist t)
  ;; just clock-out unconditionally - it seems easier to maintain (credits to @binarin)
  (setf org-clock-x11idle-program-name "xprintidle")
  (setf org-x11idle-exists-p t)
  ;; refiling customizations
  (setq org-outline-path-complete-in-steps nil)
  (setq org-refile-allow-creating-parent-nodes 'confirm)
  (setq org-refile-target-verify-function 'custom/verify-refile-target)
  (setq org-refile-targets '((org-agenda-files :maxlevel . 5) (nil :maxlevel . 5)))
  (setq org-refile-use-outline-path 'file)
  ;; various customizations
  (setf org-catch-invisible-edits nil)
  (setf org-fast-tag-selection-include-todo nil)
  (setf org-id-link-to-org-use-id t)
  (setq appt-display-interval 5)
  (setq appt-message-warning-time 10)
  (setq calendar-date-style 'european)
  (setq org-M-RET-may-split-line '((default . nil)))
  (setq org-align-all-tags t)
  (setq org-attach-directory (at-org-dir "/org-attach-data"))
  (setq org-blank-before-new-entry '((heading) (plain-list-item . auto)))
  (setq org-columns-default-format "%42ITEM %TODO %3Effort(E){:} %3CLOCKSUM_T(R) %SCHEDULED")
  (setq org-confirm-elisp-link-function 'y-or-n-p)
  (setq org-ctrl-k-protect-subtree t)
  (setq org-cycle-include-plain-lists 'integrate)
  (setq org-cycle-separator-lines 0)
  (setq org-deadline-warning-days 30)
  (setq org-default-notes-file (at-org-dir "/refile.org"))
  (setq org-ditaa-jar-path (at-config-basedir "resources/ditaa0_9.jar"))
  (setq org-element-use-cache nil)
  (setq org-enforce-todo-checkbox-dependencies t)
  (setq org-enforce-todo-dependencies t)  ;;TODO: try ORDERED/NOBLOCKING props : org-toggle-ordered-property
  (setq org-export-coding-system 'utf-8)
  (setq org-export-with-drawers t)
  (setq org-extend-today-until 2)
  (setq org-fast-tag-selection-single-key 'expert)
  (setq org-fontify-done-headline t)
  (setq org-global-properties '(("STYLE_ALL" . "habit")))
  (setq org-goto-max-level 10)
  (setq org-hide-leading-stars t)
  (setq org-indirect-buffer-display 'current-window)
  (setq org-insert-mode-line-in-empty-file t)
  (setq org-log-done t)
  (setq org-log-into-drawer t)
  (setq org-log-repeat 'time)
  (setq org-loop-over-headlines-in-active-region t)
  (setq org-read-date-prefer-future 'time)
  (setq org-return-follows-link t)
  (setq org-special-ctrl-a/e t)
  (setq org-special-ctrl-k t)
  (setq org-src-fontify-natively t)
  (setq org-src-tab-acts-natively t)
  (setq org-startup-folded nil)
  (setq org-stuck-projects '("+LEVEL=1/-DONE" ("TODO" "GOING" "NEXT" "WAITING" "HOLD" "CANCELLED") nil ""))
  (setq org-tags-column -80)
  (setq org-track-ordered-property-with-tag t)
  (setq org-use-effective-time t)
  (setq org-use-property-inheritance t)
  (setq org-use-sub-superscripts nil)
  (setq org-yank-adjusted-subtrees t)
  (setq org-agenda-show-future-repeats 'next)
  (setq org-highlight-latex-and-related '(latex))
  (setq org-confirm-shell-link-function 'y-or-n-p)
  (setq org-confirm-elisp-link-function 'y-or-n-p)
  (setq org-src-window-setup 'current-window)
  (setq org-confirm-babel-evaluate nil)
  (setf org-make-link-description-function #'custom/org-link-describe)
  (when (featurep 'unicode-fonts)
    (setq org-ellipsis ""))
  ;; keywords setup
  (setq kw-seq-common '(sequence "BACKLOG(b)" "SOON(s)" "REPEAT(r)" "GOING(g!)" "NEXT(x)" "WAITING(w@/!)" "FEEDBACK"
                                 "|" "DONE(d!/@)" "CANCELLED(c@/!)" "OUTDATED(o)"))
  (setq org-todo-keywords
        `(,kw-seq-common))
  (setq org-todo-keywords-for-agenda '("BACKLOG(b)" "SOON(s)" "REPEAT(r)" "GOING(g!)" "NEXT(x)" "WAITING(w@/!)" "FEEDBACK"))
  (setq org-done-keywords-for-agenda '("DONE(d)" "CANCELLED(c)" "OUTDATED(o)"))
  ;; faces
  (setq org-todo-keyword-faces
        '(("BACKLOG" . (:foreground "gray" :weight bold))
          ("SOON" . (:foreground "magenta" :weight bold))
          ("REPEAT" . (:foreground "blue" :weight bold))
          ("NEXT" . (:foreground "red" :weight bold))
          ("WAITING" . (:foreground "orange" :weight bold))
          ("FEEDBACK" . (:foreground "yellow" :weight bold))
          ("CANCELLED" . (:foreground "cyan" :weight bold))
          ("DONE" . (:foreground "green" :weight bold))))
  (setq org-priority-faces
        '((?A :foreground "red" :weight bold)
          (?B :foreground "#94bff3" :weight bold)
          (?C :foreground "#6f6f6f")
          (?D :foreground "#c390d4")
          (?E :foreground "#90c3d4")
          (?F :foreground "#a1d490")))
  (set-face-attribute 'org-done nil :foreground "PaleGreen" :weight 'normal :strike-through t)
  (set-face-attribute 'org-headline-done nil :foreground "LightSalmon" :weight 'normal :strike-through t)
  ;; tags
  (setq org-tag-alist '(("current" . ?c)
                        ("reference" . ?f)
                        ("orgmode" . ?g)
                        ("purchase" . ?p)
                        ("master" . ?m)
                        ("ordering" . ?o)
                        ("housekeeping" . ?h)
                        ("entertainment" . ?e)
                        ("interesting" . ?i)
                        ("repair" . ?r)
                        ))
  (setq org-tags-exclude-from-inheritance '("project"))
  (setq org-todo-state-tags-triggers
        '(("GOING" ("current" . t))
          ("DONE" ("current"))))
  ;; org-habit
  (setq org-habit-graph-column 50)
  (setq org-habit-preceding-days 10)
  (setq org-habit-following-days 4)
  (setq org-habit-show-habits-only-for-today nil)
  ;; org-capture
  (setq org-capture-templates
        `(("n" "NixOS")
          ("nt" "NixOS" entry (file "/etc/nixos/todo.org") "* BACKLOG %?[[%:link][%:description]] %U\n  %:initial")
          ("nc" "NixOS code snippet" entry (file "/etc/nixos/todo.org")
           "* %^{title} :nix:code_snippet:\n :PROPERTIES:\n :CREATED: %U\n :END:\n\n#+BEGIN_SRC nix\n %i%?\n#+END_SRC\n")
          ("ns" "NixOS shell excerpt" entry (file "/etc/nixos/todo.org") "* %? %U :%:description:\n  %:initial")
          ("e" "Emacs")
          ("et" "Emacs" entry (file ,(at-config-basedir "/todo.org")) "* BACKLOG %?[[%:link][%:description]] %U\n  %:initial")
          ("ec" "Emacs code snippet" entry (file ,(at-config-basedir "/todo.org"))
           "* %^{title} :emacs:code_snippet:\n :PROPERTIES:\n :CREATED: %U\n :END:\n\n#+BEGIN_SRC emacs-lisp\n %i%?\n#+END_SRC\n")
          ("es" "NixOS shell excerpt" entry (file ,(at-config-basedir "/todo.org")) "* %? %U :%:description:\n  %:initial")
          ("x" "XMonad")
          ("xt" "XMonad" entry (file ,(at-homedir "/.xmonad/todo.org")) "* BACKLOG %?[[%:link][%:description]] %U\n  %:initial")
          ("xc" "XMonad code snippet" entry (file ,(at-homedir "/.xmonad/todo.org"))
           "* %^{title} :xmonad:code_snippet:\n :PROPERTIES:\n :CREATED: %U\n :END:\n\n#+BEGIN_SRC haskell\n %i%?\n#+END_SRC\n")
          ("xs" "XMonad shell excerpt" entry (file ,(at-homedir "/.xmonad/todo.org")) "* %? %U :%:description:\n  %:initial")
          ("d" "deferred tabs" entry (file+olp custom/org-browser-tabs "groups" "deferred tabs") "* %?%:link %U :deferred:")
          ("p" "projects")
          ("pi" "project ideas" entry (file ,(at-org-dir "/projects.org")) "* %? %U :@project:idea:")
          ("pn" "new project" entry (file ,(at-org-dir "/projects.org")) "* %? %U :@project:")
          ("m" "mastering" entry (file+headline ,(at-org-dir "/mastering.org") "inbox") "* %? %U")
          ))
  ;; holidays
  (setq holiday-orthodox-holidays nil) ; Orthodox holidays to some extent
  (setq holiday-personal-holidays nil) ; personal anniversaries, etc.
  (setq holiday-other-holidays
        (append holiday-orthodox-holidays holiday-personal-holidays))
  (setq calendar-holidays
        (append holiday-other-holidays
                holiday-solar-holidays))
  (add-hook 'org-mode-hook 'turn-on-font-lock)
  (add-hook 'org-clock-out-hook 'custom/remove-empty-drawer-on-clock-out 'append)
  (add-hook 'org-after-refile-insert-hook 'save-buffer)
  (add-hook 'org-capture-mode-hook
            (lambda () (setq-local org-complete-tags-always-offer-all-agenda-tags t)))
  ;; run some commands
  (org-add-link-type "tag" 'custom/follow-tag-link)
  (org-clock-persistence-insinuate) ;; Resume clocking tasks when emacs is restarted
  (run-at-time "06:00" 86400 '(lambda () (setq org-habit-show-habits t)))
  (set-charset-priority 'unicode)
  (turn-on-orgtbl)
  (run-with-idle-timer custom/idle-clockout-timeout t 'custom/clockout-when-idle)
  (font-lock-add-keywords
   'org-mode
   `(("^[ \t]*\\(?:[-+*]\\|[0-9]+[).]\\)[ \t]+\\(\\(?:\\[@\\(?:start:\\)?[0-9]+\\][ \t]*\\)?\\[\\(?:X\\|\\([0-9]+\\)/\\2\\)\\][^\n]*\n\\)" 1 'org-headline-done prepend))
   'append))

(use-package org-protocol
  :after (org server))

(use-package ob-css
  :ensure org-plus-contrib
  :commands (org-babel-execute:css
             org-babel-prep-session:css))

(use-package ob-dot
  :ensure org-plus-contrib
  :commands (org-babel-execute:dot
             org-babel-expand-body:dot))

(use-package ob-ditaa
  :ensure org-plus-contrib
  :commands (org-babel-execute:ditaa
             org-babel-prep-session:ditaa))

(use-package ob-emacs-lisp
  :ensure org-plus-contrib
  :commands (org-babel-execute:emacs-lisp
             org-babel-expand-body:emacs-lisp))

(use-package ob-lisp
  :ensure org-plus-contrib
  :commands (org-babel-execute:lisp
             org-babel-expand-body:lisp))

(use-package ob-js
  :ensure org-plus-contrib
  :commands (org-babel-execute:js
             org-babel-prep-session:js
             org-babel-variable-assignments:js))

(use-package ob-latex
  :ensure org-plus-contrib
  :commands (org-babel-execute:latex
             org-babel-expand-body:latex
             org-babel-prep-session:latex))

(use-package ob-org
  :ensure org-plus-contrib
  :commands (org-babel-execute:org
             org-babel-expand-body:org
             org-babel-prep-session:org))

(use-package ob-plantuml
  :ensure org-plus-contrib
  :commands (org-babel-execute:plantuml
             org-babel-prep-session:plantuml
             org-babel-variable-assignments:plantuml))

(use-package ob-scheme
  :ensure org-plus-contrib
  :commands (org-babel-execute:scheme
             org-babel-expand-body:scheme))

(use-package ob-python
  :ensure org-plus-contrib
  :commands (org-babel-execute:python))

(use-package ob-shell
  :ensure org-plus-contrib
  :commands (org-babel-execute:sh
             org-babel-expand-body:sh
             org-babel-execute:bash
             org-babel-expand-body:bash))

(use-package ox-html
  :ensure org-plus-contrib
  :commands (org-html-convert-region-to-html
             org-html-export-as-html
             org-html-export-to-html))

additional packages / extensions

(use-package ob-async
  :ensure t
  :after (org ob))

(use-package org-pomodoro
  :ensure t
  :after (alert))

(use-package orgit
  ;;TODO: automate insertion of links below (yasnippet/whatever)
  ;;    orgit:/path/to/repo/            links to a `magit-status' buffer
  ;;    orgit-rev:/path/to/repo/::REV   links to a `magit-revision' buffer
  ;;    orgit-log:/path/to/repo/::ARGS  links to a `magit-log' buffer
  :ensure t)

(use-package orglink
  :ensure t
  :delight (orglink-mode " OL")
  :config
  ;; TODO: customize orglink-activate-in-modes
  ;; TODO: automate insertion of link types below
  ;;   [[Code]]
  ;;   [[Code][start of code]]
  ;;   [[define-derived-mode orglink-mode][orglink-mode]]
  ;;   <mailto:[email protected]>
  ;;   man:info
  ;;   <info:man>
  ;;   https://github.com/tarsius/orglink
  (global-orglink-mode))

(use-package org-clock-today
  :ensure t
  :config
  (org-clock-today-mode 1))

(use-package org-recent-headings
  :ensure t
  :disabled
  :custom
  (org-recent-headings-save-file (at-user-data-dir "org-recent-headings"))
  :config
  (org-recent-headings-mode 1))

(use-package org-link-minor-mode
  :ensure t
  :config
  (org-link-minor-mode t))

(use-package org-randomnote
  :ensure t
  :general
  (:keymaps 'mode-specific-map
            "o r" 'org-randomnote)
  :custom
  (org-randomnote-candidates org-agenda-files)
  (org-randomnote-open-behavior 'indirect-buffer))

(use-package russian-holidays
  :ensure t
  :after (org)
  :config
  (setq calendar-holidays
   (push russian-holidays calendar-holidays)))

(use-package org-rich-yank
  :ensure t
  :after (org)
  :general
  (:keymaps 'org-mode-map
            "C-M-y" 'org-rich-yank))

(use-package counsel-org-clock
  :ensure t
  :after (org counsel)
  :custom
  (counsel-org-clock-default-action 'counsel-org-clock-clock-dwim-action))

(use-package org-download
  :ensure t
  ;;TODO: bind keys ASAP
  ;;TODO: use in automation
  :hook (dired-mode-hook . org-download-enable)
  :custom
  (org-download-method 'attach))

publishing

(use-package blockdiag-mode :ensure t)

(use-package ob-blockdiag
  :ensure t
  :config
  (org-babel-do-load-languages 'org-babel-load-languages
                               '((blockdiag . t))))

(use-package plantuml-mode
  :ensure t
  :mode ("\\.plantuml\\'" . plantuml-mode)
  :custom
  (plantuml-jar-path "/usr/share/plantuml/lib/plantuml.jar")
  (org-plantuml-jar-path plantuml-jar-path)
  :config
  (add-to-list 'org-src-lang-modes '("plantuml" . plantuml))
  (org-babel-do-load-languages 'org-babel-load-languages
                               '((plantuml . t))))

help

(use-package info
  :preface
  (defun custom/open-info (topic bname)
    "Open info on TOPIC in BNAME."
    (if (get-buffer bname)
        (progn
          (switch-to-buffer bname)
          (unless (string-match topic Info-current-file)
            (Info-goto-node (format "(%s)" topic))))
      (info topic bname)))
  (defun custom/info-org () (custom/open-info "org" "*org info*"))
  (defun custom/info-org-clocking () (org-info "Clocking commands"))
  (defun custom/info-elisp () (custom/open-info "elisp" "*elisp info*"))
  (defun custom/info-emacs () (custom/open-info "emacs" "*emacs info*"))
  (defun custom/info-gcl () (custom/open-info "gcl" "*hyperspec*"))
  :general
  (:keymaps 'mode-specific-map
            "C-h o" 'custom/open-org
            "C-h ?" 'custom/info-org-clocking
            "C-h e" 'custom/open-elisp
            "C-h m" 'custom/open-emacs
            "C-h h" 'custom/open-gcl)
  :config
  (info-initialize)
  (setq Info-additional-directory-list
        (list (concat home-directory "/help/info")))
  (add-to-list 'Info-directory-list "/usr/share/info")
  (add-to-list 'Info-directory-list
               (format "%selpa/%s"
                       user-emacs-directory
                       (car (directory-files (at-config-basedir "elpa") nil "^use-package-")))))

(use-package helpful
  :ensure t
  :general
  ;;TODO: investigate more concise way
  ;;TODO: use C-u version (catch up TAPs)
  (:prefix "<f1>"
           "f" 'helpful-function
           "v" 'helpful-variable
           "C" 'helpful-callable
           "m" 'helpful-macro
           "c" 'helpful-command
           "k" 'helpful-key
           "RET" 'helpful-at-point)
  (:prefix "C-h"
           "f" 'helpful-function
           "v" 'helpful-variable
           "C" 'helpful-callable
           "m" 'helpful-macro
           "c" 'helpful-command
           "k" 'helpful-key
           "RET" 'helpful-at-point))

(use-package help-find-org-mode
  :ensure t
  :pin melpa-stable
  :config (help-find-org-mode t))

(use-package info-buffer
  :ensure t
  :general
  ("C-h i" 'info-buffer))

(use-package info-colors
  :ensure t
  :hook (Info-selection-hook . info-colors-fontify-node))

(use-package woman
  :config
  (defalias 'man 'woman) ;'Woman' offers completion better than 'man'.
  (setenv "MANPATH" "/usr/share/man:/usr/local/man"))

(use-package apropos
  :general
  (:keymaps 'mode-specific-map
            "H a" 'apropos
            "H d" 'apropos-documentation
            "H v" 'apropos-variable
            "H c" 'apropos-command
            "H l" 'apropos-library
            "H u" 'apropos-user-option
            "H i" 'info-apropos
            "H t" 'tags-apropos
            "H e" 'apropos-value))