• Stars
    star
    166
  • Rank 227,748 (Top 5 %)
  • Language
    Emacs Lisp
  • Created almost 5 years ago
  • Updated 6 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

My literate Emacs configuration

My Literate Emacs Config

Screenshots

Initial Screen (Dashboard)

./screenshots/dashboard-rms.png

No, that’s a joke! I use the logo from tecosaur & MarioRicalde:

./screenshots/dashboard.png

Python Development

File explorer (treemacs), auto complete (company), git&github integration (magit, forge), terminal (vterm, shell-pop), markdown, python interpreter (ipython) ./screenshots/python.png

Org Mode (and Olivetti)

./screenshots/org-mode_and_olivetti.png

Helm & Which Key (Dired and Elisp Mode in the background)

./screenshots/helm-posframe.png

./screenshots/whichkey-posframe.png

Table Of Contents

About

Installation

Clone this repository to ~/.emacs.d or ~/.config/emacs

git clone https://github.com/KaratasFurkan/.emacs.d.git

Open Emacs and let the configuration install necessary packages.

Note: This configuration is not intended to be directly used by others, but it can be useful to get inspired or copy some parts of it. I use Emacs 28.0.50 with feature/native-comp branch, most of this configuration will work in old versions too but some parts needs Emacs 27+.

init.el

init.el is just used to load literate config.

(defconst config-org (locate-user-emacs-file "README.org"))
(defconst config-el (locate-user-emacs-file "config.el"))

