204

Github GitHub - KaratasFurkan/.emacs.d: My literate Emacs configuration

 3 years ago
source link: https://github.com/KaratasFurkan/.emacs.d
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

My Literate Emacs Config

Screenshots

Initial Screen (Dashboard)

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

Python Development

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

Org Mode (and Olivetti)

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

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 (expand-file-name "README.org" user-emacs-directory))
(defconst config-el (expand-file-name "config.el" user-emacs-directory))

(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-name) 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
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (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 & Straigt 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)) 100)

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-file custom-file))

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

  (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)

(defalias 'yes-or-no-p 'y-or-n-p)

(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

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-full-major-mode)
 ("M-h" . help-command)
 :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")

Su/Sudo

read-only files will be writable but if you attempt to save your modifications, emacs will ask root user’s password if needed.

(use-package su
  :straight (:host github :repo "PythonNut/su.el")
  :config (su-mode))

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 '((:eval  ; TODO: maybe better in "* Better Defaults"
                       (let ((project-name (projectile-project-name)))
                         (unless (string= "-" project-name)
                           (format "%s| " project-name))))
                      "%b"))  ; project-name| file-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 20)
        (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)
        (setq fk/olivetti-fringe-face fk/darker-olivetti-fringe-face)
        (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)
    (setq fk/olivetti-fringe-face fk/default-olivetti-fringe-face)
    (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)
(defconst fk/default-font-family "Roboto Mono")
;; fk/default-font-size is calculated on start according to the primary screen
;; size. if screen-size is bigger than 16 inch: 9 else 11.
(defconst fk/default-font-size
  (let* ((command "xrandr | awk '/primary/{print sqrt( ($(NF-2)/10)^2 + ($NF/10)^2 )/2.54}'")
         (screen-size (string-to-number (shell-command-to-string command))))
    (if (or (> screen-size 16) (= screen-size 0)) 90 110)))  ; screen-size=0 if command gives error
(defconst fk/default-icon-size 15)

(defconst fk/variable-pitch-font-family "Noto Serif")
(defconst fk/variable-pitch-font-size fk/default-font-size)  ; TODO: adjust this and use in org-mode

(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 ,fk/variable-pitch-font-size))))
 ;; Characters with fixed pitch face do not shown when height is 90.
 `(fixed-pitch-serif ((t (:height 100)))))

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/decrease-font-size)
(global-set-key (kbd "C-*") 'fk/increase-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/font-color (face-attribute 'default :foreground))
  (defconst fk/background-color (face-attribute 'default :background))
  (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 20)
  (doom-modeline-bar-width 1)
  :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))
(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))

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
  ;; :preface
  ;; (defconst cursor-color+1 (format "#%x" (+ 1 (string-to-number (string-remove-prefix "#" (face-attribute 'cursor :background)) 16))))
  :custom
  (beacon-color "#D08771")  ; TODO: cursor-color+1 does not work with emacs --daemon
  ;; (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))
    (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
  (highlight-indent-guides-character-face ((t (:foreground ,fk/light-color7))))
  (highlight-indent-guides-top-character-face ((t (:foreground ,fk/light-color5))))
  :hook
  (prog-mode . highlight-indent-guides-mode))

Shackle

(use-package shackle
  :custom
  (shackle-rules '(("\\`\\*helm.*?\\*\\'" :regexp t :align t :size 0.4)  ; I use helm-posframe now, this is unnecessary but i want to keep just in case
                   ("\\`\\*helpful.*?\\*\\'" :regexp t :align t :size 0.4)
                   ("\\`\\*Go Translate*?\\*\\'" :regexp t :align t :size 0.4)
                   (help-mode :align t :size 0.4 :select t)))
  :hook
  (dashboard-after-initialize . shackle-mode))
