All Projects → KaratasFurkan → .emacs.d

KaratasFurkan / .emacs.d

Licence: other
My literate Emacs configuration

Programming Languages

emacs lisp
2029 projects

Projects that are alternatives of or similar to .emacs.d

Dotfiles
This is a mirror from https://gitlab.com/andreyorst/dotfiles
Stars: ✭ 103 (-25.9%)
Mutual labels:  emacs-configuration
Lunarymacs
Moon-based Emacs configuration.
Stars: ✭ 151 (+8.63%)
Mutual labels:  emacs-configuration
move-border
Emacs windows resizing made intuitive
Stars: ✭ 16 (-88.49%)
Mutual labels:  emacs-configuration
Emacs Gtd
Get Things Done with Emacs
Stars: ✭ 111 (-20.14%)
Mutual labels:  emacs-configuration
Writingwithemacs
Tips, Examples, and Resources for Writing with Emacs
Stars: ✭ 150 (+7.91%)
Mutual labels:  emacs-configuration
Uncledavesemacs
My personal ~/.emacs.d
Stars: ✭ 204 (+46.76%)
Mutual labels:  emacs-configuration
Quark Emacs
🚀 An incredible wonderland of code
Stars: ✭ 86 (-38.13%)
Mutual labels:  emacs-configuration
dot.me
me dot files
Stars: ✭ 33 (-76.26%)
Mutual labels:  emacs-configuration
Dotfiles
My dotfiles
Stars: ✭ 150 (+7.91%)
Mutual labels:  emacs-configuration
my-emacs
My Emacs configuration
Stars: ✭ 35 (-74.82%)
Mutual labels:  emacs-configuration
Doom Emacs
An Emacs framework for the stubborn martian hacker
Stars: ✭ 12,774 (+9089.93%)
Mutual labels:  emacs-configuration
Emacs Bootstrap
Your on-the-fly Emacs development environment.
Stars: ✭ 147 (+5.76%)
Mutual labels:  emacs-configuration
.emacs.d
My emacs configuration
Stars: ✭ 224 (+61.15%)
Mutual labels:  emacs-configuration
Circadian.el
Theme-switching for Emacs based on daytime
Stars: ✭ 105 (-24.46%)
Mutual labels:  emacs-configuration
editorPreferences
My editor preferences for Emacs / Sublime / VS Code / Clang Format etc.
Stars: ✭ 18 (-87.05%)
Mutual labels:  emacs-configuration
Emacs Config
Personal Emacs configuration files
Stars: ✭ 94 (-32.37%)
Mutual labels:  emacs-configuration
.doom.d
Private DOOM Emacs config highly focused around orgmode and GTD methodology, along with language support for Python and Elisp.
Stars: ✭ 185 (+33.09%)
Mutual labels:  emacs-configuration
emacs.d
My emacs config
Stars: ✭ 32 (-76.98%)
Mutual labels:  emacs-configuration
emacs-config
My ".emacs"
Stars: ✭ 17 (-87.77%)
Mutual labels:  emacs-configuration
dotfiles
I showed you my source code, pls respond
Stars: ✭ 45 (-67.63%)
Mutual labels:  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

(defvar fk/minibuffer-modeline--message nil)

(defun fk/minibuffer-modeline-update ()
  "Show global info in minibuffer instead of modeline."
  (let* ((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 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))

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)" "WIP(w)" "HOLD(h)" "|" "DONE(d)")))
  (org-todo-keyword-faces
   '(("TODO" :foreground "orangered2" :weight bold)
     ("WIP" :foreground "#86DC2F" :weight bold)
     ("HOLD" :foreground "#DC752F" :weight bold)))
  ;;;; 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 (:family ,fk/default-font-family :foreground "white"))))
  (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)))

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/
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].