(unless (file-exists-p config-el)
  (require 'org)
  (org-babel-tangle-file config-org config-el))

(load-file config-el)

early-init.el

Note that a few of the code blocks (mostly UI related) in this configuration tangle to early-init.el instead of config.el (which is the elisp file generated by this configuration) to get the effects in the very beginning of the initialization.

Applying Changes

(defun fk/tangle-config ()
  "Export code blocks from the literate config file
asynchronously."
  (interactive)
  ;; prevent emacs from killing until tangle-process finished
  (add-to-list 'kill-emacs-query-functions
               (lambda ()
                 (or (not (process-live-p (get-process "tangle-process")))
                     (y-or-n-p "\"fk/tangle-config\" is running; kill it? "))))
  ;; tangle config asynchronously
  (fk/async-process
   (format "emacs %s --batch --eval '(org-babel-tangle nil \"%s\")'" config-org config-el)
   "tangle-process"))

If the current org file is the literate config file, add a local hook to tangle code blocks on every save to update configuration.

(add-hook 'org-mode-hook
          (lambda ()
            (if (equal buffer-file-truename config-org)
                (fk/add-local-hook 'after-save-hook 'fk/tangle-config))))

Package Management

Straight

Installation & Initialization

Taken from: https://github.com/raxod502/straight.el#getting-started

(defvar bootstrap-version)
(let ((bootstrap-file
       (locate-user-emacs-file "straight/repos/straight.el/bootstrap.el"))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

Settings

To not increase Emacs startup time, check package modifications when packages edited (with Emacs) or manually invoke straight-check-all command, instead of checking modifications at startup.

Note: this setting should be set before the initialization of straight. early-init is a good place for this, so I used :tangle early-init.el here.

(setq straight-check-for-modifications '(check-on-save find-when-checking))

Straight uses symlinks in the build directory which causes xref-find-definition to ask =”Symbolic link to Git-controlled source file; follow link? (y or n)”= every time, to always answer yes, set vc-follow-symlinks true.

(setq vc-follow-symlinks t)

Use default depth of 1 when cloning files with git to get savings on network bandwidth and disk space.

(setq straight-vc-git-default-clone-depth 1)

Notes

  • M-x straight-pull-all: update all packages.
  • M-x straight-normalize-all: restore all packages (remove local edits)
  • M-x straight-freeze-versions and M-x straight-thaw-versions are like pip freeze requirements.txt and pip install -r requirements.txt
  • To tell straight.el that you want to use the version of Org shipped with Emacs, rather than cloning the upstream repository:

(Note: “:tangle no”)

(use-package org
  :straight (:type built-in))

Use-Package

Installation & Straight Integration

;; Install `use-package'.
(straight-use-package 'use-package)

;; Install packages in `use-package' forms with `straight'. (not the built-in
;; package.el)
(setq straight-use-package-by-default t)

;; Key Chord functionality in use-package. (I do not use it anymore.)
;; (use-package use-package-chords
;;   :hook
;;   (dashboard-after-initialize . (lambda () (key-chord-mode 1))))

Notes

  • Hooks in the :hook section, run in reverse order. Example:

(Note: “:tangle no”)

(use-package package-name
  :hook
  (x-mode . last)
  (x-mode . second)
  (x-mode . first))

Performance Optimization

A very nice source: https://github.com/hlissner/doom-emacs/blob/develop/docs/faq.org#how-does-doom-start-up-so-quickly

Garbage Collection

Make startup faster by reducing the frequency of garbage collection. Set gc-cons-threshold (the default is 800 kilobytes) to maximum value available, to prevent any garbage collection from happening during load time.

Note: tangle to early-init.el to make startup even faster

(setq gc-cons-threshold most-positive-fixnum)

Restore it to reasonable value after init. Also stop garbage collection during minibuffer interaction (helm etc.).

(defconst 1mb 1048576)
(defconst 20mb 20971520)
(defconst 30mb 31457280)
(defconst 50mb 52428800)

(defun fk/defer-garbage-collection ()
  (setq gc-cons-threshold most-positive-fixnum))

(defun fk/restore-garbage-collection ()
  (run-at-time 1 nil (lambda () (setq gc-cons-threshold 30mb))))

(add-hook 'emacs-startup-hook 'fk/restore-garbage-collection 100)
(add-hook 'minibuffer-setup-hook 'fk/defer-garbage-collection)
(add-hook 'minibuffer-exit-hook 'fk/restore-garbage-collection)

(setq read-process-output-max 1mb)  ;; lsp-mode's performance suggest

File Handler

(Note: “:tangle early-init.el”)

(defvar default-file-name-handler-alist file-name-handler-alist)
(setq file-name-handler-alist nil)

(add-hook 'emacs-startup-hook
          (lambda ()
            (setq file-name-handler-alist default-file-name-handler-alist)) 95)

Others

Copied from Doom Emacs: (Note: “:tangle early-init.el”)

;; In Emacs 27+, package initialization occurs before `user-init-file' is
;; loaded, but after `early-init-file'. straight.el handles package
;; initialization, so we must prevent Emacs from doing it early!
(setq package-enable-at-startup nil)
(advice-add 'package--ensure-init-file :override 'ignore)

;; Resizing the Emacs frame can be a terribly expensive part of changing the
;; font. By inhibiting this, we easily halve startup times with fonts that are
;; larger than the system default.
(setq frame-inhibit-implied-resize t)

Custom Functions

measure-time

(Note: “:tangle early-init.el”)

(defmacro fk/measure-time (&rest body)
  "Measure the time it takes to evaluate BODY."
  `(let ((time (current-time)))
     ,@body
     (message "%s" (float-time (time-since time)))))

time-since-startup

(Note: “:tangle early-init.el”)

(defun fk/time-since-startup (&optional prefix)
  "Display the time that past since emacs startup. Add PREFIX if given at the
start of message for debug purposes."
  (interactive)
  (let* ((prefix (or prefix ""))
         (time (float-time (time-since before-init-time)))
         (str (format "%s%s seconds" prefix time)))
    (if (or (not (string-empty-p prefix))
            (called-interactively-p 'interactive))
        (message str)
      str)))

time-since-last-check

(Note: “:tangle early-init.el”)

(defvar fk/time-last-check nil)
(defvar fk/time-threshold 0)
(setq fk/time-threshold 0.02)

(defun fk/time-since-last-check (&optional prefix)
  "Display the time that past since last check. Add PREFIX if given at the
start of message for debug purposes."
  (interactive)
  (let* ((prefix (or prefix ""))
         (time (float-time (time-since (or fk/time-last-check before-init-time))))
         (str (format "%s%s seconds" prefix time)))
    (setq fk/time-last-check (current-time))
    (if (or (not (string-empty-p prefix))
            (called-interactively-p 'interactive))
        (when (> time fk/time-threshold) (message "%s" str))
      str)))

Better Defaults

File Paths

Keep Emacs directory clean.

(use-package no-littering
  :config
  (with-eval-after-load 'recentf
    (add-to-list 'recentf-exclude no-littering-var-directory)
    (add-to-list 'recentf-exclude no-littering-etc-directory))

  (setq auto-save-file-name-transforms  ; autosaved-file-name~
        `((".*" ,(no-littering-expand-var-file-name "auto-save/") t))
        custom-file (no-littering-expand-etc-file-name "custom.el"))

  (when (file-exists-p custom-file)
    ;; Load `custom-set-variables', not load whole `custom.el' with unwanted
    ;; `custom-set-faces'
    (with-current-buffer (find-file-noselect custom-file)
      (goto-char 0)
      (forward-sexp)
      (call-interactively 'eval-last-sexp)
      (kill-buffer)))

  (defconst fk/static-directory (locate-user-emacs-file "static/"))

  (defun fk/expand-static-file-name (file)
    "Expand filename FILE relative to `fk/static-directory'."
    (expand-file-name file fk/static-directory)))

General

(setq-default
 ring-bell-function 'ignore                    ; prevent beep sound.
 inhibit-startup-screen t                      ; TODO: maybe better on early-init or performance?
 initial-major-mode 'fundamental-mode          ; TODO: maybe better on early-init or performance?
 initial-scratch-message nil                   ; TODO: maybe better on early-init?
 create-lockfiles nil                          ; .#locked-file-name
 confirm-kill-processes nil                    ; exit emacs without asking to kill processes
 backup-by-copying t                           ; prevent linked files
 require-final-newline t                       ; always end files with newline
 delete-old-versions t                         ; don't ask to delete old backup files
 revert-without-query '(".*")                  ; `revert-buffer' without confirmation
 uniquify-buffer-name-style 'forward           ; non-unique buffer name display: unique-part/non-unique-filename
 fast-but-imprecise-scrolling t                ; supposed to make scrolling faster on hold
 window-resize-pixelwise t                     ; correctly resize windows by pixels (e.g. in split-window functions)
 native-comp-async-report-warnings-errors nil  ; disable annoying native-comp warnings
 ad-redefinition-action 'accept                ; disable annoying "ad-handle-definition: ‘some-function’ got redefined" warnings
 use-short-answers t                           ; e.g. `y-or-n-p' instead of `yes-or-no-p'
 help-enable-symbol-autoload t)                ; perform autoload if docs are missing from autoload objects.

(global-auto-revert-mode)

(save-place-mode)

(global-so-long-mode)

(bind-key* "M-r" 'repeat)

(defun fk/add-local-hook (hook function)
  "Add buffer-local hook."
  (add-hook hook function :local t))

(defun fk/async-process (command &optional name filter)
  "Start an async process by running the COMMAND string with bash. Return the
process object for it.

NAME is name for the process. Default is \"async-process\".

FILTER is function that runs after the process is finished, its args should be
\"(process output)\". Default is just messages the output."
  (make-process
   :command `("bash" "-c" ,command)
   :name (if name name
           "async-process")
   :filter (if filter filter
             (lambda (process output) (message (s-trim output))))))

;; Examples:
;;
;; (fk/async-process "ls")
;;
;; (fk/async-process "ls" "my ls process"
;;                   (lambda (process output) (message "Output:\n\n%s" output)))
;;
;; (fk/async-process "unknown command")

;; Make sure to focus when a new emacsclient frame created.
(add-hook 'server-after-make-frame-hook (lambda () (select-frame-set-input-focus (selected-frame))))

(defalias 'narrow-quit 'widen)  ; I forget `widen' everytime

;; TODO: lset would be useful too
(defmacro l (func &rest args)
  "Shorter lambda."
  `(lambda nil (apply ,func '(,@args))))

(defmacro li (func &rest args)
  "Shorter lambda, interactive."
  `(lambda nil (interactive) (apply ,func '(,@args))))

;; Examples:
;; (global-set-key (kbd "C-V") (lambda () (interactive) (next-line 10))) <-- Classical
;; (global-set-key (kbd "C-V") (li 'next-line 10)) <-- With li macro

Helpful

A better, more detailed help buffer.

(use-package helpful
  :custom
  ;; Use helpful in `helm-apropos'
  (helm-describe-function-function 'helpful-function)
  (helm-describe-variable-function 'helpful-variable)
  :bind
  (([remap describe-function] . helpful-callable)
   ([remap describe-variable] . helpful-variable)
   ([remap describe-key] . helpful-key)
   :map emacs-lisp-mode-map
   ("C-c C-d" . helpful-at-point)))

Menu Style Keybindings

Menu style keybindings like Spacemacs.

;; NOTE: I use F1 as C-h (paging & help).
(bind-keys*
 :prefix-map fk/menu-map
 :prefix "M-m"
 ("M-m" . which-key-show-major-mode)
 ("M-h" . help-command)
 ("M-u" . universal-argument)
 :map fk/menu-map :prefix-map buffers         :prefix "b"
 :map fk/menu-map :prefix-map comments        :prefix "c"
 :map fk/menu-map :prefix-map django          :prefix "d"
 :map fk/menu-map :prefix-map errors          :prefix "e"
 :map fk/menu-map :prefix-map files           :prefix "f"
 :map fk/menu-map :prefix-map org             :prefix "o"
 :map fk/menu-map :prefix-map text            :prefix "t"
 :map fk/menu-map :prefix-map version-control :prefix "v"
 :map fk/menu-map :prefix-map windows         :prefix "w")

Local Variables

(defun fk/straight-ignore-local-variables (orig-func &rest args)
  "Ignore local variables when visiting an installed package
which is generally not intended to be edited."
  (unless (string-prefix-p (straight--dir) default-directory)
    (apply orig-func args)))

(advice-add 'hack-local-variables-confirm :around 'fk/straight-ignore-local-variables)

Appearance

Notes

  • To start Emacs maximized: $ emacs -mm
  • To start Emacs fullscreen: $ emacs -fs

Better Defaults

(global-hl-line-mode)
(blink-cursor-mode -1)

(setq-default
 truncate-lines t
 frame-resize-pixelwise t             ; maximized emacs may not fit screen without this
 frame-title-format '("Emacs | %b"))  ; Emacs | buffer-name

Custom Functions

disable-all-themes

(defun fk/disable-all-themes ()
  "Disable all active themes."
  (interactive)
  (dolist (theme custom-enabled-themes)
    (disable-theme theme)))

darken-background

I use this to darken non-file buffers like treemacs, helm etc.

(defun fk/darken-background ()
  "Darken the background of the buffer."
  (interactive)
  (face-remap-add-relative 'default :background fk/dark-color))

presentation-mode

(define-minor-mode fk/presentation-mode
  "A global minor mode for presentations. Make things easy to see."
  :global t
  (if fk/presentation-mode
      (progn
        (fk/adjust-font-size 40)
        (dimmer-mode 1)
        (setq zoom-size '(100 . 30))
        (zoom-mode 1)
        (setq default-window-divider-default-bottom-width window-divider-default-bottom-width
              default-window-divider-default-right-width window-divider-default-right-width)
        (setq window-divider-default-bottom-width 7
              window-divider-default-right-width 7)
        (window-divider-mode 1)
        (set-face-attribute 'olivetti-borders-face nil :background fk/darker-olivetti-borders-color)
        (olivetti-mode 1)
        (goggles-mode 1))
    (fk/adjust-font-size 0)
    (dimmer-mode -1)
    (setq zoom-size fk/zoom-default-size)
    (zoom-mode -1)
    (setq window-divider-default-bottom-width default-window-divider-default-bottom-width
          window-divider-default-right-width default-window-divider-default-right-width)
    (window-divider-mode 1)
    (set-face-attribute 'olivetti-borders-face nil :background fk/default-olivetti-borders-color)
    (olivetti-mode 1)
    (goggles-mode -1)))

toggle-ui-elements

(defun fk/toggle-ui-elements (&optional arg)
  "Toggle `display-line-numbers-mode', `highlight-indent-guides-mode' and
`display-fill-column-indicator-mode'."
  (interactive)
  (display-line-numbers-mode (or arg (if display-line-numbers-mode -1 1)))
  (highlight-indent-guides-mode (or arg (if highlight-indent-guides-mode -1 1)))
  (display-fill-column-indicator-mode (or arg (if display-fill-column-indicator-mode -1 1))))

(add-hook 'prog-mode-hook (lambda () (fk/toggle-ui-elements -1)) 100)

Remove Redundant UI

(Note: “:tangle early-init.el”)

(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
;; Do not show default modeline until doom-modeline is loaded
(setq-default mode-line-format nil)

Good Scroll (Smooth scrolling)

(use-package good-scroll
  :straight (:host github :repo "io12/good-scroll.el")
  :commands good-scroll-mode
  :custom
  (good-scroll-duration 0.2)
  (good-scroll-point-jump 4)
  ;; :bind
  ;; ("C-v" . fk/smooth-scroll-up)
  ;; ("M-v" . fk/smooth-scroll-down)
  ;; ("C-l" . fk/smooth-recenter-top-bottom)
  ;; :hook
  ;; (dashboard-after-initialize . good-scroll-mode)
  :config
  (defun fk/smooth-scroll-down (&optional pixels)
    "Smooth alternative of M-v `scroll-down-command'."
    (interactive)
    (let ((good-scroll-step (or pixels 300)))
      (good-scroll-down)))

  (defun fk/smooth-scroll-up (&optional pixels)
    "Smooth alternative of C-v `scroll-up-command'."
    (interactive)
    (let ((good-scroll-step (or pixels 300)))
      (good-scroll-up)))

  (defun fk/smooth-recenter-top-bottom ()
    "docstring"
    (interactive)
    (let* ((current-row (cdr (nth 6 (posn-at-point))))
           (target-row (save-window-excursion
                         (recenter-top-bottom)
                         (cdr (nth 6 (posn-at-point)))))
           (distance-in-pixels (* (- target-row current-row) (line-pixel-height)))
           (good-scroll-step distance-in-pixels))
      (when (not (zerop distance-in-pixels))
        (good-scroll--update -1)))))

Window Dividers

Change default window dividers to a better built-in alternative. (Note: “:tangle early-init.el”)

(setq window-divider-default-places t
      window-divider-default-bottom-width 1
      window-divider-default-right-width 1)

(window-divider-mode)

Font

Font

(defconst fk/default-font-family "RobotoMono Nerd Font")
(defconst fk/default-font-size 90)
(defconst fk/default-icon-size 15)

(defconst fk/variable-pitch-font-family "Noto Serif")

(custom-set-faces
 `(default ((t (:family ,fk/default-font-family :height ,fk/default-font-size))))
 `(variable-pitch ((t (:family ,fk/variable-pitch-font-family :height 1.0))))
 ;; Characters with fixed pitch face do not shown when height is 90.
 `(fixed-pitch-serif ((t (:height 1.2)))))

Custom Functions

adjust-font-size

(defun fk/adjust-font-size (height)
  "Adjust font size by given height. If height is '0', reset font
size. This function also handles icons and modeline font sizes."
  (interactive "nHeight ('0' to reset): ")
  (let ((new-height (if (zerop height)
                        fk/default-font-size
                      (+ height (face-attribute 'default :height)))))
    (set-face-attribute 'default nil :height new-height)
    (set-face-attribute 'mode-line nil :height new-height)
    (set-face-attribute 'mode-line-inactive nil :height new-height)
    (message "Font size: %s" new-height))
  (let ((new-size (if (zerop height)
                      fk/default-icon-size
                    (+ (/ height 5) treemacs--icon-size))))
    (when (fboundp 'treemacs-resize-icons)
      (treemacs-resize-icons new-size))
    (when (fboundp 'company-box-icons-resize)
      (company-box-icons-resize new-size)))
  (when diff-hl-mode
    (diff-hl-maybe-redefine-bitmaps)))

increase-font-size

(defun fk/increase-font-size ()
  "Increase font size by 0.5 (5 in height)."
  (interactive)
  (fk/adjust-font-size 5))

decrease-font-size

(defun fk/decrease-font-size ()
  "Decrease font size by 0.5 (5 in height)."
  (interactive)
  (fk/adjust-font-size -5))

reset-font-size

(defun fk/reset-font-size ()
  "Reset font size according to the `fk/default-font-size'."
  (interactive)
  (fk/adjust-font-size 0))

Keybindings

(global-set-key (kbd "C-=") 'fk/increase-font-size)
(global-set-key (kbd "C--") 'fk/decrease-font-size)
(global-set-key (kbd "C-0") 'fk/reset-font-size)

Theme

Theme

(use-package doom-themes
  :custom-face
  (font-lock-comment-face ((t (:slant italic))))
  (font-lock-string-face ((t (:foreground "PeachPuff3"))))
  (font-lock-function-name-face ((t (:foreground "LightGoldenrod"))))
  (highlight ((t (:underline t :background nil :foreground nil))))
  (lazy-highlight ((t (:background nil :foreground nil :box (:line-width -1)))))
  (fixed-pitch ((t (:family "Noto Sans Mono"))))
  :config
  (load-theme 'doom-spacegrey t)
  (defconst fk/cursor-color (if (daemonp) "#D08770" (face-background 'cursor)))
  (defconst fk/font-color (if (daemonp) "#C0C5CE" (face-foreground 'default)))
  (defconst fk/background-color (if (daemonp) "#2B303B" (face-background 'default)))
  (defconst fk/dark-color (doom-darken fk/background-color 0.15))
  (defconst fk/dark-color1 (doom-darken fk/background-color 0.01))
  (defconst fk/dark-color2 (doom-darken fk/background-color 0.02))
  (defconst fk/dark-color3 (doom-darken fk/background-color 0.03))
  (defconst fk/dark-color4 (doom-darken fk/background-color 0.04))
  (defconst fk/dark-color5 (doom-darken fk/background-color 0.05))
  (defconst fk/dark-color6 (doom-darken fk/background-color 0.06))
  (defconst fk/dark-color7 (doom-darken fk/background-color 0.07))
  (defconst fk/dark-color8 (doom-darken fk/background-color 0.08))
  (defconst fk/dark-color9 (doom-darken fk/background-color 0.09))
  (defconst fk/light-color (doom-lighten fk/background-color 0.15))
  (defconst fk/light-color1 (doom-lighten fk/background-color 0.09))
  (defconst fk/light-color2 (doom-lighten fk/background-color 0.08))
  (defconst fk/light-color3 (doom-lighten fk/background-color 0.07))
  (defconst fk/light-color4 (doom-lighten fk/background-color 0.06))
  (defconst fk/light-color5 (doom-lighten fk/background-color 0.05))
  (defconst fk/light-color6 (doom-lighten fk/background-color 0.04))
  (defconst fk/light-color7 (doom-lighten fk/background-color 0.03))
  (defconst fk/light-color8 (doom-lighten fk/background-color 0.02))
  (defconst fk/light-color9 (doom-lighten fk/background-color 0.01)))

Settings

Disable all themes before loading a theme

(defadvice load-theme (before disable-themes-first activate)
  (fk/disable-all-themes))

load-theme without annoying confirmation

(advice-add 'load-theme
            :around
            (lambda (fn theme &optional no-confirm no-enable)
              (funcall fn theme t)))

Alternatives

A light emacs theme that’s well suited for org-mode

(use-package poet-theme
  :defer t)

Mode Line

Doom Modeline

(use-package doom-modeline
  :init
  ;; show doom-modeline at the same time with dashboard
  (add-hook 'emacs-startup-hook 'doom-modeline-mode -100)
  :custom
  (doom-modeline-buffer-encoding nil)
  (doom-modeline-vcs-max-length 40)
  (doom-modeline-bar-width 1)
  (doom-modeline-env-python-executable "python")
  :custom-face
  (mode-line ((t (:background ,fk/dark-color))))
  (mode-line-inactive ((t (:background ,fk/dark-color5))))
  (mode-line-highlight ((t (:inherit cursor :foreground "black"))))
  (doom-modeline-bar ((t (:background ,fk/dark-color))))
  (doom-modeline-buffer-path ((t (:inherit font-lock-comment-face :slant normal))))
  :hook
  (dashboard-after-initialize . column-number-mode))

Minibuffer Modeline

;; TODO: check `set-message-functions' to fix losing minibuffer modeline
(defvar fk/minibuffer-modeline--message nil)

(defun fk/minibuffer-modeline-update ()
  "Show global info in minibuffer instead of modeline."
  (let* ((org-clock-string (when (boundp 'fk/org-clock-string) fk/org-clock-string))
         (pyvenv-icon (all-the-icons-icon-for-mode 'python-mode :height 0.9 :v-adjust 0.01))
         (pyvenv (when (and (featurep 'pyvenv) pyvenv-virtual-env-name)
                   (format "[%s %s]" pyvenv-icon pyvenv-virtual-env-name)))
         (perspectives (when (featurep 'perspective)
                         (string-join (let ((persp-show-modestring t))
                                        (persp-update-modestring)
                                        (persp-mode-line)))))
         (pomidor (when (and (featurep 'pomidor) pomidor-timer)
                    (format " [%s]"
                            (string-limit
                             (let* ((break (pomidor--break-duration (car (last pomidor-global-state))))
                                    (overwork (pomidor--overwork-duration (car (last pomidor-global-state))))
                                    (work (pomidor--work-duration (car (last pomidor-global-state)))))
                               (cond
                                (break (propertize (pomidor--format-duration break) 'face 'pomidor-break-face))
                                (overwork (propertize (pomidor--format-duration overwork) 'face 'pomidor-overwork-face))
                                (work (propertize (pomidor--format-duration work) 'face 'pomidor-work-face)))) 5))))
         (time (propertize (format-time-string " %H:%M %a %d/%m" (current-time)) 'face 'font-lock-comment-face))
         (info (string-join (list org-clock-string pyvenv perspectives pomidor time) " "))
         (message (if fk/minibuffer-modeline--message fk/minibuffer-modeline--message ""))
         (right-padding 2)
         (left-padding (make-string (max 0 (- (frame-width) (length message) (length info) right-padding)) ?\ )))
    (setq fk/minibuffer-modeline--message nil)
    (with-current-buffer " *Minibuf-0*"
      (erase-buffer)
      (insert message left-padding info))))

(add-hook 'post-command-hook 'fk/minibuffer-modeline-update)

(setq fk/minibuffer-modeline-timer (run-at-time nil 10 'fk/minibuffer-modeline-update))

;;; Advices to not lose minibuffer modeline info  ; FIXME: breaks when using isearch

;; (defun fk/minibuffer-modeline-message (func &rest args)
;;   "Show message and modeline info at the same time."
;;   (unless inhibit-message
;;     (setq fk/minibuffer-modeline--message (apply func args))
;;     (fk/minibuffer-modeline-update)
;;     fk/minibuffer-modeline--message))

;; (advice-add 'message :around 'fk/minibuffer-modeline-message)

Anzu

(use-package anzu
  :hook
  (dashboard-after-initialize . global-anzu-mode))

Page Break Lines

(use-package page-break-lines
  :custom-face
  (page-break-lines ((t (:inherit font-lock-comment-face :foreground ,fk/light-color1 :width expanded))))
  :hook
  (dashboard-after-initialize . global-page-break-lines-mode)
  :config
  (add-to-list 'page-break-lines-modes 'c-mode)
  (defun fk/insert-page-break-line ()
    "Insert a page break line character ''."
    (interactive)
    (insert "")))

(global-set-key (kbd "C-,") 'quoted-insert)

Trailing White Space-

Highlight TODOs

(use-package hl-todo
  :custom
  ;; Better hl-todo colors, taken from spacemacs
  (hl-todo-keyword-faces '(("TODO" . "#dc752f")
                           ("NEXT" . "#dc752f")
                           ("THEM" . "#2d9574")
                           ("PROG" . "#4f97d7")
                           ("OKAY" . "#4f97d7")
                           ("DONT" . "#f2241f")
                           ("FAIL" . "#f2241f")
                           ("DONE" . "#86dc2f")
                           ("NOTE" . "#b1951d")
                           ("KLUDGE" . "#b1951d")
                           ("HACK" . "#b1951d")
                           ("TEMP" . "#b1951d")
                           ("QUESTION" . "#b1951d")
                           ("HOLD" . "#dc752f")
                           ("FIXME" . "#dc752f")
                           ("XXX+" . "#dc752f")))
  :hook
  (dashboard-after-initialize . global-hl-todo-mode))

Beacon

(use-package beacon
  :custom
  ;; beacon-mode doesn't work properly with same color as cursor
  (beacon-color (doom-darken fk/cursor-color 0.001))
  ;; (beacon-blink-when-point-moves-vertically 10)
  (beacon-dont-blink-major-modes '(dashboard-mode minibuff))
  :config
  (defun fk/beacon-blink ()
    "`beacon-blink' with `beacon-dont-blink-major-modes' control."
    (interactive)
    (unless (seq-find 'derived-mode-p beacon-dont-blink-major-modes)
      (beacon-blink)))
  ;; `beacon-blink' manually instead of activating `beacon-mode' to not
  ;; calculate every time on post-command-hook if should beacon blink
  ;; TODO: create a global minor mode with this: `fk/manual-beacon-mode'
  (dolist (command '(other-window
                     winum-select-window-by-number
                     scroll-up-command
                     scroll-down-command
                     recenter-top-bottom
                     ;; fk/smooth-scroll-up
                     ;; fk/smooth-scroll-down
                     ;; fk/smooth-recenter-top-bottom
                     move-to-window-line-top-bottom
                     ace-select-window
                     ace-swap-window
                     aw-flip-window
                     avy-goto-word-or-subword-1
                     avy-pop-mark))
    (eval `(defadvice ,command (after blink activate)
             (fk/beacon-blink))))
  (dolist (hook '(find-file-hook
                  xref-after-jump-hook
                  xref-after-return-hook
                  persp-switch-hook))
    (add-hook hook 'fk/beacon-blink)))

All The Icons

;; Prerequisite for a few packages (e.g. treemacs, all-the-icons-dired)
;; "M-x all-the-icons-install-fonts" to install fonts at the first time.
(use-package all-the-icons)

Highlight Indent Guides

(use-package highlight-indent-guides
  :custom
  (highlight-indent-guides-method 'character)
  (highlight-indent-guides-responsive 'top)
  (highlight-indent-guides-auto-enabled nil)
  :custom-face  ; NOTE: The character does not work with "RobotoMono Nerd Font"
  (highlight-indent-guides-character-face ((t (:family "Source Code Pro" :foreground ,fk/light-color7))))
  (highlight-indent-guides-top-character-face ((t (:family "Source Code Pro" :foreground ,fk/light-color5))))
  :hook
  (prog-mode . highlight-indent-guides-mode))

Shackle

(use-package shackle
  :custom
  (shackle-default-size 0.4)
  (shackle-rules '(("\\`\\*helm.*?\\*\\'" :regexp t :align t)  ; I use helm-posframe now, this is unnecessary but i want to keep just in case
                   ("\\`\\*helpful.*?\\*\\'" :regexp t :align t)
                   ("\\`\\*Go Translate*?\\*\\'" :regexp t :align t)
                   (help-mode :align t :select t)
                   (org-agenda-mode :align t :select t)))
  :hook
  (dashboard-after-initialize . shackle-mode))

Zoom

;; TODO: Add a function to set window width to fill column width
;; according to current major mode
(use-package zoom
  :commands zoom-mode
  :preface
  (defvar fk/zoom-default-size '(120 . 40))
  :custom
  (zoom-size fk/zoom-default-size)
  :bind*
  (("C-M-=" . fk/enlarge-window)
   ("C-M--" . fk/shrink-window)
   ("C-M-0" . balance-windows))
  :config
  ;; TODO: handle when zoom-mode active
  (defun fk/adjust-window-width (percentage)
    (if (and olivetti-mode (= (count-windows) 1))
        (if (> percentage 1.0) (olivetti-expand) (olivetti-shrink))
      (let* ((new-width (round (* (window-width) percentage)))
             (zoom-size (cons new-width (cdr zoom-size))))
        (if (> percentage 1.0)  ; TODO: fk/smooth-zoom do not shrink
            (fk/smooth-zoom)
          (zoom)))))

  (defun fk/enlarge-window ()
    (interactive)
    (fk/adjust-window-width 1.1))

  (defun fk/shrink-window ()
    (interactive)
    (fk/adjust-window-width 0.9))

  (defvar fk/smooth-zoom-steps 10)
  (defvar fk/smooth-zoom-period 0.01)

  (defun fk/floor (number)
    "Floor by absolute value."
    (if (< number 0)
        (ceiling number)
      (floor number)))

  (defun fk/smooth-zoom ()
    "Smooth (animated) version of `zoom'."
    (interactive)
    (cancel-function-timers 'fk/smooth-zoom--resize)
    (setq fk/smooth-zoom-sizes '())
    (setq fk/smooth-zoom-window (get-buffer-window))
    (let* ((current-size (cons (window-width) (window-height)))
           (desired-size zoom-size)
           (distances (cons (- (car desired-size) (car current-size))
                            (- (cdr desired-size) (cdr current-size))))
           (step-distance (cons (fk/floor (/ (car distances) (float fk/smooth-zoom-steps)))
                                (fk/floor (/ (cdr distances) (float fk/smooth-zoom-steps))))))
      (dotimes (i fk/smooth-zoom-steps)
        (let* ((zoom-size (if (< i (1- fk/smooth-zoom-steps))
                              (cons (+ (car step-distance) (car current-size))
                                    (+ (cdr step-distance) (cdr current-size)))
                            desired-size))
               (time (concat (number-to-string (round (* i fk/smooth-zoom-period 1000))) " millisec")))
          (setq current-size zoom-size)
          (add-to-list 'fk/smooth-zoom-sizes current-size t)
          (run-at-time time nil 'fk/smooth-zoom--resize)))))

  (defun fk/smooth-zoom--resize ()
    (with-selected-window fk/smooth-zoom-window
      (let ((zoom-size (pop fk/smooth-zoom-sizes)))
        (zoom--resize)))))

Emacs Dashboard

(use-package dashboard
  :custom
  ;; Source for logo: https://github.com/tecosaur/emacs-config/blob/master/config.org#splash-screen
  (dashboard-startup-banner (fk/expand-static-file-name "logos/emacs-e-medium.png"))
  ;; Do not show package count, it is meaningless because of lazy loading.
  (dashboard-banner-logo-title "Welcome to Emacs!                          \n")
  (dashboard-init-info (format "  Emacs started in %s\n\n" (fk/time-since-startup)))
  (dashboard-set-heading-icons t)
  (dashboard-set-file-icons t)
  (dashboard-center-content t)
  (dashboard-items '((agenda . 0)  ; I override the insert-agenda function
                     (todo-items . 0)  ; Custom section
                     (inbox-entries . 0)))  ; Custom section
  :custom-face
  (dashboard-heading ((t (:inherit font-lock-keyword-face :height 1.2))))
  (dashboard-items-face ((t (:weight normal))))
  (dashboard-banner-logo-title ((t (:family "AV Qest" :height 3.0 :weight bold :foreground "#8583C7"))))
  :hook
  (dashboard-mode . (lambda () (setq-local cursor-type nil)))
  :config
  (dashboard-setup-startup-hook)

  ;; Run the hooks even if dashboard initialization is skipped
  (when (> (length command-line-args) 1)
    (add-hook 'emacs-startup-hook (lambda () (run-hooks 'dashboard-after-initialize-hook))))

  (defun fk/home ()
    "Switch to home (dashboard) buffer."
    (interactive)
    (if (get-buffer dashboard-buffer-name)
        (switch-to-buffer dashboard-buffer-name)
      (dashboard-refresh-buffer)))

  (defun fk/dashboard-get-section (expression)
    "Get expression output from Emacs daemon. Faster than reading it
in normal way if required libraries are already loaded in
daemon."
    (let* ((output-buffer (generate-new-buffer "*dashboard-temp*"))
           (exit-status (call-process "emacsclient" nil output-buffer nil
                                      "--eval" expression)))
      (if (zerop exit-status)
          (let* ((output (with-current-buffer output-buffer
                           (buffer-substring-no-properties (point-min) (point-max))))
                 (clean-output (string-trim (string-replace "#<marker" "<marker" output)))
                 (propertized-output (car (read-from-string clean-output))))
            (kill-buffer output-buffer)
            propertized-output)
        "Emacs server (daemon) is not running, Section couldn't loaded.")))

  ;; TODO: convert these string codes to normal code, investigate how emacs-async do that
  (defun fk/dashboard-get-agenda ()
    "Get a copy of the agenda buffer from Emacs daemon."
    (fk/dashboard-get-section
     "(progn
        (setq org-agenda-span 2)
        (org-agenda-list)
        (read-only-mode -1)
        (goto-char (point-min))
        (kill-line)
        (buffer-string))"))

  (defun dashboard-insert-agenda (&rest _)
    "Insert a copy of org-agenda buffer."
    (dashboard-insert-heading "Agenda for today:")
    (insert (fk/dashboard-get-agenda)))

  (defun fk/dashboard-get-inbox-entries ()
    ;; TODO: appearance is not consistent, seems like there is some sort of caching
    "Get inbox entry list from Emacs daemon."
    (fk/dashboard-get-section
     "(let* ((file (expand-file-name \"inbox.org\" org-directory))
             (file-buffer (find-file-noselect file))
             (file-content (with-current-buffer file-buffer (buffer-string)))
             (temp-buffer (generate-new-buffer \"*dashboard-temp*\"))
             (bullet (propertize \"\" 'face 'org-level-1)))
        (with-current-buffer temp-buffer
          (kill-buffer file-buffer)
          (org-mode)
          (insert file-content)
          (delete-non-matching-lines \"^*\" (point-min) (point-max))
          (string-replace \"*\" (format \"  %s\" bullet) (string-replace \"**\" (format \"   %s\" bullet) (buffer-string)))))"))

  (defun fk/dashboard-insert-inbox-entries (&rest _)
    "Insert inbox entries items."
    (insert (all-the-icons-octicon "pin" :height 1.2 :v-adjust 0.02 :face 'dashboard-heading)
            (propertize " Inbox Entries:\n" 'face 'dashboard-heading 'line-spacing 10)
            (fk/dashboard-get-inbox-entries)))

  (defun fk/dashboard-get-todo-items ()
    "Get high priority todo items from Emacs daemon."
    (fk/dashboard-get-section
     "(let* ((file (expand-file-name \"todos.org\" org-directory))
             (file-buffer (find-file-noselect file))
             (file-content (with-current-buffer file-buffer (buffer-string)))
             (temp-buffer (generate-new-buffer \"*dashboard-temp*\"))
             (bullet (propertize \"\" 'face 'org-level-1)))
        (with-current-buffer temp-buffer
          (kill-buffer file-buffer)
          (org-mode)
          (insert file-content)
          (delete-matching-lines (regexp-quote \"[#B]\") (point-min) (point-max))
          (delete-matching-lines (regexp-quote \"[#C]\") (point-min) (point-max))
          (delete-non-matching-lines \"^*\" (point-min) (point-max))
          (string-replace \"*\" (format \"  %s\" bullet) (string-replace \"**\" (format \"   %s\" bullet) (buffer-string)))))"))

  (defun fk/dashboard-insert-todo-items (&rest _)
    "Insert high priority todo items."
    (insert (all-the-icons-octicon "checklist" :height 1.2 :v-adjust 0.02 :face 'dashboard-heading)
            (propertize " TODOs:\n" 'face 'dashboard-heading 'line-spacing 10)
            (fk/dashboard-get-todo-items)))

  (add-to-list 'dashboard-item-generators  '(inbox-entries . fk/dashboard-insert-inbox-entries))
  (add-to-list 'dashboard-item-generators  '(todo-items . fk/dashboard-insert-todo-items))

  ;; Colorize org entries even if org.el or org-agenda.el hasn't loaded.
  ;; Note: defining faces is enough, color values comes from propertized string
  (defmacro fk/defface-nil (&rest faces)
    "Macro for defining nil faces. Instead of:
`(defface org-level-1 nil nil)'"
    `(progn ,@(cl-loop for face in faces
                       collect `(defface ,face nil nil))))
  (fk/defface-nil
   org-agenda-calendar-event
   org-agenda-current-time
   org-agenda-date
   org-agenda-date-today
   org-agenda-date-weekend
   org-agenda-date-weekend
   org-agenda-date-weekend-today
   org-agenda-structure
   org-checkbox-statistics-todo
   org-habit-alert-face
   org-habit-clear-future-face
   org-habit-overdue-future-face
   org-habit-ready-face
   org-hide
   org-imminent-deadline
   org-level-1
   org-level-2
   org-link
   org-scheduled
   org-scheduled-today
   org-super-agenda-header
   org-tag
   org-time-grid
   org-upcoming-deadline
   org-upcoming-distant-deadline
   org-warning))

Stripe Buffer

(use-package stripe-buffer
  :custom-face
  (stripe-highlight ((t (:background ,fk/light-color7))))
  :config
  ;; hl-line (higher priority stripes) fix:
  (defadvice sb/redraw-region (after stripe-set-priority activate)
    (when (or stripe-buffer-mode stripe-table-mode)
      (dolist (overlay sb/overlays)
        (overlay-put overlay 'priority -100))))
  ;; :hook
  ;; (org-mode . turn-on-stripe-table-mode)
  )

Fill Column Indicator

(use-package display-fill-column-indicator
  :straight (:type built-in)
  :custom
  (display-fill-column-indicator-character ?│)
  :custom-face  ; NOTE: The character above does not work with "Roboto Mono"
  (fill-column-indicator ((t (:family "Source Code Pro" :foreground ,fk/light-color7))))
  :hook
  (prog-mode . display-fill-column-indicator-mode))

Line Numbers

(use-package display-line-numbers
  :straight (:type built-in)
  :custom-face
  (line-number ((t (:foreground ,fk/light-color2))))
  (line-number-current-line ((t (:foreground ,fk/light-color))))
  :hook
  (prog-mode . display-line-numbers-mode))

Dired Icons-

Rainbow Delimiters-

Helm Icons-

Symbol Overlay-

Olivetti

(use-package olivetti
  :straight (:host github :repo "KaratasFurkan/olivetti")
  :preface
  ;; Body width
  (setq fk/olivetti-body-width-default 120)
  (setq fk/olivetti-body-width-large 180)
  (setq olivetti-body-width fk/olivetti-body-width-default)
  ;; Borders
  (setq olivetti-enable-borders t)
  (setq fk/default-olivetti-borders-color fk/dark-color2)
  (setq fk/darker-olivetti-borders-color fk/dark-color9)
  :custom
  (olivetti-enable-visual-line-mode nil)
  (olivetti-window-local t)
  :custom-face
  (olivetti-borders-face ((t (:background ,fk/default-olivetti-borders-color))))
  :bind*
  (("C-1" . fk/smart-C-x-1)
   :map windows
   ("c" . olivetti-mode)
   :map windows
   :prefix-map olivetti
   :prefix "o"
   ("o" . global-olivetti-mode)
   ("e" . olivetti-expand)
   ("s" . olivetti-shrink))
  :hook
  (dashboard-after-initialize . global-olivetti-mode)
  :config
  (setq olivetti-excluded-buffer-regexps
        `(,@olivetti-excluded-buffer-regexps
          "\\`\\*vterm" "*fireplace*"))

  (defun fk/smart-C-x-1 ()
    (interactive)
    (if (= (count-windows) 1)
        (if (and global-olivetti-mode
                 (= olivetti-body-width fk/olivetti-body-width-default))
            (progn
              (setq olivetti-body-width fk/olivetti-body-width-large)
              (olivetti-mode))
          (call-interactively 'global-olivetti-mode)
          (setq olivetti-body-width fk/olivetti-body-width-default))
      (delete-other-windows))))

Emojify-

Tree Sitter

(use-package tree-sitter
  :commands fk/tree-sitter-hl-mode
  :config
  (defun fk/tree-sitter-hl-mode ()
    "Require `tree-sitter-langs' + Activate `tree-sitter-hl-mode'."
    (interactive)
    (require 'tree-sitter-langs)
    (call-interactively 'tree-sitter-hl-mode))

  (with-eval-after-load 'expand-region
    (defun tree-sitter-mark-bigger-node ()
      (interactive)
      (let* ((p (point))
             (m (or (mark) p))
             (beg (min p m))
             (end (max p m))
             (root (ts-root-node tree-sitter-tree))
             (node (ts-get-descendant-for-position-range root beg end))
             (node-beg (ts-node-start-position node))
             (node-end (ts-node-end-position node)))
        ;; Node fits the region exactly. Try its parent node instead.
        (when (and (= beg node-beg) (= end node-end))
          (when-let ((node (ts-get-parent node)))
            (setq node-beg (ts-node-start-position node)
                  node-end (ts-node-end-position node))))
        (set-mark node-end)
        (goto-char node-beg)))

    (setq er/try-expand-list (append er/try-expand-list
                                     '(tree-sitter-mark-bigger-node)))))

(use-package tree-sitter-langs
  :defer t
  :config
  ;; Custom patterns to make it look like in old versions:
  ;; See: https://github.com/ubolonton/emacs-tree-sitter/issues/153
  (tree-sitter-hl-add-patterns 'python
    [(assignment left: (identifier) @variable)])

  (tree-sitter-hl-add-patterns 'python
    [(decorator (call (identifier) @function.special))]))

Visual Fill Column

(use-package visual-fill-column
  :commands visual-fill-column-mode
  :bind
  ( :map windows
    ("v" . visual-fill-column-mode))
  :hook
  (visual-fill-column-mode . visual-line-mode))

Color Identifiers Mode-

Goggles Mode (Highlight Changes)

(use-package goggles
  :straight (:host github :repo "minad/goggles")
  :commands goggles-mode
  :custom
  (goggles-pulse-delay 0.1))

Hide/Show

(use-package hideshow
  :straight (:type built-in)
  :defer nil
  :custom
  (hs-isearch-open t)
  :bind
  ( :map hs-minor-mode-map
    ("TAB" . fk/hs-smart-tab)
    ("<tab>" . fk/hs-smart-tab)
    ("<backtab>" . hs-toggle-hiding))
  :config
  (defun fk/hs-smart-tab ()
    "Pretend like `hs-toggle-hiding' if point is on a hiding block."
    (interactive)
    (if (save-excursion
          (move-beginning-of-line 1)
          (hs-looking-at-block-start-p))
        (hs-show-block)
      (indent-for-tab-command)))

  (defun fk/hide-second-level-blocks ()
    "Hide second level blocks (mostly class methods in python) in
current buffer."
    (interactive)
    (hs-minor-mode)
    (save-excursion
      (goto-char (point-min))
      (hs-hide-level 2))))

Topspace (Upper margin)

(use-package topspace
  :bind
  ( :map windows
    ("m" . topspace-recenter-buffer)))

Redacted (Hide text)

(use-package redacted
  :commands redacted-mode
  :hook
  (redacted-mode . (lambda () (read-only-mode (if redacted-mode 1 -1)))))

Posframe

(use-package posframe
  :defer t
  :custom
  (setq posframe-mouse-banish '(0 . 5000)))  ; Bottom-left corner to prevent EAF stealing focus

Completion

Better Defaults

(add-to-list 'completion-styles 'flex t)

(setq completion-ignore-case t)
(setq read-buffer-completion-ignore-case t)
(setq read-file-name-completion-ignore-case t)

Which Key (Keybinding Completion)

(use-package which-key-posframe
  :custom
  (which-key-idle-delay 2)
  (which-key-idle-secondary-delay 0)
  (which-key-posframe-border-width 2)
  (which-key-posframe-parameters '((left-fringe . 5) (right-fringe . 5)))
  :custom-face
  (which-key-posframe ((t (:background ,fk/dark-color))))
  (which-key-posframe-border ((t (:background ,fk/light-color))))
  :hook
  (dashboard-after-initialize . which-key-posframe-mode)
  (dashboard-after-initialize . which-key-mode))

Helm (General Completion & Selection)

Helm

(use-package helm
  :custom
  (helm-M-x-always-save-history t)
  (helm-display-function 'pop-to-buffer)
  (savehist-additional-variables '(extended-command-history))
  (history-delete-duplicates t)
  (helm-command-prefix-key nil)
  ;; Just move the selected text to the top of kill-ring, do not insert the text
  (helm-kill-ring-actions '(("Copy marked" . (lambda (_str) (kill-new _str)))
                            ("Delete marked" . helm-kill-ring-action-delete)))
  :custom-face
  (helm-non-file-buffer ((t (:inherit font-lock-comment-face))))
  (helm-ff-file-extension ((t (:inherit default))))
  (helm-buffer-file ((t (:inherit default))))
  :bind
  (("M-x" . helm-M-x)
   ("C-x C-f" . helm-find-files)
   ("C-x C-b" . helm-buffers-list)
   ("C-x b" . helm-buffers-list)
   ("C-x C-r" . helm-recentf)
   ("C-x C-i" . fk/helm-imenu)
   ("C-x C-j" . fk/helm-imenu)
   ("M-y" . fk/yank-pop-or-helm-show-kill-ring)
   :map helm-map
   ("TAB" . helm-execute-persistent-action)
   ("<tab>" . helm-execute-persistent-action)
   ("C-z" . helm-select-action)
   ("C-w" . backward-kill-word)  ; Fix C-w
   :map files
   ("f" . helm-find-files)
   ("r" . helm-recentf)
   ("b" . helm-bookmarks)
   :map buffers
   ("b" . helm-buffers-list)
   :map help-map
   ("a" . helm-apropos))
  :hook
  (dashboard-after-initialize . helm-mode)
  (helm-mode . savehist-mode)
  (helm-major-mode . fk/darken-background)
  :config
  (with-eval-after-load 'helm-buffers
    (dolist (regexp '("\\*epc con" "\\*helm" "\\*EGLOT" "\\*straight" "\\*Flymake"
                      "\\*eldoc" "\\*Compile-Log" "\\*xref" "\\*company"
                      "\\*aw-posframe" "\\*Warnings" "\\*Backtrace" "\\*helpful"
                      "\\*Messages" "\\*dashboard"))
      (add-to-list 'helm-boring-buffer-regexp-list regexp))
    (bind-keys
     :map helm-buffer-map
     ("M-d" . helm-buffer-run-kill-buffers)
     ("C-M-d" . helm-buffer-run-kill-persistent)))

  ;; "Waiting for process to die...done" fix.
  ;; Source: https://github.com/bbatsov/helm-projectile/issues/136#issuecomment-688444955
  (defun fk/helm--collect-matches (orig-fun src-list &rest args)
    (let ((matches
           (cl-loop for src in src-list
                    collect (helm-compute-matches src))))
      (unless (eq matches t) matches)))

  (advice-add 'helm--collect-matches :around 'fk/helm--collect-matches)

  (require 'helm-imenu)  ; Fixes buggy helm-imenu at first usage

  (defun fk/helm-imenu ()
    "helm-imenu without initializion (preselect)."
    (interactive)
    (unless helm-source-imenu
      (setq helm-source-imenu
            (helm-make-source "Imenu" 'helm-imenu-source
              :fuzzy-match helm-imenu-fuzzy-match)))
    (let* ((imenu-auto-rescan t)
           (helm-highlight-matches-around-point-max-lines 'never))
      (helm :sources 'helm-source-imenu
            :default ""
            :preselect ""
            :buffer "*helm imenu*")))

  (add-hook 'imenu-after-jump-hook (lambda ()
                                     (when (derived-mode-p 'outline-mode)
                                       (show-subtree))))

  (defun fk/yank-pop-or-helm-show-kill-ring ()
    "If called after a yank, call `yank-pop'. Otherwise, call
`helm-show-kill-ring'."
    (interactive)
    (if (eq last-command 'yank)
        (if (eq major-mode 'vterm-mode)
            (vterm-yank-pop)
          (yank-pop))
      (helm-show-kill-ring))))

Helm Projectile-

Helm Ag-

Helm Xref-

Helm Swoop-

Helm Descbinds

(use-package helm-descbinds
  :commands helm-descbinds)

Helm Icons-

Helm Posframe

(use-package helm-posframe
  :straight (:host github :repo "KaratasFurkan/helm-posframe")
  :after helm
  :custom
  (helm-display-header-line nil)
  (helm-echo-input-in-header-line t)
  (helm-posframe-border-width 2)
  (helm-posframe-border-color fk/light-color)
  (helm-posframe-parameters '((left-fringe . 5) (right-fringe . 5)))
  (helm-posframe-size-function 'fk/helm-posframe-get-size)
  :config
  (helm-posframe-enable)
  ;; Remove annoying error message that displayed everytime after closing
  ;; helm-posframe. The message is:
  ;; Error during redisplay: (run-hook-with-args helm--delete-frame-function
  ;; #<frame 0x5586330a1f90>) signaled (user-error "No recursive edit is in
  ;; progress")
  (remove-hook 'delete-frame-functions 'helm--delete-frame-function)

  ;; Fix helm-posframe-display: Wrong type argument: window-live-p, #<window XYZ>
  (defun fk/helm-posframe-disable-on-minibuffer (orig-func &rest args)
    "Disable `helm-posframe' if it is called from minibuffer."
    (let ((helm-display-function 'helm-default-display-buffer))
      (apply orig-func args)))

  (advice-add 'helm-read-pattern-maybe :around 'fk/helm-posframe-disable-on-minibuffer)

  (defun fk/helm-posframe-get-size ()
    (list
     :min-width (or helm-posframe-min-width
                    (let ((half-frame-width (round (* (frame-width) 0.5)))
                          (three-quarter-frame-width (round (* (frame-width) 0.75))))
                      (if (> half-frame-width 100)
                          half-frame-width
                        three-quarter-frame-width)))
     :min-height (or helm-posframe-min-height
                     (let ((half-frame-height (round (* (frame-height) 0.5)))
                           (three-quarter-frame-height (round (* (frame-height) 0.75))))
                       (if (> half-frame-height 25)
                           half-frame-height
                         three-quarter-frame-height))))))

Company (Code & Text Completion)

Company

(use-package company
  :custom
  (company-idle-delay 0)
  (company-minimum-prefix-length 1)
  (company-tooltip-align-annotations t)
  (company-dabbrev-downcase nil)
  (company-dabbrev-other-buffers t) ; search buffers with the same major mode
  :bind
  ( :map company-active-map
    ("RET" . nil)
    ([return] . nil)
    ("C-w" . nil)
    ("TAB" . company-complete-selection)
    ("<tab>" . company-complete-selection)
    ("C-s" . company-complete-selection)  ; Mostly to use during yasnippet expansion
    ("C-n" . company-select-next)
    ("C-p" . company-select-previous))
  :hook
  (dashboard-after-initialize . global-company-mode)
  :config
  (add-to-list 'company-begin-commands 'backward-delete-char-untabify)

  ;; Show YASnippet snippets in company

  (defun fk/company-backend-with-yas (backend)
    "Add ':with company-yasnippet' to the given company backend."
    (if (and (listp backend) (member 'company-yasnippet backend))
        backend
      (append (if (consp backend)
                  backend
                (list backend))
              '(:with company-yasnippet))))

  (defun fk/company-smart-snippets (fn command &optional arg &rest _)
    "Do not show yasnippet candidates after dot."
    ;;Source:
    ;;https://www.reddit.com/r/emacs/comments/7dnbxl/how_to_temporally_filter_companymode_candidates/
    (unless (when (and (equal command 'prefix) (> (point) 0))
              (let* ((prefix (company-grab-symbol))
                     (point-before-prefix (if (> (- (point) (length prefix) 1) 0)
                                              (- (point) (length prefix) 1)
                                            1))
                     (char (buffer-substring-no-properties point-before-prefix (1+ point-before-prefix))))
                (string= char ".")))
      (funcall fn command arg)))

  ;; TODO: maybe show snippets at first?
  (defun fk/company-enable-snippets ()
    "Enable snippet suggestions in company by adding ':with
company-yasnippet' to all company backends."
    (interactive)
    (setq company-backends (mapcar 'fk/company-backend-with-yas company-backends))
    (advice-add 'company-yasnippet :around 'fk/company-smart-snippets))

  (fk/company-enable-snippets))

Company Box

(use-package company-box
  :disabled
  :straight (:host github :repo "KaratasFurkan/company-box" :branch "consider-icon-right-margin-for-frame")
  :custom
  ;; Disable `single-candidate' and `echo-area' frontends
  (company-frontends '(company-box-frontend))
  (company-box-show-single-candidate t)
  ;;(company-box-frame-behavior 'point)
  (company-box-icon-right-margin 0.5)
  (company-box-backends-colors '((company-yasnippet . (:annotation default))))
  ;;:hook
  ;;(company-mode . company-box-mode)
  )

(use-package company-posframe
  :hook
  (company-mode . company-posframe-mode))

Company Statistics/Prescient

(use-package prescient
  :hook (dashboard-after-initialize . prescient-persist-mode))

(use-package company-prescient
  :after company
  :config (company-prescient-mode))

;; It turns out company-prescient could not be disabled locally, lets go back to
;; company-statistics
;; (use-package company-statistics
;;   :hook (global-company-mode . company-statistics-mode))

YASnippet (Snippet Completion)

(use-package yasnippet
  ;; Expand snippets with `C-j', not with `TAB'. Use `TAB' to always
  ;; jump to next field, even when company window is active. If there
  ;; is need to complete company's selection, use `C-s'
  ;; (`company-complete-selection').
  :custom
  (yas-indent-line nil)
  (yas-inhibit-overlay-modification-protection t)
  :custom-face
  (yas-field-highlight-face ((t (:inherit region))))
  :bind*
  (("C-j" . yas-expand)
   :map yas-minor-mode-map
   ("TAB" . nil)
   ("<tab>" . nil)
   :map yas-keymap
   ("TAB" . (lambda () (interactive) (company-abort) (yas-next-field)))
   ("<tab>" . (lambda () (interactive) (company-abort) (yas-next-field))))
  :hook
  (dashboard-after-initialize . yas-global-mode)
  (snippet-mode . (lambda () (setq-local require-final-newline nil))))

Emmet- (Snippet Completion for HTML & CSS)

Hydra

(use-package hydra
  :defer t
  :init
  (setq hydra-hint-display-type 'posframe)
  (setq hydra-posframe-show-params
        `( :internal-border-width 2
           :internal-border-color ,fk/light-color
           :left-fringe 5
           :right-fringe 5
           :poshandler posframe-poshandler-frame-bottom-center)))

Search & Navigation

Better Defaults

(global-subword-mode)  ; navigationInCamelCase

(setq-default
 recenter-positions '(middle 0.15 top 0.85 bottom)  ; C-l positions
 scroll-conservatively 101)                         ; Smooth scrolling

;; Scroll less than default
(defvar fk/default-scroll-lines 15)

(defun fk/scroll-up (orig-func &optional arg)
  "Scroll up `fk/default-scroll-lines' lines (probably less than default)."
  (apply orig-func (list (or arg fk/default-scroll-lines))))

(defun fk/scroll-down (orig-func &optional arg)
  "Scroll down `fk/default-scroll-lines' lines (probably less than default)."
  (apply orig-func (list (or arg fk/default-scroll-lines))))

(advice-add 'scroll-up :around 'fk/scroll-up)
(advice-add 'scroll-down :around 'fk/scroll-down)

Custom Functions

find-config

(defun fk/find-config ()
  "Open config file."
  (interactive)
  (find-file config-org))

(defun fk/persp-switch-config ()
  "Open config file in a dedicated perspective."
  (interactive)
  (persp-switch "config")
  (fk/find-config))

go-scratch

(defun fk/scratch ()
  "Switch to scratch buffer."
  (interactive)
  (switch-to-buffer "*scratch*"))

go-messages

(defun fk/messages ()
  "Switch to Messages buffer."
  (interactive)
  (switch-to-buffer "*Messages*"))

go-home-

split-window-and-switch

(defun fk/split-window-below-and-switch ()
  "Split the window below, then switch to the new window."
  (interactive)
  (split-window-below)
  (other-window 1))

(defun fk/split-window-right-and-switch ()
  "Split the window right, then switch to the new window."
  (interactive)
  (split-window-right)
  (other-window 1))

generate-random-elisp-scratch

(defun fk/generate-random-elisp-scratch ()
  "Create and switch to a temporary scratch buffer with a random name and
`emacs-lisp-mode' activated."
  (interactive)
  (switch-to-buffer (make-temp-name "scratch-elisp-"))
  (emacs-lisp-mode))

generate-random-org-scratch

(defun fk/generate-random-org-scratch ()
  "Create and switch to a temporary scratch buffer with a random name and
`org-mode' activated."
  (interactive)
  (switch-to-buffer (make-temp-name "scratch-org-"))
  (org-mode))

generate-random-text-scratch

(defun fk/generate-random-text-scratch ()
  "Create and switch to a temporary scratch buffer with a random name and
`text-mode' activated."
  (interactive)
  (switch-to-buffer (make-temp-name "scratch-text-"))
  (text-mode))

convert-string-to-rg-compatible

(setq fk/rg-special-characters '("(" ")" "[" "{" "*"))

(defun fk/convert-string-to-rg-compatible (str)
  "Escape special characters defined in `fk/rg-special-characters' of STR."
  (seq-reduce (lambda (str char) (string-replace char (concat "\\" char) str))
              fk/rg-special-characters
              str))

get-selected-text

(defun fk/get-selected-text ()
  "Return selected text if region is active, else nil."
  (when (region-active-p)
    (let ((text (buffer-substring-no-properties (region-beginning) (region-end))))
      (deactivate-mark) text)))

find-installed-packages

(defun fk/find-installed-packages ()
  "Quick way of opening the source code of an installed package."
  (interactive)
  (helm-find-files-1 (straight--repos-dir)))

switch-last-buffer

(defun fk/switch-last-buffer ()
  "Switch to last buffer."
  (interactive)
  (switch-to-buffer (other-buffer (current-buffer) nil)))

switch-last-window

(defun fk/switch-last-window ()
  (interactive)
  (when-let ((last-win (get-mru-window nil nil t)))
    (select-window last-win)))

Keybindings

(global-set-key (kbd "<F1>") 'help-command)
(bind-key* (kbd "M-h") 'help-command)
(bind-key* (kbd "M-h M-h") 'help-for-help)
(global-set-key (kbd "C-x c") 'fk/persp-switch-config)
(global-set-key (kbd "C-x C-k") 'kill-current-buffer)
(global-set-key (kbd "M-l") 'move-to-window-line-top-bottom)
(bind-key* "C-q" 'fk/switch-last-window)
(global-set-key (kbd "M-g M-g") (lambda ()
                                  (interactive)
                                  (require 'avy)
                                  (avy-push-mark)
                                  (goto-line 1)))

;; Split & Switch
;; I use `fk/smart-C-x-1' for (kbd "C-1"), see Appearance / Olivetti
(global-set-key (kbd "C-2") 'fk/split-window-below-and-switch)
(global-set-key (kbd "C-3") 'fk/split-window-right-and-switch)

(bind-keys*
 :map files
 ("c" . fk/find-config)
 ("C" . fk/persp-switch-config)
 ("p" . fk/find-installed-packages))

(bind-keys*
 :map buffers
 ("s" . fk/scratch)
 ("r" . fk/generate-random-elisp-scratch)
 ("o" . fk/generate-random-org-scratch)
 ("t" . fk/generate-random-text-scratch)
 ("h" . fk/home)
 ("m" . fk/messages))

;; (defmacro fk/define-scratch-command (name mode kbd)
;;   "Define a scratch generator command and keybinding with the given
;; major-mode. Usage:
;; (fk/define-scratch-command
;;  \"my-search\" global-map \"C-M-s\" \"*.el\" \"foo\")"
;;   `(progn
;;      (defun ,(intern (concat "fk/" name)) ()
;;        (interactive)
;;        (require 'helm-rg)
;;        (fk/helm-rg-dwim-with-glob (or ,glob "") ,query))
;;      (define-key ,keymap (kbd ,kbd) ',(intern (concat "fk/" name)))))

;; (defmacro fk/helm-rg-define-search-commands (&rest args)
;;   "Define multiple search command at once. Usage:
;; (fk/helm-rg-define-search-commands
;;  (\"my-search\" global-map \"C-M-s\" \"*.el\" \"foo\")
;;  (\"my-other-search\" global-map \"C-M-S\" \"*.el\" \"bar\"))"
;;   `(progn ,@(cl-loop for expr in args
;;                      collect `(fk/helm-rg-define-search-command ,@expr))))

;; (defun fk/generate-random-elisp-scratch ()
;;   "Create and switch to a temporary scratch buffer with a random name and
;; `emacs-lisp-mode' activated."
;;   (interactive)
;;   (switch-to-buffer (make-temp-name "scratch-elisp-"))
;;   (emacs-lisp-mode))

;; (fk/define-scratch-commands
;;  ("generate-elisp-scratch"  'emacs-lisp-mode "b")
;;  ("generate-org-scratch"    'org-mode        "o")
;;  ("generate-text-scratch"   'text-mode       "t")
;;  ("generate-python-scratch" 'python-mode     "p")
;;  ("generate-json-scratch"   'json-mode       "j")

(bind-keys*
 :map windows
 ("b" . balance-windows)
 ("d" . delete-window)
 ("k" . kill-buffer-and-window))

Recentf (Recent Files)

(use-package recentf
  ;; Use with `helm-recentf'
  :straight (:type built-in)
  :preface
  (setq recentf-max-saved-items 200)
  :custom
  (recentf-exclude `(,(straight--build-dir)
                     ,(locate-user-emacs-file "eln-cache/")
                     "/usr/share/emacs/"
                     "/usr/local/share/emacs/"
                     "emacs/src/"
                     ,(expand-file-name "~/.virtualenvs")
                     "/usr/lib/node_modules/"
                     "/tmp/")))

Winner Mode

(use-package winner
  :straight (:type built-in)
  :bind
  (("M-u" . winner-undo)
   ;; ("M-u" . (lambda () (interactive) (condition-case nil
   ;;                                       (xref-pop-marker-stack)
   ;;                                     (error (winner-undo)))))
   ("M-U" . winner-redo)
   :map windows
   ("u" . winner-undo)
   ("r" . winner-redo))
  :config
  (winner-mode))

Ace Window

(use-package ace-window
  :straight (:host github :repo "KaratasFurkan/ace-window" :branch "feature/posframe")
  :custom
  (aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
  (aw-ignore-current t)
  (aw-dispatch-when-more-than 3)  ; TODO: does not work
  :custom-face
  (aw-leading-char-face ((t (:height 15.0 :foreground "orangered2"))))
  :bind
  (("M-o" . ace-window)
   :map windows
   ("w" . ace-window)
   ("D" . ace-delete-window)
   ("s" . ace-swap-window)
   ("l" . aw-flip-window))
  :config
  (ace-window-posframe-mode))

Dependents

Those packages should load after ace-window to not install ace-window from melpa. TODO: fix this

Helm Icons

(use-package helm-icons
  :straight (:host github :repo "yyoncho/helm-icons")
  :after helm
  :config
  (treemacs-resize-icons fk/default-icon-size)
  (helm-icons-enable))

Winum

(use-package winum
  :bind*
  ("M-1" . winum-select-window-1)
  ("M-2" . winum-select-window-2)
  ("M-3" . winum-select-window-3)
  ("M-4" . winum-select-window-4)
  ("M-5" . winum-select-window-5)
  ("M-6" . winum-select-window-6)
  ("M-7" . winum-select-window-7)
  ("M-8" . winum-select-window-8)
  ("M-9" . winum-select-window-9)
  :hook
  (dashboard-after-initialize . winum-mode))

Mwim (Move Where I Mean)

(use-package mwim
  :bind
  ("C-a" . mwim-beginning-of-code-or-line)
  ("C-e" . mwim-end-of-line-or-code)
  ;; NOTE: Functions below are built-in but I think they fit in this context
  ("M-a" . fk/backward-sexp)
  ("M-e" . fk/forward-sexp)
  :config
  (defun fk/forward-sexp (&optional N)
    "Call `forward-sexp', fallback `forward-char' on error."
    (interactive)
    (condition-case nil
        (forward-sexp N)
      (error (forward-char N))))

  (defun fk/backward-sexp ()
    "`fk/forward-sexp' with negative argument."
    (interactive)
    (fk/forward-sexp -1)))

Helm Projectile

(use-package helm-projectile
  :custom
  (helm-projectile-sources-list '(helm-source-projectile-buffers-list
                                  helm-source-projectile-recentf-list
                                  helm-source-projectile-files-list
                                  helm-source-projectile-projects))
  :bind
  ("C-x f" . helm-projectile)
  :hook
  (projectile-mode . helm-projectile-on)
  :config
  (defun fk/projectile-recentf-files-first-five (original-function)
    "Return a list of five recently visited files in a project."
    (let ((files (funcall original-function)))
      (if (> (length files) 5)
          (seq-subseq files 0 5)
        files)))
  (advice-add 'projectile-recentf-files :around 'fk/projectile-recentf-files-first-five))

Helm Ag

Note: I use helm-rg for search (grep) functionality, keep helm-ag only to use its edit feature.

(use-package helm-ag
  :custom
  (helm-ag-base-command
   "rg -S --no-heading --color=never --line-number --max-columns 400")
  :bind
  (("C-M-S-s" . fk/helm-ag-dwim)
   :map helm-ag-map
   ("C-o" . helm-ag--run-other-window-action))
  :config
  (defun fk/helm-ag-dwim (&optional query)
    "Smarter version of helm-ag.
- Search in project if in a project else search in default (current) directory.
- Start search with selected text if region is active or empty string.
- Escape special characters when searching with selected text."
    (interactive)
    (let ((root-dir (or (projectile-project-root) default-directory))
          (query (or (fk/get-selected-text) query)))
      (helm-do-ag root-dir nil query)))

  (defun fk/helm-ag-dwim-with-glob (glob &optional query)
    (interactive)
    (let ((helm-ag-base-command (concat helm-ag-base-command " --glob " glob)))
      (fk/helm-ag-dwim query)))

  (defun fk/helm-ag--parse-options-and-query (func input)
    "Make `helm-ag' input ripgrep compatible."
    (apply func (list (fk/convert-string-to-rg-compatible input))))

  (advice-add 'helm-ag--parse-options-and-query :around 'fk/helm-ag--parse-options-and-query))

Helm Rg

(use-package helm-rg
  :init
  ;; Load this macro even if helm-rg is not loaded yet
  (defmacro fk/helm-rg-define-search-command (name keymap kbd &optional glob query)
    "Define search commands and keybindings with predefined glob and query. Usage:
(fk/helm-rg-define-search-command
 \"my-search\" global-map \"C-M-s\" \"*.el\" \"foo\")"
    `(progn
       (defun ,(intern (concat "fk/" name)) ()
         (interactive)
         (require 'helm-rg)
         (fk/helm-rg-dwim-with-glob (or ,glob "") ,query))
       (define-key ,keymap (kbd ,kbd) ',(intern (concat "fk/" name)))))

  (defmacro fk/helm-rg-define-search-commands (&rest args)
    "Define multiple search command at once. Usage:
(fk/helm-rg-define-search-commands
 (\"my-search\" global-map \"C-M-s\" \"*.el\" \"foo\")
 (\"my-other-search\" global-map \"C-M-S\" \"*.el\" \"bar\"))"
    `(progn ,@(cl-loop for expr in args
                       collect `(fk/helm-rg-define-search-command ,@expr))))
  :custom
  (helm-rg-default-extra-args '("--max-columns" "400"
                                "-g" "!{*.min.css,*.min.js,*.svg,*.po}"
                                "-g" "!migrations/"))
  :custom-face
  (helm-rg-file-match-face ((t (:foreground nil :inherit font-lock-type-face :weight bold :underline nil :slant italic))))
  (helm-rg-line-number-match-face ((t (:foreground nil :underline nil :inherit line-number))))
  :bind
  (("C-M-s" . fk/helm-rg-dwim)
   :map helm-rg-map
   ("C-c C-e" . fk/helm-rg-switch-helm-ag)
   ("C-c C-d" . fk/helm-rg-switch-deadgrep))
  :config
  (defun fk/helm-rg-dwim (&optional query)
    "Smarter version of helm-rg.
- Search in project if in a project else search in default (current) directory.
- Start search with selected text if region is active or empty string.
- Escape special characters when searching with selected text."
    (interactive)
    (let ((helm-rg-default-directory (or (projectile-project-root) default-directory))
          (query (or (fk/get-selected-text) query)))
      (cl-letf (((symbol-function 'helm-rg--get-thing-at-pt) (lambda () query)))
        (call-interactively 'helm-rg))))

  (defun fk/helm-rg-dwim-with-glob (glob &optional query)
    (interactive)
    (let ((helm-rg-default-glob-string glob))
      (fk/helm-rg-dwim query)))


  ;;;; Input normalization

  (defvar fk/helm-rg-fuzzy-max-words 6)  ; rg returns "Arguments list too long" after this point

  (defun fk/helm-input-to-ripgrep-regexp (func input)
    "Make `helm-rg' input ripgrep compatible. Escape special
characters and disable fuzzy matching if input has more than
`fk/helm-rg-fuzzy-max-words' words."
    (let* ((processed-input (fk/convert-string-to-rg-compatible input))
           (word-count (with-temp-buffer
                         (insert processed-input)
                         (count-words (point-min) (point-max)))))
      (if (> word-count fk/helm-rg-fuzzy-max-words)
          (string-replace " " ".*" processed-input)  ; simpler fuzzy
        (apply func (list processed-input)))))

  (advice-add 'helm-rg--helm-pattern-to-ripgrep-regexp :around 'fk/helm-input-to-ripgrep-regexp)


  ;;;; Appearance

  ;; Use a simpler header in the helm buffer.
  (fset 'helm-rg--header-name
        (lambda (_)
          (format "Search at %s\nArgs: %s" helm-rg--current-dir (string-join helm-rg--extra-args " "))))

  ;; Create bigger window for helm-rg
  (advice-add 'helm-rg :around
              (lambda (orig-func &rest args)
                (let ((helm-posframe-min-height (round (* (frame-height) 0.66)))
                      (helm-candidate-number-limit 99999))  ; show all matching lines. TODO open a PR and make this default.
                  (apply orig-func args))))


  ;;;; Switch to another frontend functions

  (defun fk/helm-rg-switch-helm-ag ()
    "Switch to `helm-ag' to use its edit feature."
    (interactive)
    (helm-rg--run-after-exit
     (require 'helm-ag)
     (fk/helm-ag-dwim helm-pattern))
    (minibuffer-keyboard-quit))

  (defun fk/helm-rg-switch-deadgrep ()
    "Switch to `deadgrep' to use its seperated buffer and `before
n line' / `after n line' features."
    (interactive)
    (helm-rg--run-after-exit
     (require 'deadgrep)
     (deadgrep helm-pattern))
    (minibuffer-keyboard-quit)))

Helm Xref

(use-package xref
  :custom
  (xref-prompt-for-identifier nil)
  :bind
  ("C-M-j" . xref-find-definitions)
  ("C-M-k" . xref-pop-marker-stack)
  ("C-9" . xref-find-definitions)
  ("C-8" . xref-pop-marker-stack)
  ("C-M-9" . xref-find-definitions-other-window)
  ("C-M-r" . xref-find-references))

(use-package helm-xref
  :after helm xref)

Dumb Jump

(use-package dumb-jump
  :custom
  (dumb-jump-aggressive t)
  :bind
  ([remap xref-find-definitions] . fk/smart-jump-go)
  ([remap xref-pop-marker-stack] . fk/smart-jump-back)
  ("C-M-S-j" . fk/smart-jump-peek)
  :config
  (defun fk/smart-jump-go ()
    "Fallback `dumb-jump-go' if `xref-find-definitions' cannot find the source."
    (interactive)
    (condition-case nil
        (call-interactively 'xref-find-definitions)
      (error (call-interactively 'dumb-jump-go))))

  (defun fk/smart-jump-back ()
    "Fallback `dumb-jump-back' if xref-pop-marker-stack cannot return back."
    (interactive)
    (if (string= (frame-parameter (selected-frame) 'name) "*Smart Jump Peek*")
        (progn (make-frame-invisible) (delete-frame))
      (condition-case nil
          (call-interactively 'xref-pop-marker-stack)
        (error (call-interactively 'dumb-jump-back)))))

  (defun fk/smart-jump-peek ()
    "`fk/smart-jump-go' in a new frame.
Source: http://tuhdo.github.io/emacs-frame-peek.html"
    (interactive)
    (let (summary
          doc-frame
          x y
          ;; 1. Find the absolute position of the current beginning of the symbol
          ;; at point, in pixels.
          (abs-pixel-pos (save-excursion
                           (beginning-of-thing 'symbol)
                           (window-absolute-pixel-position))))
      (setq x (car abs-pixel-pos))
      (setq y (+ (cdr abs-pixel-pos) (frame-char-height)))

      ;; 2. Create a new invisible frame, with the current buffer in it.
      (setq doc-frame (make-frame `((name . "*Smart Jump Peek*")
                                    (width . 100)
                                    (visibility . nil)
                                    (height . 20))))

      ;; 3. Position the new frame right under the beginning of the symbol at point.
      (set-frame-position doc-frame x y)

      ;; 4. Jump to the symbol at point.
      (with-selected-frame doc-frame
        (fk/smart-jump-go)
        (read-only-mode -1)
        (recenter-top-bottom 0))

      ;; 5. Make frame visible again
      (make-frame-visible doc-frame)
      (x-focus-frame doc-frame))))

Helm Swoop

(use-package helm-swoop
  :custom
  (helm-swoop-speed-or-color t)
  (helm-swoop-min-overlay-length 0)
  ;;(helm-swoop-use-fuzzy-match t)
  :custom-face
  (helm-swoop-target-line-face ((t (:background "black" :foreground nil :inverse-video nil :extend t))))
  (helm-swoop-target-word-face ((t (:inherit lazy-highlight :foreground nil))))
  :bind
  (("M-s" . helm-swoop)
   :map isearch-mode-map
   ("M-s" . helm-swoop-from-isearch)
   :map helm-swoop-map
   ("M-s" . helm-multi-swoop-all-from-helm-swoop)
   :map helm-swoop-edit-map
   ("C-c C-c" . helm-swoop--edit-complete)
   ("C-c C-k" . helm-swoop--edit-cancel))
  :config
  (with-eval-after-load 'shackle
    (setq helm-swoop-split-window-function 'display-buffer)))  ; shackle rules doesn't work without this

Deadgrep

(use-package deadgrep
  :commands deadgrep
  :bind
  ( :map deadgrep-mode-map
    ("C-c C-e" . deadgrep-edit-mode)))

Avy

(use-package avy
  :custom
  (avy-dispatch-alist '((?c . avy-action-copy)
                        (?y . avy-action-yank)
                        (?t . avy-action-teleport)))
  :bind
  ("M-j" . avy-goto-word-or-subword-1)
  ("C-M-u" . avy-pop-mark)
  ("C-M-i" . (lambda () (interactive) (require 'avy) (avy-push-mark))))

Treemacs

Treemacs

(use-package treemacs
  :custom
  (treemacs-width 20)
  :bind
  ("M-0" . treemacs-select-window)
  :hook
  (treemacs-mode . (lambda ()
                     (face-remap-add-relative 'default :height .85)
                     (face-remap-add-relative 'mode-line-inactive :background fk/dark-color)
                     (face-remap-add-relative 'mode-line :background fk/dark-color)
                     (face-remap-add-relative 'hl-line :background fk/background-color :weight 'bold)
                     (fk/darken-background)))
  :config
  (treemacs-project-follow-mode))

Treemacs Projectile

(use-package treemacs-projectile
  :after treemacs projectile)

Perspective

(use-package perspective
  :preface
  (defvar persp-icon (all-the-icons-material "dashboard" :height 0.9 :v-adjust -0.17 :face 'all-the-icons-blue))
  (defcustom persp-project-name nil "Should be set as directory local variable.")
  :custom
  (persp-mode-prefix-key (kbd "M-m p"))
  (persp-state-default-file (no-littering-expand-var-file-name "perspective.el"))
  (persp-modestring-dividers `(,(format "[%s " persp-icon) "]" ""))
  (persp-show-modestring nil)  ; I show this in `fk/minibuffer-modeline-update' manually.
  :custom-face
  (persp-selected-face ((t (:foreground nil :inherit 'doom-modeline-warning))))
  :bind*
  ( :map persp-mode-map
    ("C-M-o" . persp-next)
    ("C-x p" . persp-switch)
    ("C-x C-p" . persp-switch-quick)
    ("M-q" . persp-switch-last)
    :map perspective-map
    ("p" . persp-switch)
    ("k" . persp-kill)
    ("l" . persp-switch-last)
    ("q" . persp-switch-quick)
    ("n" . (lambda () (interactive) (persp-switch (make-temp-name "p-"))))
    ("R" . fk/perspective-rename-with-project-name))
  :hook
  (dashboard-after-initialize . persp-mode)
  (kill-emacs . persp-state-save)
  :config
  (with-eval-after-load 'projectile
    (defun fk/perspective-rename-with-project-name ()
      "Rename current perspective according to current project name."
      (interactive)
      (when (projectile-project-p)
        (let* ((project-name (or persp-project-name (projectile-project-name)))
               (ellipsis "")
               (short-name (if (> (length project-name) 10)
                               (concat (substring project-name 0 9) ellipsis)
                             project-name))
               (name (if (gethash short-name (perspectives-hash))
                         (concat "2—" short-name)
                       short-name)))
          (persp-rename name))))

    (define-minor-mode fk/perspective-auto-rename-mode
      "Rename perspectives according to project name automatically."
      :global t
      (if fk/perspective-auto-rename-mode
          (progn
            (ignore-errors (fk/perspective-rename-with-project-name))
            (add-hook 'projectile-after-switch-project-hook 'fk/perspective-rename-with-project-name))
        (remove-hook 'projectile-after-switch-project-hook 'fk/perspective-rename-with-project-name)))

    (fk/perspective-auto-rename-mode)))

Dired Sidebar-

IBuffer Sidebar-

Block Nav

(use-package block-nav
  :straight (:host github :repo "nixin72/block-nav.el")
  :config
  ;; TODO: DRY
  ;; (defun fk/block-nav-activate (file keymap)
  ;;   (with-eval-after-load file
  ;;     (define-key keymap (kbd "M-n") 'block-nav-next-block)
  ;;     (define-key keymap (kbd "M-p") 'block-nav-previous-block)))
  ;; (fk/block-nav-activate 'python 'python-mode-map)
  ;; (fk/block-nav-activate 'yaml-mode 'yaml-mode-map)
  ;; (fk/block-nav-activate 'docker-compose-mode 'docker-compose-mode-map)
  (with-eval-after-load 'python
    (define-key python-mode-map (kbd "M-n") 'block-nav-next-block)
    (define-key python-mode-map (kbd "M-p") 'block-nav-previous-block))
  (with-eval-after-load 'yaml-mode
    (define-key yaml-mode-map (kbd "M-n") 'block-nav-next-block)
    (define-key yaml-mode-map (kbd "M-p") 'block-nav-previous-block))
  (with-eval-after-load 'docker-compose-mode
    (define-key docker-compose-mode-map (kbd "M-n") 'block-nav-next-block)
    (define-key docker-compose-mode-map (kbd "M-p") 'block-nav-previous-block))
  (with-eval-after-load 'elisp-mode
    (define-key emacs-lisp-mode-map (kbd "M-n") 'block-nav-next-block)
    (define-key emacs-lisp-mode-map (kbd "M-p") 'block-nav-previous-block)))

Goto Line Preview

(use-package goto-line-preview
  :commands goto-line-preview
  :bind
  ([remap goto-line] . goto-line-preview))

God Mode

(use-package god-mode
  :preface
  (setq god-mode-cursor-color "#FFF8DC")
  :bind
  (("C-;" . god-mode-all)
   :map god-local-mode-map
   ("j" . avy-goto-word-or-subword-1))
  :hook
  (god-mode-enabled . (lambda ()
                        (set-face-attribute 'cursor nil :background god-mode-cursor-color)
                        ;; beacon-mode doesn't work properly with same color as cursor
                        (setq beacon-color (doom-darken god-mode-cursor-color 0.001))))
  (god-mode-disabled . (lambda ()
                         (set-face-attribute 'cursor nil :background fk/cursor-color)
                         ;; beacon-mode doesn't work properly with same color as cursor
                         (setq beacon-color (doom-darken fk/cursor-color 0.001)))))

Text Editing

Better Defaults

(delete-selection-mode)
(electric-pair-mode)

(setq-default
 fill-column 80
 sentence-end-double-space nil
 indent-tabs-mode nil  ; Use spaces instead of tabs
 tab-width 4)

Custom Functions

backward-kill-word-or-region

(defun fk/backward-kill-word-or-region ()
  "Calls `kill-region' when a region is active and `backward-kill-word'
otherwise."
  (interactive)
  (call-interactively (if (region-active-p)
                          'kill-region
                        'backward-kill-word)))

newline-below

(defun fk/newline-below ()
  "Insert newline below the current line."
  (interactive)
  (save-excursion (end-of-line) (open-line 1)))

remove-hypens-and-underscores-region

(defun fk/remove-hypens-and-underscores-region (beg end)
  "Remove hypens and underscores from region."
  (interactive "*r")
  (save-excursion
    (let* ((raw-str (buffer-substring-no-properties beg end))
           (clean-str (string-replace "_" " " (string-replace "-" " " raw-str))))
      (delete-region beg end)
      (insert clean-str))))

increment-number-at-point

(defun fk/increment-number-at-point ()
  "Increment the number at point."
  (interactive)
  (when (number-at-point)
    (skip-chars-backward "0-9")
    (replace-match (number-to-string (1+ (string-to-number (match-string 0)))))))

decrement-number-at-point

(defun fk/decrement-number-at-point ()
  "Decrement the number at point."
  (interactive)
  (when (number-at-point)
    (skip-chars-backward "0-9")
    (replace-match (number-to-string (1- (string-to-number (match-string 0)))))))

Keybindings

(keyboard-translate ?\C-h ?\C-?)  ; C-h as DEL, (I use F1 and M-h as `help-command')
(add-hook 'server-after-make-frame-hook (lambda () (keyboard-translate ?\C-h ?\C-?)))  ; Fix emacs --daemon
(global-set-key (kbd "C-w") 'fk/backward-kill-word-or-region)
(global-set-key (kbd "C-o") 'fk/newline-below)
(global-set-key (kbd "C-x C-=") 'fk/increment-number-at-point)
(global-set-key (kbd "C-x C--") 'fk/decrement-number-at-point)

(bind-keys*
 :map text
 ("s" . sort-lines)
 ("r" . fk/remove-hypens-and-underscores-region))

Electric Indent Mode-

Undo Tree

(use-package undo-tree
  :custom
  (undo-tree-visualizer-diff t)
  (undo-tree-enable-undo-in-region t)
  :bind
  (("C-u" . undo-tree-undo)
   ("C-S-u" . undo-tree-redo))
  :hook
  (dashboard-after-initialize . global-undo-tree-mode))

Trailing White Space

(use-package whitespace-cleanup-mode
  :custom
  (show-trailing-whitespace t)  ; not from whitespace-cleanup-mode.el
  :custom-face
  (trailing-whitespace ((t (:background ,fk/light-color7))))  ; not from whitespace-cleanup-mode.el
  :hook
  (dashboard-after-initialize . global-whitespace-cleanup-mode)
  (after-change-major-mode . (lambda ()
                              (unless (buffer-file-name)
                                (setq-local show-trailing-whitespace nil)))))

Case Switching

(put 'upcase-region 'disabled nil)
(put 'downcase-region 'disabled nil)

;; built-in functions
(bind-keys
 :map text
 ("u" . upcase-dwim)
 ("d" . downcase-dwim)
 ("c" . capitalize-dwim))

(use-package string-inflection
  :bind
  ( :map text
    ("t" . string-inflection-all-cycle)
    ("k" . string-inflection-kebab-case)))

Paren

(use-package paren
  :straight (:type built-in)
  :custom
  (show-paren-when-point-inside-paren t)
  :custom-face
  (show-paren-match ((t (:background nil :weight bold :foreground "white"))))
  :hook
  (dashboard-after-initialize . show-paren-mode))

Multiple Cursors

(use-package multiple-cursors
  :custom
  (mc/always-run-for-all t)
  :bind*
  (("C-M-n" . mc/mark-next-like-this)
   ("C-M-p" . mc/mark-previous-like-this)
   ("C-M-S-n" . mc/skip-to-next-like-this)
   ("C-M-S-p" . mc/skip-to-previous-like-this)
   ("C-S-n" . mc/unmark-previous-like-this)
   ("C-S-p" . mc/unmark-next-like-this)
   ("C-M-<mouse-1>" . mc/add-cursor-on-click)
   ("C-x C-n" . mc/insert-numbers)))

Wrap Region

(use-package wrap-region
  :hook
  (dashboard-after-initialize . wrap-region-global-mode)
  :config
  (wrap-region-add-wrapper "=" "=" nil 'org-mode)
  (wrap-region-add-wrapper "*" "*" nil 'org-mode)
  (wrap-region-add-wrapper "_" "_" nil 'org-mode)
  (wrap-region-add-wrapper "/" "/" nil 'org-mode)
  (wrap-region-add-wrapper "+" "+" nil 'org-mode)
  (wrap-region-add-wrapper "~" "~" nil 'org-mode)
  (wrap-region-add-wrapper "#" "#" nil 'org-mode)
  (wrap-region-add-wrapper "`" "`" nil 'markdown-mode)
  (wrap-region-add-wrapper "`" "`" nil 'forge-post-mode)
  (wrap-region-add-wrapper "_(" ")" "_" 'python-mode)
  (wrap-region-add-wrapper "len(" ")" "l" 'python-mode))

Fill-Unfill Paragraph

(use-package unfill
  :bind
  ( :map text
    ("f" . unfill-toggle)))

Expand Region

(use-package expand-region
  :custom
  (expand-region-fast-keys-enabled nil)
  (expand-region-subword-enabled t)
  :bind*
  ("C-t" . er/expand-region))

Flyspell Popup

(use-package flyspell-popup
  :after flyspell
  :custom
  (flyspell-popup-correct-delay 1)
  :config
  (flyspell-popup-auto-correct-mode))

Company Wordfreq

(use-package company-wordfreq
  :straight (:host github :repo "johannes-mueller/company-wordfreq.el")
  :commands fk/company-wordfreq-mode
  :custom
  (company-wordfreq-path (concat no-littering-var-directory "wordfreq-dicts"))
  (ispell-local-dictionary "english")
  :config
  (define-minor-mode fk/company-wordfreq-mode
    "Suggest words by frequency."
    :global nil
    (if fk/company-wordfreq-mode
        (progn
          (setq-local company-backends-backup company-backends)
          (setq-local company-transformers-backup company-transformers)
          (setq-local company-backends '(company-wordfreq))
          (setq-local company-transformers nil))
      (setq-local company-backends company-backends-backup)
      (setq-local company-transformers company-transformers-backup)))

  (defun fk/company-wordfreq-toggle-language (&optional language)
    (interactive)
    (setq ispell-local-dictionary (or language
                                      (if (string= ispell-local-dictionary "english")
                                          "turkish"
                                        "english")))
    (message ispell-local-dictionary)))

Programming

General

Better Defaults

Custom Functions

align-comments

(defun fk/align-comments (beginning end)
  "Align comments in region."
  (interactive "*r")
  (align-regexp beginning end (concat "\\(\\s-*\\)"
                                      (regexp-quote comment-start)) nil 2))

indent-buffer

(defun fk/indent-buffer ()
  "Indent buffer."
  (interactive)
  (indent-region (point-min) (point-max)))

comment-or-uncomment-region

(defun fk/comment-or-uncomment-region ()
  "Comment or uncomment region with just a character (e.g. '/'). If a region is
active call comment-or-uncomment-region, otherwise just insert the given char."
  (interactive)
  (call-interactively (if (region-active-p)
                          'comment-or-uncomment-region
                        'self-insert-command)))

Fill Column Indicator-

Line Numbers-

Electric Indent Mode

(use-package electric
  :straight (:type built-in)
  :bind
  ( :map prog-mode-map
    ("M-RET" . electric-indent-just-newline))
  :hook
  (dashboard-after-initialize . electric-indent-mode))

Comments

(use-package newcomment
  :straight (:type built-in)
  :custom
  (comment-column 0)
  (comment-inline-offset 2)
  :bind*
  ( :map comments
    ("c" . comment-dwim)
    ("k" . comment-kill)
    ("l" . comment-line)
    ("n" . (lambda () (interactive) (next-line) (comment-indent)))
    ("N" . comment-indent-new-line)
    ("b" . comment-box)
    ("a" . fk/align-comments))
  :hook
  (emacs-lisp-mode . (lambda ()
                       (setq-local comment-start "; ")
                       (setq-local comment-column 0))))

YASnippet-

Projectile

(use-package projectile
  :custom
  (projectile-auto-discover nil)
  (projectile-project-search-path (directory-files "~/projects" t "[^.]"))
  ;; Open magit when switching project
  (projectile-switch-project-action
   (lambda ()
     (let ((magit-display-buffer-function
            'magit-display-buffer-same-window-except-diff-v1))
       (magit))))
  ;; Ignore emacs project (source codes)
  (projectile-ignored-projects '("~/emacs/"))
  ;; Do not include straight repos (emacs packages) and emacs directory itself
  ;; to project list
  (projectile-ignored-project-function
   (lambda (project-root)
     (or (string-prefix-p (expand-file-name user-emacs-directory) project-root)
         (string-prefix-p "/usr/lib/node_modules/" project-root))))
  (projectile-kill-buffers-filter 'kill-only-files)
  :bind*
  ("C-M-t" . fk/projectile-vterm)
  :hook
  (dashboard-after-initialize . projectile-mode)
  :config
  (defun fk/projectile-vterm ()
    "Open `vterm' in project root directory."
    (interactive)
    (let* ((default-directory (or (projectile-project-root) default-directory))
           (project-name (projectile-project-name default-directory))
           (buffer-name (format "vterm @%s" project-name))
           (buffer (get-buffer buffer-name)))
      (if (or (not buffer) (eq buffer (current-buffer)))
          (vterm buffer-name)
        (switch-to-buffer buffer)))))

Flycheck

(use-package flycheck
  :custom
  (flycheck-check-syntax-automatically '(save mode-enabled))
  :bind
  ( :map errors
    ("n" . flycheck-next-error)
    ("p" . flycheck-previous-error)
    ("l" . flycheck-list-errors)
    ("v" . flycheck-verify-setup)))

;; Spacemacs' custom fringes

;; :config
;; (define-fringe-bitmap 'fk/flycheck-fringe-indicator
;;   (vector #b00000000
;;           #b00000000
;;           #b00000000
;;           #b00000000
;;           #b00000000
;;           #b00000000
;;           #b00000000
;;           #b00011100
;;           #b00111110
;;           #b00111110
;;           #b00111110
;;           #b00011100
;;           #b00000000
;;           #b00000000
;;           #b00000000
;;           #b00000000
;;           #b00000000))
;; (flycheck-define-error-level 'error
;;   :severity 2
;;   :overlay-category 'flycheck-error-overlay
;;   :fringe-bitmap 'fk/flycheck-fringe-indicator
;;   :error-list-face 'flycheck-error-list-error
;;   :fringe-face 'flycheck-fringe-error)
;; (flycheck-define-error-level 'warning
;;   :severity 1
;;   :overlay-category 'flycheck-warning-overlay
;;   :fringe-bitmap 'fk/flycheck-fringe-indicator
;;   :error-list-face 'flycheck-error-list-warning
;;   :fringe-face 'flycheck-fringe-warning)
;; (flycheck-define-error-level 'info
;;   :severity 0
;;   :overlay-category 'flycheck-info-overlay
;;   :fringe-bitmap 'fk/flycheck-fringe-indicator
;;   :error-list-face 'flycheck-error-list-info
;;   :fringe-face 'flycheck-fringe-info)

Language Server Protocol

Eglot

Eglot
(use-package eglot
  :commands eglot
  :init
  (setq eglot-stay-out-of '(flymake))
  :custom
  (eglot-ignored-server-capabilites '(:documentHighlightProvider))
  (eglot-autoshutdown t)
  :hook
  ;; (eglot-managed-mode . eldoc-box-hover-mode)
  (eglot-managed-mode . fk/company-enable-snippets)
  :config
  (add-to-list 'eglot-server-programs '(python-mode . ("pyright-langserver" "--stdio")))
  (with-eval-after-load 'eglot
    (load-library "project")))
Eldoc Box
(use-package eldoc-box
  :commands (eldoc-box-hover-mode eldoc-box-hover-at-point-mode)
  :custom
  (eldoc-box-clear-with-C-g t))

LSP Mode

LSP Mode
(use-package lsp-mode
  :commands lsp
  :custom
  (lsp-auto-guess-root t)
  (lsp-keymap-prefix "M-m l")
  (lsp-modeline-diagnostics-enable nil)
  (lsp-keep-workspace-alive nil)
  (lsp-auto-execute-action nil)
  (lsp-before-save-edits nil)
  (lsp-eldoc-enable-hover nil)
  (lsp-diagnostic-package :none)
  (lsp-completion-provider :none)
  (lsp-file-watch-threshold 1500)  ; pyright has more than 1000
  (lsp-enable-links nil)
  (lsp-headerline-breadcrumb-enable nil)
  ;; Maybe set in future:
  ;;(lsp-enable-on-type-formatting nil)
  :custom-face
  (lsp-face-highlight-read ((t (:underline t :background nil :foreground nil))))
  (lsp-face-highlight-write ((t (:underline t :background nil :foreground nil))))
  (lsp-face-highlight-textual ((t (:underline t :background nil :foreground nil))))
  :hook
  (lsp-mode . lsp-enable-which-key-integration))
LSP UI
(use-package lsp-ui
  :after lsp-mode
  :custom
  (lsp-ui-doc-show-with-cursor nil)
  (lsp-ui-doc-show-with-mouse nil)
  (lsp-ui-doc-position 'at-point)
  (lsp-ui-sideline-delay 0.5)
  (lsp-ui-peek-always-show t)
  (lsp-ui-peek-fontify 'always)
  :custom-face
  (lsp-ui-peek-highlight ((t (:inherit nil :background nil :foreground nil :weight semi-bold :box (:line-width -1)))))
  :bind
  ( :map lsp-ui-mode-map
    ([remap xref-find-references] . lsp-ui-peek-find-references)
    ("C-M-l" . lsp-ui-peek-find-definitions)
    ("C-c C-d" . lsp-ui-doc-show))
  :config
  ;;;; LSP UI posframe ;;;;
  (defun lsp-ui-peek--peek-display (src1 src2)
    (-let* ((win-width (frame-width))
            (lsp-ui-peek-list-width (/ (frame-width) 2))
            (string (-some--> (-zip-fill "" src1 src2)
                      (--map (lsp-ui-peek--adjust win-width it) it)
                      (-map-indexed 'lsp-ui-peek--make-line it)
                      (-concat it (lsp-ui-peek--make-footer))))
            )
      (setq lsp-ui-peek--buffer (get-buffer-create " *lsp-peek--buffer*"))
      (posframe-show lsp-ui-peek--buffer
                     :string (mapconcat 'identity string "")
                     :min-width (frame-width)
                     :poshandler 'posframe-poshandler-frame-center)))

  (defun lsp-ui-peek--peek-destroy ()
    (when (bufferp lsp-ui-peek--buffer)
      (posframe-delete lsp-ui-peek--buffer))
    (setq lsp-ui-peek--buffer nil
          lsp-ui-peek--last-xref nil)
    (set-window-start (get-buffer-window) lsp-ui-peek--win-start))

  (advice-add 'lsp-ui-peek--peek-new :override 'lsp-ui-peek--peek-display)
  (advice-add 'lsp-ui-peek--peek-hide :override 'lsp-ui-peek--peek-destroy)
  ;;;; LSP UI posframe ;;;;
  )
LSP Pyright-

YASnippet-snippets

(use-package yasnippet-snippets
  :straight (:host github :repo "KaratasFurkan/yasnippet-snippets" :branch "furkan")
  :after yasnippet)

Rainbow Delimiters

(use-package rainbow-delimiters
  :hook (prog-mode . rainbow-delimiters-mode))

Color Identifiers Mode

(use-package color-identifiers-mode
  :commands color-identifiers-mode)

Symbol Overlay

(use-package symbol-overlay
  :commands (symbol-overlay-mode symbol-overlay-put fk/highlight-occurrences)
  :bind
  ( :map symbol-overlay-mode-map
    ("C-c C-n" . symbol-overlay-jump-next)
    ("C-c C-p" . symbol-overlay-jump-prev))
  :hook
  (emacs-lisp-mode . symbol-overlay-mode)
  (python-mode . symbol-overlay-mode)
  :config
  (defun fk/highlight-occurrences ()
    "Put highlight to the occurrences of the symbol at point or the
string in the region. Uses `hi-lock' to highlight,
`symbol-overlay' to generate a random face. To remove highlights,
use `hi-lock-unface-buffer' or disable `hi-lock-mode'."
    (interactive)
    (let ((str (fk/get-selected-text))
          (face (nth (random (length symbol-overlay-faces)) symbol-overlay-faces)))
      (if str
          (highlight-regexp (regexp-quote str) face)
        (hi-lock-face-symbol-at-point))))

  (defalias 'fk/highlight-remove (lambda () (interactive) (hi-lock-unface-buffer t)))
  (defalias 'fk/highlight-remove-one-by-one 'hi-lock-unface-buffer))

Rainbow Mode

(use-package rainbow-mode
  :hook (prog-mode . rainbow-mode))

Bug Reference Mode

;; (use-package bug-reference
;;   :straight (:type built-in)
;;   ;; In default, this package is auto-activated and sometimes breaks syntax
;;   ;; highlighting by putting unrelevant bug-reference highlight and clickable
;;   ;; link.
;;   ;; E.g. `#112' at `my_color = "#112233"' gets bug-reference highlight.
;;   :disabled)

Emacs Lisp

Elisp Slime Nav

(use-package elisp-slime-nav
  :bind
  ( :map emacs-lisp-mode-map
    ("M-." . elisp-slime-nav-find-elisp-thing-at-point)))

Aggressive Indent

;; TODO: try in other languages (html, css, js, c)
(use-package aggressive-indent
  :straight ( :host github
              :repo "KaratasFurkan/aggressive-indent-mode"
              :branch "146-emacs28-compatible-suppress-messages")
  :hook (emacs-lisp-mode . aggressive-indent-mode))

Lisp Data Mode

(use-package lisp-mode
  :straight (:type built-in)
  :hook
  (lisp-data-mode . (lambda ()
                      ;; NOTE: `emacs-lisp-mode' derives from `lisp-data-mode',
                      ;; so make sure that the major-mode is `lisp-data-mode'.
                      (when (string= major-mode "lisp-data-mode")
                        (fk/add-local-hook 'before-save-hook
                                           (lambda ()
                                             (align-regexp (point-min) (point-max) "\\(\\s-*\\). (")
                                             (fk/indent-buffer)))))))

Python

Python

(use-package python
  :straight (:type built-in)
  :init
  (add-to-list 'all-the-icons-icon-alist
               '("\\.py$" all-the-icons-alltheicon "python" :height 1.1 :face all-the-icons-dblue))
  :custom
  (python-shell-interpreter "ipython")
  (python-shell-interpreter-args "-i --simple-prompt")
  (python-indent-guess-indent-offset-verbose nil)
  :bind
  ( :map python-mode-map
    ("C-c r" . python-indent-shift-right)
    ("C-c l" . python-indent-shift-left))
  :hook
  ;; With pyls:
  ;; pip install python-language-server flake8 pyls-black(optional) pyls-isort(optional)
  ;; With pyright
  ;; sudo npm install -g pyright && pip install flake8 black(optional)
  ;; NOTE: these hooks runs in reverse order
  (python-mode . (lambda () (setq-local company-prescient-sort-length-enable nil)))
  (python-mode . (lambda () (unless (and buffer-file-name (file-in-directory-p buffer-file-name "~/.virtualenvs/"))
                              (flycheck-mode))))
  ;; (python-mode . lsp-deferred)
  ;;(python-mode . (lambda () (fk/add-local-hook 'before-save-hook 'eglot-format-buffer)))
  (python-mode . eglot-ensure)
  ;; importmagic runs ~100mb ipython process per python file, and it does not
  ;; always find imports, 60%-70% maybe. I stop using this, but still want to keep.
  ;; (python-mode . importmagic-mode)
  (python-mode . fk/activate-pyvenv)
  (python-mode . (lambda ()
                   (when (and (buffer-file-name)
                              (string=
                               (car (last (f-split (f-parent (buffer-file-name)))))
                               "tests"))
                     (fk/hide-second-level-blocks))))
  (python-mode . fk/tree-sitter-hl-mode)
  (python-mode . (lambda () (setq-local fill-column 88)))
  :config
  (defvar python-walrus-operator-regexp ":=")

  ;; Make walrus operator ":=" more visible
  (font-lock-add-keywords
   'python-mode
   `((,python-walrus-operator-regexp 0 'escape-glyph t))
   'set))

Pyvenv

(use-package pyvenv
  :after python
  :config
  ;; I show this in `fk/minibuffer-modeline-update' manually.
  (setq pyvenv-mode-line-indicator nil)

  (defun fk/get-venv-name ()
    "Get venv name of current python project."
    (when-let* ((root-dir (projectile-project-root))
                (venv-file (concat root-dir ".venv"))
                (venv-exists (file-exists-p venv-file))
                (venv-name (with-temp-buffer
                             (insert-file-contents venv-file)
                             (nth 0 (split-string (buffer-string))))))
      venv-name))

  (defun fk/activate-pyvenv ()
    "Activate python environment according to the `project-root/.venv' file."
    (interactive)
    (when-let ((venv-name (fk/get-venv-name)))
      (pyvenv-mode)
      (pyvenv-workon venv-name)))

  (defun fk/open-venv-dir ()
    "Open the directory of installed libraries in `dired'."
    (interactive)
    (when-let* ((venv-name (fk/get-venv-name))
                (venv-dir (expand-file-name venv-name "~/.virtualenvs")))
      (dired (car (directory-files-recursively venv-dir "site-packages" t)))))

  ;; python-mode hook is not enough when more than one project's files are open.
  ;; It just re-activate pyvenv when a new file is opened, it should re-activate
  ;; on buffer or perspective switching too. NOTE: restarting lsp server is
  ;; heavy, so it should be done manually if needed.
  (add-hook 'window-configuration-change-hook 'fk/activate-pyvenv))

Import Magic

(use-package importmagic
  ;; pip install importmagic epc
  ;;
  ;; importmagic runs ~100mb ipython process per python file, and it does not
  ;; always find imports, 60%-70% maybe. I stop using this, but still want to keep.
  :commands importmagic-mode)

Black

(use-package blacken
  :commands blacken-mode blacken-buffer)

Isort

(use-package py-isort
  :commands py-isort-buffer)

LSP Pyright

(use-package lsp-pyright
  :after lsp-mode
  :custom
  (lsp-pyright-auto-import-completions nil)
  (lsp-pyright-typechecking-mode "off")
  :config
  (fk/async-process
   "npm outdated -g | grep pyright | wc -l" nil
   (lambda (process output)
     (pcase output
       ("0\n" (message "Pyright is up to date."))
       ("1\n" (message "A pyright update is available."))))))

Django

;; Search functions for Django

(fk/helm-rg-define-search-commands
 ("django-search-models"      django "m" "models.py"        "^class ")
 ("django-search-factories"   django "f" "factories.py"     "^class ")
 ("django-search-views"       django "v" "views*.py"        "^class ")
 ("django-search-serializers" django "s" "*serializers*.py" "^class ")
 ("django-search-tests"       django "t" "*test*.py"        "^class ")
 ("django-search-settings"    django "S" "*/settings/*"     "")
 ("django-search-admins"      django "a" "admin.py"         "^class admin( ")
 ("django-search-permissions" django "p" "permissions.py"   "^class ")
 ("django-search-mixins"      django "x" "mixins.py"        "^class ")
 ("django-search-urls"        django "u" "*.py"             "path( "))

;; Utility functions for Django

(defcustom fk/django-test-args "" "Should be set as directory local variable.")

(defun fk/django-get-module ()
  "pony-get-module originally."
  (let* ((root (projectile-project-root))
         (path (file-name-sans-extension (or buffer-file-name (expand-file-name default-directory)))))
    (when (string-match (projectile-project-root) path)
      (let ((path-to-class (substring path (match-end 0))))
        (mapconcat 'identity (split-string path-to-class "/") ".")))))

(defun fk/django-copy-path-of-test-at-point ()
  "Add path of the test at point to kill-ring. Returns the path."
  (interactive)
  (require 'which-func)
  (let* ((defuns (seq-subseq (split-string (which-function) "\\.") 0 2))
         (class (car defuns))
         (func (let ((f (-second-item defuns))) (and f (string-match "^test" f) f)))
         (module (fk/django-get-module))
         (path (concat module (and module class ".") class (and class func ".") func)))
    (kill-new path)))

(defun fk/django-run-test-at-point ()
  "Run test at point."
  (interactive)
  (fk/django-copy-path-of-test-at-point)
  (let ((vterm-buffer (save-window-excursion
                        (fk/projectile-vterm)
                        (current-buffer))))
    (if (window-live-p (get-buffer-window vterm-buffer))
        (select-window (get-buffer-window vterm-buffer))
      (fk/projectile-vterm)))
  (vterm-insert (format "python manage.py test --keepdb %s %s" fk/django-test-args
                        (substring-no-properties (pop kill-ring)))))

(bind-keys*
 :map django
 ("c" . fk/django-copy-path-of-test-at-point)
 ("d" . fk/django-run-test-at-point))

;; Highlighting of django template blocks
(defvar django-block-regexp (rx "{%" (zero-or-more space) (zero-or-one "end") "block " (group (zero-or-more (not (any ?\n ?{)))) "%}"))

(defface django-block-keyword-face
  '((t (:foreground "tomato" :bold t)))
  "Face for django template blocks.")

(defface django-block-name-face
  '((t (:foreground "wheat" :bold t)))
  "Face for django template blocks.")

(font-lock-add-keywords
 'web-mode
 `((,django-block-regexp 0 'django-block-keyword-face t)
   (,django-block-regexp 1 'django-block-name-face t))
 t)

;; Highlighting of django template comments
(defvar django-comment-regexp
  (rx "{%" (zero-or-more space) "comment" (zero-or-more space) "%}"
      (zero-or-more anything)
      "{%" (zero-or-more space) "endcomment" (zero-or-more space) "%}"))

(defface django-comment-face
  '((t (:inherit 'font-lock-comment-face)))
  "Face for django template comments.")

(font-lock-add-keywords
 'web-mode
 `((,django-comment-regexp 0 'django-comment-face t))
 t)

Jupyter Notebook

(use-package ein
  :commands ein:run
  :custom-face
  (ein:cell-input-area ((t (:background "#21262E"))))
  :hook
  (ein:ipynb-mode . fk/activate-pyvenv))

Web Mode

TODO: seperate sections (html, css..)

Web Mode (HTML)

(use-package web-mode
  :custom
  (css-indent-offset 2)
  (web-mode-markup-indent-offset 2)
  (web-mode-enable-auto-indentation nil)
  (web-mode-enable-auto-pairing nil)
  (web-mode-engines-alist '(("django" . "\\.html\\'")))
  :custom-face
  (web-mode-block-string-face ((t (:inherit font-lock-string-face))))
  (web-mode-html-attr-value-face ((t (:inherit font-lock-string-face :foreground nil))))
  (web-mode-current-element-highlight-face ((t (:inherit highlight))))
  :mode ;; Copied from spacemacs
  (("\\.phtml\\'"      . web-mode)
   ("\\.tpl\\.php\\'"  . web-mode)
   ("\\.twig\\'"       . web-mode)
   ("\\.xml\\'"        . web-mode)
   ("\\.html\\'"       . web-mode)
   ("\\.htm\\'"        . web-mode)
   ("\\.[gj]sp\\'"     . web-mode)
   ("\\.as[cp]x?\\'"   . web-mode)
   ("\\.eex\\'"        . web-mode)
   ("\\.erb\\'"        . web-mode)
   ("\\.mustache\\'"   . web-mode)
   ("\\.handlebars\\'" . web-mode)
   ("\\.hbs\\'"        . web-mode)
   ("\\.eco\\'"        . web-mode)
   ("\\.ejs\\'"        . web-mode)
   ("\\.svelte\\'"     . web-mode)
   ("\\.djhtml\\'"     . web-mode)
   ("\\.mjml\\'"       . web-mode))
  :hook
  (web-mode . web-mode-toggle-current-element-highlight))

Emmet Mode

Emmet Mode

(use-package emmet-mode
  :custom
  (emmet-move-cursor-between-quotes t)
  :custom-face
  (emmet-preview-input ((t (:inherit lazy-highlight))))
  :bind
  ( :map emmet-mode-keymap
    ([remap yas-expand] . emmet-expand-line)
    ("M-n"  . emmet-next-edit-point)
    ("M-p"  . emmet-prev-edit-point)
    ("C-c p" . emmet-preview-mode))
  :hook
  ;;(rjsx-mode . (lambda () (setq emmet-expand-jsx-className? t)))
  (web-mode . emmet-mode)
  (css-mode . emmet-mode))

Helm Emmet

(use-package helm-emmet
  :after helm emmet)

Company Web

(use-package company-web
  :after web-mode
  :config
  (add-to-list 'company-backends '(company-web-html :with company-yasnippet)))

Json Mode

(use-package json-mode
  :mode ("\\.json\\'" . json-mode))
(use-package json-navigator
  :commands json-navigator-navigate-region)

Prettier

(use-package prettier-js
  :hook
  ;;(web-mode . prettier-js-mode) ;; breaks django templates
  (css-mode . prettier-js-mode)
  (json-mode . prettier-js-mode)
  (js2-mode . prettier-js-mode))

Auto Rename Tag

(use-package auto-rename-tag
  :hook
  (web-mode . auto-rename-tag-mode))

JavaScript

JavaScript

(use-package js2-mode
  :mode "\\.js\\'"
  :custom
  (js-indent-level 2)
  :hook
  (js2-mode . flycheck-mode)
  ;;(js2-mode . (lambda () (require 'tree-sitter-langs) (tree-sitter-hl-mode)))
  (js2-mode . lsp-deferred))

Go

(use-package go-mode
  ;; install go & go-tools, for arch based linux:
  ;; sudo pacman -S go go-tools
  :mode "\\.go\\'"
  :init
  (defface golang-blue
    '((((background dark)) :foreground "#69D7E4")
      (((background light)) :foreground "#69D7E4"))
    "Face for golang icon")
  (add-to-list 'all-the-icons-icon-alist
               '("\\.go$" all-the-icons-fileicon "go" :height 1 :face golang-blue))
  :custom
  (gofmt-command "goimports")
  :hook
  (go-mode . flycheck-mode)
  (go-mode . lsp-deferred)
  (go-mode . (lambda () (require 'tree-sitter-langs) (tree-sitter-hl-mode)))
  (go-mode . (lambda () (fk/add-local-hook 'before-save-hook 'gofmt))))

C

(use-package cc-mode
  :bind
  ( :map c-mode-base-map
    ("C-c C-c" . fk/c-run))
  :hook
  (c-mode . lsp-deferred)
  (c++-mode . lsp-deferred))

(use-package clang-format
  :commands clang-format-buffer clang-format-region
  :hook
  (c-mode . (lambda () (fk/add-local-hook 'before-save-hook 'clang-format-buffer)))
  (c++-mode . (lambda () (fk/add-local-hook 'before-save-hook 'clang-format-buffer))))

Lua

(use-package lua-mode
  :mode "\\.lua\\'")

Tools

Dired

Dired

(use-package dired
  :straight (:type built-in)
  :custom
  ;; ls parameters:
  ;; -l     use a long listing format
  ;; -A, --almost-all
  ;;        do not list implied . and ..
  ;; -h, --human-readable
  ;;        with -l and -s, print sizes like 1K 234M 2G etc.
  ;; -p, --indicator-style=slash
  ;;        append / indicator to directories
  (dired-listing-switches "-lAhp --group-directories-first")
  (dired-dwim-target t)
  (mouse-1-click-follows-link nil)
  (wdired-allow-to-change-permissions 'advanced)
  :bind
  ( :map dired-mode-map
    ("H" . dired-hide-details-mode)
    ("C-M-u" . dired-up-directory)
    ("O" . browse-url-of-dired-file)                  ; open with associated app
    ("<mouse-1>" . fk/dired-left-click)               ; left click
    ("<mouse-2>" . dired-up-directory)                ; middle click
    ("<mouse-3>" . (lambda (event) (interactive "e")  ; right click
                     (mouse-set-point event)
                     (dired-subtree-toggle)))
    ("RET" . fk/dired-smart-open)
    ("C-c C-e" . wdired-change-to-wdired-mode)
    ("f". fk/dired-open-systems-file-manager))
  :hook
  (dired-mode . dired-hide-details-mode)
  ;; Fix `save-place' in dired when opening from bookmarks (mostly from dashboard)
  (bookmark-after-jump . save-place-dired-hook)
  (dired-after-readin . (lambda () (when (string= (dired-current-directory) (concat (getenv "HOME") "/"))
                                     (dired-omit-mode))))
  :config
  (defun fk/dired-left-click (event)
    "When file is a directory, open directory in dired. Otherwise, open file
with associated application."
    (interactive "e")
    (mouse-set-point event)
    (let ((file (dired-get-file-for-visit)))
      (if (file-directory-p file)
          (dired-mouse-find-file event)
        (browse-url-of-dired-file))))

  ;; TODO: change this to "open video (maybe some other types too) files with
  ;; associated apps".
  (advice-add 'browse-url :override 'browse-url-xdg-open)  ; I had to add this in emacs28
  (defun fk/dired-smart-open ()
    "If file size bigger than 50mb, open with associated system application,
else call `dired-find-file'"
    (interactive)
    (if (> (file-attribute-size (file-attributes (dired-file-name-at-point)))
           50000000)
        (browse-url-of-dired-file)
      (dired-find-file)))

  ;;:: Dired in single buffer (prevent dired from opening a lot of buffers)
  (put 'dired-find-alternate-file 'disabled nil)

  (defun fk/dired-up-directory ()
    "`dired-up-directory' in same buffer."
    (interactive)
    (find-alternate-file ".."))

  (defun fk/dired-find-file (orig-func &rest args)
    "If selected file is a directory, open it in same buffer,
otherwise, open the file in a new buffer."
    (let ((file (dired-get-file-for-visit)))
      (if (file-directory-p file)
          (apply 'dired-find-alternate-file args)
        (apply orig-func args))))

  (advice-add 'dired-up-directory :override 'fk/dired-up-directory)
  (advice-add 'dired-find-file :around 'fk/dired-find-file)
  ;;;; Dired in single buffer

  (defun fk/dired-open-systems-file-manager ()
    "Open current directory with the system's default file manager."
    (interactive)
    (call-process-shell-command (concat "xdg-open " default-directory " &")))

  (defun fk/dired-sort-by-extension ()
    "Sort files by extension."
    (interactive)
    (dired-sort-other (concat dired-listing-switches " -X"))))

Dired-X

(use-package dired-x
  :straight (:type built-in)
  :after dired
  :custom
  (dired-omit-files "^\\..*$")
  :bind
  ( :map dired-mode-map
    ("h" . dired-omit-mode)))

Dired Icons

(use-package all-the-icons-dired
  :hook (dired-mode . all-the-icons-dired-mode)
  :config
  (add-to-list 'all-the-icons-icon-alist
               '("\\.mkv" all-the-icons-faicon "film"
                 :face all-the-icons-blue))
  (add-to-list 'all-the-icons-icon-alist
               '("\\.srt" all-the-icons-octicon "file-text"
                 :v-adjust 0.0 :face all-the-icons-dcyan))

  ;; Turn off all-the-icons-dired-mode before wdired-mode
  ;; TODO: disable icons just before save, not during wdired-mode
  (defadvice wdired-change-to-wdired-mode (before turn-off-icons activate)
    (all-the-icons-dired-mode -1))
  (defadvice wdired-change-to-dired-mode (after turn-on-icons activate)
    (all-the-icons-dired-mode 1)))

Dired Subtree

(use-package dired-subtree
  :after dired
  :custom
  (dired-subtree-use-backgrounds nil)
  :bind
  ( :map dired-mode-map
    ("TAB" . dired-subtree-toggle)
    ("<tab>" . dired-subtree-toggle))
  :config
  ;; Fix "no icons in subtree" issue.
  (defadvice dired-subtree-toggle
      (after add-icons activate) (revert-buffer)))

Dired Sidebar

(use-package dired-sidebar
  :commands dired-sidebar-toggle-sidebar
  :bind*
  ( :map windows
    ("t" . dired-sidebar-toggle-sidebar))
  :hook
  (dired-sidebar-mode . fk/darken-background)
  :config
  (defun fk/sidebar-toggle ()
    "Toggle both `dired-sidebar' and `ibuffer-sidebar'."
    (interactive)
    (dired-sidebar-toggle-sidebar)
    (ibuffer-sidebar-toggle-sidebar)))

IBuffer Sidebar

(use-package ibuffer-sidebar
  :commands ibuffer-sidebar-toggle-sidebar
  :bind
  ( :map ibuffer-mode-map
    ("M-o" . nil)))

Dired Show Readme

(use-package dired-show-readme
  :straight (:host gitlab :repo "kisaragi-hiu/dired-show-readme")
  :commands dired-show-readme-mode
  ;; :hook
  ;; (dired-mode . dired-show-readme-mode)
  )

;; Alternative
(use-package dired-auto-readme
  :straight (:host github :repo "amno1/dired-auto-readme")
  :commands dired-auto-readme-mode)

Dired Posframe

(use-package dired-posframe
  :straight (:host github :repo "conao3/dired-posframe.el")
  :commands dired-posframe-mode)

Dired Recent

(use-package dired-recent
  :bind
  ( :map files
    ("d" . dired-recent-open))
  :hook
  (dashboard-after-initialize . dired-recent-mode))

Dired Git Info-

Org

Org

(use-package org
  :straight (:type built-in)
  :init
  (setq org-directory "~/org")  ; This is default already but lets declare it explicitly
  (setq org-gtd-files  `(,(concat org-directory "/inbox.org")
                         ,(concat org-directory "/todos.org")
                         ,(concat org-directory "/someday.org")))
  :custom
  (org-confirm-babel-evaluate nil)
  (org-ellipsis "") ;; ↴, ▼, ▶, ⤵
  (org-src-window-setup 'current-window)
  (org-startup-indented t)
  (org-startup-folded 'content)  ; show only headlines (and sub headlines, recursively) at startup
  (org-startup-with-inline-images t)
  (org-image-actual-width '(400))
  (org-hierarchical-todo-statistics nil)
  (org-checkbox-hierarchical-statistics nil)
  (org-src-preserve-indentation t)
  (org-adapt-indentation nil)
  (org-tags-column -120)
  (org-imenu-depth 20)
  (org-hide-emphasis-markers t)
  (org-catch-invisible-edits 'show-and-error)
  (org-cycle-separator-lines 0)  ; Never leave empty lines between headings in collapsed view
  ;;;; Getting Things Done ;;;;
  (org-agenda-files `(,@org-gtd-files ,(concat org-directory "/agenda.org")))
  (org-complete-tags-always-offer-all-agenda-tags t)
  (org-agenda-start-on-weekday nil)
  (org-agenda-current-time-string "────────── now ──────────")
  (org-agenda-format-date (lambda (date) (concat "\n" (org-agenda-format-date-aligned date))))
  (org-agenda-prefix-format '((agenda  . "     %i %?-12t% s")
                              (todo  . " %i %-12:c")
                              (tags  . " %i %-12:c")
                              (search . " %i %-12:c")))
  (org-agenda-time-grid '((daily today require-timed remove-match)
                          (1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000)
                          "" "················"))
  (org-deadline-warning-days 5)
  ;; (org-display-custom-times t)
  ;; (org-time-stamp-custom-formats '("<%d/%m/%Y %A>" . "<%d/%m/%Y %A %H:%M>"))
  (org-bookmark-names-plist '())  ; Do not create bookmarks
  (org-capture-templates '(("i" "Capture to inbox" entry
                            (file "inbox.org")
                            "* %?\nCREATED: %U"
                            :empty-lines 1)))
  (org-refile-targets '(("todos.org" :level . 1)
                        ("someday.org" :level . 1)
                        ("archive.org" :level . 1)
                        ("agenda.org" :level . 1)))
  (org-priority-default ?A)  ; Highest
  ;; (org-log-done 'time)
  (org-fontify-done-headline t)
  (org-log-into-drawer t)  ; Log TODO state changes into :LOGBOOK: drawer insted of directly adding lines to the subtree
  (org-todo-keywords '((sequence "TODO(t)" "WAIT(w)" "HOLD(h)" "STRT(s)" "WIP.(i)" "STAG(a)" "PROD(p)" "|" "DONE(d)")))
  (org-todo-keyword-faces
   '(("TODO" :foreground "orangered2" :weight bold)
     ("WAIT" :foreground "goldenrod" :weight bold)
     ("HOLD" :foreground "#DC752F" :weight bold)
     ("STRT" :foreground "PaleGreen" :weight bold)
     ("WIP." :foreground "#86DC2F" :weight bold)
     ("STAG" :foreground "DarkTurquoise" :weight bold)
     ("PROD" :foreground "DodgerBlue" :weight bold)))
  (org-clock-clocktable-default-properties '(:maxlevel 10))
  (org-extend-today-until 4)
  (org-clock-mode-line-total 'today)
  (org-clock-total-time-cell-format "%s")  ; remove styling to copy paste correctly
  (org-duration-format '((special . h:mm)))  ; to display 30 hours as 30:00 instead of 1d 6:00
  ;;;; Getting Things Done ;;;;
  :custom-face
  (org-block ((t (:family ,fk/default-font-family :extend t))))
  (org-ellipsis ((t (:foreground nil :inherit org-tag :weight light :height 0.9))))
  (org-checkbox ((t (:foreground "white"))))
  (org-level-1 ((t (:height 1.3 :weight bold))))
  (org-level-2 ((t (:height 1.2 :weight bold))))
  (org-level-3 ((t (:height 1.15 :weight bold))))
  (org-level-4 ((t (:height 1.1 :weight bold))))
  (org-level-5 ((t (:height 1.0 :weight bold))))
  (org-level-6 ((t (:height 1.0 :weight bold))))
  (org-level-7 ((t (:height 1.0 :weight bold))))
  (org-level-8 ((t (:height 1.0 :weight bold))))
  (org-drawer ((t (:foreground nil :inherit font-lock-comment-face))))
  (org-table ((t (:inherit org-block :family ,fk/default-font-family :foreground ,(face-foreground 'default)))))
  (org-document-title ((t (:family "AV Qest" :height 3.0))))
  (org-block-begin-line ((t (:foreground ,fk/light-color1 :background ,fk/background-color :extend t))))
  (org-document-info-keyword ((t (:foreground ,fk/background-color))))  ; Make #+TITLE: invisible
  (org-meta-line ((t (:foreground ,fk/light-color1))))  ; Less distractive
  (org-agenda-date ((t (:foreground "#ECBE7B"))))
  (org-agenda-date-today ((t (:foreground "LightGoldenrod"))))
  (org-agenda-current-time ((t (:foreground "LightGoldenrod"))))
  (org-agenda-calendar-event ((t (:weight bold))))
  :bind
  ( :map org
    ("a" . fk/org-agenda-posframe)
    ("f" . (lambda () (interactive) (helm-find-files-1 (concat org-directory "/"))))
    ("c" . (lambda () (interactive) (org-capture :keys "i")))
    ;; ("t" . fk/org-babel-tangle-block)
    ("d" . (lambda () (interactive) (org-todo "DONE")))
    :map org-mode-map
    ("C-c C-e" . org-edit-special)
    ("M-n" . org-next-visible-heading)
    ("M-p" . org-previous-visible-heading)
    ("C-x C-1" . outline-hide-other)
    ("C-c C-r" . org-refile-hydra/body)
    ("C-c C-a" . fk/org-refile-done)  ; "a" for archive
    ("C-c C-t" . fk/org-refile-trash)
    ("C-c t" . org-todo)
    ("C-c C-p" . org-priority-down)
    ("C-M-j" . org-open-at-point)
    ("C-c r" . org-shiftright)
    ("C-c l" . org-shiftleft)
    ("C-c u" . org-shiftup)
    ("C-c d" . org-shiftdown)
    ("C-c R" . org-metaright)
    ("C-c L" . org-metaleft)
    ("C-c U" . org-metaup)
    ("C-c D" . org-metadown)
    ("C-c C-x C-e" . org-set-effort)  ; ‘3:12’, ‘1:23:45’, or ‘1d3h5min’
    :map org-src-mode-map
    ("C-c C-c" . org-edit-src-exit)
    ;; Better, intuitive movement when selecting a date for schedule or deadline
    :map org-read-date-minibuffer-local-map
    ("C-n". (lambda () (interactive) (org-eval-in-calendar '(calendar-forward-week 1))))
    ("C-p". (lambda () (interactive) (org-eval-in-calendar '(calendar-backward-week 1))))
    ("C-f". (lambda () (interactive) (org-eval-in-calendar '(calendar-forward-day 1))))
    ("C-b". (lambda () (interactive) (org-eval-in-calendar '(calendar-backward-day 1))))
    ("C-v". (lambda () (interactive) (org-eval-in-calendar '(calendar-forward-month 1))))
    ("M-v". (lambda () (interactive) (org-eval-in-calendar '(calendar-backward-month 1)))))
  :hook
  (org-babel-after-execute . org-redisplay-inline-images)
  (org-mode . (lambda () (fk/add-local-hook 'before-save-hook 'org-redisplay-inline-images)))
  (org-after-refile-insert . (lambda () (fk/org-sort-by-priority) (save-buffer)))
  (org-capture-mode . delete-other-windows)  ; make capture buffer fullscreen
  ;; (org-agenda-mode . (lambda () (require 'org-habit)))
  :config
  (add-to-list 'org-emphasis-alist '("#" (:box '(:line-width -1))))  ; FIXME: does not work.
  (setf (cdr (assoc "*" org-emphasis-alist)) '((:weight extra-bold :foreground "#DDDDDD")))

  (defun fk/org-babel-load-languages ()
    "Load languages I use."
    (interactive)
    (org-babel-do-load-languages 'org-babel-load-languages '((python . t)
                                                             (emacs-lisp . t)
                                                             (shell . t)
                                                             (ein . t))))

  (defun fk/org-babel-tangle-block()
    (interactive)
    (let ((current-prefix-arg '(4)))
      (call-interactively 'org-babel-tangle)))

  (with-eval-after-load 'org-agenda
    (bind-key "m" 'org-agenda-month-view org-agenda-mode-map))

  ;; Beautify org mode
  (font-lock-add-keywords 'org-mode
                          '(("^ *\\([-]\\) "
                             (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) ""))))))
  (font-lock-add-keywords 'org-mode
                          '(("^ *\\([+]\\) "
                             (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) ""))))))
  (defface org-checkbox-done-text
    '((t (:inherit 'font-lock-comment-face :slant normal)))
    "Face for the text part of a checked org-mode checkbox.")

  (font-lock-add-keywords
   'org-mode
   `(("^[ \t]*\\(?:[-+*]\\|[0-9]+[).]\\)[ \t]+\\(\\(?:\\[@\\(?:start:\\)?[0-9]+\\][ \t]*\\)?\\[\\(?:X\\|\\([0-9]+\\)/\\2\\)\\][^\n]*\n\\)"
      1 'org-checkbox-done-text prepend))
   'append)

  (defun fk/org-insert-created-time ()
    (interactive)
    (insert "CREATED: " (format-time-string (org-time-stamp-format t t) (current-time))))

  (defun fk/org-refile-fixed-location (file headline)
    "Refile headline without selecting from refile-targets."
    (let ((pos (save-window-excursion
                 (find-file file)
                 (org-find-exact-headline-in-buffer headline))))
      (org-refile nil nil (list headline file nil pos))))

  (defun fk/org-refile-fixed-location-with-closed-timestamp (file headline)
    "Refile headline without selecting from refile-targets. Add
    \"CLOSED\" timestamp info."
    (add-hook 'org-after-refile-insert-hook (lambda () (org-add-planning-info 'closed (org-current-effective-time))) -100)
    (fk/org-refile-fixed-location file headline)
    (remove-hook 'org-after-refile-insert-hook (lambda () (org-add-planning-info 'closed (org-current-effective-time)))))

  (defun fk/org-refile-done ()
    (interactive)
    (fk/org-refile-fixed-location-with-closed-timestamp "archive.org" "Done"))

  (defun fk/org-refile-trash ()
    (interactive)
    (fk/org-refile-fixed-location-with-closed-timestamp "archive.org" "Trash"))

  (defhydra org-refile-hydra
    (:color red :hint nil)
    "
^Move^                 ^Todo^         ^Someday^          ^Archive^
-----------------------------------------------------------
_n_: Next              _w_: Work      _E_: Emacs         _d_: Done
_p_: Previous          _e_: Emacs     _P_: Presentation  _x_: Trash
^^                     _t_: Tech      _T_: Tech          ^^
_1_: Low Priority      _h_: Home      _H_: Home          ^^
_2_: Medium Priority   _o_: Other     _W_: Watch         ^^
_3_: High Priority     ^^             _R_: Read          ^^
^^                     ^^             _O_: Other         ^^
_c_: Set Time Effort   ^^                                ^^
_a_: Set Tags          ^^                                ^^


"
    ;; Move
    ("n" next-line)
    ("p" previous-line)
    ("1" (lambda () (interactive) (org-priority ?C)))
    ("2" (lambda () (interactive) (org-priority ?B)))
    ("3" (lambda () (interactive) (org-priority ?A)))
    ("c" org-set-effort)
    ("a" org-set-tags-command)
    ;; Todo
    ("w" (lambda () (interactive) (fk/org-refile-fixed-location "todos.org" "Work")))
    ("e" (lambda () (interactive) (fk/org-refile-fixed-location "todos.org" "Emacs")))
    ("t" (lambda () (interactive) (fk/org-refile-fixed-location "todos.org" "Tech")))
    ("h" (lambda () (interactive) (fk/org-refile-fixed-location "todos.org" "Home")))
    ("o" (lambda () (interactive) (fk/org-refile-fixed-location "todos.org" "Other")))
    ;; Someday
    ("E" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Emacs")))
    ("P" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Emacs Presentation")))
    ("T" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Tech")))
    ("H" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Home")))
    ("W" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Watch")))
    ("R" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Read")))
    ("O" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Other")))
    ;; Archive
    ("d" fk/org-refile-done)
    ("x" fk/org-refile-trash)
    ;; General
    ("m" org-refile "Refile manually")
    ("s" save-buffer "Save buffer")
    ("q" nil "Quit" :color blue))

  (set-face-attribute 'org-mode-line-clock nil :inherit 'default)

  (defun fk/org-clock-update-mode-line (&optional _)
    "Display org-clock info in echo area. See `fk/minibuffer-modeline-update'."
    (if org-clock-effort
        (org-clock-notify-once-if-expired)
      (setq org-clock-task-overrun nil))
    (setq fk/org-clock-string
          (let ((clock-string (org-clock-get-clock-string)))
            (if (and (> org-clock-string-limit 0)
                     (> (length clock-string) org-clock-string-limit))
                (substring clock-string 0 org-clock-string-limit)
              clock-string)))
    (if (and org-clock-task-overrun org-clock-task-overrun-text)
        (setq fk/org-clock-string
              (concat (propertize
                       org-clock-task-overrun-text
                       'face 'org-mode-line-clock-overrun)
                      fk/org-clock-string))))

  (advice-add 'org-clock-update-mode-line :override 'fk/org-clock-update-mode-line)

  (add-hook 'org-clock-in-hook (lambda ()
                                 (interactive)
                                 (setq fk/org-clocking-buffer (org-clocking-buffer))
                                 (save-buffer)))
  (add-hook 'org-clock-out-hook (lambda ()
                                  (interactive)
                                  (with-current-buffer fk/org-clocking-buffer
                                    (save-buffer))
                                  (setq fk/org-clocking-buffer nil)
                                  (setq fk/org-clock-string "")))
  (add-hook 'org-clock-cancel-hook (lambda ()
                                     (interactive)
                                     (with-current-buffer fk/org-clocking-buffer
                                       (save-buffer))
                                     (setq fk/org-clocking-buffer nil)
                                     (setq fk/org-clock-string "")))

  (defun fk/org-clock-out-and-update-report (orig-func &rest args)
    "Update org clock report table after clock out."
    (interactive)
    (let ((buffer (org-clocking-buffer)))
      (apply orig-func args)  ; clock out
      ;; `org-clock-report' updates the first table in the buffer
      ;; when called with a prefix argument
      (save-excursion
        (with-current-buffer buffer
          (let ((current-prefix-arg 4))
            (call-interactively 'org-clock-report)
            (save-buffer))))))

  (advice-add 'org-clock-out :around 'fk/org-clock-out-and-update-report)

  ;; Similar to `fk/org-clock-out-and-update-report',
  ;; I added this file local variable to my time tracking file to update report on save:
  ;; # Local Variables:
  ;; # eval: (add-hook 'before-save-hook (lambda () (save-excursion (let ((current-prefix-arg 4)) (call-interactively 'org-clock-report)))) :local t)
  ;; # End:
  )

Org Super Agenda

(use-package org-super-agenda
  :after org-agenda
  :custom
  (org-super-agenda-groups '(( :name "Calendar"
                               :time-grid t)
                             ( :name "Deadlines"
                               :deadline t)
                             ( :name "Scheduled"
                               :scheduled t)))
  :config
  (org-super-agenda-mode))

Org QL

(use-package org-ql
  :commands org-ql-search org-ql-view
  :bind
  ( :map org
    ("q" . org-ql-view))
  :config
  (with-eval-after-load 'org-ql-view
    (define-key org-ql-view-map (kbd "q") 'quit-window)

    (defmacro fk/org-ql-view (title query)
      `'(,title
         :title ,title
         :buffers-files ,org-gtd-files
         :query ,query
         :super-groups ((:auto-parent))
         :sort (priority)))

    (setq org-ql-views
          `(,(fk/org-ql-view "Effort <=15min"         (effort "<=" "15min"))
            ,(fk/org-ql-view "Effort >15min <=30min"  (and (effort ">" "15min") (effort "<=" "30min")))
            ,(fk/org-ql-view "Effort >30min <=1h"     (and (effort ">" "30min") (effort "<=" "1h")))
            ,(fk/org-ql-view "Effort >1h <=2h"        (and (effort ">" "1h") (effort "<=" "2h")))
            ,(fk/org-ql-view "Effort >2h <=4h"        (and (effort ">" "2h") (effort "<=" "4h")))
            ,(fk/org-ql-view "Effort >4h"             (effort ">" "4h"))))))

Custom Functions

org-screenshot

(defun fk/org-screenshot ()
  ;; fork from: https://delta.re/org-screenshot/
  ;; https://github.com/kadircancetin/.emacs.d
  "Take a screenshot into a time stamped unique-named file in the
  same directory as the org-buffer and insert a link to this file."
  (interactive)
  (when (eq major-mode 'org-mode)
    (suspend-frame)
    (run-at-time
     "500 millisec" nil  ; I have animation when minimize window
     (lambda ()
       (org-display-inline-images)
       (setq filename
             (concat
              (make-temp-name
               (concat (file-name-nondirectory (buffer-file-name))
                       "_imgs/"
                       (format-time-string "%Y%m%d_%H%M%S_")) ) ".png"))
       (unless (file-exists-p (file-name-directory filename))
         (make-directory (file-name-directory filename)))
       ;; take screenshot
       (if (eq system-type 'darwin)
           (call-process "screencapture" nil nil nil "-i" filename))
       (if (eq system-type 'gnu/linux)
           (call-process "import" nil nil nil filename))
       ;; insert into file if correctly taken
       (if (file-exists-p filename)
           (insert (concat "[[file:" filename "]]")))
       (org-remove-inline-images)
       (org-display-inline-images)
       (other-frame 0)))))

org-indent-src-block

(defun fk/org-indent-src-block ()
  (interactive)
  (org-edit-special)
  (fk/indent-buffer)
  (org-edit-src-exit))

org-sort-by-priority

(defun fk/org-sort-by-priority ()
  "Sort entries in level=2 by priority."
  (interactive)
  (org-map-entries (lambda () (condition-case nil
                                  (org-sort-entries nil ?p)
                                (error nil)))
                   "LEVEL=1")
  (org-set-startup-visibility))

org-agenda-posframe

(defun fk/org-agenda-posframe ()
  "`org-agenda-list' in a posframe. Quit with 'q' as usual."
  (interactive)
  (save-window-excursion
    (org-agenda-list)
    (fk/darken-background))
  (let ((frame (posframe-show org-agenda-buffer
                              :poshandler 'posframe-poshandler-frame-center
                              :border-width 30
                              :border-color fk/dark-color)))
    (x-focus-frame frame)
    (with-current-buffer org-agenda-buffer
      (setq-local cursor-type 'box))))

Org Bullets

(use-package org-bullets
  :custom
  (org-bullets-bullet-list '(""))
  ;;;; Alternatives
  ;; (org-bullets-bullet-list '("①" "②" "③" "④" "⑤" "⑥" "⑦" "⑧" "⑨"))
  ;; (org-bullets-bullet-list '("➀" "➁" "➂" "➃" "➄" "➅" "➆" "➇" "➈"))
  ;; (org-bullets-bullet-list '("❶" "❷" "❸" "❹" "❺" "❻" "❼" "❽" "❾"))
  ;; (org-bullets-bullet-list '("➊" "➋" "➌" "➍" "➎" "➏" "➐" "➑" "➒"))
  ;; (org-bullets-bullet-list '("⒈" "⒉" "⒊" "⒋" "⒌" "⒍" "⒎" "⒏" "⒐"))
  :hook (org-mode . org-bullets-mode))

Toc Org

(use-package toc-org
  :straight (:host github :repo "KaratasFurkan/toc-org" :branch "insert-silently")
  :custom
  (toc-org-max-depth 10)
  (toc-org-insert-silently t)
  :hook (org-mode . toc-org-mode))

Org Table Auto Align

;; TODO: make this snippet a package
;; (use-package org-table-auto-align-mode ; NOTE: breaks undo
;;   :load-path (lambda () (concat user-emacs-directory "load/org-table-auto-align-mode"))
;;   :hook org-mode)

ob-async

(use-package ob-async
  :after org)

Org Pomodoro

(use-package org-pomodoro
  :straight (:files ("*"))  ; For sound files
  :commands org-pomodoro
  :custom
  (org-pomodoro-audio-player "ffplay")
  :config
  ;; Apply args for all sounds
  (advice-add 'org-pomodoro-sound-args :override (lambda (_) "-volume 20 -nodisp -nostats -hide_banner")))

Org Roam

Org Roam

(use-package org-roam
  :custom
  (org-roam-directory "~/org/roam/")
  :bind
  ( :map org
    ("o" . org-roam-find-file)))

Org Roam Server

;;(use-package org-roam-server
;;  :after org-roam)

Company Org Roam

(use-package company-org-roam
  :disabled
  :after org-roam
  :config
  (add-to-list 'company-backends 'company-org-roam))

Org Fancy Priorities

(use-package org-fancy-priorities
  :custom
  (org-fancy-priorities-list '("[!!!]" "[!!] " "[!]  "))  ; same length
  (org-priority-faces '((?A . (:foreground "orangered2" :weight extrabold :height 1.3))  ; org-mode
                        (?B . (:foreground "orange" :weight extrabold :height 1.3))
                        (?C . (:foreground "Burlywood" :weight extrabold :height 1.3))))
  :hook
  (org-mode . org-fancy-priorities-mode)
  (org-agenda-finalize . org-fancy-priorities-mode))

Org Tree Slide

(use-package org-tree-slide
  :commands org-tree-slide-mode
  :custom
  (org-tree-slide-activate-message "")
  (org-tree-slide-deactivate-message "")
  (org-tree-slide-breadcrumbs "    >    ")
  (org-tree-slide-heading-emphasis t)
  (org-tree-slide-slide-in-waiting 0.025)
  (org-tree-slide-content-margin-top 4)
  :custom-face
  (org-tree-slide-heading-level-1 ((t (:height 1.8 :weight bold))))
  (org-tree-slide-heading-level-2 ((t (:height 1.5 :weight bold))))
  (org-tree-slide-heading-level-3 ((t (:height 1.5 :weight bold))))
  (org-tree-slide-heading-level-4 ((t (:height 1.5 :weight bold))))
  :bind
  ( :map org
    ("s" . org-tree-slide-mode)
    :map org-tree-slide-mode-map
    ("<f8>" . org-tree-slide-content)
    ("<f9>" . org-tree-slide-move-previous-tree)
    ("<f10>" . org-tree-slide-move-next-tree)
    ("<left>" . org-tree-slide-move-previous-tree)
    ("<right>" . org-tree-slide-move-next-tree)
    ("C-n" . (lambda () (interactive) (if cursor-type
                                          (next-line)
                                        (setq-local cursor-type t)
                                        (next-line)))))
  :hook
  (org-tree-slide-before-narrow . (lambda () (setq-local cursor-type nil)))
  (org-tree-slide-stop . (lambda () (setq-local cursor-type t)))
  (org-tree-slide-play . variable-pitch-mode)
  (org-tree-slide-stop . (lambda () (variable-pitch-mode -1)))
  (org-tree-slide-play . fk/hide-org-metalines-toggle)
  (org-tree-slide-stop . fk/hide-org-metalines-toggle)
  (org-tree-slide-before-narrow . org-remove-inline-images)
  (org-tree-slide-after-narrow . org-display-inline-images)
  (org-tree-slide-play . fk/org-tree-slide-update-modeline)
  (org-tree-slide-stop . fk/org-tree-slide-update-modeline)
  (org-tree-slide-mode . (lambda () (fk/adjust-font-size 40)))
  ;; (org-tree-slide-stop . (lambda () (fk/adjust-font-size -40)))
  ;; (org-tree-slide-play . (lambda () (setq-local olivetti-body-width 95) (olivetti-mode 1)))
  ;; (org-tree-slide-stop . (lambda () (setq-local olivetti-body-width 120) (olivetti-mode 1)))
  (org-tree-slide-mode . (lambda () (org-appear-mode -1)))
  (org-tree-slide-mode . (lambda () (setq olivetti-enable-borders nil) (olivetti-mode 1)))
  :config
  (defun fk/buffer-contains-substring (string)
    (save-excursion
      (save-match-data
        (goto-char (point-min))
        (and-let* ((pos (search-forward string nil t))
                   (visible (not (outline-invisible-p pos))))))))

  (setq fk/org-meta-line-hide-p nil)
  (setq fk/org-meta-line-face-remap nil)

  (defun fk/hide-org-metalines-toggle ()
    "Hide or unhide meta lines starting with \"#+\" in org-mode."
    (interactive)
    (if fk/org-meta-line-hide-p
        (face-remap-remove-relative fk/org-meta-line-face-remap)
      (setq fk/org-meta-line-face-remap (face-remap-add-relative 'org-meta-line
                                                                 :foreground fk/background-color)))
    (setq fk/org-meta-line-hide-p (not fk/org-meta-line-hide-p)))

  (defun fk/org-tree-slide-update-modeline ()
    "Show current page in modeline."
    (let ((slide-position '(:eval (format " %s " (org-tree-slide--count-slide (point))))))
      (if (org-tree-slide--active-p)
          (setq-local global-mode-string (append global-mode-string (list slide-position)))
        (setq-local global-mode-string (delete slide-position global-mode-string))))))

;; Alternative
(use-package epresent
  :commands epresent-run)

Org Export Twitter Bootstrap

(use-package ox-twbs
  :after org)

Valign Mode

(use-package valign
  :straight (:host github :repo "casouri/valign")
  :commands valign-mode
  :custom
  (valign-fancy-bar t))

Org Appear

(use-package org-appear
  :hook (org-mode . org-appear-mode))

Org Rainbow Tags

(use-package org-rainbow-tags
  :straight (:host github :repo "KaratasFurkan/org-rainbow-tags")
  :custom
  (org-rainbow-tags-hash-start-index 9)
  :hook
  (org-mode . (lambda () (unless (daemonp) (org-rainbow-tags-mode)))))

Calendar

(use-package calendar
  :commands calendar
  :custom
  (calendar-week-start-day 1)  ; start at Monday
  :hook
  ;; bigger calendar like OS calendars
  (calendar-mode . (lambda () (text-scale-increase 8)))
  (calendar-mode . delete-other-windows))

Version Control

Magit

Magit

(use-package magit
  :commands magit
  :custom
  (magit-section-initial-visibility-alist '((stashes . show)
                                            (unpushed . show)
                                            (pullreqs . show)
                                            (issues . show)))
  (magit-display-buffer-function 'magit-display-buffer-same-window-except-diff-v1)
  :bind*
  ( :map version-control
    ("v" . magit-status)
    ("s" . magit-status)
    :map magit-mode-map
    ("o" . (lambda () (interactive)
             (call-interactively 'magit-diff-visit-file-other-window)
             (recenter-top-bottom)))
    ("C-c C-f" . magit-find-file))
  :hook
  (git-commit-setup . git-commit-turn-on-flyspell)
  (magit-mode . hack-dir-local-variables-non-file-buffer))

Magit Todos

(use-package magit-todos
  :commands magit-todos-list
  :custom
  (magit-todos-exclude-globs '("*jquery*.js" "*min.js" "*min.css" "*.mjml"))
  (magit-todos-max-items 40)  ; It's actually 20 but needed to set x2 for some reason
  :bind*
  ( :map version-control
    ("T" . magit-todos-list))
  :hook (magit-mode . magit-todos-mode))

Magit Forge

Pull Requests, Issues etc.

(use-package forge
  :after magit
  :config
  (advice-add 'magit-pull-from-upstream :after
              (lambda (&rest _) (call-interactively 'forge-pull)))
  (advice-add 'magit-fetch-all :after
              (lambda (&rest _) (call-interactively 'forge-pull)))

  (defun fk/forge-create-pullreq--read-args ()
    (let* ((source  (magit-completing-read
                     "Source branch"
                     (magit-list-remote-branch-names)
                     ;; `magit-get-current-branch' as initial input
                     nil t (concat "origin/" (magit-get-current-branch)) 'magit-revision-history
                     (or (and-let* ((d (magit-branch-at-point)))
                           (if (magit-remote-branch-p d)
                               d
                             (magit-get-push-branch d t)))
                         (and-let* ((d (magit-get-current-branch)))
                           (if (magit-remote-branch-p d)
                               d
                             (magit-get-push-branch d t))))))
           (repo    (forge-get-repository t))
           (remote  (oref repo remote))
           (targets (delete source (magit-list-remote-branch-names remote)))
           (target  (magit-completing-read
                     ;; TODO: show history at first
                     "Target branch" targets nil t nil 'magit-revision-history
                     (let* ((d (cdr (magit-split-branch-name source)))
                            (d (and (magit-branch-p d) d))
                            (d (and d (magit-get-upstream-branch d)))
                            (d (and d (if (magit-remote-branch-p d)
                                          d
                                        (magit-get-upstream-branch d))))
                            (d (or d (concat remote "/"
                                             (or (oref repo default-branch)
                                                 "master")))))
                       (car (member d targets))))))
      (list source target)))

  (defun fk/forge-prepare-topic (source target)
    "Prepare topic for `forge-create-pullreq'."
    (if-let* ((target-name (upcase (string-remove-prefix "origin/" target)))
              (source-name (string-remove-prefix "origin/" source))
              (match (string-match "^[a-z]+-[0-9]+" source-name))
              (match-string (match-string-no-properties 0 source-name))
              (issue-id (when match (upcase match-string)))
              ;; Branch may not have issue-id
              (issue-id-string (if issue-id (format "%s | " issue-id) ""))
              (topic (string-remove-prefix (concat match-string "-") source-name))
              (capitalized-topic (upcase-initials (string-replace "-" " " topic))))
        (format "# %s | %s%s" target-name issue-id-string capitalized-topic)
      (format "# %s | " target-name )))

  (defun fk/forge--prepare-post-buffer (filename &optional header source target)
    (let ((file (magit-git-dir
                 (convert-standard-filename
                  (concat "magit/posts/" filename)))))
      (make-directory (file-name-directory file) t)
      (let ((prevbuf (current-buffer))
            (resume (and (file-exists-p file)
                         (> (file-attribute-size (file-attributes file)) 0)))
            (buf (find-file-noselect file)))
        (with-current-buffer buf
          (forge-post-mode)
          (when header
            (magit-set-header-line-format header))
          (setq forge--pre-post-buffer prevbuf)
          (when resume
            (forge--display-post-buffer buf)
            (when (magit-read-char-case "A draft already exists.  " nil
                    (?r "[r]esume editing existing draft")
                    (?d "[d]iscard draft and start over" t))
              (erase-buffer)
              (setq resume nil)))
          (when (and (not resume) (string-prefix-p "new" filename))
            (let-alist (forge--topic-template
                        (forge-get-repository t)
                        (if source 'forge-pullreq 'forge-issue))
              (cond
               (.url
                (browse-url .url)
                (forge-post-cancel)
                (setq buf nil)
                (message "Using browser to visit %s instead of opening an issue"
                         .url))
               (.name
                ;; A Github issue with yaml frontmatter.
                (save-excursion (insert .text))
                (unless (re-search-forward "^title: " nil t)
                  (when (re-search-forward "^---" nil t 2)
                    (beginning-of-line)
                    (insert "title: \n")
                    (backward-char))))
               (t
                ;; Custom part:
                (insert (fk/forge-prepare-topic source target)))))))
        buf)))

  ;; I just added `magit-get-current-branch' as initial input
  (advice-add 'forge-create-pullreq--read-args :override 'fk/forge-create-pullreq--read-args)
  ;; I just added a custom dynamic topic template: `fk/forge-prepare-topic'
  (advice-add 'forge--prepare-post-buffer :override 'fk/forge--prepare-post-buffer))

Magit Delta

;; (use-package magit-delta
;;   :hook (magit-mode . magit-delta-mode))

diff-hl

(use-package diff-hl
  :custom
  (diff-hl-global-modes '(not org-mode))
  (diff-hl-ask-before-revert-hunk nil)
  :custom-face
  (diff-hl-insert ((t (:background "#224022"))))
  (diff-hl-change ((t (:background "#492949" :foreground "mediumpurple1"))))
  (diff-hl-delete ((t (:background "#492929" :foreground "orangered2"))))
  :bind
  ( :map version-control
    ("n" . diff-hl-next-hunk)
    ("p" . diff-hl-previous-hunk)
    ("r" . diff-hl-revert-hunk))
  :hook
  (dashboard-after-initialize . global-diff-hl-mode)
  (diff-hl-mode . diff-hl-flydiff-mode)
  (magit-pre-refresh . diff-hl-magit-pre-refresh)
  (magit-post-refresh . diff-hl-magit-post-refresh))

Smerge

;; Source: https://github.com/alphapapa/unpackaged.el#smerge-mode
(use-package smerge-mode
  :straight (:type built-in)
  :after magit
  :config
  (defhydra smerge-hydra
    (
     :color red
     :hint nil
     ;; :pre (progn
     ;;        (setq-local global-hl-line-mode nil)
     ;;        (when tree-sitter-hl-mode
     ;;          (tree-sitter-hl-mode -1))
     ;;        (smerge-mode))
     ;; :post (progn
     ;;         (smerge-auto-leave)
     ;;         (setq-local global-hl-line-mode t))
     )
    "
^Move^       ^Keep^               ^Diff^                 ^Other^
^^-----------^^-------------------^^---------------------^^-------
_n_ext       _b_ase               _<_: upper/base        _C_ombine
_p_rev       _u_pper              _=_: upper/lower       _r_esolve
^^           _l_ower              _>_: base/lower        _k_ill current
^^           _a_ll                _R_efine
^^           _RET_: current       _E_diff
"
    ("n" (lambda () (interactive) (smerge-next) (recenter (round (* 0.2 (window-height))) t)))
    ("p" (lambda () (interactive) (smerge-prev) (recenter (round (* 0.2 (window-height))) t)))
    ("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))
     "Save and bury buffer" :color blue)
    ("q" nil "cancel" :color blue))
  :hook
  (magit-diff-visit-file . (lambda ()
                             (when smerge-mode
                               (smerge-hydra/body)))))

Git Link

(use-package git-link
  :commands git-link
  :custom
  (git-link-use-commit t)
  :bind
  ( :map version-control
    ("l" . git-link)))

Git Timemachine

(use-package git-timemachine
  :commands git-timemachine
  :bind
  ( :map version-control
    ("t" . git-timemachine))
  :hook
  (git-timemachine-mode . fk/tree-sitter-hl-mode))

Git Blame (vc-msg)

(use-package vc-msg
  :commands vc-msg-show
  :bind
  ( :map version-control
    ("b" . vc-msg-show)))

Dired Git Info

;; (use-package dired-git-info
;;   :hook (dired-after-readin . dired-git-info-auto-enable)
;;   :config
;;   (defun dired-subtree-toggle-advice (orig-fn &rest args)
;;     "Source: https://github.com/clemera/dired-git-info/issues/9#issuecomment-1013520395"
;;     (cond ((bound-and-true-p dired-git-info-mode)
;;            (dired-git-info-mode -1)
;;            (apply orig-fn args)
;;            (dired-git-info-mode +1))
;;           (t (apply orig-fn args))))

;;   (advice-add 'dired-subtree-toggle :around 'dired-subtree-toggle-advice))

Terminal Emulation

Vterm

(use-package vterm
  :load-path "~/.emacs.d/straight/repos/emacs-libvterm"  ; manually cloned
  :custom
  (vterm-max-scrollback 100000)
  (vterm-buffer-name "vterm")
  :custom-face
  ;; match with fk/darken-background
  (vterm-color-default ((t (:background ,fk/dark-color))))
  :bind
  ( :map vterm-mode-map
    ("C-c C-e" . vterm-copy-mode)
    ("C-c C-n" . fk/vterm-next-prompt)
    ("C-c C-p" . fk/vterm-previous-prompt)
    ;; Disabled vterm keybindings: (in order to use their global values)
    ("M-m" . nil)
    ("M-u" . nil)
    ("M-j" . nil)
    ("<f1>" . nil)
    ("C-M-s" . nil)
    :map vterm-copy-mode-map
    ("C-c C-e" . vterm-copy-mode)
    ("C-c C-c" . vterm-copy-mode)
    ("M-n" . fk/vterm-next-prompt)
    ("M-p" . fk/vterm-previous-prompt))
  :hook
  (vterm-mode . (lambda () (setq-local global-hl-line-mode nil
                                       show-trailing-whitespace nil)))
  (vterm-mode . fk/darken-background)
  (vterm-mode . hack-dir-local-variables-non-file-buffer)  ; TODO: doesn't work
  (vterm-copy-mode . (lambda ()
                       (face-remap-add-relative 'hl-line :background fk/background-color)
                       (call-interactively 'hl-line-mode)))
  :config
  (defvar docker-container-prompt-regexp "^[\\^A-Z]*root@[A-z0-9-]*:/[^#]*# ")
  (defvar python-pdb-prompt-regexp "^[\\^A-Z]*(Pdb) ")  ; TODO: add next-prompt function for this one too

  (defface docker-container-prompt-face
    '((t (:foreground "green yellow")))
    "Face for docker container prompt in vterm.")

  ;; NOTE: https://github.com/akermu/emacs-libvterm/pull/430 this PR is needed.
  (font-lock-add-keywords
   'vterm-mode
   `((,docker-container-prompt-regexp 0 'docker-container-prompt-face t)
     (,python-pdb-prompt-regexp       0 'docker-container-prompt-face t))
   'set)

  (defun fk/docker-container-next-prompt ()
    "Move to end of next docker-container prompt in the buffer. According to the
`docker-container-prompt-regexp'."
    (interactive)
    (search-forward-regexp docker-container-prompt-regexp nil t))

  (defun fk/docker-container-prev-prompt ()
    "Move to end of previous docker-container prompt in the buffer. According to
the `docker-container-prompt-regexp'."
    (interactive)
    (beginning-of-line)  ; not to catch same prompt
    (when (search-backward-regexp docker-container-prompt-regexp nil t)
      ;; to go to the end of the prompt
      (search-forward-regexp docker-container-prompt-regexp nil t)))

  (defun fk/vterm-next-prompt ()
    "Move to end of next prompt in the buffer. In addition to
`vterm-next-prompt', this catches docker containers prompts too."
    (interactive)
    (if-let*
        ((current-line (line-number-at-pos))
         (prompt-by-regexp (save-excursion
                             (when (or (fk/docker-container-next-prompt)
                                       ;; to not return nil at the last prompt
                                       (= (line-number-at-pos) (1- (line-number-at-pos (point-max)))))
                               (point))))
         (prompt-by-vterm (save-excursion
                            (call-interactively 'vterm-next-prompt)
                            (point)))
         (is-next-docker-prompt (or (< prompt-by-regexp prompt-by-vterm)
                                    (> current-line (line-number-at-pos prompt-by-vterm))
                                    (= current-line (line-number-at-pos prompt-by-vterm)))))
        (fk/docker-container-next-prompt)
      (call-interactively 'vterm-next-prompt)))

  (defun fk/vterm-previous-prompt ()
    "Move to end of previous prompt in the buffer. In addition to
`vterm-previous-prompt', this catches docker containers prompts too."
    (interactive)
    (if-let*
        ((current-line (line-number-at-pos))
         (prompt-by-regexp (save-excursion
                             (when (fk/docker-container-prev-prompt)
                               (point))))
         (prompt-by-vterm (save-excursion
                            (call-interactively 'vterm-previous-prompt)
                            (point)))
         (is-prev-docker-prompt (or (> prompt-by-regexp prompt-by-vterm)
                                    (< current-line (line-number-at-pos prompt-by-vterm))
                                    (= current-line (line-number-at-pos prompt-by-vterm)))))
        (fk/docker-container-prev-prompt)
      (call-interactively 'vterm-previous-prompt))))

Shell Pop

(use-package shell-pop
  :custom
  (shell-pop-shell-type '("vterm" "*vterm*" (lambda () (vterm))))
  (shell-pop-full-span t)
  :bind*
  (("M-t" . shell-pop)))

Restclient

Restclient

(use-package restclient
  :mode ("\\.http\\'" . restclient-mode)
  :custom
  (restclient-log-request nil)
  ;;:config
  ;;(setcdr (assoc "application/json" restclient-content-type-modes) 'json-mode)
)

Company Restclient

(use-package company-restclient
  :after restclient
  :hook
  (restclient-mode . (lambda ()
                       (add-to-list 'company-backends 'company-restclient))))

ob-restclient

(use-package ob-restclient
  :after org
  :config
  (org-babel-do-load-languages 'org-babel-load-languages '((restclient . t)))  ; TODO: this may slow down org load
  (add-hook 'org-babel-after-execute-hook (lambda () (let ((lang (nth 0 (org-babel-get-src-block-info))))
                                                       (when (and buffer-file-name (string= lang "restclient"))
                                                         (save-buffer))))))

Password Mode

(use-package password-mode
  :hook
  (restclient-mode . password-mode)
  (org-mode . (lambda ()
                (when (buffer-file-name)
                  (let ((filename (file-name-nondirectory
                                   (directory-file-name buffer-file-name))))
                    (when (or (string-match "rest" filename)
                              (string-match "api" filename))
                      (password-mode))))))
  :config
  (add-to-list 'password-mode-password-prefix-regexs "\"password\":?[[:space:]]+"))

EAF

(use-package eaf
  :load-path "~/.emacs.d/straight/repos/emacs-application-framework"  ; manually cloned
  ;; :straight
  ;; (:host github :repo "emacs-eaf/emacs-application-framework" :depth 1 :files ("*"))
  :commands eaf-open-browser eaf-open-camera
  :custom
  (eaf-python-command "/usr/bin/python")  ; don't use venv python
  :config
  (require 'eaf-browser)
  (require 'eaf-camera))

Sudo Edit

(use-package sudo-edit
  :commands sudo-edit)

Google Translate

(use-package go-translate
  :straight (:host github :repo "lorniu/go-translate")
  :custom
  (go-translate-local-language "tr")
  (go-translate-target-language "en")
  (go-translate-inputs-function 'go-translate-inputs-current-or-prompt)
  (go-translate-buffer-follow-p t)
  (go-translate-token-current (cons 430675 2721866130)) ; Fix https://github.com/lorniu/go-translate/issues/7
  :bind*
  ( :map text
    :prefix-map google-translate
    :prefix "g"
    ("g" . go-translate-popup-current)
    ("G" . go-translate)
    ("b" . go-translate)
    ("e" . go-translate-echo-area)))

PDF

PDF Tools

(use-package pdf-tools
  :mode ("\\.pdf\\'" . pdf-view-mode)
  :magic ("%PDF" . pdf-view-mode)
  :custom
  (pdf-view-display-size 'fit-page)
  :bind
  ( :map pdf-view-mode-map
    ("O" . pdf-occur)
    ("d" . pdf-view-midnight-minor-mode)
    ("s a" . pdf-view-auto-slice-minor-mode)
    ("t" . (lambda (beg end) (interactive "r") (go-translate))))
  :hook
  (pdf-view-mode . pdf-links-minor-mode)
  (pdf-view-mode . pdf-isearch-minor-mode)
  (pdf-view-mode . pdf-outline-minor-mode)
  (pdf-view-mode . pdf-history-minor-mode)
  :config
  (with-eval-after-load 'pdf-links
    (define-key pdf-links-minor-mode-map (kbd "f") 'pdf-links-action-perform)))

Interleave

(use-package interleave
  :commands interleave-mode
  :custom
  (interleave-disable-narrowing t))

PDF Continuous Scroll Mode

(use-package pdf-continuous-scroll-mode
  :straight (:host github :repo "dalanicolai/pdf-continuous-scroll-mode.el")
  ;; M-x pdf-view-fit-width-to-window and disable olivetti before run this
  :commands pdf-continuous-scroll-mode)

Emacs Screencast

(use-package gif-screencast
  :straight (:host gitlab :repo "ambrevar/emacs-gif-screencast")
  :bind
  ( :map gif-screencast-mode-map
    ("<f8>". gif-screencast-toggle-pause)
    ("<f9>". gif-screencast-stop)))

Slack

Slack

(use-package slack
  :commands slack-start
  :custom
  (slack-buffer-function 'switch-to-buffer)
  (slack-buffer-emojify t)
  (slack-prefer-current-team t)
  (slack-alert-icon (fk/expand-static-file-name "slack/icon.png"))
  :custom-face
  (slack-preview-face ((t (:inherit (fixed-pitch shadow org-block) :extend nil))))
  :hook
  (slack-message-buffer-mode . (lambda () (setq-local truncate-lines nil)))
  (slack-message-buffer-mode . (lambda () (setq-local olivetti-body-width 80)))
  :config
  (slack-register-team
   :name "hipo"
   :default t
   :token (auth-source-pick-first-password :host "slack")
   :full-and-display-names t)

  (defun fk/alert-with-sound (orig-func &rest args)
    "Play sound with alert."
    (apply orig-func args)
    (when (eq (plist-get (cdr args) :category) 'slack)
      (let* ((sound-file (fk/expand-static-file-name "slack/sound.mp3"))
             (command (concat "ffplay -volume 20 -nodisp -nostats -hide_banner " sound-file)))
        (when (file-exists-p sound-file)
          (fk/async-process command)))))

  (advice-add 'alert :around 'fk/alert-with-sound))

Emojify

(use-package emojify
  :commands emojify-mode)

;; (use-package company-emoji
;;   :after slack
;;   :config
;;   (add-to-list 'company-backends 'company-emoji))

Alert

(use-package alert
  :commands alert
  :custom
  (alert-default-style 'libnotify))

Helm Slack

(use-package helm-slack
  :straight (:host github :repo "yuya373/helm-slack")
  :after slack)

PlantUML

(use-package plantuml-mode
  :mode "\\.plantuml\\'"
  :preface
  (setq plantuml-jar-path (concat no-littering-etc-directory "plantuml.jar"))
  :custom
  (plantuml-default-exec-mode 'jar)
  (plantuml-indent-level 4)
  :init
  (with-eval-after-load "org"
    (add-to-list 'org-src-lang-modes '("plantuml" . plantuml))
    (org-babel-do-load-languages 'org-babel-load-languages '((plantuml . t)))
    (setq org-plantuml-jar-path plantuml-jar-path)))

EWW

Highlight Code Blocks

(use-package shr-tag-pre-highlight
  :after shr
  :config
  (add-to-list 'shr-external-rendering-functions '(pre . shr-tag-pre-highlight)))

XWWP (Xwidget Webkit Enhancement)

(use-package xwidget
  :straight (:type built-in)
  :commands xwidget-webkit-browse-url)

(use-package xwwp
  :commands (xwwp xwwp-browse-url-other-window)
  :bind
  ( :map xwidget-webkit-mode-map
    ("f" . xwwp-follow-link)))

Screenshot

(use-package screenshot
  :straight (:host github :repo "tecosaur/screenshot")
  :commands screenshot
  :custom
  (screenshot-max-width 300)
  :hook
  (screenshot-buffer-creation . (lambda () (hl-line-mode -1))))

Emacs Everywhere

(use-package emacs-everywhere
  :straight (:host github :repo "tecosaur/emacs-everywhere")
  :commands emacs-everywhere
  :bind
  ( :map emacs-everywhere-mode-map
    ("C-x C-c" . emacs-everywhere-abort))
  :hook
  (emacs-everywhere-mode . (lambda () (require 'turkish) (turkish-mode)))
  ;; Banish mouse to prevent focusing to company's childframe
  (emacs-everywhere-mode . (lambda () (set-mouse-position (selected-frame) 0 0)))
  :config
  ;; I disabled insert selection because it inserts from clipboard if there is
  ;; no selection.
  (advice-add 'emacs-everywhere-insert-selection :override 'ignore))

Pomidor (Pomodoro)

(use-package pomidor
  :commands pomidor
  :custom
  (pomidor-update-interval 30)
  (pomidor-confirm-end-break nil)
  (pomidor-breaks-before-long 3)          ; work-b-work-b-work-b-work-longb
  (pomidor-long-break-seconds (* 30 60))  ; 30 mins
  (pomidor-sound-tick nil)
  (pomidor-sound-tack nil)
  (pomidor-save-session-file (expand-file-name "pomidor-session.json" no-littering-var-directory))
  :custom-face
  (pomidor-work-face ((t (:inherit success :width ultra-condensed))))
  (pomidor-overwork-face ((t (:inherit warning :width ultra-condensed))))
  (pomidor-break-face ((t (:inherit font-lock-keyword-face :width ultra-condensed))))
  (pomidor-skip-face ((t (:inherit font-lock-comment-face :width ultra-condensed))))
  :hook
  (kill-emacs . fk/pomidor-save-session)
  :config
  (defun fk/pomidor-save-session ()
    "Call `pomidor-save-session' if pomidor is active, without asking yes or no."
    (interactive)
    (when (and (featurep 'pomidor) (get-buffer pomidor-buffer-name))
      (cl-letf (((symbol-function 'y-or-n-p) (lambda (_) t)))
        (pomidor-save-session))))

  ;; Use a dedicated perspective for pomidor
  (advice-add 'pomidor :before (lambda () (persp-switch "pomidor")))
  (advice-add 'pomidor-quit :after (lambda () (persp-kill "pomidor")))

  ;; Unhold with space
  (advice-add 'pomidor-break :around (lambda (orig-func &rest args)
                                       (if pomidor--system-on-hold-p
                                           (pomidor-unhold)
                                         (apply orig-func args)))))

Speed Type

(use-package speed-type
  :commands fk/speed-type-project
  :config
  (defun fk/speed-type-project ()
    "Select example paragraphs from projects to exercise touch
typing with actual code."
    (interactive)
    (let* ((projects (directory-files "~/projects/python/" t "[^.][^zip]$"))
           (project (nth (random (length projects)) projects))
           (files (directory-files-recursively project "[^gunicorn][^__].py$" t
                                               (lambda (path)
                                                 (let ((dir (file-name-base path)))
                                                   (not (or (string-prefix-p "." dir)
                                                            (string-prefix-p "__" dir)
                                                            (string= "migrations" dir)))))))
           (file (nth (random (length files)) files)))
      (speed-type--setup (with-temp-buffer
                           (insert-file-contents file)
                           (delete-whitespace-rectangle (point-min) (point-max))
                           (speed-type--pick-text-to-type)))
      (setq-local show-paren-mode nil)))

  (advice-add 'speed-type--play-next :override 'fk/speed-type-project))

Docker

(use-package docker
  :commands docker)

Sozluk

(use-package sozluk
  :straight (:host github :repo "isamert/sozluk.el")
  :commands sozluk
  :config
  (with-eval-after-load 'shackle
    (add-to-list 'shackle-rules '("\\`\\*sozluk.*?\\*\\'" :regexp t :align t :size 0.4))))

Epub

(use-package nov
  :mode ("\\.epub\\'" . nov-mode))

File Modes

Markdown

(use-package markdown-mode
  :mode "\\.md\\'"
  :custom (markdown-header-scaling t)
  :bind
  ( :map markdown-mode-map
    ("M-n" . markdown-next-visible-heading)
    ("M-p" . markdown-previous-visible-heading)
    ("C-M-j" . markdown-follow-thing-at-point))
  :hook
  (markdown-mode . emojify-mode))

Fish

(use-package fish-mode
  :mode "\\.fish\\'")

Docker

Dockerfile

(use-package dockerfile-mode
  :mode "Dockerfile\\'")

Docker Compose

(use-package docker-compose-mode
  :mode "docker-compose\\'")

Yaml

(use-package yaml-mode
  :mode "\\.ya?ml\\'"
  :hook
  (yaml-mode . highlight-indent-guides-mode)
  (yaml-mode . display-line-numbers-mode))

requirements.txt (pip)

(use-package pip-requirements
  :mode (("\\.pip\\'" . pip-requirements-mode)
         ("requirements[^z-a]*\\.txt\\'" . pip-requirements-mode)
         ("requirements\\.in" . pip-requirements-mode))
  :config
  ;; Assign a non nil value to `pip-packages' to prevent fetching pip packages.
  (setq pip-packages '("ipython")))

PDF-

Git Modes

(use-package git-modes
  :mode (("/.gitignore\\'" . gitignore-mode)
         ("/.dockerignore\\'" . gitignore-mode)))

Csv

(use-package csv-mode
  :mode "\\.csv\\'"
  :custom
  (csv-invisibility-default nil)
  (csv-align-max-width 999))

Po

;; (use-package po-mode
;;   :commands po-mode)

Terraform

(use-package terraform-mode
  :mode "\\.tf\\'"
  :hook
  (terraform-mode . (lambda ()
                      (require 'eglot)
                      (add-to-list 'eglot-server-programs '(terraform-mode . ("terraform-ls" "serve")))
                      (eglot-ensure))))

Fun

Play Free Software Song

(defun fk/play-free-software-song ()
  "Play Richard Stallman's free software song."
  (interactive)
  (call-process-shell-command
   "youtube-dl -f 251 'https://www.youtube.com/watch?v=9sJUDx7iEJw' -o - | ffplay -nodisp -autoexit -i -" nil 0))

;;(add-hook 'after-init-hook 'play-free-software-song)

Selectric Mode

(use-package selectric-mode
  :straight (:files ("*"))
  :commands selectric-mode)

Fireplace

;; TODO: find mp3 file does not work with straight
(use-package fireplace
  :straight (:files ("*"))  ; Fix fireplace.mp3 not found issue
  :commands fireplace
  :custom
  (fireplace-sound-on t))

Pacmacs

(use-package pacmacs
  :commands pacmacs)

2048

(use-package 2048-game
  :commands 2048-game)

Artist Mode

(use-package artist
  :straight (:type built-in)
  :commands artist-mode
  :bind
  ( :map artist-mode-map
    ("C-c C-c" . 'artist-select-operation)))

Rubik’s Cube

(use-package rubik
  :commands rubik)

Packages I almost never use but want to keep

Turkish Mode

(use-package turkish
  :commands turkish-mode turkish-correct-region turkish-asciify-region
  :bind
  ( :map text
    ("a" . turkish-asciify-region)
    :map turkish-mode-map
    ("C-g" . fk/turkish-mode-C-g))
  :hook
  (turkish-mode . fk/company-turkish-mode)
  :config
  (defun fk/company-grab-word-turkish (orig-func)
    "Convert the word company grabbed to Turkish with `turkish-mode' before
processing it."
    (let ((word (funcall orig-func)))
      (with-temp-buffer
        (insert word)
        (turkish-correct-buffer)
        (buffer-string))))

  (define-minor-mode fk/company-turkish-mode
    "Suggest candidates by the turkish version of the word."
    :global t
    (if fk/company-turkish-mode
        (progn
          (require 'turkish)
          (fk/company-wordfreq-mode 1)
          (setq ispell-local-dictionary-backup ispell-local-dictionary)
          (setq ispell-local-dictionary "turkish")
          (advice-add 'company-grab-word :around 'fk/company-grab-word-turkish))
      (fk/company-wordfreq-mode -1)
      (setq ispell-local-dictionary ispell-local-dictionary-backup)
      (advice-remove 'company-grab-word 'fk/company-grab-word-turkish)))

  (defun fk/turkish-mode-C-g ()
    "Toggle last word with `C-g' if didn't like last correctify."
    (interactive)
    (if (eq last-command 'turkish-correct-last-word)
        (turkish-toggle-last-word)
      (keyboard-quit))))

Minimap

(use-package minimap
  :commands minimap-mode)

Helm System Packages

(use-package helm-system-packages
  :commands helm-system-packages)

Dimmer

(use-package dimmer
  :commands dimmer-mode
  :custom
  (dimmer-fraction 0.5)
  :config
  (dimmer-configure-company-box)
  (dimmer-configure-which-key)
  (dimmer-configure-helm)
  (dimmer-configure-magit)
  (dimmer-configure-posframe)
  ;; I tried to fix lsp-ui-doc but it seems did not work
  (defun fk/dimmer-lsp-ui-doc-p ()
    "Return non-nil if current buffer is a lsp-ui-doc buffer."
    (string-prefix-p " *lsp-ui-doc-" (buffer-name)))

  (defun fk/dimmer-configure-lsp-ui-doc ()
    "Convenience setting for lsp-ui-doc users.
This predicate prevents dimming the buffer you are editing when
lsp-ui-doc pops up a documentation."
    (add-to-list
     'dimmer-prevent-dimming-predicates 'dimmer-lsp-ui-doc-p))

  (fk/dimmer-configure-lsp-ui-doc))

Focus

(use-package focus
  :commands focus-mode
  :config
  (add-to-list 'focus-mode-to-thing '(python-mode . paragraph)))

Command Log Mode

(use-package command-log-mode
  :commands command-log-mode)

Keypression

(use-package keypression
  :commands keypression-mode
  :custom
  (keypression-cast-command-name t)
  (keypression-combine-same-keystrokes t)
  ;;(keypression-use-child-frame t) ; broken
  (keypression-font-face-attribute '(:width normal :height 150 :weight bold)))

Literate Calc Mode

(use-package literate-calc-mode
  :commands literate-calc-minor-mode)

Some Other Emacs Configurations

https://emacs.christianbaeuerlein.com/
https://emacs.nasy.moe/
https://emacs.zeef.com/ehartc
https://github.com/alhassy/ElispCheatSheet (elisp cheatsheet)
https://github.com/alhassy/emacs.d
https://github.com/angrybacon/dotemacs
https://github.com/Atman50/emacs-config
https://github.com/belak/dotfiles/tree/master/emacs.d
https://github.com/caisah/emacs.dz (a list of emacs config files)
https://github.com/codemac/config/tree/master/emacs.d
https://github.com/dakra/dmacs
https://github.com/emacs-tw/awesome-emacs (awesome emacs)
https://github.com/hrs/dotfiles/tree/master/emacs/.emacs.d
https://github.com/ianpan870102/.personal-emacs.d
https://github.com/ianpan870102/yay-evil-emacs
https://github.com/iqss/IQSS.emacs
https://github.com/jamiecollinson/dotfiles/blob/master/config.org/
https://github.com/jonathanchu/dotemacs
https://github.com/kadircancetin/.emacs.d
https://github.com/MatthewZMD/.emacs.d
https://github.com/mrvdb/emacs-config
https://github.com/novoid/dot-emacs
https://github.com/redguardtoo/emacs.d
https://github.com/rememberYou/.emacs.d
https://github.com/sachac/.emacs.d/
https://github.com/zarkone/literally.el/blob/master/literally.org
https://github.com/zzamboni/dot-emacs/blob/master/init.org
https://gitlab.com/protesilaos/dotfiles/tree/master/emacs/.emacs.d
https://medium.com/@suvratapte/configuring-emacs-from-scratch-intro-3157bed9d040
https://sam217pa.github.io/2016/09/02/how-to-build-your-own-spacemacs/
https://ladicle.com/post/config/