;; 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)
    (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-small.png"))
  ;; Do not show package count, it is meaningless because of lazy loading.
  (dashboard-init-info (format "Emacs started in %s" (fk/time-since-startup)))
  (dashboard-set-heading-icons t)
  (dashboard-set-file-icons t)
  (dashboard-center-content t)
  (dashboard-week-agenda t)
  (dashboard-agenda-time-string-format "%d/%m/%Y %A %H:%M")
  ;; (dashboard-agenda-release-buffers t) ; Has bugs
  (dashboard-item-shortcuts '((recents . "r")
                              (bookmarks . "b")
                              (projects . "p")
                              (agenda . "a")))
  (dashboard-items '((recents  . 5)
                     (projects . 5)
                     ;;(bookmarks . 5)
                     ;;(agenda . 10) ;; I load agenda in :hook section
                     ))
  (dashboard-set-navigator t)
  ;; Format: "(icon title help action face prefix suffix)"
  (dashboard-navigator-buttons
   `((;; Github
      (,(all-the-icons-octicon "mark-github" :height 1.1 :v-adjust 0.0)
       "Github"
       "Browse github"
       (lambda (&rest _) (browse-url "https://github.com/")))
      ;; Codebase
      (,(all-the-icons-faicon "briefcase" :height 1.1 :v-adjust -0.1)
       "Codebase"
       "My assigned tickets"
       (lambda (&rest _) (browse-url "https://hipo.codebasehq.com/tickets")))
      ;; Perspective
      (,(all-the-icons-octicon "history" :height 1.1 :v-adjust 0.0)
       "Reload last session"
       "Reload last session"
       (lambda (&rest _) (persp-state-load persp-state-default-file))))))
  :custom-face
  (dashboard-heading-face ((t (:weight bold))))
  (dashboard-items-face ((t (:weight normal))))
  :hook
  (dashboard-mode . (lambda () (setq-local cursor-type nil)))
  ;; Load agenda after showing dashboard to decrease waiting time to see the
  ;; initial screen (dashboard). TODO: try to make this asynchronously to not
  ;; block Emacs.
  (dashboard-after-initialize . (lambda ()
                                  (add-to-list 'dashboard-items '(agenda . 10) t)
                                  (dashboard-refresh-buffer)))
  :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-agenda ()
    "Copy org-agenda (week) buffer"
    (save-window-excursion
      (org-agenda-list)
      (read-only-mode -1)
      (delete-matching-lines "...... now - - -")
      (delete-matching-lines "----------------")
      (let ((agenda (buffer-substring (point-min) (point-max))))
        (kill-buffer)
        agenda)))

  (defun fk/dashboard-insert-agenda (list-size)
    "Insert directly org-agenda buffer."
    (insert (fk/dashboard-get-agenda)))

  (setcdr (assoc 'agenda dashboard-item-generators) 'fk/dashboard-insert-agenda))

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
  :custom
  (olivetti-body-width-default 120)
  (olivetti-body-width-large 180)
  (olivetti-body-width olivetti-body-width-default)
  (olivetti-enable-visual-line-mode nil)
  :bind*
  (("C-1" . (lambda ()  ; TODO: a named function would be better
              (interactive)
              (if (= (count-windows) 1)
                  (if (and fk/olivetti-single-window-mode
                           (= olivetti-body-width olivetti-body-width-default))
                      (progn
                        (setq olivetti-body-width olivetti-body-width-large)
                        (olivetti-mode))
                    (call-interactively 'fk/olivetti-single-window-mode)
                    (setq olivetti-body-width olivetti-body-width-default))
                (delete-other-windows))))
   :map windows
   ("c" . olivetti-mode)
   ("v" . visual-line-mode)
   :map windows
   :prefix-map olivetti
   :prefix "o"
   ("o" . fk/olivetti-single-window-mode)
   ("e" . olivetti-expand)
   ("s" . olivetti-shrink))
  :hook
  (olivetti-mode . (lambda () (face-remap-add-relative 'fringe fk/olivetti-fringe-face)))
  (dashboard-after-initialize . fk/olivetti-single-window-mode)
  :config
  (defvar fk/default-olivetti-fringe-face `(:background ,fk/dark-color2))
  (defvar fk/darker-olivetti-fringe-face `(:background ,fk/dark-color9))
  (defvar fk/olivetti-fringe-face fk/default-olivetti-fringe-face)
  (defvar fk/olivetti-excluded-buffers '("*dashboard*" " *which-key*" "*helm"
                                         " *Minibuf-1*" "*vterm" "*fireplace*"
                                         " *lsp-peek--buffer*" "*pomidor*"))

  (defun fk/windows-vertical-p ()
    "Return t if windows placed vertically."
    (interactive)
    (not (catch 'horizontal
           (dolist (window (window-list))
             (when (not (= (car (window-edges window)) 0))
               (throw 'horizontal t))))))

  ;; Calculate the necessary `olivetti-body-width' according to
  ;; `fill-column' and `line-number-display-width' values.
  ;; (defadvice olivetti-mode (before get-fill-column-width activate)
  ;;   (setq-local olivetti-body-width (+ fill-column (line-number-display-width))))

  (defun fk/activate-olivetti-if-single-window ()
    "Activate olivetti-mode if there is only one window visible."
    (if (or (= (count-windows) 1)
            (fk/windows-vertical-p))
        (unless (or (member (buffer-name) fk/olivetti-excluded-buffers)
                    (catch 'found
                      (dolist (prefix fk/olivetti-excluded-buffers)
                        (when (string-prefix-p prefix (buffer-name))
                          (throw 'found t)))))
          (olivetti-mode))
      (fk/olivetti-reset)))

  (defun fk/olivetti-reset ()
    ;; TODO: do not reset full-span windows
    "Reset all windows' margins and face-remaps."
    (olivetti-mode -1)
    (olivetti-reset-all-windows)
    (dolist (window (window-list))
      (with-selected-window window
        (face-remap-remove-relative (cons 'fringe fk/default-olivetti-fringe-face))
        (face-remap-remove-relative (cons 'fringe fk/darker-olivetti-fringe-face)))))

  (define-minor-mode fk/olivetti-single-window-mode
    "Toggle olivetti-mode when there is only one window visible."
    :global t
    (if fk/olivetti-single-window-mode
        (progn
          (fk/activate-olivetti-if-single-window)
          (add-hook 'window-configuration-change-hook 'fk/activate-olivetti-if-single-window))
      (remove-hook 'window-configuration-change-hook 'fk/activate-olivetti-if-single-window)
      (fk/olivetti-reset))))

Emojify-

Tree Sitter

(use-package tree-sitter
  :defer t
  :straight
  (tree-sitter :host github
               :repo "ubolonton/emacs-tree-sitter"
               :files ("lisp/*.el")))

(use-package tree-sitter-langs
  :defer t
  :straight
  (tree-sitter-langs :host github
                     :repo "ubolonton/emacs-tree-sitter"
                     :files ("langs/*.el" "langs/queries")))

Visual Fill Column

(use-package visual-fill-column
  :commands 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))))

Completion

Better Defaults

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

Which Key (Keybinding Completion)

(use-package which-key-posframe
  :custom
  (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)

(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" . helm-imenu)
   ("C-x C-j" . helm-imenu)
   ("M-y" . 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"))
      (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))

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)))
  :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))

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)
    ("C-v" . scroll-up-command)
    ("M-v" . scroll-down-command))
  :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
  :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))

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
  :custom
  (hydra-hint-display-type 'posframe)
  (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

Custom Functions

find-config

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

;; Use a dedicated perspective for config
(advice-add 'fk/find-config :before (lambda () (persp-switch "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-scratch

(defun fk/generate-random-scratch ()
  "Create and switch to a temporary scratch buffer with a random name."
  (interactive)
  (switch-to-buffer (make-temp-name "scratch-"))
  (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))

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 (expand-file-name "straight/repos/" user-emacs-directory)))

Keybindings

(global-set-key (kbd "<F1>") 'help-command)
(global-set-key (kbd "C-x c") 'fk/find-config)
(global-set-key (kbd "M-o") 'other-window)
(global-set-key (kbd "C-x C-k") 'kill-current-buffer)
(global-set-key (kbd "M-l") 'move-to-window-line-top-bottom)
(global-set-key (kbd "C-M-u") 'pop-global-mark)
;; Split & Switch
(global-set-key (kbd "C-1") 'delete-other-windows)
(global-set-key (kbd "C-2") 'fk/split-window-below-and-switch)
(global-set-key (kbd "C-3") 'fk/split-window-right-and-switch)
;; Scroll less than default
(global-set-key (kbd "C-v") (lambda () (interactive) (scroll-up-command 15)))
(global-set-key (kbd "M-v") (lambda () (interactive) (scroll-down-command 15)))

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

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

(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)
  :custom
  (recentf-exclude `(,(expand-file-name "straight/build/" user-emacs-directory)
                     ,(expand-file-name "eln-cache/" user-emacs-directory)
                     "/usr/share/emacs/"
                     "/usr/local/share/emacs/"
                     "emacs/src/"
                     ,(expand-file-name "~/.virtualenvs")
                     "/usr/lib/node_modules/"
                     "/tmp/"))
  (recentf-max-saved-items 200))

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)
  :custom-face
  (aw-leading-char-face ((t (:height 1000 :foreground "red"))))
  :bind*
  (("C-q" . aw-flip-window)  ; last window
   :map windows
   ("w" . ace-window)
   ("D" . ace-delete-window)
   ("s" . ace-swap-window)
   ("l" . aw-flip-window))
  :config
  (ace-window-posframe-mode)  ; FIXME: Posframe is very slow at the first time
  (advice-add 'other-window :before (lambda (&rest _) (aw--push-window (selected-window))))
  (advice-add 'winum-select-window-by-number :before (lambda (&rest _) (aw--push-window (selected-window)))))

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)
  :config
  (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

(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 query (fk/convert-string-to-rg-compatible (or (fk/get-selected-text) "")))))
      (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))))

Helm Rg

(use-package helm-rg
  :custom
  (helm-rg--extra-args '("--max-columns" "400"))
  (fk/helm-rg-fuzzy-match t)  ; I may wanna disable helm-rg's transform functionality
  :custom-face
  (helm-rg-file-match-face ((t (:inherit font-lock-type-face :weight bold :underline nil))))
  (helm-rg-line-number-match-face ((t (:inherit line-number))))
  :bind
  ("C-M-s" . fk/helm-rg-dwim)
  :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 query (fk/convert-string-to-rg-compatible (or (fk/get-selected-text) "")))))
      (cl-letf (((symbol-function 'helm-rg--get-thing-at-pt) (lambda () query)))
        (if fk/helm-rg-fuzzy-match
            (call-interactively 'helm-rg)
          (cl-letf (((symbol-function 'helm-rg--helm-pattern-to-ripgrep-regexp) (lambda (_) _)))
            (call-interactively 'helm-rg))))))

  ;; Use a simpler header in the helm buffer.
  (fset 'helm-rg--header-name (lambda (_) (concat "Search at " helm-rg--current-dir)))

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

  ;; Push mark before `helm-rg' to be able to come back with `pop-global-mark'
  (advice-add 'helm-rg :before (lambda (&rest _) (push-mark))))

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-S-j" . xref-find-definitions-other-window)
  ("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)
  :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)
    (condition-case nil
        (call-interactively 'xref-pop-marker-stack)
      (error (call-interactively 'dumb-jump-back)))))

Helm Swoop

(use-package helm-swoop
  :custom
  (helm-swoop-speed-or-color t)
  (helm-swoop-split-window-function 'display-buffer)
  (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)))

Deadgrep

(use-package deadgrep
  :commands deadgrep
  :bind
  ( :map deadgrep-mode-map
    ("C-c C-e" . deadgrep-edit-mode)))
(use-package avy
  :bind
  (("M-j" . avy-goto-word-or-subword-1)))

Treemacs

Treemacs

(use-package treemacs
  :custom
  (treemacs-width 20)
  :bind
  ("M-0" . treemacs-select-window)
  :hook
  ;; Add current project to treemacs if not already added
  (treemacs-select . (lambda ()
                       (let* ((project-path (projectile-project-root))
                              (project-name (treemacs--filename project-path)))
                         (unless (treemacs--find-project-for-path project-path)
                           (treemacs-add-project project-path project-name)))))
  (treemacs-mode . (lambda ()
                     (face-remap-add-relative 'default :height .75)
                     (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))))

Treemacs Projectile

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

Perspective

(use-package perspective
  :custom
  (persp-mode-prefix-key (kbd "M-m p"))
  (persp-state-default-file (no-littering-expand-var-file-name "perspective.el"))
  :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 (projectile-project-name)))
          (persp-rename (if (> (length project-name) 10)
                            (concat (substring project-name 0 9) "…")
                          project-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
  ( :map global-map
    ([remap goto-line] . goto-line-preview)))

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))))

Keybindings

(keyboard-translate ?\C-h ?\C-?)  ; C-h as DEL, (F1 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)

(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)
  :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)))

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))

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
  :custom
  (eglot-ignored-server-capabilites '(:documentHighlightProvider))
  (eglot-stay-out-of '(flymake))
  (eglot-autoshutdown t)
  :hook
  (eglot-managed-mode . eldoc-box-hover-mode)
  (eglot-managed-mode . fk/company-enable-snippets)
  (eglot-managed-mode . (lambda () (flymake-mode 0)))
  :config
  (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)
  ;; 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)
  :hook
  (emacs-lisp-mode . symbol-overlay-mode))

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
    ;;("M-n" . python-nav-forward-block)
    ;;("M-p" . python-nav-backward-block)
    ("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 . fk/python-auto-f-string-mode)
  (python-mode . (lambda () (setq-local company-prescient-sort-length-enable nil)))
  (python-mode . 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 . (lambda () (require 'tree-sitter-langs) (tree-sitter-hl-mode)))
  (python-mode . (lambda () (setq-local fill-column 88)))
  :config
  ;;;; Smart f-strings
  ;; https://github.com/ubolonton/emacs-tree-sitter/issues/52
  ;; TODO: Create a mode from this
  (defun fk/python-f-string-ify ()
    ;; Does nothing if major-mode is not python or point is not on a string.
    (when-let* ((python-mode-p (eq major-mode 'python-mode))
                (str (tree-sitter-node-at-point 'string))
                (text (ts-node-text str)))
      (let* ((is-f-string (string-match-p "^[bru]*f+[bru]*\\(\"\\|'\\)" text))
             (end-of-string (ts-node-end-position (tree-sitter-node-at-point 'string)))
             (is-there-format-method (string= ".format"
                                              (buffer-substring-no-properties end-of-string(+ end-of-string 7))))
             (should-f-string (and (s-contains-p "{" text)
                                   (s-contains-p "}" text)
                                   (not is-there-format-method))))
        (if should-f-string
            (unless is-f-string
              (save-excursion
                (goto-char (ts-node-start-position str))
                (insert "f")))
          (when is-f-string
            (save-excursion
              (goto-char (ts-node-start-position str))
              (when (char-equal (char-after) ?f)
                (delete-char 1))))))))

  (define-minor-mode fk/python-auto-f-string-mode  ; TODO: does not work well
    "Toggle fk/python-auto-f-string-mode which adds 'f' at the
beginning of the string that has curly brackets in it."
    :init-value t
    (if fk/python-auto-f-string-mode
        (progn
          (defadvice wrap-region-trigger (after smart-f-string activate) (fk/python-f-string-ify))
          (defadvice delete-char (after smart-f-string activate) (fk/python-f-string-ify))
          (defadvice delete-active-region (after smart-f-string activate) (fk/python-f-string-ify))
          (defadvice kill-region (after smart-f-string activate) (fk/python-f-string-ify)))
      (ad-remove-advice 'wrap-region-trigger 'after 'smart-f-string)
      (ad-update 'wrap-region-trigger)
      (ad-remove-advice 'delete-char 'after 'smart-f-string)
      (ad-update 'delete-char)
      (ad-remove-advice 'delete-active-region 'after 'smart-f-string)
      (ad-update 'delete-active-region)
      (ad-remove-advice 'kill-region 'after 'smart-f-string)
      (ad-update 'kill-region))))

Pyvenv

(use-package pyvenv
  :after python
  :config
  (defun fk/activate-pyvenv ()
    "Activate python environment according to the `project-root/.venv' file."
    (interactive)
    (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))))))
      (pyvenv-mode)
      (pyvenv-workon venv-name)))

  ;; 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

;; Grep functions for Django

(defun fk/django-search-models ()
  (interactive)
  (fk/helm-rg-dwim-with-glob "models.py" (unless (region-active-p) "^class ")))

(defun fk/django-search-views ()
  (interactive)
  (fk/helm-rg-dwim-with-glob "views*.py" (unless (region-active-p) "^class ")))

(defun fk/django-search-serializers ()
  (interactive)
  (fk/helm-rg-dwim-with-glob "*serializers*.py" (unless (region-active-p) "^class ")))

(defun fk/django-search-tests ()
  (interactive)
  (fk/helm-rg-dwim-with-glob "*test*.py" (unless (region-active-p) "^class ")))

(defun fk/django-search-settings ()
  (interactive)
  ;; TODO: this glob does not work
  (fk/helm-rg-dwim-with-glob "settings*.py" (unless (region-active-p) "")))

(defun fk/django-search-admins ()
  (interactive)
  (fk/helm-rg-dwim-with-glob "admin.py" (unless (region-active-p) "")))

(defun fk/django-search-permissions ()
  (interactive)
  (fk/helm-rg-dwim-with-glob "permissions.py" (unless (region-active-p) "^class ")))

(defun fk/django-search-mixins ()
  (interactive)
  (fk/helm-rg-dwim-with-glob "mixins.py" (unless (region-active-p) "^class ")))

(defun fk/django-search-urls ()
  (interactive)
  (fk/helm-rg-dwim-with-glob "*.py" (unless (region-active-p) "path\\( ")))

(bind-keys*
 :map django
 ("m" . fk/django-search-models)
 ("v" . fk/django-search-views)
 ("s" . fk/django-search-serializers)
 ("t" . fk/django-search-tests)
 ("S" . fk/django-search-settings)
 ("a" . fk/django-search-admins)
 ("p" . fk/django-search-permissions)
 ("x" . fk/django-search-mixins)
 ("u" . fk/django-search-urls))

;; Utility functions for Django

(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-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 "/") ".")))))

(bind-keys*
 :map django
 ("c" . fk/django-copy-path-of-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) set in .dir-locals.el according to project
  (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))
(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))))
(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))))
(use-package lua-mode
  :mode "\\.lua\\'")

Tools

Dired

Dired

(use-package dired
  :straight (:type built-in)
  :custom
  (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)
  :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-open-systems-file-manager ()
    "Open current directory with the system's default file manager."
    (interactive)
    (call-process-shell-command (concat "xdg-open " default-directory " &")))

  (advice-add 'dired-up-directory :override 'fk/dired-up-directory)
  (advice-add 'dired-find-file :override 'dired-find-alternate-file))

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)
  )

Dired Posframe

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

Dired Recent

(use-package dired-recent
  :after dired  ; TODO: is bind still defer?
  :bind
  ( :map files
    ("d" . dired-recent-open))
  :config
  (dired-recent-mode))
(use-package org
  :straight (:type built-in)
  :custom
  (org-confirm-babel-evaluate nil)
  (org-ellipsis "↴") ;; ↴, ▼, ▶, ⤵
  (org-src-window-setup 'current-window)
  (org-startup-indented t)
  (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 0)
  (org-imenu-depth 20)
  (org-hide-emphasis-markers t)
  ;;;; Getting Things Done ;;;;
  (org-directory "~/org")  ; This is default already but lets declare explicitly
  (org-agenda-files `(,(expand-file-name "agenda.org" org-directory)))
  (org-agenda-start-on-weekday nil)
  (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-default-priority ?A)  ; Highest
  (org-log-done 'time)
  (org-todo-keywords '((sequence "TODO(1)" "WIP(w)" "WAITING_REVIEW(r)" "WAITING_TEST(t)" "READY_PROD(p)" "HOLD(h)" "|"
                                 "DONE(d)" "CANCELLED(c)")))
  (org-todo-keyword-faces
   '(("TODO" :foreground "orangered2" :weight bold)
     ("WIP" :foreground "#86DC2F" :weight bold)
     ("WAITING_REVIEW" :foreground "cyan" :weight bold)
     ("WAITING_TEST" :foreground "cyan" :weight bold)
     ("READY_PROD" :foreground "greenyellow" :weight bold)
     ("HOLD" :foreground "#DC752F" :weight bold)
     ("CANCELLED" :inherit org-done)))
  ;;;; 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-4 ((t (:height 1.1 :weight bold))))
  (org-level-3 ((t (:height 1.15 :weight bold))))
  (org-level-2 ((t (:height 1.2 :weight bold))))
  (org-level-1 ((t (:height 1.3 :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 (:height 1.5))))
  (org-block-begin-line ((t (:foreground ,fk/light-color3 :background ,fk/background-color :extend t))))
  (org-document-info-keyword ((t (:foreground ,fk/light-color3 :height 10))))  ; Make #+TITLE: invisible
  (org-meta-line ((t (:foreground ,fk/light-color3))))  ; Less distractive
  (org-agenda-current-time ((t (:foreground "LightGoldenrod"))))
  (org-agenda-date-today ((t (:foreground "LightGoldenrod"))))
  (org-agenda-calendar-event ((t (:weight bold))))
  :bind
  ( :map org
    ("a" . org-agenda)
    ("f" . (lambda () (interactive) (helm-find-files-1 "~/org/")))
    ("c" . (lambda () (interactive) (org-capture :keys "i")))
    :map org-mode-map
    ("C-c C-e" . org-edit-special)
    ("M-n" . org-next-visible-heading)
    ("M-p" . org-previous-visible-heading)
    ("C-c C-f". fk/org-imenu)
    ("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)
    :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
  ;; TODO: bunlar yerine prettify + box face'i ile yap
  (org-mode . prettify-symbols-mode)
  (org-mode . (lambda () (setq prettify-symbols-alist
                               '(("[ ]" . "☐")
                                 ("[X]" . "☑") ;; ✔
                                 ("[-]" . "◿"))))) ;; ◪, ⬔
  (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
  :config
  (add-to-list 'org-emphasis-alist '("#" (:box '(:line-width -1))))  ; FIXME: does not work.
  (setf (cdr (assoc "*" org-emphasis-alist)) '((:weight extra-bold :underline t :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))))
  (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-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     _T_: Tech        _t_: Trash
_P_: Priority     _h_: Home      _M_: Movie       ^^
^^                _o_: Other     _S_: TV Show     ^^
^^                ^^             _A_: Anime       ^^
^^                ^^             _V_: Video       ^^
^^                ^^             _F_: Food        ^^
^^                ^^             _O_: Other       ^^

"
    ;; Move
    ("n" next-line)
    ("p" previous-line)
    ("P" org-priority-down)
    ;; Todo
    ("w" (lambda () (interactive) (fk/org-refile-fixed-location "todos.org" "Work")))
    ("e" (lambda () (interactive) (fk/org-refile-fixed-location "todos.org" "Emacs")))
    ("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")))
    ("T" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Tech")))
    ("M" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Movie")))
    ("S" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "TV Show")))
    ("A" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Anime")))
    ("V" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Video")))
    ("F" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Food")))
    ("O" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Other")))
    ;; Archive
    ("d" fk/org-refile-done)
    ("t" fk/org-refile-trash)
    ;; General
    ("m" org-refile "Refile manually")
    ("s" save-buffer "Save buffer")
    ("q" nil "Quit" :color blue)))

Custom Functions

org-imenu

(defun fk/helm-imenu ()
  "helm-imenu without initializion."
  (interactive)
  (require 'which-func)
  (require 'helm-imenu)
  (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-execute-action-at-once-if-one
          helm-imenu-execute-action-at-once-if-one))
    (helm :sources 'helm-source-imenu
          :buffer "*helm imenu*")))

(defun fk/org-imenu ()
  "Go to a heading with helm-imenu and expand the heading."
  (interactive)
  (fk/helm-imenu)
  (show-subtree))

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 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
  :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 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)
    ("C-n" . (lambda () (interactive) (if cursor-type
                                          (next-line)
                                        (setq-local cursor-type t)
                                        (next-line)))))
  :hook
  (org-tree-slide-play . (lambda () (setq-local beacon-mode nil)))
  (org-tree-slide-stop . (lambda () (setq-local beacon-mode t)))
  (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-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)))
  :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")
  :custom
  (valign-fancy-bar t)
  :hook
  (org-mode . valign-mode))

Org Appear

(use-package org-appear
  :straight (:host github :repo "awth13/org-appear" :branch "feature/time-stamps")
  :custom
  (org-appear-autolinks t)
  :hook
  (org-mode . org-appear-mode))

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
  ;;(magit-mode . magit-toggle-margin) FIXME: does not work
  ;;(magit-mode . magit-toggle-margin-details)
  (git-commit-setup . git-commit-turn-on-flyspell))

Magit Todos

(use-package magit-todos
  :commands helm-magit-todos
  :custom
  (magit-todos-ignored-keywords '("DONE"))
  (magit-todos-exclude-globs '("*jquery*.js" "*min.js" "*min.css"))
  (magit-todos-max-items 30)
  (magit-todos-auto-group-items 30)
  :bind*
  ( :map version-control
    ("t" . helm-magit-todos))
  :hook (magit-mode . magit-todos-mode))

Magit Forge

Pull Requests, Issues etc.

(use-package forge
  :straight (:host github :repo "KaratasFurkan/forge" :branch "add-pr-message-prefix-and-number-prefix")
  :after magit
  :custom
  (forge-create-pullreq-message-prefix "STAGING -")  ; TODO: add this at .dir-locals.el
  (forge-create-pullreq-message-number-prefix t)
  :config
  (defadvice magit-pull-from-upstream (after forge-pull activate)
    (forge-pull))
  (defadvice magit-fetch-all (after forge-pull activate)
    (forge-pull))

  (defun fk/forge-request-review (users &optional labels)
    "Request review from USERS (a string list of usernames) and
set LABELS (a string list of labels) if given."
    (let* ((topic (forge-current-pullreq))
           (repo  (forge-get-repository topic)))
      (forge--set-topic-review-requests repo topic users)
      (when labels
        (forge--set-topic-labels repo topic labels))))

  (defun fk/mastermind-request-review ()
    (interactive)
    (fk/forge-request-review '("omerfarukabaci" "alicertel")
                             '("Waiting for Code Review"))))

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)

Git Timemachine

(use-package git-timemachine
  :commands git-timemachine)

Git Blame (vc-msg)

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

Terminal Emulation

Vterm

(use-package vterm
  :custom
  (vterm-max-scrollback 100000)
  :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)
    ("M-m" . nil)
    ("M-u" . nil)
    ("M-j" . nil)
    ("<f1>" . nil)
    ("C-c C-n" . fk/vterm-next-prompt)
    ("C-c C-p" . fk/vterm-previous-prompt)
    :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-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]*:/[^#]*# ")

  (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))
   '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))))

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:]]+"))
;; TODO: does not work
;; (use-package eaf
;;   :straight
;;   (:host github :repo "manateelazycat/emacs-application-framework" :depth 1 :files ("*")))

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 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\\'"
  :custom
  (plantuml-jar-path (concat no-littering-etc-directory "plantuml.jar"))
  (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)))

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-breaks-before-long 3)
  (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"))))

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))
(use-package fish-mode
  :mode "\\.fish\\'")

Docker

Dockerfile

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

Docker Compose

(use-package docker-compose-mode
  :mode "docker-compose\\'")
(use-package yaml-mode
  :mode "\\.yaml\\'"
  :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)))

PDF-

.gitignore

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

Csv mode

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

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
  :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)
(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))
  :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))))

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK