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
- Screenshots
- About
- Package Management
- Performance Optimization
- Better Defaults
- Appearance
- Notes
- Better Defaults
- Custom Functions
- Remove Redundant UI
- Good Scroll (Smooth scrolling)
- Window Dividers
- Font
- Theme
- Mode Line
- Page Break Lines
- Trailing White Space-
- Highlight TODOs
- Beacon
- All The Icons
- Highlight Indent Guides
- Shackle
- Zoom
- Emacs Dashboard
- Stripe Buffer
- Fill Column Indicator
- Line Numbers
- Dired Icons-
- Rainbow Delimiters-
- Helm Icons-
- Symbol Overlay-
- Olivetti
- Emojify-
- Tree Sitter
- Visual Fill Column
- Color Identifiers Mode-
- Goggles Mode (Highlight Changes)
- Hide/Show
- Topspace (Upper margin)
- Redacted (Hide text)
- Posframe
- Completion
- Search & Navigation
- Text Editing
- Programming
- General
- Emacs Lisp
- Python
- Web Mode
- JavaScript
- Go
- C
- Lua
- Tools
- Dired
- Org
- Calendar
- Version Control
- Terminal Emulation
- Restclient
- EAF
- Sudo Edit
- Google Translate
- Emacs Screencast
- Slack
- PlantUML
- EWW
- XWWP (Xwidget Webkit Enhancement)
- Screenshot
- Emacs Everywhere
- Pomidor (Pomodoro)
- Speed Type
- Docker
- Sozluk
- Epub
- File Modes
- Fun
- Packages I almost never use but want to keep
- Some Other Emacs Configurations
About
Installation
Clone this repository to ~/.emacs.d or ~/.config/emacs
git clone https://github.com/KaratasFurkan/.emacs.d.git
Open Emacs and let the configuration install necessary packages.
Note: This configuration is not intended to be directly used by others, but it can be useful to get inspired or copy some parts of it. I use Emacs 28.0.50 with feature/native-comp branch, most of this configuration will work in old versions too but some parts needs Emacs 27+.
init.el
init.el is just used to load literate config.
(defconst config-org (locate-user-emacs-file "README.org"))
(defconst config-el (locate-user-emacs-file "config.el"))
(unless (file-exists-p config-el)
(require 'org)
(org-babel-tangle-file config-org config-el))
(load-file config-el)
early-init.el
Note that a few of the code blocks (mostly UI related) in this configuration
tangle to early-init.el
instead of config.el
(which is the elisp file
generated by this configuration) to get the effects in the very beginning of the
initialization.
Applying Changes
(defun fk/tangle-config ()
"Export code blocks from the literate config file
asynchronously."
(interactive)
;; prevent emacs from killing until tangle-process finished
(add-to-list 'kill-emacs-query-functions
(lambda ()
(or (not (process-live-p (get-process "tangle-process")))
(y-or-n-p "\"fk/tangle-config\" is running; kill it? "))))
;; tangle config asynchronously
(fk/async-process
(format "emacs %s --batch --eval '(org-babel-tangle nil \"%s\")'" config-org config-el)
"tangle-process"))
If the current org file is the literate config file, add a local hook to tangle code blocks on every save to update configuration.
(add-hook 'org-mode-hook
(lambda ()
(if (equal buffer-file-truename config-org)
(fk/add-local-hook 'after-save-hook 'fk/tangle-config))))
Package Management
Straight
Installation & Initialization
Taken from: https://github.com/raxod502/straight.el#getting-started
(defvar bootstrap-version)
(let ((bootstrap-file
(locate-user-emacs-file "straight/repos/straight.el/bootstrap.el"))
(bootstrap-version 5))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
Settings
To not increase Emacs startup time, check package modifications when packages
edited (with Emacs) or manually invoke straight-check-all
command, instead of
checking modifications at startup.
Note: this setting should be set before the initialization of straight.
early-init is a good place for this, so I used :tangle early-init.el
here.
(setq straight-check-for-modifications '(check-on-save find-when-checking))
Straight uses symlinks in the build
directory which causes
xref-find-definition
to ask =”Symbolic link to Git-controlled source
file; follow link? (y or n)”= every time, to always answer yes
, set
vc-follow-symlinks
true.
(setq vc-follow-symlinks t)
Use default depth of 1 when cloning files with git to get savings on network bandwidth and disk space.
(setq straight-vc-git-default-clone-depth 1)
Notes
M-x straight-pull-all
: update all packages.M-x straight-normalize-all
: restore all packages (remove local edits)M-x straight-freeze-versions
andM-x straight-thaw-versions
are likepip freeze requirements.txt
andpip install -r requirements.txt
- To tell straight.el that you want to use the version of Org shipped with Emacs, rather than cloning the upstream repository:
(Note: “:tangle no”)
(use-package org
:straight (:type built-in))
Use-Package
Installation & Straight Integration
;; Install `use-package'.
(straight-use-package 'use-package)
;; Install packages in `use-package' forms with `straight'. (not the built-in
;; package.el)
(setq straight-use-package-by-default t)
;; Key Chord functionality in use-package. (I do not use it anymore.)
;; (use-package use-package-chords
;; :hook
;; (dashboard-after-initialize . (lambda () (key-chord-mode 1))))
Notes
- Hooks in the
:hook
section, run in reverse order. Example:
(Note: “:tangle no”)
(use-package package-name
:hook
(x-mode . last)
(x-mode . second)
(x-mode . first))
Performance Optimization
A very nice source: https://github.com/hlissner/doom-emacs/blob/develop/docs/faq.org#how-does-doom-start-up-so-quickly
Garbage Collection
Make startup faster by reducing the frequency of garbage collection. Set gc-cons-threshold (the default is 800 kilobytes) to maximum value available, to prevent any garbage collection from happening during load time.
Note: tangle to early-init.el to make startup even faster
(setq gc-cons-threshold most-positive-fixnum)
Restore it to reasonable value after init. Also stop garbage collection during minibuffer interaction (helm etc.).
(defconst 1mb 1048576)
(defconst 20mb 20971520)
(defconst 30mb 31457280)
(defconst 50mb 52428800)
(defun fk/defer-garbage-collection ()
(setq gc-cons-threshold most-positive-fixnum))
(defun fk/restore-garbage-collection ()
(run-at-time 1 nil (lambda () (setq gc-cons-threshold 30mb))))
(add-hook 'emacs-startup-hook 'fk/restore-garbage-collection 100)
(add-hook 'minibuffer-setup-hook 'fk/defer-garbage-collection)
(add-hook 'minibuffer-exit-hook 'fk/restore-garbage-collection)
(setq read-process-output-max 1mb) ;; lsp-mode's performance suggest
File Handler
(Note: “:tangle early-init.el”)
(defvar default-file-name-handler-alist file-name-handler-alist)
(setq file-name-handler-alist nil)
(add-hook 'emacs-startup-hook
(lambda ()
(setq file-name-handler-alist default-file-name-handler-alist)) 95)
Others
Copied from Doom Emacs: (Note: “:tangle early-init.el”)
;; In Emacs 27+, package initialization occurs before `user-init-file' is
;; loaded, but after `early-init-file'. straight.el handles package
;; initialization, so we must prevent Emacs from doing it early!
(setq package-enable-at-startup nil)
(advice-add 'package--ensure-init-file :override 'ignore)
;; Resizing the Emacs frame can be a terribly expensive part of changing the
;; font. By inhibiting this, we easily halve startup times with fonts that are
;; larger than the system default.
(setq frame-inhibit-implied-resize t)
Custom Functions
measure-time
(Note: “:tangle early-init.el”)
(defmacro fk/measure-time (&rest body)
"Measure the time it takes to evaluate BODY."
`(let ((time (current-time)))
,@body
(message "%s" (float-time (time-since time)))))
time-since-startup
(Note: “:tangle early-init.el”)
(defun fk/time-since-startup (&optional prefix)
"Display the time that past since emacs startup. Add PREFIX if given at the
start of message for debug purposes."
(interactive)
(let* ((prefix (or prefix ""))
(time (float-time (time-since before-init-time)))
(str (format "%s%s seconds" prefix time)))
(if (or (not (string-empty-p prefix))
(called-interactively-p 'interactive))
(message str)
str)))
time-since-last-check
(Note: “:tangle early-init.el”)
(defvar fk/time-last-check nil)
(defvar fk/time-threshold 0)
(setq fk/time-threshold 0.02)
(defun fk/time-since-last-check (&optional prefix)
"Display the time that past since last check. Add PREFIX if given at the
start of message for debug purposes."
(interactive)
(let* ((prefix (or prefix ""))
(time (float-time (time-since (or fk/time-last-check before-init-time))))
(str (format "%s%s seconds" prefix time)))
(setq fk/time-last-check (current-time))
(if (or (not (string-empty-p prefix))
(called-interactively-p 'interactive))
(when (> time fk/time-threshold) (message "%s" str))
str)))
Better Defaults
File Paths
Keep Emacs directory clean.
(use-package no-littering
:config
(with-eval-after-load 'recentf
(add-to-list 'recentf-exclude no-littering-var-directory)
(add-to-list 'recentf-exclude no-littering-etc-directory))
(setq auto-save-file-name-transforms ; autosaved-file-name~
`((".*" ,(no-littering-expand-var-file-name "auto-save/") t))
custom-file (no-littering-expand-etc-file-name "custom.el"))
(when (file-exists-p custom-file)
;; Load `custom-set-variables', not load whole `custom.el' with unwanted
;; `custom-set-faces'
(with-current-buffer (find-file-noselect custom-file)
(goto-char 0)
(forward-sexp)
(call-interactively 'eval-last-sexp)
(kill-buffer)))
(defconst fk/static-directory (locate-user-emacs-file "static/"))
(defun fk/expand-static-file-name (file)
"Expand filename FILE relative to `fk/static-directory'."
(expand-file-name file fk/static-directory)))
General
(setq-default
ring-bell-function 'ignore ; prevent beep sound.
inhibit-startup-screen t ; TODO: maybe better on early-init or performance?
initial-major-mode 'fundamental-mode ; TODO: maybe better on early-init or performance?
initial-scratch-message nil ; TODO: maybe better on early-init?
create-lockfiles nil ; .#locked-file-name
confirm-kill-processes nil ; exit emacs without asking to kill processes
backup-by-copying t ; prevent linked files
require-final-newline t ; always end files with newline
delete-old-versions t ; don't ask to delete old backup files
revert-without-query '(".*") ; `revert-buffer' without confirmation
uniquify-buffer-name-style 'forward ; non-unique buffer name display: unique-part/non-unique-filename
fast-but-imprecise-scrolling t ; supposed to make scrolling faster on hold
window-resize-pixelwise t ; correctly resize windows by pixels (e.g. in split-window functions)
native-comp-async-report-warnings-errors nil ; disable annoying native-comp warnings
ad-redefinition-action 'accept ; disable annoying "ad-handle-definition: ‘some-function’ got redefined" warnings
use-short-answers t ; e.g. `y-or-n-p' instead of `yes-or-no-p'
help-enable-symbol-autoload t) ; perform autoload if docs are missing from autoload objects.
(global-auto-revert-mode)
(save-place-mode)
(global-so-long-mode)
(bind-key* "M-r" 'repeat)
(defun fk/add-local-hook (hook function)
"Add buffer-local hook."
(add-hook hook function :local t))
(defun fk/async-process (command &optional name filter)
"Start an async process by running the COMMAND string with bash. Return the
process object for it.
NAME is name for the process. Default is \"async-process\".
FILTER is function that runs after the process is finished, its args should be
\"(process output)\". Default is just messages the output."
(make-process
:command `("bash" "-c" ,command)
:name (if name name
"async-process")
:filter (if filter filter
(lambda (process output) (message (s-trim output))))))
;; Examples:
;;
;; (fk/async-process "ls")
;;
;; (fk/async-process "ls" "my ls process"
;; (lambda (process output) (message "Output:\n\n%s" output)))
;;
;; (fk/async-process "unknown command")
;; Make sure to focus when a new emacsclient frame created.
(add-hook 'server-after-make-frame-hook (lambda () (select-frame-set-input-focus (selected-frame))))
(defalias 'narrow-quit 'widen) ; I forget `widen' everytime
;; TODO: lset would be useful too
(defmacro l (func &rest args)
"Shorter lambda."
`(lambda nil (apply ,func '(,@args))))
(defmacro li (func &rest args)
"Shorter lambda, interactive."
`(lambda nil (interactive) (apply ,func '(,@args))))
;; Examples:
;; (global-set-key (kbd "C-V") (lambda () (interactive) (next-line 10))) <-- Classical
;; (global-set-key (kbd "C-V") (li 'next-line 10)) <-- With li macro
Helpful
A better, more detailed help buffer.
(use-package helpful
:custom
;; Use helpful in `helm-apropos'
(helm-describe-function-function 'helpful-function)
(helm-describe-variable-function 'helpful-variable)
:bind
(([remap describe-function] . helpful-callable)
([remap describe-variable] . helpful-variable)
([remap describe-key] . helpful-key)
:map emacs-lisp-mode-map
("C-c C-d" . helpful-at-point)))
Menu Style Keybindings
Menu style keybindings like Spacemacs.
;; NOTE: I use F1 as C-h (paging & help).
(bind-keys*
:prefix-map fk/menu-map
:prefix "M-m"
("M-m" . which-key-show-major-mode)
("M-h" . help-command)
("M-u" . universal-argument)
:map fk/menu-map :prefix-map buffers :prefix "b"
:map fk/menu-map :prefix-map comments :prefix "c"
:map fk/menu-map :prefix-map django :prefix "d"
:map fk/menu-map :prefix-map errors :prefix "e"
:map fk/menu-map :prefix-map files :prefix "f"
:map fk/menu-map :prefix-map org :prefix "o"
:map fk/menu-map :prefix-map text :prefix "t"
:map fk/menu-map :prefix-map version-control :prefix "v"
:map fk/menu-map :prefix-map windows :prefix "w")
Local Variables
(defun fk/straight-ignore-local-variables (orig-func &rest args)
"Ignore local variables when visiting an installed package
which is generally not intended to be edited."
(unless (string-prefix-p (straight--dir) default-directory)
(apply orig-func args)))
(advice-add 'hack-local-variables-confirm :around 'fk/straight-ignore-local-variables)
Appearance
Notes
- To start Emacs maximized:
$ emacs -mm
- To start Emacs fullscreen:
$ emacs -fs
Better Defaults
(global-hl-line-mode)
(blink-cursor-mode -1)
(setq-default
truncate-lines t
frame-resize-pixelwise t ; maximized emacs may not fit screen without this
frame-title-format '("Emacs | %b")) ; Emacs | buffer-name
Custom Functions
disable-all-themes
(defun fk/disable-all-themes ()
"Disable all active themes."
(interactive)
(dolist (theme custom-enabled-themes)
(disable-theme theme)))
darken-background
I use this to darken non-file buffers like treemacs, helm etc.
(defun fk/darken-background ()
"Darken the background of the buffer."
(interactive)
(face-remap-add-relative 'default :background fk/dark-color))
presentation-mode
(define-minor-mode fk/presentation-mode
"A global minor mode for presentations. Make things easy to see."
:global t
(if fk/presentation-mode
(progn
(fk/adjust-font-size 40)
(dimmer-mode 1)
(setq zoom-size '(100 . 30))
(zoom-mode 1)
(setq default-window-divider-default-bottom-width window-divider-default-bottom-width
default-window-divider-default-right-width window-divider-default-right-width)
(setq window-divider-default-bottom-width 7
window-divider-default-right-width 7)
(window-divider-mode 1)
(set-face-attribute 'olivetti-borders-face nil :background fk/darker-olivetti-borders-color)
(olivetti-mode 1)
(goggles-mode 1))
(fk/adjust-font-size 0)
(dimmer-mode -1)
(setq zoom-size fk/zoom-default-size)
(zoom-mode -1)
(setq window-divider-default-bottom-width default-window-divider-default-bottom-width
window-divider-default-right-width default-window-divider-default-right-width)
(window-divider-mode 1)
(set-face-attribute 'olivetti-borders-face nil :background fk/default-olivetti-borders-color)
(olivetti-mode 1)
(goggles-mode -1)))
toggle-ui-elements
(defun fk/toggle-ui-elements (&optional arg)
"Toggle `display-line-numbers-mode', `highlight-indent-guides-mode' and
`display-fill-column-indicator-mode'."
(interactive)
(display-line-numbers-mode (or arg (if display-line-numbers-mode -1 1)))
(highlight-indent-guides-mode (or arg (if highlight-indent-guides-mode -1 1)))
(display-fill-column-indicator-mode (or arg (if display-fill-column-indicator-mode -1 1))))
(add-hook 'prog-mode-hook (lambda () (fk/toggle-ui-elements -1)) 100)
Remove Redundant UI
(Note: “:tangle early-init.el”)
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
;; Do not show default modeline until doom-modeline is loaded
(setq-default mode-line-format nil)
Good Scroll (Smooth scrolling)
(use-package good-scroll
:straight (:host github :repo "io12/good-scroll.el")
:commands good-scroll-mode
:custom
(good-scroll-duration 0.2)
(good-scroll-point-jump 4)
;; :bind
;; ("C-v" . fk/smooth-scroll-up)
;; ("M-v" . fk/smooth-scroll-down)
;; ("C-l" . fk/smooth-recenter-top-bottom)
;; :hook
;; (dashboard-after-initialize . good-scroll-mode)
:config
(defun fk/smooth-scroll-down (&optional pixels)
"Smooth alternative of M-v `scroll-down-command'."
(interactive)
(let ((good-scroll-step (or pixels 300)))
(good-scroll-down)))
(defun fk/smooth-scroll-up (&optional pixels)
"Smooth alternative of C-v `scroll-up-command'."
(interactive)
(let ((good-scroll-step (or pixels 300)))
(good-scroll-up)))
(defun fk/smooth-recenter-top-bottom ()
"docstring"
(interactive)
(let* ((current-row (cdr (nth 6 (posn-at-point))))
(target-row (save-window-excursion
(recenter-top-bottom)
(cdr (nth 6 (posn-at-point)))))
(distance-in-pixels (* (- target-row current-row) (line-pixel-height)))
(good-scroll-step distance-in-pixels))
(when (not (zerop distance-in-pixels))
(good-scroll--update -1)))))
Window Dividers
Change default window dividers to a better built-in alternative. (Note: “:tangle early-init.el”)
(setq window-divider-default-places t
window-divider-default-bottom-width 1
window-divider-default-right-width 1)
(window-divider-mode)
Font
Font
(defconst fk/default-font-family "RobotoMono Nerd Font")
(defconst fk/default-font-size 90)
(defconst fk/default-icon-size 15)
(defconst fk/variable-pitch-font-family "Noto Serif")
(custom-set-faces
`(default ((t (:family ,fk/default-font-family :height ,fk/default-font-size))))
`(variable-pitch ((t (:family ,fk/variable-pitch-font-family :height 1.0))))
;; Characters with fixed pitch face do not shown when height is 90.
`(fixed-pitch-serif ((t (:height 1.2)))))
Custom Functions
adjust-font-size
(defun fk/adjust-font-size (height)
"Adjust font size by given height. If height is '0', reset font
size. This function also handles icons and modeline font sizes."
(interactive "nHeight ('0' to reset): ")
(let ((new-height (if (zerop height)
fk/default-font-size
(+ height (face-attribute 'default :height)))))
(set-face-attribute 'default nil :height new-height)
(set-face-attribute 'mode-line nil :height new-height)
(set-face-attribute 'mode-line-inactive nil :height new-height)
(message "Font size: %s" new-height))
(let ((new-size (if (zerop height)
fk/default-icon-size
(+ (/ height 5) treemacs--icon-size))))
(when (fboundp 'treemacs-resize-icons)
(treemacs-resize-icons new-size))
(when (fboundp 'company-box-icons-resize)
(company-box-icons-resize new-size)))
(when diff-hl-mode
(diff-hl-maybe-redefine-bitmaps)))
increase-font-size
(defun fk/increase-font-size ()
"Increase font size by 0.5 (5 in height)."
(interactive)
(fk/adjust-font-size 5))
decrease-font-size
(defun fk/decrease-font-size ()
"Decrease font size by 0.5 (5 in height)."
(interactive)
(fk/adjust-font-size -5))
reset-font-size
(defun fk/reset-font-size ()
"Reset font size according to the `fk/default-font-size'."
(interactive)
(fk/adjust-font-size 0))
Keybindings
(global-set-key (kbd "C-=") 'fk/increase-font-size)
(global-set-key (kbd "C--") 'fk/decrease-font-size)
(global-set-key (kbd "C-0") 'fk/reset-font-size)
Theme
Theme
(use-package doom-themes
:custom-face
(font-lock-comment-face ((t (:slant italic))))
(font-lock-string-face ((t (:foreground "PeachPuff3"))))
(font-lock-function-name-face ((t (:foreground "LightGoldenrod"))))
(highlight ((t (:underline t :background nil :foreground nil))))
(lazy-highlight ((t (:background nil :foreground nil :box (:line-width -1)))))
(fixed-pitch ((t (:family "Noto Sans Mono"))))
:config
(load-theme 'doom-spacegrey t)
(defconst fk/cursor-color (if (daemonp) "#D08770" (face-background 'cursor)))
(defconst fk/font-color (if (daemonp) "#C0C5CE" (face-foreground 'default)))
(defconst fk/background-color (if (daemonp) "#2B303B" (face-background 'default)))
(defconst fk/dark-color (doom-darken fk/background-color 0.15))
(defconst fk/dark-color1 (doom-darken fk/background-color 0.01))
(defconst fk/dark-color2 (doom-darken fk/background-color 0.02))
(defconst fk/dark-color3 (doom-darken fk/background-color 0.03))
(defconst fk/dark-color4 (doom-darken fk/background-color 0.04))
(defconst fk/dark-color5 (doom-darken fk/background-color 0.05))
(defconst fk/dark-color6 (doom-darken fk/background-color 0.06))
(defconst fk/dark-color7 (doom-darken fk/background-color 0.07))
(defconst fk/dark-color8 (doom-darken fk/background-color 0.08))
(defconst fk/dark-color9 (doom-darken fk/background-color 0.09))
(defconst fk/light-color (doom-lighten fk/background-color 0.15))
(defconst fk/light-color1 (doom-lighten fk/background-color 0.09))
(defconst fk/light-color2 (doom-lighten fk/background-color 0.08))
(defconst fk/light-color3 (doom-lighten fk/background-color 0.07))
(defconst fk/light-color4 (doom-lighten fk/background-color 0.06))
(defconst fk/light-color5 (doom-lighten fk/background-color 0.05))
(defconst fk/light-color6 (doom-lighten fk/background-color 0.04))
(defconst fk/light-color7 (doom-lighten fk/background-color 0.03))
(defconst fk/light-color8 (doom-lighten fk/background-color 0.02))
(defconst fk/light-color9 (doom-lighten fk/background-color 0.01)))
Settings
Disable all themes before loading a theme
(defadvice load-theme (before disable-themes-first activate)
(fk/disable-all-themes))
load-theme without annoying confirmation
(advice-add 'load-theme
:around
(lambda (fn theme &optional no-confirm no-enable)
(funcall fn theme t)))
Alternatives
A light emacs theme that’s well suited for org-mode
(use-package poet-theme
:defer t)
Mode Line
Doom Modeline
(use-package doom-modeline
:init
;; show doom-modeline at the same time with dashboard
(add-hook 'emacs-startup-hook 'doom-modeline-mode -100)
:custom
(doom-modeline-buffer-encoding nil)
(doom-modeline-vcs-max-length 40)
(doom-modeline-bar-width 1)
(doom-modeline-env-python-executable "python")
:custom-face
(mode-line ((t (:background ,fk/dark-color))))
(mode-line-inactive ((t (:background ,fk/dark-color5))))
(mode-line-highlight ((t (:inherit cursor :foreground "black"))))
(doom-modeline-bar ((t (:background ,fk/dark-color))))
(doom-modeline-buffer-path ((t (:inherit font-lock-comment-face :slant normal))))
:hook
(dashboard-after-initialize . column-number-mode))
Minibuffer Modeline
;; TODO: check `set-message-functions' to fix losing minibuffer modeline
(defvar fk/minibuffer-modeline--message nil)
(defun fk/minibuffer-modeline-update ()
"Show global info in minibuffer instead of modeline."
(let* ((org-clock-string (when (boundp 'fk/org-clock-string) fk/org-clock-string))
(pyvenv-icon (all-the-icons-icon-for-mode 'python-mode :height 0.9 :v-adjust 0.01))
(pyvenv (when (and (featurep 'pyvenv) pyvenv-virtual-env-name)
(format "[%s %s]" pyvenv-icon pyvenv-virtual-env-name)))
(perspectives (when (featurep 'perspective)
(string-join (let ((persp-show-modestring t))
(persp-update-modestring)
(persp-mode-line)))))
(pomidor (when (and (featurep 'pomidor) pomidor-timer)
(format " [%s]"
(string-limit
(let* ((break (pomidor--break-duration (car (last pomidor-global-state))))
(overwork (pomidor--overwork-duration (car (last pomidor-global-state))))
(work (pomidor--work-duration (car (last pomidor-global-state)))))
(cond
(break (propertize (pomidor--format-duration break) 'face 'pomidor-break-face))
(overwork (propertize (pomidor--format-duration overwork) 'face 'pomidor-overwork-face))
(work (propertize (pomidor--format-duration work) 'face 'pomidor-work-face)))) 5))))
(time (propertize (format-time-string " %H:%M %a %d/%m" (current-time)) 'face 'font-lock-comment-face))
(info (string-join (list org-clock-string pyvenv perspectives pomidor time) " "))
(message (if fk/minibuffer-modeline--message fk/minibuffer-modeline--message ""))
(right-padding 2)
(left-padding (make-string (max 0 (- (frame-width) (length message) (length info) right-padding)) ?\ )))
(setq fk/minibuffer-modeline--message nil)
(with-current-buffer " *Minibuf-0*"
(erase-buffer)
(insert message left-padding info))))
(add-hook 'post-command-hook 'fk/minibuffer-modeline-update)
(setq fk/minibuffer-modeline-timer (run-at-time nil 10 'fk/minibuffer-modeline-update))
;;; Advices to not lose minibuffer modeline info ; FIXME: breaks when using isearch
;; (defun fk/minibuffer-modeline-message (func &rest args)
;; "Show message and modeline info at the same time."
;; (unless inhibit-message
;; (setq fk/minibuffer-modeline--message (apply func args))
;; (fk/minibuffer-modeline-update)
;; fk/minibuffer-modeline--message))
;; (advice-add 'message :around 'fk/minibuffer-modeline-message)
Anzu
(use-package anzu
:hook
(dashboard-after-initialize . global-anzu-mode))
Page Break Lines
(use-package page-break-lines
:custom-face
(page-break-lines ((t (:inherit font-lock-comment-face :foreground ,fk/light-color1 :width expanded))))
:hook
(dashboard-after-initialize . global-page-break-lines-mode)
:config
(add-to-list 'page-break-lines-modes 'c-mode)
(defun fk/insert-page-break-line ()
"Insert a page break line character ''."
(interactive)
(insert "")))
(global-set-key (kbd "C-,") 'quoted-insert)
Trailing White Space-
Highlight TODOs
(use-package hl-todo
:custom
;; Better hl-todo colors, taken from spacemacs
(hl-todo-keyword-faces '(("TODO" . "#dc752f")
("NEXT" . "#dc752f")
("THEM" . "#2d9574")
("PROG" . "#4f97d7")
("OKAY" . "#4f97d7")
("DONT" . "#f2241f")
("FAIL" . "#f2241f")
("DONE" . "#86dc2f")
("NOTE" . "#b1951d")
("KLUDGE" . "#b1951d")
("HACK" . "#b1951d")
("TEMP" . "#b1951d")
("QUESTION" . "#b1951d")
("HOLD" . "#dc752f")
("FIXME" . "#dc752f")
("XXX+" . "#dc752f")))
:hook
(dashboard-after-initialize . global-hl-todo-mode))
Beacon
(use-package beacon
:custom
;; beacon-mode doesn't work properly with same color as cursor
(beacon-color (doom-darken fk/cursor-color 0.001))
;; (beacon-blink-when-point-moves-vertically 10)
(beacon-dont-blink-major-modes '(dashboard-mode minibuff))
:config
(defun fk/beacon-blink ()
"`beacon-blink' with `beacon-dont-blink-major-modes' control."
(interactive)
(unless (seq-find 'derived-mode-p beacon-dont-blink-major-modes)
(beacon-blink)))
;; `beacon-blink' manually instead of activating `beacon-mode' to not
;; calculate every time on post-command-hook if should beacon blink
;; TODO: create a global minor mode with this: `fk/manual-beacon-mode'
(dolist (command '(other-window
winum-select-window-by-number
scroll-up-command
scroll-down-command
recenter-top-bottom
;; fk/smooth-scroll-up
;; fk/smooth-scroll-down
;; fk/smooth-recenter-top-bottom
move-to-window-line-top-bottom
ace-select-window
ace-swap-window
aw-flip-window
avy-goto-word-or-subword-1
avy-pop-mark))
(eval `(defadvice ,command (after blink activate)
(fk/beacon-blink))))
(dolist (hook '(find-file-hook
xref-after-jump-hook
xref-after-return-hook
persp-switch-hook))
(add-hook hook 'fk/beacon-blink)))
All The Icons
;; Prerequisite for a few packages (e.g. treemacs, all-the-icons-dired)
;; "M-x all-the-icons-install-fonts" to install fonts at the first time.
(use-package all-the-icons)
Highlight Indent Guides
(use-package highlight-indent-guides
:custom
(highlight-indent-guides-method 'character)
(highlight-indent-guides-responsive 'top)
(highlight-indent-guides-auto-enabled nil)
:custom-face ; NOTE: The character does not work with "RobotoMono Nerd Font"
(highlight-indent-guides-character-face ((t (:family "Source Code Pro" :foreground ,fk/light-color7))))
(highlight-indent-guides-top-character-face ((t (:family "Source Code Pro" :foreground ,fk/light-color5))))
:hook
(prog-mode . highlight-indent-guides-mode))
Shackle
(use-package shackle
:custom
(shackle-default-size 0.4)
(shackle-rules '(("\\`\\*helm.*?\\*\\'" :regexp t :align t) ; I use helm-posframe now, this is unnecessary but i want to keep just in case
("\\`\\*helpful.*?\\*\\'" :regexp t :align t)
("\\`\\*Go Translate*?\\*\\'" :regexp t :align t)
(help-mode :align t :select t)
(org-agenda-mode :align t :select t)))
:hook
(dashboard-after-initialize . shackle-mode))
Zoom
;; TODO: Add a function to set window width to fill column width
;; according to current major mode
(use-package zoom
:commands zoom-mode
:preface
(defvar fk/zoom-default-size '(120 . 40))
:custom
(zoom-size fk/zoom-default-size)
:bind*
(("C-M-=" . fk/enlarge-window)
("C-M--" . fk/shrink-window)
("C-M-0" . balance-windows))
:config
;; TODO: handle when zoom-mode active
(defun fk/adjust-window-width (percentage)
(if (and olivetti-mode (= (count-windows) 1))
(if (> percentage 1.0) (olivetti-expand) (olivetti-shrink))
(let* ((new-width (round (* (window-width) percentage)))
(zoom-size (cons new-width (cdr zoom-size))))
(if (> percentage 1.0) ; TODO: fk/smooth-zoom do not shrink
(fk/smooth-zoom)
(zoom)))))
(defun fk/enlarge-window ()
(interactive)
(fk/adjust-window-width 1.1))
(defun fk/shrink-window ()
(interactive)
(fk/adjust-window-width 0.9))
(defvar fk/smooth-zoom-steps 10)
(defvar fk/smooth-zoom-period 0.01)
(defun fk/floor (number)
"Floor by absolute value."
(if (< number 0)
(ceiling number)
(floor number)))
(defun fk/smooth-zoom ()
"Smooth (animated) version of `zoom'."
(interactive)
(cancel-function-timers 'fk/smooth-zoom--resize)
(setq fk/smooth-zoom-sizes '())
(setq fk/smooth-zoom-window (get-buffer-window))
(let* ((current-size (cons (window-width) (window-height)))
(desired-size zoom-size)
(distances (cons (- (car desired-size) (car current-size))
(- (cdr desired-size) (cdr current-size))))
(step-distance (cons (fk/floor (/ (car distances) (float fk/smooth-zoom-steps)))
(fk/floor (/ (cdr distances) (float fk/smooth-zoom-steps))))))
(dotimes (i fk/smooth-zoom-steps)
(let* ((zoom-size (if (< i (1- fk/smooth-zoom-steps))
(cons (+ (car step-distance) (car current-size))
(+ (cdr step-distance) (cdr current-size)))
desired-size))
(time (concat (number-to-string (round (* i fk/smooth-zoom-period 1000))) " millisec")))
(setq current-size zoom-size)
(add-to-list 'fk/smooth-zoom-sizes current-size t)
(run-at-time time nil 'fk/smooth-zoom--resize)))))
(defun fk/smooth-zoom--resize ()
(with-selected-window fk/smooth-zoom-window
(let ((zoom-size (pop fk/smooth-zoom-sizes)))
(zoom--resize)))))
Emacs Dashboard
(use-package dashboard
:custom
;; Source for logo: https://github.com/tecosaur/emacs-config/blob/master/config.org#splash-screen
(dashboard-startup-banner (fk/expand-static-file-name "logos/emacs-e-medium.png"))
;; Do not show package count, it is meaningless because of lazy loading.
(dashboard-banner-logo-title "Welcome to Emacs! \n")
(dashboard-init-info (format " Emacs started in %s\n\n" (fk/time-since-startup)))
(dashboard-set-heading-icons t)
(dashboard-set-file-icons t)
(dashboard-center-content t)
(dashboard-items '((agenda . 0) ; I override the insert-agenda function
(todo-items . 0) ; Custom section
(inbox-entries . 0))) ; Custom section
:custom-face
(dashboard-heading ((t (:inherit font-lock-keyword-face :height 1.2))))
(dashboard-items-face ((t (:weight normal))))
(dashboard-banner-logo-title ((t (:family "AV Qest" :height 3.0 :weight bold :foreground "#8583C7"))))
:hook
(dashboard-mode . (lambda () (setq-local cursor-type nil)))
:config
(dashboard-setup-startup-hook)
;; Run the hooks even if dashboard initialization is skipped
(when (> (length command-line-args) 1)
(add-hook 'emacs-startup-hook (lambda () (run-hooks 'dashboard-after-initialize-hook))))
(defun fk/home ()
"Switch to home (dashboard) buffer."
(interactive)
(if (get-buffer dashboard-buffer-name)
(switch-to-buffer dashboard-buffer-name)
(dashboard-refresh-buffer)))
(defun fk/dashboard-get-section (expression)
"Get expression output from Emacs daemon. Faster than reading it
in normal way if required libraries are already loaded in
daemon."
(let* ((output-buffer (generate-new-buffer "*dashboard-temp*"))
(exit-status (call-process "emacsclient" nil output-buffer nil
"--eval" expression)))
(if (zerop exit-status)
(let* ((output (with-current-buffer output-buffer
(buffer-substring-no-properties (point-min) (point-max))))
(clean-output (string-trim (string-replace "#<marker" "<marker" output)))
(propertized-output (car (read-from-string clean-output))))
(kill-buffer output-buffer)
propertized-output)
"Emacs server (daemon) is not running, Section couldn't loaded.")))
;; TODO: convert these string codes to normal code, investigate how emacs-async do that
(defun fk/dashboard-get-agenda ()
"Get a copy of the agenda buffer from Emacs daemon."
(fk/dashboard-get-section
"(progn
(setq org-agenda-span 2)
(org-agenda-list)
(read-only-mode -1)
(goto-char (point-min))
(kill-line)
(buffer-string))"))
(defun dashboard-insert-agenda (&rest _)
"Insert a copy of org-agenda buffer."
(dashboard-insert-heading "Agenda for today:")
(insert (fk/dashboard-get-agenda)))
(defun fk/dashboard-get-inbox-entries ()
;; TODO: appearance is not consistent, seems like there is some sort of caching
"Get inbox entry list from Emacs daemon."
(fk/dashboard-get-section
"(let* ((file (expand-file-name \"inbox.org\" org-directory))
(file-buffer (find-file-noselect file))
(file-content (with-current-buffer file-buffer (buffer-string)))
(temp-buffer (generate-new-buffer \"*dashboard-temp*\"))
(bullet (propertize \"⁖\" 'face 'org-level-1)))
(with-current-buffer temp-buffer
(kill-buffer file-buffer)
(org-mode)
(insert file-content)
(delete-non-matching-lines \"^*\" (point-min) (point-max))
(string-replace \"*\" (format \" %s\" bullet) (string-replace \"**\" (format \" %s\" bullet) (buffer-string)))))"))
(defun fk/dashboard-insert-inbox-entries (&rest _)
"Insert inbox entries items."
(insert (all-the-icons-octicon "pin" :height 1.2 :v-adjust 0.02 :face 'dashboard-heading)
(propertize " Inbox Entries:\n" 'face 'dashboard-heading 'line-spacing 10)
(fk/dashboard-get-inbox-entries)))
(defun fk/dashboard-get-todo-items ()
"Get high priority todo items from Emacs daemon."
(fk/dashboard-get-section
"(let* ((file (expand-file-name \"todos.org\" org-directory))
(file-buffer (find-file-noselect file))
(file-content (with-current-buffer file-buffer (buffer-string)))
(temp-buffer (generate-new-buffer \"*dashboard-temp*\"))
(bullet (propertize \"⁖\" 'face 'org-level-1)))
(with-current-buffer temp-buffer
(kill-buffer file-buffer)
(org-mode)
(insert file-content)
(delete-matching-lines (regexp-quote \"[#B]\") (point-min) (point-max))
(delete-matching-lines (regexp-quote \"[#C]\") (point-min) (point-max))
(delete-non-matching-lines \"^*\" (point-min) (point-max))
(string-replace \"*\" (format \" %s\" bullet) (string-replace \"**\" (format \" %s\" bullet) (buffer-string)))))"))
(defun fk/dashboard-insert-todo-items (&rest _)
"Insert high priority todo items."
(insert (all-the-icons-octicon "checklist" :height 1.2 :v-adjust 0.02 :face 'dashboard-heading)
(propertize " TODOs:\n" 'face 'dashboard-heading 'line-spacing 10)
(fk/dashboard-get-todo-items)))
(add-to-list 'dashboard-item-generators '(inbox-entries . fk/dashboard-insert-inbox-entries))
(add-to-list 'dashboard-item-generators '(todo-items . fk/dashboard-insert-todo-items))
;; Colorize org entries even if org.el or org-agenda.el hasn't loaded.
;; Note: defining faces is enough, color values comes from propertized string
(defmacro fk/defface-nil (&rest faces)
"Macro for defining nil faces. Instead of:
`(defface org-level-1 nil nil)'"
`(progn ,@(cl-loop for face in faces
collect `(defface ,face nil nil))))
(fk/defface-nil
org-agenda-calendar-event
org-agenda-current-time
org-agenda-date
org-agenda-date-today
org-agenda-date-weekend
org-agenda-date-weekend
org-agenda-date-weekend-today
org-agenda-structure
org-checkbox-statistics-todo
org-habit-alert-face
org-habit-clear-future-face
org-habit-overdue-future-face
org-habit-ready-face
org-hide
org-imminent-deadline
org-level-1
org-level-2
org-link
org-scheduled
org-scheduled-today
org-super-agenda-header
org-tag
org-time-grid
org-upcoming-deadline
org-upcoming-distant-deadline
org-warning))
Stripe Buffer
(use-package stripe-buffer
:custom-face
(stripe-highlight ((t (:background ,fk/light-color7))))
:config
;; hl-line (higher priority stripes) fix:
(defadvice sb/redraw-region (after stripe-set-priority activate)
(when (or stripe-buffer-mode stripe-table-mode)
(dolist (overlay sb/overlays)
(overlay-put overlay 'priority -100))))
;; :hook
;; (org-mode . turn-on-stripe-table-mode)
)
Fill Column Indicator
(use-package display-fill-column-indicator
:straight (:type built-in)
:custom
(display-fill-column-indicator-character ?│)
:custom-face ; NOTE: The character above does not work with "Roboto Mono"
(fill-column-indicator ((t (:family "Source Code Pro" :foreground ,fk/light-color7))))
:hook
(prog-mode . display-fill-column-indicator-mode))
Line Numbers
(use-package display-line-numbers
:straight (:type built-in)
:custom-face
(line-number ((t (:foreground ,fk/light-color2))))
(line-number-current-line ((t (:foreground ,fk/light-color))))
:hook
(prog-mode . display-line-numbers-mode))
Dired Icons-
Rainbow Delimiters-
Helm Icons-
Symbol Overlay-
Olivetti
(use-package olivetti
:straight (:host github :repo "KaratasFurkan/olivetti")
:preface
;; Body width
(setq fk/olivetti-body-width-default 120)
(setq fk/olivetti-body-width-large 180)
(setq olivetti-body-width fk/olivetti-body-width-default)
;; Borders
(setq olivetti-enable-borders t)
(setq fk/default-olivetti-borders-color fk/dark-color2)
(setq fk/darker-olivetti-borders-color fk/dark-color9)
:custom
(olivetti-enable-visual-line-mode nil)
(olivetti-window-local t)
:custom-face
(olivetti-borders-face ((t (:background ,fk/default-olivetti-borders-color))))
:bind*
(("C-1" . fk/smart-C-x-1)
:map windows
("c" . olivetti-mode)
:map windows
:prefix-map olivetti
:prefix "o"
("o" . global-olivetti-mode)
("e" . olivetti-expand)
("s" . olivetti-shrink))
:hook
(dashboard-after-initialize . global-olivetti-mode)
:config
(setq olivetti-excluded-buffer-regexps
`(,@olivetti-excluded-buffer-regexps
"\\`\\*vterm" "*fireplace*"))
(defun fk/smart-C-x-1 ()
(interactive)
(if (= (count-windows) 1)
(if (and global-olivetti-mode
(= olivetti-body-width fk/olivetti-body-width-default))
(progn
(setq olivetti-body-width fk/olivetti-body-width-large)
(olivetti-mode))
(call-interactively 'global-olivetti-mode)
(setq olivetti-body-width fk/olivetti-body-width-default))
(delete-other-windows))))
Emojify-
Tree Sitter
(use-package tree-sitter
:commands fk/tree-sitter-hl-mode
:config
(defun fk/tree-sitter-hl-mode ()
"Require `tree-sitter-langs' + Activate `tree-sitter-hl-mode'."
(interactive)
(require 'tree-sitter-langs)
(call-interactively 'tree-sitter-hl-mode))
(with-eval-after-load 'expand-region
(defun tree-sitter-mark-bigger-node ()
(interactive)
(let* ((p (point))
(m (or (mark) p))
(beg (min p m))
(end (max p m))
(root (ts-root-node tree-sitter-tree))
(node (ts-get-descendant-for-position-range root beg end))
(node-beg (ts-node-start-position node))
(node-end (ts-node-end-position node)))
;; Node fits the region exactly. Try its parent node instead.
(when (and (= beg node-beg) (= end node-end))
(when-let ((node (ts-get-parent node)))
(setq node-beg (ts-node-start-position node)
node-end (ts-node-end-position node))))
(set-mark node-end)
(goto-char node-beg)))
(setq er/try-expand-list (append er/try-expand-list
'(tree-sitter-mark-bigger-node)))))
(use-package tree-sitter-langs
:defer t
:config
;; Custom patterns to make it look like in old versions:
;; See: https://github.com/ubolonton/emacs-tree-sitter/issues/153
(tree-sitter-hl-add-patterns 'python
[(assignment left: (identifier) @variable)])
(tree-sitter-hl-add-patterns 'python
[(decorator (call (identifier) @function.special))]))
Visual Fill Column
(use-package visual-fill-column
:commands visual-fill-column-mode
:bind
( :map windows
("v" . visual-fill-column-mode))
:hook
(visual-fill-column-mode . visual-line-mode))
Color Identifiers Mode-
Goggles Mode (Highlight Changes)
(use-package goggles
:straight (:host github :repo "minad/goggles")
:commands goggles-mode
:custom
(goggles-pulse-delay 0.1))
Hide/Show
(use-package hideshow
:straight (:type built-in)
:defer nil
:custom
(hs-isearch-open t)
:bind
( :map hs-minor-mode-map
("TAB" . fk/hs-smart-tab)
("<tab>" . fk/hs-smart-tab)
("<backtab>" . hs-toggle-hiding))
:config
(defun fk/hs-smart-tab ()
"Pretend like `hs-toggle-hiding' if point is on a hiding block."
(interactive)
(if (save-excursion
(move-beginning-of-line 1)
(hs-looking-at-block-start-p))
(hs-show-block)
(indent-for-tab-command)))
(defun fk/hide-second-level-blocks ()
"Hide second level blocks (mostly class methods in python) in
current buffer."
(interactive)
(hs-minor-mode)
(save-excursion
(goto-char (point-min))
(hs-hide-level 2))))
Topspace (Upper margin)
(use-package topspace
:bind
( :map windows
("m" . topspace-recenter-buffer)))
Redacted (Hide text)
(use-package redacted
:commands redacted-mode
:hook
(redacted-mode . (lambda () (read-only-mode (if redacted-mode 1 -1)))))
Posframe
(use-package posframe
:defer t
:custom
(setq posframe-mouse-banish '(0 . 5000))) ; Bottom-left corner to prevent EAF stealing focus
Completion
Better Defaults
(add-to-list 'completion-styles 'flex t)
(setq completion-ignore-case t)
(setq read-buffer-completion-ignore-case t)
(setq read-file-name-completion-ignore-case t)
Which Key (Keybinding Completion)
(use-package which-key-posframe
:custom
(which-key-idle-delay 2)
(which-key-idle-secondary-delay 0)
(which-key-posframe-border-width 2)
(which-key-posframe-parameters '((left-fringe . 5) (right-fringe . 5)))
:custom-face
(which-key-posframe ((t (:background ,fk/dark-color))))
(which-key-posframe-border ((t (:background ,fk/light-color))))
:hook
(dashboard-after-initialize . which-key-posframe-mode)
(dashboard-after-initialize . which-key-mode))
Helm (General Completion & Selection)
Helm
(use-package helm
:custom
(helm-M-x-always-save-history t)
(helm-display-function 'pop-to-buffer)
(savehist-additional-variables '(extended-command-history))
(history-delete-duplicates t)
(helm-command-prefix-key nil)
;; Just move the selected text to the top of kill-ring, do not insert the text
(helm-kill-ring-actions '(("Copy marked" . (lambda (_str) (kill-new _str)))
("Delete marked" . helm-kill-ring-action-delete)))
:custom-face
(helm-non-file-buffer ((t (:inherit font-lock-comment-face))))
(helm-ff-file-extension ((t (:inherit default))))
(helm-buffer-file ((t (:inherit default))))
:bind
(("M-x" . helm-M-x)
("C-x C-f" . helm-find-files)
("C-x C-b" . helm-buffers-list)
("C-x b" . helm-buffers-list)
("C-x C-r" . helm-recentf)
("C-x C-i" . fk/helm-imenu)
("C-x C-j" . fk/helm-imenu)
("M-y" . fk/yank-pop-or-helm-show-kill-ring)
:map helm-map
("TAB" . helm-execute-persistent-action)
("<tab>" . helm-execute-persistent-action)
("C-z" . helm-select-action)
("C-w" . backward-kill-word) ; Fix C-w
:map files
("f" . helm-find-files)
("r" . helm-recentf)
("b" . helm-bookmarks)
:map buffers
("b" . helm-buffers-list)
:map help-map
("a" . helm-apropos))
:hook
(dashboard-after-initialize . helm-mode)
(helm-mode . savehist-mode)
(helm-major-mode . fk/darken-background)
:config
(with-eval-after-load 'helm-buffers
(dolist (regexp '("\\*epc con" "\\*helm" "\\*EGLOT" "\\*straight" "\\*Flymake"
"\\*eldoc" "\\*Compile-Log" "\\*xref" "\\*company"
"\\*aw-posframe" "\\*Warnings" "\\*Backtrace" "\\*helpful"
"\\*Messages" "\\*dashboard"))
(add-to-list 'helm-boring-buffer-regexp-list regexp))
(bind-keys
:map helm-buffer-map
("M-d" . helm-buffer-run-kill-buffers)
("C-M-d" . helm-buffer-run-kill-persistent)))
;; "Waiting for process to die...done" fix.
;; Source: https://github.com/bbatsov/helm-projectile/issues/136#issuecomment-688444955
(defun fk/helm--collect-matches (orig-fun src-list &rest args)
(let ((matches
(cl-loop for src in src-list
collect (helm-compute-matches src))))
(unless (eq matches t) matches)))
(advice-add 'helm--collect-matches :around 'fk/helm--collect-matches)
(require 'helm-imenu) ; Fixes buggy helm-imenu at first usage
(defun fk/helm-imenu ()
"helm-imenu without initializion (preselect)."
(interactive)
(unless helm-source-imenu
(setq helm-source-imenu
(helm-make-source "Imenu" 'helm-imenu-source
:fuzzy-match helm-imenu-fuzzy-match)))
(let* ((imenu-auto-rescan t)
(helm-highlight-matches-around-point-max-lines 'never))
(helm :sources 'helm-source-imenu
:default ""
:preselect ""
:buffer "*helm imenu*")))
(add-hook 'imenu-after-jump-hook (lambda ()
(when (derived-mode-p 'outline-mode)
(show-subtree))))
(defun fk/yank-pop-or-helm-show-kill-ring ()
"If called after a yank, call `yank-pop'. Otherwise, call
`helm-show-kill-ring'."
(interactive)
(if (eq last-command 'yank)
(if (eq major-mode 'vterm-mode)
(vterm-yank-pop)
(yank-pop))
(helm-show-kill-ring))))
Helm Projectile-
Helm Ag-
Helm Xref-
Helm Swoop-
Helm Descbinds
(use-package helm-descbinds
:commands helm-descbinds)
Helm Icons-
Helm Posframe
(use-package helm-posframe
:straight (:host github :repo "KaratasFurkan/helm-posframe")
:after helm
:custom
(helm-display-header-line nil)
(helm-echo-input-in-header-line t)
(helm-posframe-border-width 2)
(helm-posframe-border-color fk/light-color)
(helm-posframe-parameters '((left-fringe . 5) (right-fringe . 5)))
(helm-posframe-size-function 'fk/helm-posframe-get-size)
:config
(helm-posframe-enable)
;; Remove annoying error message that displayed everytime after closing
;; helm-posframe. The message is:
;; Error during redisplay: (run-hook-with-args helm--delete-frame-function
;; #<frame 0x5586330a1f90>) signaled (user-error "No recursive edit is in
;; progress")
(remove-hook 'delete-frame-functions 'helm--delete-frame-function)
;; Fix helm-posframe-display: Wrong type argument: window-live-p, #<window XYZ>
(defun fk/helm-posframe-disable-on-minibuffer (orig-func &rest args)
"Disable `helm-posframe' if it is called from minibuffer."
(let ((helm-display-function 'helm-default-display-buffer))
(apply orig-func args)))
(advice-add 'helm-read-pattern-maybe :around 'fk/helm-posframe-disable-on-minibuffer)
(defun fk/helm-posframe-get-size ()
(list
:min-width (or helm-posframe-min-width
(let ((half-frame-width (round (* (frame-width) 0.5)))
(three-quarter-frame-width (round (* (frame-width) 0.75))))
(if (> half-frame-width 100)
half-frame-width
three-quarter-frame-width)))
:min-height (or helm-posframe-min-height
(let ((half-frame-height (round (* (frame-height) 0.5)))
(three-quarter-frame-height (round (* (frame-height) 0.75))))
(if (> half-frame-height 25)
half-frame-height
three-quarter-frame-height))))))
Company (Code & Text Completion)
Company
(use-package company
:custom
(company-idle-delay 0)
(company-minimum-prefix-length 1)
(company-tooltip-align-annotations t)
(company-dabbrev-downcase nil)
(company-dabbrev-other-buffers t) ; search buffers with the same major mode
:bind
( :map company-active-map
("RET" . nil)
([return] . nil)
("C-w" . nil)
("TAB" . company-complete-selection)
("<tab>" . company-complete-selection)
("C-s" . company-complete-selection) ; Mostly to use during yasnippet expansion
("C-n" . company-select-next)
("C-p" . company-select-previous))
:hook
(dashboard-after-initialize . global-company-mode)
:config
(add-to-list 'company-begin-commands 'backward-delete-char-untabify)
;; Show YASnippet snippets in company
(defun fk/company-backend-with-yas (backend)
"Add ':with company-yasnippet' to the given company backend."
(if (and (listp backend) (member 'company-yasnippet backend))
backend
(append (if (consp backend)
backend
(list backend))
'(:with company-yasnippet))))
(defun fk/company-smart-snippets (fn command &optional arg &rest _)
"Do not show yasnippet candidates after dot."
;;Source:
;;https://www.reddit.com/r/emacs/comments/7dnbxl/how_to_temporally_filter_companymode_candidates/
(unless (when (and (equal command 'prefix) (> (point) 0))
(let* ((prefix (company-grab-symbol))
(point-before-prefix (if (> (- (point) (length prefix) 1) 0)
(- (point) (length prefix) 1)
1))
(char (buffer-substring-no-properties point-before-prefix (1+ point-before-prefix))))
(string= char ".")))
(funcall fn command arg)))
;; TODO: maybe show snippets at first?
(defun fk/company-enable-snippets ()
"Enable snippet suggestions in company by adding ':with
company-yasnippet' to all company backends."
(interactive)
(setq company-backends (mapcar 'fk/company-backend-with-yas company-backends))
(advice-add 'company-yasnippet :around 'fk/company-smart-snippets))
(fk/company-enable-snippets))
Company Box
(use-package company-box
:disabled
:straight (:host github :repo "KaratasFurkan/company-box" :branch "consider-icon-right-margin-for-frame")
:custom
;; Disable `single-candidate' and `echo-area' frontends
(company-frontends '(company-box-frontend))
(company-box-show-single-candidate t)
;;(company-box-frame-behavior 'point)
(company-box-icon-right-margin 0.5)
(company-box-backends-colors '((company-yasnippet . (:annotation default))))
;;:hook
;;(company-mode . company-box-mode)
)
(use-package company-posframe
:hook
(company-mode . company-posframe-mode))
Company Statistics/Prescient
(use-package prescient
:hook (dashboard-after-initialize . prescient-persist-mode))
(use-package company-prescient
:after company
:config (company-prescient-mode))
;; It turns out company-prescient could not be disabled locally, lets go back to
;; company-statistics
;; (use-package company-statistics
;; :hook (global-company-mode . company-statistics-mode))
YASnippet (Snippet Completion)
(use-package yasnippet
;; Expand snippets with `C-j', not with `TAB'. Use `TAB' to always
;; jump to next field, even when company window is active. If there
;; is need to complete company's selection, use `C-s'
;; (`company-complete-selection').
:custom
(yas-indent-line nil)
(yas-inhibit-overlay-modification-protection t)
:custom-face
(yas-field-highlight-face ((t (:inherit region))))
:bind*
(("C-j" . yas-expand)
:map yas-minor-mode-map
("TAB" . nil)
("<tab>" . nil)
:map yas-keymap
("TAB" . (lambda () (interactive) (company-abort) (yas-next-field)))
("<tab>" . (lambda () (interactive) (company-abort) (yas-next-field))))
:hook
(dashboard-after-initialize . yas-global-mode)
(snippet-mode . (lambda () (setq-local require-final-newline nil))))
Emmet- (Snippet Completion for HTML & CSS)
Hydra
(use-package hydra
:defer t
:init
(setq hydra-hint-display-type 'posframe)
(setq hydra-posframe-show-params
`( :internal-border-width 2
:internal-border-color ,fk/light-color
:left-fringe 5
:right-fringe 5
:poshandler posframe-poshandler-frame-bottom-center)))
Search & Navigation
Better Defaults
(global-subword-mode) ; navigationInCamelCase
(setq-default
recenter-positions '(middle 0.15 top 0.85 bottom) ; C-l positions
scroll-conservatively 101) ; Smooth scrolling
;; Scroll less than default
(defvar fk/default-scroll-lines 15)
(defun fk/scroll-up (orig-func &optional arg)
"Scroll up `fk/default-scroll-lines' lines (probably less than default)."
(apply orig-func (list (or arg fk/default-scroll-lines))))
(defun fk/scroll-down (orig-func &optional arg)
"Scroll down `fk/default-scroll-lines' lines (probably less than default)."
(apply orig-func (list (or arg fk/default-scroll-lines))))
(advice-add 'scroll-up :around 'fk/scroll-up)
(advice-add 'scroll-down :around 'fk/scroll-down)
Custom Functions
find-config
(defun fk/find-config ()
"Open config file."
(interactive)
(find-file config-org))
(defun fk/persp-switch-config ()
"Open config file in a dedicated perspective."
(interactive)
(persp-switch "config")
(fk/find-config))
go-scratch
(defun fk/scratch ()
"Switch to scratch buffer."
(interactive)
(switch-to-buffer "*scratch*"))
go-messages
(defun fk/messages ()
"Switch to Messages buffer."
(interactive)
(switch-to-buffer "*Messages*"))
go-home-
split-window-and-switch
(defun fk/split-window-below-and-switch ()
"Split the window below, then switch to the new window."
(interactive)
(split-window-below)
(other-window 1))
(defun fk/split-window-right-and-switch ()
"Split the window right, then switch to the new window."
(interactive)
(split-window-right)
(other-window 1))
generate-random-elisp-scratch
(defun fk/generate-random-elisp-scratch ()
"Create and switch to a temporary scratch buffer with a random name and
`emacs-lisp-mode' activated."
(interactive)
(switch-to-buffer (make-temp-name "scratch-elisp-"))
(emacs-lisp-mode))
generate-random-org-scratch
(defun fk/generate-random-org-scratch ()
"Create and switch to a temporary scratch buffer with a random name and
`org-mode' activated."
(interactive)
(switch-to-buffer (make-temp-name "scratch-org-"))
(org-mode))
generate-random-text-scratch
(defun fk/generate-random-text-scratch ()
"Create and switch to a temporary scratch buffer with a random name and
`text-mode' activated."
(interactive)
(switch-to-buffer (make-temp-name "scratch-text-"))
(text-mode))
convert-string-to-rg-compatible
(setq fk/rg-special-characters '("(" ")" "[" "{" "*"))
(defun fk/convert-string-to-rg-compatible (str)
"Escape special characters defined in `fk/rg-special-characters' of STR."
(seq-reduce (lambda (str char) (string-replace char (concat "\\" char) str))
fk/rg-special-characters
str))
get-selected-text
(defun fk/get-selected-text ()
"Return selected text if region is active, else nil."
(when (region-active-p)
(let ((text (buffer-substring-no-properties (region-beginning) (region-end))))
(deactivate-mark) text)))
find-installed-packages
(defun fk/find-installed-packages ()
"Quick way of opening the source code of an installed package."
(interactive)
(helm-find-files-1 (straight--repos-dir)))
switch-last-buffer
(defun fk/switch-last-buffer ()
"Switch to last buffer."
(interactive)
(switch-to-buffer (other-buffer (current-buffer) nil)))
switch-last-window
(defun fk/switch-last-window ()
(interactive)
(when-let ((last-win (get-mru-window nil nil t)))
(select-window last-win)))
Keybindings
(global-set-key (kbd "<F1>") 'help-command)
(bind-key* (kbd "M-h") 'help-command)
(bind-key* (kbd "M-h M-h") 'help-for-help)
(global-set-key (kbd "C-x c") 'fk/persp-switch-config)
(global-set-key (kbd "C-x C-k") 'kill-current-buffer)
(global-set-key (kbd "M-l") 'move-to-window-line-top-bottom)
(bind-key* "C-q" 'fk/switch-last-window)
(global-set-key (kbd "M-g M-g") (lambda ()
(interactive)
(require 'avy)
(avy-push-mark)
(goto-line 1)))
;; Split & Switch
;; I use `fk/smart-C-x-1' for (kbd "C-1"), see Appearance / Olivetti
(global-set-key (kbd "C-2") 'fk/split-window-below-and-switch)
(global-set-key (kbd "C-3") 'fk/split-window-right-and-switch)
(bind-keys*
:map files
("c" . fk/find-config)
("C" . fk/persp-switch-config)
("p" . fk/find-installed-packages))
(bind-keys*
:map buffers
("s" . fk/scratch)
("r" . fk/generate-random-elisp-scratch)
("o" . fk/generate-random-org-scratch)
("t" . fk/generate-random-text-scratch)
("h" . fk/home)
("m" . fk/messages))
;; (defmacro fk/define-scratch-command (name mode kbd)
;; "Define a scratch generator command and keybinding with the given
;; major-mode. Usage:
;; (fk/define-scratch-command
;; \"my-search\" global-map \"C-M-s\" \"*.el\" \"foo\")"
;; `(progn
;; (defun ,(intern (concat "fk/" name)) ()
;; (interactive)
;; (require 'helm-rg)
;; (fk/helm-rg-dwim-with-glob (or ,glob "") ,query))
;; (define-key ,keymap (kbd ,kbd) ',(intern (concat "fk/" name)))))
;; (defmacro fk/helm-rg-define-search-commands (&rest args)
;; "Define multiple search command at once. Usage:
;; (fk/helm-rg-define-search-commands
;; (\"my-search\" global-map \"C-M-s\" \"*.el\" \"foo\")
;; (\"my-other-search\" global-map \"C-M-S\" \"*.el\" \"bar\"))"
;; `(progn ,@(cl-loop for expr in args
;; collect `(fk/helm-rg-define-search-command ,@expr))))
;; (defun fk/generate-random-elisp-scratch ()
;; "Create and switch to a temporary scratch buffer with a random name and
;; `emacs-lisp-mode' activated."
;; (interactive)
;; (switch-to-buffer (make-temp-name "scratch-elisp-"))
;; (emacs-lisp-mode))
;; (fk/define-scratch-commands
;; ("generate-elisp-scratch" 'emacs-lisp-mode "b")
;; ("generate-org-scratch" 'org-mode "o")
;; ("generate-text-scratch" 'text-mode "t")
;; ("generate-python-scratch" 'python-mode "p")
;; ("generate-json-scratch" 'json-mode "j")
(bind-keys*
:map windows
("b" . balance-windows)
("d" . delete-window)
("k" . kill-buffer-and-window))
Recentf (Recent Files)
(use-package recentf
;; Use with `helm-recentf'
:straight (:type built-in)
:preface
(setq recentf-max-saved-items 200)
:custom
(recentf-exclude `(,(straight--build-dir)
,(locate-user-emacs-file "eln-cache/")
"/usr/share/emacs/"
"/usr/local/share/emacs/"
"emacs/src/"
,(expand-file-name "~/.virtualenvs")
"/usr/lib/node_modules/"
"/tmp/")))
Winner Mode
(use-package winner
:straight (:type built-in)
:bind
(("M-u" . winner-undo)
;; ("M-u" . (lambda () (interactive) (condition-case nil
;; (xref-pop-marker-stack)
;; (error (winner-undo)))))
("M-U" . winner-redo)
:map windows
("u" . winner-undo)
("r" . winner-redo))
:config
(winner-mode))
Ace Window
(use-package ace-window
:straight (:host github :repo "KaratasFurkan/ace-window" :branch "feature/posframe")
:custom
(aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
(aw-ignore-current t)
(aw-dispatch-when-more-than 3) ; TODO: does not work
:custom-face
(aw-leading-char-face ((t (:height 15.0 :foreground "orangered2"))))
:bind
(("M-o" . ace-window)
:map windows
("w" . ace-window)
("D" . ace-delete-window)
("s" . ace-swap-window)
("l" . aw-flip-window))
:config
(ace-window-posframe-mode))
Dependents
Those packages should load after ace-window to not install ace-window from melpa. TODO: fix this
Helm Icons
(use-package helm-icons
:straight (:host github :repo "yyoncho/helm-icons")
:after helm
:config
(treemacs-resize-icons fk/default-icon-size)
(helm-icons-enable))
Winum
(use-package winum
:bind*
("M-1" . winum-select-window-1)
("M-2" . winum-select-window-2)
("M-3" . winum-select-window-3)
("M-4" . winum-select-window-4)
("M-5" . winum-select-window-5)
("M-6" . winum-select-window-6)
("M-7" . winum-select-window-7)
("M-8" . winum-select-window-8)
("M-9" . winum-select-window-9)
:hook
(dashboard-after-initialize . winum-mode))
Mwim (Move Where I Mean)
(use-package mwim
:bind
("C-a" . mwim-beginning-of-code-or-line)
("C-e" . mwim-end-of-line-or-code)
;; NOTE: Functions below are built-in but I think they fit in this context
("M-a" . fk/backward-sexp)
("M-e" . fk/forward-sexp)
:config
(defun fk/forward-sexp (&optional N)
"Call `forward-sexp', fallback `forward-char' on error."
(interactive)
(condition-case nil
(forward-sexp N)
(error (forward-char N))))
(defun fk/backward-sexp ()
"`fk/forward-sexp' with negative argument."
(interactive)
(fk/forward-sexp -1)))
Helm Projectile
(use-package helm-projectile
:custom
(helm-projectile-sources-list '(helm-source-projectile-buffers-list
helm-source-projectile-recentf-list
helm-source-projectile-files-list
helm-source-projectile-projects))
:bind
("C-x f" . helm-projectile)
:hook
(projectile-mode . helm-projectile-on)
:config
(defun fk/projectile-recentf-files-first-five (original-function)
"Return a list of five recently visited files in a project."
(let ((files (funcall original-function)))
(if (> (length files) 5)
(seq-subseq files 0 5)
files)))
(advice-add 'projectile-recentf-files :around 'fk/projectile-recentf-files-first-five))
Helm Ag
Note: I use helm-rg for search (grep) functionality, keep helm-ag only to use its edit feature.
(use-package helm-ag
:custom
(helm-ag-base-command
"rg -S --no-heading --color=never --line-number --max-columns 400")
:bind
(("C-M-S-s" . fk/helm-ag-dwim)
:map helm-ag-map
("C-o" . helm-ag--run-other-window-action))
:config
(defun fk/helm-ag-dwim (&optional query)
"Smarter version of helm-ag.
- Search in project if in a project else search in default (current) directory.
- Start search with selected text if region is active or empty string.
- Escape special characters when searching with selected text."
(interactive)
(let ((root-dir (or (projectile-project-root) default-directory))
(query (or (fk/get-selected-text) query)))
(helm-do-ag root-dir nil query)))
(defun fk/helm-ag-dwim-with-glob (glob &optional query)
(interactive)
(let ((helm-ag-base-command (concat helm-ag-base-command " --glob " glob)))
(fk/helm-ag-dwim query)))
(defun fk/helm-ag--parse-options-and-query (func input)
"Make `helm-ag' input ripgrep compatible."
(apply func (list (fk/convert-string-to-rg-compatible input))))
(advice-add 'helm-ag--parse-options-and-query :around 'fk/helm-ag--parse-options-and-query))
Helm Rg
(use-package helm-rg
:init
;; Load this macro even if helm-rg is not loaded yet
(defmacro fk/helm-rg-define-search-command (name keymap kbd &optional glob query)
"Define search commands and keybindings with predefined glob and query. Usage:
(fk/helm-rg-define-search-command
\"my-search\" global-map \"C-M-s\" \"*.el\" \"foo\")"
`(progn
(defun ,(intern (concat "fk/" name)) ()
(interactive)
(require 'helm-rg)
(fk/helm-rg-dwim-with-glob (or ,glob "") ,query))
(define-key ,keymap (kbd ,kbd) ',(intern (concat "fk/" name)))))
(defmacro fk/helm-rg-define-search-commands (&rest args)
"Define multiple search command at once. Usage:
(fk/helm-rg-define-search-commands
(\"my-search\" global-map \"C-M-s\" \"*.el\" \"foo\")
(\"my-other-search\" global-map \"C-M-S\" \"*.el\" \"bar\"))"
`(progn ,@(cl-loop for expr in args
collect `(fk/helm-rg-define-search-command ,@expr))))
:custom
(helm-rg-default-extra-args '("--max-columns" "400"
"-g" "!{*.min.css,*.min.js,*.svg,*.po}"
"-g" "!migrations/"))
:custom-face
(helm-rg-file-match-face ((t (:foreground nil :inherit font-lock-type-face :weight bold :underline nil :slant italic))))
(helm-rg-line-number-match-face ((t (:foreground nil :underline nil :inherit line-number))))
:bind
(("C-M-s" . fk/helm-rg-dwim)
:map helm-rg-map
("C-c C-e" . fk/helm-rg-switch-helm-ag)
("C-c C-d" . fk/helm-rg-switch-deadgrep))
:config
(defun fk/helm-rg-dwim (&optional query)
"Smarter version of helm-rg.
- Search in project if in a project else search in default (current) directory.
- Start search with selected text if region is active or empty string.
- Escape special characters when searching with selected text."
(interactive)
(let ((helm-rg-default-directory (or (projectile-project-root) default-directory))
(query (or (fk/get-selected-text) query)))
(cl-letf (((symbol-function 'helm-rg--get-thing-at-pt) (lambda () query)))
(call-interactively 'helm-rg))))
(defun fk/helm-rg-dwim-with-glob (glob &optional query)
(interactive)
(let ((helm-rg-default-glob-string glob))
(fk/helm-rg-dwim query)))
;;;; Input normalization
(defvar fk/helm-rg-fuzzy-max-words 6) ; rg returns "Arguments list too long" after this point
(defun fk/helm-input-to-ripgrep-regexp (func input)
"Make `helm-rg' input ripgrep compatible. Escape special
characters and disable fuzzy matching if input has more than
`fk/helm-rg-fuzzy-max-words' words."
(let* ((processed-input (fk/convert-string-to-rg-compatible input))
(word-count (with-temp-buffer
(insert processed-input)
(count-words (point-min) (point-max)))))
(if (> word-count fk/helm-rg-fuzzy-max-words)
(string-replace " " ".*" processed-input) ; simpler fuzzy
(apply func (list processed-input)))))
(advice-add 'helm-rg--helm-pattern-to-ripgrep-regexp :around 'fk/helm-input-to-ripgrep-regexp)
;;;; Appearance
;; Use a simpler header in the helm buffer.
(fset 'helm-rg--header-name
(lambda (_)
(format "Search at %s\nArgs: %s" helm-rg--current-dir (string-join helm-rg--extra-args " "))))
;; Create bigger window for helm-rg
(advice-add 'helm-rg :around
(lambda (orig-func &rest args)
(let ((helm-posframe-min-height (round (* (frame-height) 0.66)))
(helm-candidate-number-limit 99999)) ; show all matching lines. TODO open a PR and make this default.
(apply orig-func args))))
;;;; Switch to another frontend functions
(defun fk/helm-rg-switch-helm-ag ()
"Switch to `helm-ag' to use its edit feature."
(interactive)
(helm-rg--run-after-exit
(require 'helm-ag)
(fk/helm-ag-dwim helm-pattern))
(minibuffer-keyboard-quit))
(defun fk/helm-rg-switch-deadgrep ()
"Switch to `deadgrep' to use its seperated buffer and `before
n line' / `after n line' features."
(interactive)
(helm-rg--run-after-exit
(require 'deadgrep)
(deadgrep helm-pattern))
(minibuffer-keyboard-quit)))
Helm Xref
(use-package xref
:custom
(xref-prompt-for-identifier nil)
:bind
("C-M-j" . xref-find-definitions)
("C-M-k" . xref-pop-marker-stack)
("C-9" . xref-find-definitions)
("C-8" . xref-pop-marker-stack)
("C-M-9" . xref-find-definitions-other-window)
("C-M-r" . xref-find-references))
(use-package helm-xref
:after helm xref)
Dumb Jump
(use-package dumb-jump
:custom
(dumb-jump-aggressive t)
:bind
([remap xref-find-definitions] . fk/smart-jump-go)
([remap xref-pop-marker-stack] . fk/smart-jump-back)
("C-M-S-j" . fk/smart-jump-peek)
:config
(defun fk/smart-jump-go ()
"Fallback `dumb-jump-go' if `xref-find-definitions' cannot find the source."
(interactive)
(condition-case nil
(call-interactively 'xref-find-definitions)
(error (call-interactively 'dumb-jump-go))))
(defun fk/smart-jump-back ()
"Fallback `dumb-jump-back' if xref-pop-marker-stack cannot return back."
(interactive)
(if (string= (frame-parameter (selected-frame) 'name) "*Smart Jump Peek*")
(progn (make-frame-invisible) (delete-frame))
(condition-case nil
(call-interactively 'xref-pop-marker-stack)
(error (call-interactively 'dumb-jump-back)))))
(defun fk/smart-jump-peek ()
"`fk/smart-jump-go' in a new frame.
Source: http://tuhdo.github.io/emacs-frame-peek.html"
(interactive)
(let (summary
doc-frame
x y
;; 1. Find the absolute position of the current beginning of the symbol
;; at point, in pixels.
(abs-pixel-pos (save-excursion
(beginning-of-thing 'symbol)
(window-absolute-pixel-position))))
(setq x (car abs-pixel-pos))
(setq y (+ (cdr abs-pixel-pos) (frame-char-height)))
;; 2. Create a new invisible frame, with the current buffer in it.
(setq doc-frame (make-frame `((name . "*Smart Jump Peek*")
(width . 100)
(visibility . nil)
(height . 20))))
;; 3. Position the new frame right under the beginning of the symbol at point.
(set-frame-position doc-frame x y)
;; 4. Jump to the symbol at point.
(with-selected-frame doc-frame
(fk/smart-jump-go)
(read-only-mode -1)
(recenter-top-bottom 0))
;; 5. Make frame visible again
(make-frame-visible doc-frame)
(x-focus-frame doc-frame))))
Helm Swoop
(use-package helm-swoop
:custom
(helm-swoop-speed-or-color t)
(helm-swoop-min-overlay-length 0)
;;(helm-swoop-use-fuzzy-match t)
:custom-face
(helm-swoop-target-line-face ((t (:background "black" :foreground nil :inverse-video nil :extend t))))
(helm-swoop-target-word-face ((t (:inherit lazy-highlight :foreground nil))))
:bind
(("M-s" . helm-swoop)
:map isearch-mode-map
("M-s" . helm-swoop-from-isearch)
:map helm-swoop-map
("M-s" . helm-multi-swoop-all-from-helm-swoop)
:map helm-swoop-edit-map
("C-c C-c" . helm-swoop--edit-complete)
("C-c C-k" . helm-swoop--edit-cancel))
:config
(with-eval-after-load 'shackle
(setq helm-swoop-split-window-function 'display-buffer))) ; shackle rules doesn't work without this
Deadgrep
(use-package deadgrep
:commands deadgrep
:bind
( :map deadgrep-mode-map
("C-c C-e" . deadgrep-edit-mode)))
Avy
(use-package avy
:custom
(avy-dispatch-alist '((?c . avy-action-copy)
(?y . avy-action-yank)
(?t . avy-action-teleport)))
:bind
("M-j" . avy-goto-word-or-subword-1)
("C-M-u" . avy-pop-mark)
("C-M-i" . (lambda () (interactive) (require 'avy) (avy-push-mark))))
Treemacs
Treemacs
(use-package treemacs
:custom
(treemacs-width 20)
:bind
("M-0" . treemacs-select-window)
:hook
(treemacs-mode . (lambda ()
(face-remap-add-relative 'default :height .85)
(face-remap-add-relative 'mode-line-inactive :background fk/dark-color)
(face-remap-add-relative 'mode-line :background fk/dark-color)
(face-remap-add-relative 'hl-line :background fk/background-color :weight 'bold)
(fk/darken-background)))
:config
(treemacs-project-follow-mode))
Treemacs Projectile
(use-package treemacs-projectile
:after treemacs projectile)
Perspective
(use-package perspective
:preface
(defvar persp-icon (all-the-icons-material "dashboard" :height 0.9 :v-adjust -0.17 :face 'all-the-icons-blue))
(defcustom persp-project-name nil "Should be set as directory local variable.")
:custom
(persp-mode-prefix-key (kbd "M-m p"))
(persp-state-default-file (no-littering-expand-var-file-name "perspective.el"))
(persp-modestring-dividers `(,(format "[%s " persp-icon) "]" " • "))
(persp-show-modestring nil) ; I show this in `fk/minibuffer-modeline-update' manually.
:custom-face
(persp-selected-face ((t (:foreground nil :inherit 'doom-modeline-warning))))
:bind*
( :map persp-mode-map
("C-M-o" . persp-next)
("C-x p" . persp-switch)
("C-x C-p" . persp-switch-quick)
("M-q" . persp-switch-last)
:map perspective-map
("p" . persp-switch)
("k" . persp-kill)
("l" . persp-switch-last)
("q" . persp-switch-quick)
("n" . (lambda () (interactive) (persp-switch (make-temp-name "p-"))))
("R" . fk/perspective-rename-with-project-name))
:hook
(dashboard-after-initialize . persp-mode)
(kill-emacs . persp-state-save)
:config
(with-eval-after-load 'projectile
(defun fk/perspective-rename-with-project-name ()
"Rename current perspective according to current project name."
(interactive)
(when (projectile-project-p)
(let* ((project-name (or persp-project-name (projectile-project-name)))
(ellipsis "…")
(short-name (if (> (length project-name) 10)
(concat (substring project-name 0 9) ellipsis)
project-name))
(name (if (gethash short-name (perspectives-hash))
(concat "2—" short-name)
short-name)))
(persp-rename name))))
(define-minor-mode fk/perspective-auto-rename-mode
"Rename perspectives according to project name automatically."
:global t
(if fk/perspective-auto-rename-mode
(progn
(ignore-errors (fk/perspective-rename-with-project-name))
(add-hook 'projectile-after-switch-project-hook 'fk/perspective-rename-with-project-name))
(remove-hook 'projectile-after-switch-project-hook 'fk/perspective-rename-with-project-name)))
(fk/perspective-auto-rename-mode)))
Dired Sidebar-
IBuffer Sidebar-
Block Nav
(use-package block-nav
:straight (:host github :repo "nixin72/block-nav.el")
:config
;; TODO: DRY
;; (defun fk/block-nav-activate (file keymap)
;; (with-eval-after-load file
;; (define-key keymap (kbd "M-n") 'block-nav-next-block)
;; (define-key keymap (kbd "M-p") 'block-nav-previous-block)))
;; (fk/block-nav-activate 'python 'python-mode-map)
;; (fk/block-nav-activate 'yaml-mode 'yaml-mode-map)
;; (fk/block-nav-activate 'docker-compose-mode 'docker-compose-mode-map)
(with-eval-after-load 'python
(define-key python-mode-map (kbd "M-n") 'block-nav-next-block)
(define-key python-mode-map (kbd "M-p") 'block-nav-previous-block))
(with-eval-after-load 'yaml-mode
(define-key yaml-mode-map (kbd "M-n") 'block-nav-next-block)
(define-key yaml-mode-map (kbd "M-p") 'block-nav-previous-block))
(with-eval-after-load 'docker-compose-mode
(define-key docker-compose-mode-map (kbd "M-n") 'block-nav-next-block)
(define-key docker-compose-mode-map (kbd "M-p") 'block-nav-previous-block))
(with-eval-after-load 'elisp-mode
(define-key emacs-lisp-mode-map (kbd "M-n") 'block-nav-next-block)
(define-key emacs-lisp-mode-map (kbd "M-p") 'block-nav-previous-block)))
Goto Line Preview
(use-package goto-line-preview
:commands goto-line-preview
:bind
([remap goto-line] . goto-line-preview))
God Mode
(use-package god-mode
:preface
(setq god-mode-cursor-color "#FFF8DC")
:bind
(("C-;" . god-mode-all)
:map god-local-mode-map
("j" . avy-goto-word-or-subword-1))
:hook
(god-mode-enabled . (lambda ()
(set-face-attribute 'cursor nil :background god-mode-cursor-color)
;; beacon-mode doesn't work properly with same color as cursor
(setq beacon-color (doom-darken god-mode-cursor-color 0.001))))
(god-mode-disabled . (lambda ()
(set-face-attribute 'cursor nil :background fk/cursor-color)
;; beacon-mode doesn't work properly with same color as cursor
(setq beacon-color (doom-darken fk/cursor-color 0.001)))))
Text Editing
Better Defaults
(delete-selection-mode)
(electric-pair-mode)
(setq-default
fill-column 80
sentence-end-double-space nil
indent-tabs-mode nil ; Use spaces instead of tabs
tab-width 4)
Custom Functions
backward-kill-word-or-region
(defun fk/backward-kill-word-or-region ()
"Calls `kill-region' when a region is active and `backward-kill-word'
otherwise."
(interactive)
(call-interactively (if (region-active-p)
'kill-region
'backward-kill-word)))
newline-below
(defun fk/newline-below ()
"Insert newline below the current line."
(interactive)
(save-excursion (end-of-line) (open-line 1)))
remove-hypens-and-underscores-region
(defun fk/remove-hypens-and-underscores-region (beg end)
"Remove hypens and underscores from region."
(interactive "*r")
(save-excursion
(let* ((raw-str (buffer-substring-no-properties beg end))
(clean-str (string-replace "_" " " (string-replace "-" " " raw-str))))
(delete-region beg end)
(insert clean-str))))
increment-number-at-point
(defun fk/increment-number-at-point ()
"Increment the number at point."
(interactive)
(when (number-at-point)
(skip-chars-backward "0-9")
(replace-match (number-to-string (1+ (string-to-number (match-string 0)))))))
decrement-number-at-point
(defun fk/decrement-number-at-point ()
"Decrement the number at point."
(interactive)
(when (number-at-point)
(skip-chars-backward "0-9")
(replace-match (number-to-string (1- (string-to-number (match-string 0)))))))
Keybindings
(keyboard-translate ?\C-h ?\C-?) ; C-h as DEL, (I use F1 and M-h as `help-command')
(add-hook 'server-after-make-frame-hook (lambda () (keyboard-translate ?\C-h ?\C-?))) ; Fix emacs --daemon
(global-set-key (kbd "C-w") 'fk/backward-kill-word-or-region)
(global-set-key (kbd "C-o") 'fk/newline-below)
(global-set-key (kbd "C-x C-=") 'fk/increment-number-at-point)
(global-set-key (kbd "C-x C--") 'fk/decrement-number-at-point)
(bind-keys*
:map text
("s" . sort-lines)
("r" . fk/remove-hypens-and-underscores-region))
Electric Indent Mode-
Undo Tree
(use-package undo-tree
:custom
(undo-tree-visualizer-diff t)
(undo-tree-enable-undo-in-region t)
:bind
(("C-u" . undo-tree-undo)
("C-S-u" . undo-tree-redo))
:hook
(dashboard-after-initialize . global-undo-tree-mode))
Trailing White Space
(use-package whitespace-cleanup-mode
:custom
(show-trailing-whitespace t) ; not from whitespace-cleanup-mode.el
:custom-face
(trailing-whitespace ((t (:background ,fk/light-color7)))) ; not from whitespace-cleanup-mode.el
:hook
(dashboard-after-initialize . global-whitespace-cleanup-mode)
(after-change-major-mode . (lambda ()
(unless (buffer-file-name)
(setq-local show-trailing-whitespace nil)))))
Case Switching
(put 'upcase-region 'disabled nil)
(put 'downcase-region 'disabled nil)
;; built-in functions
(bind-keys
:map text
("u" . upcase-dwim)
("d" . downcase-dwim)
("c" . capitalize-dwim))
(use-package string-inflection
:bind
( :map text
("t" . string-inflection-all-cycle)
("k" . string-inflection-kebab-case)))
Paren
(use-package paren
:straight (:type built-in)
:custom
(show-paren-when-point-inside-paren t)
:custom-face
(show-paren-match ((t (:background nil :weight bold :foreground "white"))))
:hook
(dashboard-after-initialize . show-paren-mode))
Multiple Cursors
(use-package multiple-cursors
:custom
(mc/always-run-for-all t)
:bind*
(("C-M-n" . mc/mark-next-like-this)
("C-M-p" . mc/mark-previous-like-this)
("C-M-S-n" . mc/skip-to-next-like-this)
("C-M-S-p" . mc/skip-to-previous-like-this)
("C-S-n" . mc/unmark-previous-like-this)
("C-S-p" . mc/unmark-next-like-this)
("C-M-<mouse-1>" . mc/add-cursor-on-click)
("C-x C-n" . mc/insert-numbers)))
Wrap Region
(use-package wrap-region
:hook
(dashboard-after-initialize . wrap-region-global-mode)
:config
(wrap-region-add-wrapper "=" "=" nil 'org-mode)
(wrap-region-add-wrapper "*" "*" nil 'org-mode)
(wrap-region-add-wrapper "_" "_" nil 'org-mode)
(wrap-region-add-wrapper "/" "/" nil 'org-mode)
(wrap-region-add-wrapper "+" "+" nil 'org-mode)
(wrap-region-add-wrapper "~" "~" nil 'org-mode)
(wrap-region-add-wrapper "#" "#" nil 'org-mode)
(wrap-region-add-wrapper "`" "`" nil 'markdown-mode)
(wrap-region-add-wrapper "`" "`" nil 'forge-post-mode)
(wrap-region-add-wrapper "_(" ")" "_" 'python-mode)
(wrap-region-add-wrapper "len(" ")" "l" 'python-mode))
Fill-Unfill Paragraph
(use-package unfill
:bind
( :map text
("f" . unfill-toggle)))
Expand Region
(use-package expand-region
:custom
(expand-region-fast-keys-enabled nil)
(expand-region-subword-enabled t)
:bind*
("C-t" . er/expand-region))
Flyspell Popup
(use-package flyspell-popup
:after flyspell
:custom
(flyspell-popup-correct-delay 1)
:config
(flyspell-popup-auto-correct-mode))
Company Wordfreq
(use-package company-wordfreq
:straight (:host github :repo "johannes-mueller/company-wordfreq.el")
:commands fk/company-wordfreq-mode
:custom
(company-wordfreq-path (concat no-littering-var-directory "wordfreq-dicts"))
(ispell-local-dictionary "english")
:config
(define-minor-mode fk/company-wordfreq-mode
"Suggest words by frequency."
:global nil
(if fk/company-wordfreq-mode
(progn
(setq-local company-backends-backup company-backends)
(setq-local company-transformers-backup company-transformers)
(setq-local company-backends '(company-wordfreq))
(setq-local company-transformers nil))
(setq-local company-backends company-backends-backup)
(setq-local company-transformers company-transformers-backup)))
(defun fk/company-wordfreq-toggle-language (&optional language)
(interactive)
(setq ispell-local-dictionary (or language
(if (string= ispell-local-dictionary "english")
"turkish"
"english")))
(message ispell-local-dictionary)))
Programming
General
Better Defaults
Custom Functions
align-comments
(defun fk/align-comments (beginning end)
"Align comments in region."
(interactive "*r")
(align-regexp beginning end (concat "\\(\\s-*\\)"
(regexp-quote comment-start)) nil 2))
indent-buffer
(defun fk/indent-buffer ()
"Indent buffer."
(interactive)
(indent-region (point-min) (point-max)))
comment-or-uncomment-region
(defun fk/comment-or-uncomment-region ()
"Comment or uncomment region with just a character (e.g. '/'). If a region is
active call comment-or-uncomment-region, otherwise just insert the given char."
(interactive)
(call-interactively (if (region-active-p)
'comment-or-uncomment-region
'self-insert-command)))
Fill Column Indicator-
Line Numbers-
Electric Indent Mode
(use-package electric
:straight (:type built-in)
:bind
( :map prog-mode-map
("M-RET" . electric-indent-just-newline))
:hook
(dashboard-after-initialize . electric-indent-mode))
Comments
(use-package newcomment
:straight (:type built-in)
:custom
(comment-column 0)
(comment-inline-offset 2)
:bind*
( :map comments
("c" . comment-dwim)
("k" . comment-kill)
("l" . comment-line)
("n" . (lambda () (interactive) (next-line) (comment-indent)))
("N" . comment-indent-new-line)
("b" . comment-box)
("a" . fk/align-comments))
:hook
(emacs-lisp-mode . (lambda ()
(setq-local comment-start "; ")
(setq-local comment-column 0))))
YASnippet-
Projectile
(use-package projectile
:custom
(projectile-auto-discover nil)
(projectile-project-search-path (directory-files "~/projects" t "[^.]"))
;; Open magit when switching project
(projectile-switch-project-action
(lambda ()
(let ((magit-display-buffer-function
'magit-display-buffer-same-window-except-diff-v1))
(magit))))
;; Ignore emacs project (source codes)
(projectile-ignored-projects '("~/emacs/"))
;; Do not include straight repos (emacs packages) and emacs directory itself
;; to project list
(projectile-ignored-project-function
(lambda (project-root)
(or (string-prefix-p (expand-file-name user-emacs-directory) project-root)
(string-prefix-p "/usr/lib/node_modules/" project-root))))
(projectile-kill-buffers-filter 'kill-only-files)
:bind*
("C-M-t" . fk/projectile-vterm)
:hook
(dashboard-after-initialize . projectile-mode)
:config
(defun fk/projectile-vterm ()
"Open `vterm' in project root directory."
(interactive)
(let* ((default-directory (or (projectile-project-root) default-directory))
(project-name (projectile-project-name default-directory))
(buffer-name (format "vterm @%s" project-name))
(buffer (get-buffer buffer-name)))
(if (or (not buffer) (eq buffer (current-buffer)))
(vterm buffer-name)
(switch-to-buffer buffer)))))
Flycheck
(use-package flycheck
:custom
(flycheck-check-syntax-automatically '(save mode-enabled))
:bind
( :map errors
("n" . flycheck-next-error)
("p" . flycheck-previous-error)
("l" . flycheck-list-errors)
("v" . flycheck-verify-setup)))
;; Spacemacs' custom fringes
;; :config
;; (define-fringe-bitmap 'fk/flycheck-fringe-indicator
;; (vector #b00000000
;; #b00000000
;; #b00000000
;; #b00000000
;; #b00000000
;; #b00000000
;; #b00000000
;; #b00011100
;; #b00111110
;; #b00111110
;; #b00111110
;; #b00011100
;; #b00000000
;; #b00000000
;; #b00000000
;; #b00000000
;; #b00000000))
;; (flycheck-define-error-level 'error
;; :severity 2
;; :overlay-category 'flycheck-error-overlay
;; :fringe-bitmap 'fk/flycheck-fringe-indicator
;; :error-list-face 'flycheck-error-list-error
;; :fringe-face 'flycheck-fringe-error)
;; (flycheck-define-error-level 'warning
;; :severity 1
;; :overlay-category 'flycheck-warning-overlay
;; :fringe-bitmap 'fk/flycheck-fringe-indicator
;; :error-list-face 'flycheck-error-list-warning
;; :fringe-face 'flycheck-fringe-warning)
;; (flycheck-define-error-level 'info
;; :severity 0
;; :overlay-category 'flycheck-info-overlay
;; :fringe-bitmap 'fk/flycheck-fringe-indicator
;; :error-list-face 'flycheck-error-list-info
;; :fringe-face 'flycheck-fringe-info)
Language Server Protocol
Eglot
Eglot
(use-package eglot
:commands eglot
:init
(setq eglot-stay-out-of '(flymake))
:custom
(eglot-ignored-server-capabilites '(:documentHighlightProvider))
(eglot-autoshutdown t)
:hook
;; (eglot-managed-mode . eldoc-box-hover-mode)
(eglot-managed-mode . fk/company-enable-snippets)
:config
(add-to-list 'eglot-server-programs '(python-mode . ("pyright-langserver" "--stdio")))
(with-eval-after-load 'eglot
(load-library "project")))
Eldoc Box
(use-package eldoc-box
:commands (eldoc-box-hover-mode eldoc-box-hover-at-point-mode)
:custom
(eldoc-box-clear-with-C-g t))
LSP Mode
LSP Mode
(use-package lsp-mode
:commands lsp
:custom
(lsp-auto-guess-root t)
(lsp-keymap-prefix "M-m l")
(lsp-modeline-diagnostics-enable nil)
(lsp-keep-workspace-alive nil)
(lsp-auto-execute-action nil)
(lsp-before-save-edits nil)
(lsp-eldoc-enable-hover nil)
(lsp-diagnostic-package :none)
(lsp-completion-provider :none)
(lsp-file-watch-threshold 1500) ; pyright has more than 1000
(lsp-enable-links nil)
(lsp-headerline-breadcrumb-enable nil)
;; Maybe set in future:
;;(lsp-enable-on-type-formatting nil)
:custom-face
(lsp-face-highlight-read ((t (:underline t :background nil :foreground nil))))
(lsp-face-highlight-write ((t (:underline t :background nil :foreground nil))))
(lsp-face-highlight-textual ((t (:underline t :background nil :foreground nil))))
:hook
(lsp-mode . lsp-enable-which-key-integration))
LSP UI
(use-package lsp-ui
:after lsp-mode
:custom
(lsp-ui-doc-show-with-cursor nil)
(lsp-ui-doc-show-with-mouse nil)
(lsp-ui-doc-position 'at-point)
(lsp-ui-sideline-delay 0.5)
(lsp-ui-peek-always-show t)
(lsp-ui-peek-fontify 'always)
:custom-face
(lsp-ui-peek-highlight ((t (:inherit nil :background nil :foreground nil :weight semi-bold :box (:line-width -1)))))
:bind
( :map lsp-ui-mode-map
([remap xref-find-references] . lsp-ui-peek-find-references)
("C-M-l" . lsp-ui-peek-find-definitions)
("C-c C-d" . lsp-ui-doc-show))
:config
;;;; LSP UI posframe ;;;;
(defun lsp-ui-peek--peek-display (src1 src2)
(-let* ((win-width (frame-width))
(lsp-ui-peek-list-width (/ (frame-width) 2))
(string (-some--> (-zip-fill "" src1 src2)
(--map (lsp-ui-peek--adjust win-width it) it)
(-map-indexed 'lsp-ui-peek--make-line it)
(-concat it (lsp-ui-peek--make-footer))))
)
(setq lsp-ui-peek--buffer (get-buffer-create " *lsp-peek--buffer*"))
(posframe-show lsp-ui-peek--buffer
:string (mapconcat 'identity string "")
:min-width (frame-width)
:poshandler 'posframe-poshandler-frame-center)))
(defun lsp-ui-peek--peek-destroy ()
(when (bufferp lsp-ui-peek--buffer)
(posframe-delete lsp-ui-peek--buffer))
(setq lsp-ui-peek--buffer nil
lsp-ui-peek--last-xref nil)
(set-window-start (get-buffer-window) lsp-ui-peek--win-start))
(advice-add 'lsp-ui-peek--peek-new :override 'lsp-ui-peek--peek-display)
(advice-add 'lsp-ui-peek--peek-hide :override 'lsp-ui-peek--peek-destroy)
;;;; LSP UI posframe ;;;;
)
LSP Pyright-
YASnippet-snippets
(use-package yasnippet-snippets
:straight (:host github :repo "KaratasFurkan/yasnippet-snippets" :branch "furkan")
:after yasnippet)
Rainbow Delimiters
(use-package rainbow-delimiters
:hook (prog-mode . rainbow-delimiters-mode))
Color Identifiers Mode
(use-package color-identifiers-mode
:commands color-identifiers-mode)
Symbol Overlay
(use-package symbol-overlay
:commands (symbol-overlay-mode symbol-overlay-put fk/highlight-occurrences)
:bind
( :map symbol-overlay-mode-map
("C-c C-n" . symbol-overlay-jump-next)
("C-c C-p" . symbol-overlay-jump-prev))
:hook
(emacs-lisp-mode . symbol-overlay-mode)
(python-mode . symbol-overlay-mode)
:config
(defun fk/highlight-occurrences ()
"Put highlight to the occurrences of the symbol at point or the
string in the region. Uses `hi-lock' to highlight,
`symbol-overlay' to generate a random face. To remove highlights,
use `hi-lock-unface-buffer' or disable `hi-lock-mode'."
(interactive)
(let ((str (fk/get-selected-text))
(face (nth (random (length symbol-overlay-faces)) symbol-overlay-faces)))
(if str
(highlight-regexp (regexp-quote str) face)
(hi-lock-face-symbol-at-point))))
(defalias 'fk/highlight-remove (lambda () (interactive) (hi-lock-unface-buffer t)))
(defalias 'fk/highlight-remove-one-by-one 'hi-lock-unface-buffer))
Rainbow Mode
(use-package rainbow-mode
:hook (prog-mode . rainbow-mode))
Bug Reference Mode
;; (use-package bug-reference
;; :straight (:type built-in)
;; ;; In default, this package is auto-activated and sometimes breaks syntax
;; ;; highlighting by putting unrelevant bug-reference highlight and clickable
;; ;; link.
;; ;; E.g. `#112' at `my_color = "#112233"' gets bug-reference highlight.
;; :disabled)
Emacs Lisp
Elisp Slime Nav
(use-package elisp-slime-nav
:bind
( :map emacs-lisp-mode-map
("M-." . elisp-slime-nav-find-elisp-thing-at-point)))
Aggressive Indent
;; TODO: try in other languages (html, css, js, c)
(use-package aggressive-indent
:straight ( :host github
:repo "KaratasFurkan/aggressive-indent-mode"
:branch "146-emacs28-compatible-suppress-messages")
:hook (emacs-lisp-mode . aggressive-indent-mode))
Lisp Data Mode
(use-package lisp-mode
:straight (:type built-in)
:hook
(lisp-data-mode . (lambda ()
;; NOTE: `emacs-lisp-mode' derives from `lisp-data-mode',
;; so make sure that the major-mode is `lisp-data-mode'.
(when (string= major-mode "lisp-data-mode")
(fk/add-local-hook 'before-save-hook
(lambda ()
(align-regexp (point-min) (point-max) "\\(\\s-*\\). (")
(fk/indent-buffer)))))))
Python
Python
(use-package python
:straight (:type built-in)
:init
(add-to-list 'all-the-icons-icon-alist
'("\\.py$" all-the-icons-alltheicon "python" :height 1.1 :face all-the-icons-dblue))
:custom
(python-shell-interpreter "ipython")
(python-shell-interpreter-args "-i --simple-prompt")
(python-indent-guess-indent-offset-verbose nil)
:bind
( :map python-mode-map
("C-c r" . python-indent-shift-right)
("C-c l" . python-indent-shift-left))
:hook
;; With pyls:
;; pip install python-language-server flake8 pyls-black(optional) pyls-isort(optional)
;; With pyright
;; sudo npm install -g pyright && pip install flake8 black(optional)
;; NOTE: these hooks runs in reverse order
(python-mode . (lambda () (setq-local company-prescient-sort-length-enable nil)))
(python-mode . (lambda () (unless (and buffer-file-name (file-in-directory-p buffer-file-name "~/.virtualenvs/"))
(flycheck-mode))))
;; (python-mode . lsp-deferred)
;;(python-mode . (lambda () (fk/add-local-hook 'before-save-hook 'eglot-format-buffer)))
(python-mode . eglot-ensure)
;; importmagic runs ~100mb ipython process per python file, and it does not
;; always find imports, 60%-70% maybe. I stop using this, but still want to keep.
;; (python-mode . importmagic-mode)
(python-mode . fk/activate-pyvenv)
(python-mode . (lambda ()
(when (and (buffer-file-name)
(string=
(car (last (f-split (f-parent (buffer-file-name)))))
"tests"))
(fk/hide-second-level-blocks))))
(python-mode . fk/tree-sitter-hl-mode)
(python-mode . (lambda () (setq-local fill-column 88)))
:config
(defvar python-walrus-operator-regexp ":=")
;; Make walrus operator ":=" more visible
(font-lock-add-keywords
'python-mode
`((,python-walrus-operator-regexp 0 'escape-glyph t))
'set))
Pyvenv
(use-package pyvenv
:after python
:config
;; I show this in `fk/minibuffer-modeline-update' manually.
(setq pyvenv-mode-line-indicator nil)
(defun fk/get-venv-name ()
"Get venv name of current python project."
(when-let* ((root-dir (projectile-project-root))
(venv-file (concat root-dir ".venv"))
(venv-exists (file-exists-p venv-file))
(venv-name (with-temp-buffer
(insert-file-contents venv-file)
(nth 0 (split-string (buffer-string))))))
venv-name))
(defun fk/activate-pyvenv ()
"Activate python environment according to the `project-root/.venv' file."
(interactive)
(when-let ((venv-name (fk/get-venv-name)))
(pyvenv-mode)
(pyvenv-workon venv-name)))
(defun fk/open-venv-dir ()
"Open the directory of installed libraries in `dired'."
(interactive)
(when-let* ((venv-name (fk/get-venv-name))
(venv-dir (expand-file-name venv-name "~/.virtualenvs")))
(dired (car (directory-files-recursively venv-dir "site-packages" t)))))
;; python-mode hook is not enough when more than one project's files are open.
;; It just re-activate pyvenv when a new file is opened, it should re-activate
;; on buffer or perspective switching too. NOTE: restarting lsp server is
;; heavy, so it should be done manually if needed.
(add-hook 'window-configuration-change-hook 'fk/activate-pyvenv))
Import Magic
(use-package importmagic
;; pip install importmagic epc
;;
;; importmagic runs ~100mb ipython process per python file, and it does not
;; always find imports, 60%-70% maybe. I stop using this, but still want to keep.
:commands importmagic-mode)
Black
(use-package blacken
:commands blacken-mode blacken-buffer)
Isort
(use-package py-isort
:commands py-isort-buffer)
LSP Pyright
(use-package lsp-pyright
:after lsp-mode
:custom
(lsp-pyright-auto-import-completions nil)
(lsp-pyright-typechecking-mode "off")
:config
(fk/async-process
"npm outdated -g | grep pyright | wc -l" nil
(lambda (process output)
(pcase output
("0\n" (message "Pyright is up to date."))
("1\n" (message "A pyright update is available."))))))
Django
;; Search functions for Django
(fk/helm-rg-define-search-commands
("django-search-models" django "m" "models.py" "^class ")
("django-search-factories" django "f" "factories.py" "^class ")
("django-search-views" django "v" "views*.py" "^class ")
("django-search-serializers" django "s" "*serializers*.py" "^class ")
("django-search-tests" django "t" "*test*.py" "^class ")
("django-search-settings" django "S" "*/settings/*" "")
("django-search-admins" django "a" "admin.py" "^class admin( ")
("django-search-permissions" django "p" "permissions.py" "^class ")
("django-search-mixins" django "x" "mixins.py" "^class ")
("django-search-urls" django "u" "*.py" "path( "))
;; Utility functions for Django
(defcustom fk/django-test-args "" "Should be set as directory local variable.")
(defun fk/django-get-module ()
"pony-get-module originally."
(let* ((root (projectile-project-root))
(path (file-name-sans-extension (or buffer-file-name (expand-file-name default-directory)))))
(when (string-match (projectile-project-root) path)
(let ((path-to-class (substring path (match-end 0))))
(mapconcat 'identity (split-string path-to-class "/") ".")))))
(defun fk/django-copy-path-of-test-at-point ()
"Add path of the test at point to kill-ring. Returns the path."
(interactive)
(require 'which-func)
(let* ((defuns (seq-subseq (split-string (which-function) "\\.") 0 2))
(class (car defuns))
(func (let ((f (-second-item defuns))) (and f (string-match "^test" f) f)))
(module (fk/django-get-module))
(path (concat module (and module class ".") class (and class func ".") func)))
(kill-new path)))
(defun fk/django-run-test-at-point ()
"Run test at point."
(interactive)
(fk/django-copy-path-of-test-at-point)
(let ((vterm-buffer (save-window-excursion
(fk/projectile-vterm)
(current-buffer))))
(if (window-live-p (get-buffer-window vterm-buffer))
(select-window (get-buffer-window vterm-buffer))
(fk/projectile-vterm)))
(vterm-insert (format "python manage.py test --keepdb %s %s" fk/django-test-args
(substring-no-properties (pop kill-ring)))))
(bind-keys*
:map django
("c" . fk/django-copy-path-of-test-at-point)
("d" . fk/django-run-test-at-point))
;; Highlighting of django template blocks
(defvar django-block-regexp (rx "{%" (zero-or-more space) (zero-or-one "end") "block " (group (zero-or-more (not (any ?\n ?{)))) "%}"))
(defface django-block-keyword-face
'((t (:foreground "tomato" :bold t)))
"Face for django template blocks.")
(defface django-block-name-face
'((t (:foreground "wheat" :bold t)))
"Face for django template blocks.")
(font-lock-add-keywords
'web-mode
`((,django-block-regexp 0 'django-block-keyword-face t)
(,django-block-regexp 1 'django-block-name-face t))
t)
;; Highlighting of django template comments
(defvar django-comment-regexp
(rx "{%" (zero-or-more space) "comment" (zero-or-more space) "%}"
(zero-or-more anything)
"{%" (zero-or-more space) "endcomment" (zero-or-more space) "%}"))
(defface django-comment-face
'((t (:inherit 'font-lock-comment-face)))
"Face for django template comments.")
(font-lock-add-keywords
'web-mode
`((,django-comment-regexp 0 'django-comment-face t))
t)
Jupyter Notebook
(use-package ein
:commands ein:run
:custom-face
(ein:cell-input-area ((t (:background "#21262E"))))
:hook
(ein:ipynb-mode . fk/activate-pyvenv))
Web Mode
TODO: seperate sections (html, css..)
Web Mode (HTML)
(use-package web-mode
:custom
(css-indent-offset 2)
(web-mode-markup-indent-offset 2)
(web-mode-enable-auto-indentation nil)
(web-mode-enable-auto-pairing nil)
(web-mode-engines-alist '(("django" . "\\.html\\'")))
:custom-face
(web-mode-block-string-face ((t (:inherit font-lock-string-face))))
(web-mode-html-attr-value-face ((t (:inherit font-lock-string-face :foreground nil))))
(web-mode-current-element-highlight-face ((t (:inherit highlight))))
:mode ;; Copied from spacemacs
(("\\.phtml\\'" . web-mode)
("\\.tpl\\.php\\'" . web-mode)
("\\.twig\\'" . web-mode)
("\\.xml\\'" . web-mode)
("\\.html\\'" . web-mode)
("\\.htm\\'" . web-mode)
("\\.[gj]sp\\'" . web-mode)
("\\.as[cp]x?\\'" . web-mode)
("\\.eex\\'" . web-mode)
("\\.erb\\'" . web-mode)
("\\.mustache\\'" . web-mode)
("\\.handlebars\\'" . web-mode)
("\\.hbs\\'" . web-mode)
("\\.eco\\'" . web-mode)
("\\.ejs\\'" . web-mode)
("\\.svelte\\'" . web-mode)
("\\.djhtml\\'" . web-mode)
("\\.mjml\\'" . web-mode))
:hook
(web-mode . web-mode-toggle-current-element-highlight))
Emmet Mode
Emmet Mode
(use-package emmet-mode
:custom
(emmet-move-cursor-between-quotes t)
:custom-face
(emmet-preview-input ((t (:inherit lazy-highlight))))
:bind
( :map emmet-mode-keymap
([remap yas-expand] . emmet-expand-line)
("M-n" . emmet-next-edit-point)
("M-p" . emmet-prev-edit-point)
("C-c p" . emmet-preview-mode))
:hook
;;(rjsx-mode . (lambda () (setq emmet-expand-jsx-className? t)))
(web-mode . emmet-mode)
(css-mode . emmet-mode))
Helm Emmet
(use-package helm-emmet
:after helm emmet)
Company Web
(use-package company-web
:after web-mode
:config
(add-to-list 'company-backends '(company-web-html :with company-yasnippet)))
Json Mode
(use-package json-mode
:mode ("\\.json\\'" . json-mode))
(use-package json-navigator
:commands json-navigator-navigate-region)
Prettier
(use-package prettier-js
:hook
;;(web-mode . prettier-js-mode) ;; breaks django templates
(css-mode . prettier-js-mode)
(json-mode . prettier-js-mode)
(js2-mode . prettier-js-mode))
Auto Rename Tag
(use-package auto-rename-tag
:hook
(web-mode . auto-rename-tag-mode))
JavaScript
JavaScript
(use-package js2-mode
:mode "\\.js\\'"
:custom
(js-indent-level 2)
:hook
(js2-mode . flycheck-mode)
;;(js2-mode . (lambda () (require 'tree-sitter-langs) (tree-sitter-hl-mode)))
(js2-mode . lsp-deferred))
Go
(use-package go-mode
;; install go & go-tools, for arch based linux:
;; sudo pacman -S go go-tools
:mode "\\.go\\'"
:init
(defface golang-blue
'((((background dark)) :foreground "#69D7E4")
(((background light)) :foreground "#69D7E4"))
"Face for golang icon")
(add-to-list 'all-the-icons-icon-alist
'("\\.go$" all-the-icons-fileicon "go" :height 1 :face golang-blue))
:custom
(gofmt-command "goimports")
:hook
(go-mode . flycheck-mode)
(go-mode . lsp-deferred)
(go-mode . (lambda () (require 'tree-sitter-langs) (tree-sitter-hl-mode)))
(go-mode . (lambda () (fk/add-local-hook 'before-save-hook 'gofmt))))
C
(use-package cc-mode
:bind
( :map c-mode-base-map
("C-c C-c" . fk/c-run))
:hook
(c-mode . lsp-deferred)
(c++-mode . lsp-deferred))
(use-package clang-format
:commands clang-format-buffer clang-format-region
:hook
(c-mode . (lambda () (fk/add-local-hook 'before-save-hook 'clang-format-buffer)))
(c++-mode . (lambda () (fk/add-local-hook 'before-save-hook 'clang-format-buffer))))
Lua
(use-package lua-mode
:mode "\\.lua\\'")
Tools
Dired
Dired
(use-package dired
:straight (:type built-in)
:custom
;; ls parameters:
;; -l use a long listing format
;; -A, --almost-all
;; do not list implied . and ..
;; -h, --human-readable
;; with -l and -s, print sizes like 1K 234M 2G etc.
;; -p, --indicator-style=slash
;; append / indicator to directories
(dired-listing-switches "-lAhp --group-directories-first")
(dired-dwim-target t)
(mouse-1-click-follows-link nil)
(wdired-allow-to-change-permissions 'advanced)
:bind
( :map dired-mode-map
("H" . dired-hide-details-mode)
("C-M-u" . dired-up-directory)
("O" . browse-url-of-dired-file) ; open with associated app
("<mouse-1>" . fk/dired-left-click) ; left click
("<mouse-2>" . dired-up-directory) ; middle click
("<mouse-3>" . (lambda (event) (interactive "e") ; right click
(mouse-set-point event)
(dired-subtree-toggle)))
("RET" . fk/dired-smart-open)
("C-c C-e" . wdired-change-to-wdired-mode)
("f". fk/dired-open-systems-file-manager))
:hook
(dired-mode . dired-hide-details-mode)
;; Fix `save-place' in dired when opening from bookmarks (mostly from dashboard)
(bookmark-after-jump . save-place-dired-hook)
(dired-after-readin . (lambda () (when (string= (dired-current-directory) (concat (getenv "HOME") "/"))
(dired-omit-mode))))
:config
(defun fk/dired-left-click (event)
"When file is a directory, open directory in dired. Otherwise, open file
with associated application."
(interactive "e")
(mouse-set-point event)
(let ((file (dired-get-file-for-visit)))
(if (file-directory-p file)
(dired-mouse-find-file event)
(browse-url-of-dired-file))))
;; TODO: change this to "open video (maybe some other types too) files with
;; associated apps".
(advice-add 'browse-url :override 'browse-url-xdg-open) ; I had to add this in emacs28
(defun fk/dired-smart-open ()
"If file size bigger than 50mb, open with associated system application,
else call `dired-find-file'"
(interactive)
(if (> (file-attribute-size (file-attributes (dired-file-name-at-point)))
50000000)
(browse-url-of-dired-file)
(dired-find-file)))
;;:: Dired in single buffer (prevent dired from opening a lot of buffers)
(put 'dired-find-alternate-file 'disabled nil)
(defun fk/dired-up-directory ()
"`dired-up-directory' in same buffer."
(interactive)
(find-alternate-file ".."))
(defun fk/dired-find-file (orig-func &rest args)
"If selected file is a directory, open it in same buffer,
otherwise, open the file in a new buffer."
(let ((file (dired-get-file-for-visit)))
(if (file-directory-p file)
(apply 'dired-find-alternate-file args)
(apply orig-func args))))
(advice-add 'dired-up-directory :override 'fk/dired-up-directory)
(advice-add 'dired-find-file :around 'fk/dired-find-file)
;;;; Dired in single buffer
(defun fk/dired-open-systems-file-manager ()
"Open current directory with the system's default file manager."
(interactive)
(call-process-shell-command (concat "xdg-open " default-directory " &")))
(defun fk/dired-sort-by-extension ()
"Sort files by extension."
(interactive)
(dired-sort-other (concat dired-listing-switches " -X"))))
Dired-X
(use-package dired-x
:straight (:type built-in)
:after dired
:custom
(dired-omit-files "^\\..*$")
:bind
( :map dired-mode-map
("h" . dired-omit-mode)))
Dired Icons
(use-package all-the-icons-dired
:hook (dired-mode . all-the-icons-dired-mode)
:config
(add-to-list 'all-the-icons-icon-alist
'("\\.mkv" all-the-icons-faicon "film"
:face all-the-icons-blue))
(add-to-list 'all-the-icons-icon-alist
'("\\.srt" all-the-icons-octicon "file-text"
:v-adjust 0.0 :face all-the-icons-dcyan))
;; Turn off all-the-icons-dired-mode before wdired-mode
;; TODO: disable icons just before save, not during wdired-mode
(defadvice wdired-change-to-wdired-mode (before turn-off-icons activate)
(all-the-icons-dired-mode -1))
(defadvice wdired-change-to-dired-mode (after turn-on-icons activate)
(all-the-icons-dired-mode 1)))
Dired Subtree
(use-package dired-subtree
:after dired
:custom
(dired-subtree-use-backgrounds nil)
:bind
( :map dired-mode-map
("TAB" . dired-subtree-toggle)
("<tab>" . dired-subtree-toggle))
:config
;; Fix "no icons in subtree" issue.
(defadvice dired-subtree-toggle
(after add-icons activate) (revert-buffer)))
Dired Sidebar
(use-package dired-sidebar
:commands dired-sidebar-toggle-sidebar
:bind*
( :map windows
("t" . dired-sidebar-toggle-sidebar))
:hook
(dired-sidebar-mode . fk/darken-background)
:config
(defun fk/sidebar-toggle ()
"Toggle both `dired-sidebar' and `ibuffer-sidebar'."
(interactive)
(dired-sidebar-toggle-sidebar)
(ibuffer-sidebar-toggle-sidebar)))
IBuffer Sidebar
(use-package ibuffer-sidebar
:commands ibuffer-sidebar-toggle-sidebar
:bind
( :map ibuffer-mode-map
("M-o" . nil)))
Dired Show Readme
(use-package dired-show-readme
:straight (:host gitlab :repo "kisaragi-hiu/dired-show-readme")
:commands dired-show-readme-mode
;; :hook
;; (dired-mode . dired-show-readme-mode)
)
;; Alternative
(use-package dired-auto-readme
:straight (:host github :repo "amno1/dired-auto-readme")
:commands dired-auto-readme-mode)
Dired Posframe
(use-package dired-posframe
:straight (:host github :repo "conao3/dired-posframe.el")
:commands dired-posframe-mode)
Dired Recent
(use-package dired-recent
:bind
( :map files
("d" . dired-recent-open))
:hook
(dashboard-after-initialize . dired-recent-mode))
Dired Git Info-
Org
Org
(use-package org
:straight (:type built-in)
:init
(setq org-directory "~/org") ; This is default already but lets declare it explicitly
(setq org-gtd-files `(,(concat org-directory "/inbox.org")
,(concat org-directory "/todos.org")
,(concat org-directory "/someday.org")))
:custom
(org-confirm-babel-evaluate nil)
(org-ellipsis "↴") ;; ↴, ▼, ▶, ⤵
(org-src-window-setup 'current-window)
(org-startup-indented t)
(org-startup-folded 'content) ; show only headlines (and sub headlines, recursively) at startup
(org-startup-with-inline-images t)
(org-image-actual-width '(400))
(org-hierarchical-todo-statistics nil)
(org-checkbox-hierarchical-statistics nil)
(org-src-preserve-indentation t)
(org-adapt-indentation nil)
(org-tags-column -120)
(org-imenu-depth 20)
(org-hide-emphasis-markers t)
(org-catch-invisible-edits 'show-and-error)
(org-cycle-separator-lines 0) ; Never leave empty lines between headings in collapsed view
;;;; Getting Things Done ;;;;
(org-agenda-files `(,@org-gtd-files ,(concat org-directory "/agenda.org")))
(org-complete-tags-always-offer-all-agenda-tags t)
(org-agenda-start-on-weekday nil)
(org-agenda-current-time-string "────────── now ──────────")
(org-agenda-format-date (lambda (date) (concat "\n" (org-agenda-format-date-aligned date))))
(org-agenda-prefix-format '((agenda . " %i %?-12t% s")
(todo . " %i %-12:c")
(tags . " %i %-12:c")
(search . " %i %-12:c")))
(org-agenda-time-grid '((daily today require-timed remove-match)
(1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000)
"" "················"))
(org-deadline-warning-days 5)
;; (org-display-custom-times t)
;; (org-time-stamp-custom-formats '("<%d/%m/%Y %A>" . "<%d/%m/%Y %A %H:%M>"))
(org-bookmark-names-plist '()) ; Do not create bookmarks
(org-capture-templates '(("i" "Capture to inbox" entry
(file "inbox.org")
"* %?\nCREATED: %U"
:empty-lines 1)))
(org-refile-targets '(("todos.org" :level . 1)
("someday.org" :level . 1)
("archive.org" :level . 1)
("agenda.org" :level . 1)))
(org-priority-default ?A) ; Highest
;; (org-log-done 'time)
(org-fontify-done-headline t)
(org-log-into-drawer t) ; Log TODO state changes into :LOGBOOK: drawer insted of directly adding lines to the subtree
(org-todo-keywords '((sequence "TODO(t)" "WAIT(w)" "HOLD(h)" "STRT(s)" "WIP.(i)" "STAG(a)" "PROD(p)" "|" "DONE(d)")))
(org-todo-keyword-faces
'(("TODO" :foreground "orangered2" :weight bold)
("WAIT" :foreground "goldenrod" :weight bold)
("HOLD" :foreground "#DC752F" :weight bold)
("STRT" :foreground "PaleGreen" :weight bold)
("WIP." :foreground "#86DC2F" :weight bold)
("STAG" :foreground "DarkTurquoise" :weight bold)
("PROD" :foreground "DodgerBlue" :weight bold)))
(org-clock-clocktable-default-properties '(:maxlevel 10))
(org-extend-today-until 4)
(org-clock-mode-line-total 'today)
(org-clock-total-time-cell-format "%s") ; remove styling to copy paste correctly
(org-duration-format '((special . h:mm))) ; to display 30 hours as 30:00 instead of 1d 6:00
;;;; Getting Things Done ;;;;
:custom-face
(org-block ((t (:family ,fk/default-font-family :extend t))))
(org-ellipsis ((t (:foreground nil :inherit org-tag :weight light :height 0.9))))
(org-checkbox ((t (:foreground "white"))))
(org-level-1 ((t (:height 1.3 :weight bold))))
(org-level-2 ((t (:height 1.2 :weight bold))))
(org-level-3 ((t (:height 1.15 :weight bold))))
(org-level-4 ((t (:height 1.1 :weight bold))))
(org-level-5 ((t (:height 1.0 :weight bold))))
(org-level-6 ((t (:height 1.0 :weight bold))))
(org-level-7 ((t (:height 1.0 :weight bold))))
(org-level-8 ((t (:height 1.0 :weight bold))))
(org-drawer ((t (:foreground nil :inherit font-lock-comment-face))))
(org-table ((t (:inherit org-block :family ,fk/default-font-family :foreground ,(face-foreground 'default)))))
(org-document-title ((t (:family "AV Qest" :height 3.0))))
(org-block-begin-line ((t (:foreground ,fk/light-color1 :background ,fk/background-color :extend t))))
(org-document-info-keyword ((t (:foreground ,fk/background-color)))) ; Make #+TITLE: invisible
(org-meta-line ((t (:foreground ,fk/light-color1)))) ; Less distractive
(org-agenda-date ((t (:foreground "#ECBE7B"))))
(org-agenda-date-today ((t (:foreground "LightGoldenrod"))))
(org-agenda-current-time ((t (:foreground "LightGoldenrod"))))
(org-agenda-calendar-event ((t (:weight bold))))
:bind
( :map org
("a" . fk/org-agenda-posframe)
("f" . (lambda () (interactive) (helm-find-files-1 (concat org-directory "/"))))
("c" . (lambda () (interactive) (org-capture :keys "i")))
;; ("t" . fk/org-babel-tangle-block)
("d" . (lambda () (interactive) (org-todo "DONE")))
:map org-mode-map
("C-c C-e" . org-edit-special)
("M-n" . org-next-visible-heading)
("M-p" . org-previous-visible-heading)
("C-x C-1" . outline-hide-other)
("C-c C-r" . org-refile-hydra/body)
("C-c C-a" . fk/org-refile-done) ; "a" for archive
("C-c C-t" . fk/org-refile-trash)
("C-c t" . org-todo)
("C-c C-p" . org-priority-down)
("C-M-j" . org-open-at-point)
("C-c r" . org-shiftright)
("C-c l" . org-shiftleft)
("C-c u" . org-shiftup)
("C-c d" . org-shiftdown)
("C-c R" . org-metaright)
("C-c L" . org-metaleft)
("C-c U" . org-metaup)
("C-c D" . org-metadown)
("C-c C-x C-e" . org-set-effort) ; ‘3:12’, ‘1:23:45’, or ‘1d3h5min’
:map org-src-mode-map
("C-c C-c" . org-edit-src-exit)
;; Better, intuitive movement when selecting a date for schedule or deadline
:map org-read-date-minibuffer-local-map
("C-n". (lambda () (interactive) (org-eval-in-calendar '(calendar-forward-week 1))))
("C-p". (lambda () (interactive) (org-eval-in-calendar '(calendar-backward-week 1))))
("C-f". (lambda () (interactive) (org-eval-in-calendar '(calendar-forward-day 1))))
("C-b". (lambda () (interactive) (org-eval-in-calendar '(calendar-backward-day 1))))
("C-v". (lambda () (interactive) (org-eval-in-calendar '(calendar-forward-month 1))))
("M-v". (lambda () (interactive) (org-eval-in-calendar '(calendar-backward-month 1)))))
:hook
(org-babel-after-execute . org-redisplay-inline-images)
(org-mode . (lambda () (fk/add-local-hook 'before-save-hook 'org-redisplay-inline-images)))
(org-after-refile-insert . (lambda () (fk/org-sort-by-priority) (save-buffer)))
(org-capture-mode . delete-other-windows) ; make capture buffer fullscreen
;; (org-agenda-mode . (lambda () (require 'org-habit)))
:config
(add-to-list 'org-emphasis-alist '("#" (:box '(:line-width -1)))) ; FIXME: does not work.
(setf (cdr (assoc "*" org-emphasis-alist)) '((:weight extra-bold :foreground "#DDDDDD")))
(defun fk/org-babel-load-languages ()
"Load languages I use."
(interactive)
(org-babel-do-load-languages 'org-babel-load-languages '((python . t)
(emacs-lisp . t)
(shell . t)
(ein . t))))
(defun fk/org-babel-tangle-block()
(interactive)
(let ((current-prefix-arg '(4)))
(call-interactively 'org-babel-tangle)))
(with-eval-after-load 'org-agenda
(bind-key "m" 'org-agenda-month-view org-agenda-mode-map))
;; Beautify org mode
(font-lock-add-keywords 'org-mode
'(("^ *\\([-]\\) "
(0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "•"))))))
(font-lock-add-keywords 'org-mode
'(("^ *\\([+]\\) "
(0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "◦"))))))
(defface org-checkbox-done-text
'((t (:inherit 'font-lock-comment-face :slant normal)))
"Face for the text part of a checked org-mode checkbox.")
(font-lock-add-keywords
'org-mode
`(("^[ \t]*\\(?:[-+*]\\|[0-9]+[).]\\)[ \t]+\\(\\(?:\\[@\\(?:start:\\)?[0-9]+\\][ \t]*\\)?\\[\\(?:X\\|\\([0-9]+\\)/\\2\\)\\][^\n]*\n\\)"
1 'org-checkbox-done-text prepend))
'append)
(defun fk/org-insert-created-time ()
(interactive)
(insert "CREATED: " (format-time-string (org-time-stamp-format t t) (current-time))))
(defun fk/org-refile-fixed-location (file headline)
"Refile headline without selecting from refile-targets."
(let ((pos (save-window-excursion
(find-file file)
(org-find-exact-headline-in-buffer headline))))
(org-refile nil nil (list headline file nil pos))))
(defun fk/org-refile-fixed-location-with-closed-timestamp (file headline)
"Refile headline without selecting from refile-targets. Add
\"CLOSED\" timestamp info."
(add-hook 'org-after-refile-insert-hook (lambda () (org-add-planning-info 'closed (org-current-effective-time))) -100)
(fk/org-refile-fixed-location file headline)
(remove-hook 'org-after-refile-insert-hook (lambda () (org-add-planning-info 'closed (org-current-effective-time)))))
(defun fk/org-refile-done ()
(interactive)
(fk/org-refile-fixed-location-with-closed-timestamp "archive.org" "Done"))
(defun fk/org-refile-trash ()
(interactive)
(fk/org-refile-fixed-location-with-closed-timestamp "archive.org" "Trash"))
(defhydra org-refile-hydra
(:color red :hint nil)
"
^Move^ ^Todo^ ^Someday^ ^Archive^
-----------------------------------------------------------
_n_: Next _w_: Work _E_: Emacs _d_: Done
_p_: Previous _e_: Emacs _P_: Presentation _x_: Trash
^^ _t_: Tech _T_: Tech ^^
_1_: Low Priority _h_: Home _H_: Home ^^
_2_: Medium Priority _o_: Other _W_: Watch ^^
_3_: High Priority ^^ _R_: Read ^^
^^ ^^ _O_: Other ^^
_c_: Set Time Effort ^^ ^^
_a_: Set Tags ^^ ^^
"
;; Move
("n" next-line)
("p" previous-line)
("1" (lambda () (interactive) (org-priority ?C)))
("2" (lambda () (interactive) (org-priority ?B)))
("3" (lambda () (interactive) (org-priority ?A)))
("c" org-set-effort)
("a" org-set-tags-command)
;; Todo
("w" (lambda () (interactive) (fk/org-refile-fixed-location "todos.org" "Work")))
("e" (lambda () (interactive) (fk/org-refile-fixed-location "todos.org" "Emacs")))
("t" (lambda () (interactive) (fk/org-refile-fixed-location "todos.org" "Tech")))
("h" (lambda () (interactive) (fk/org-refile-fixed-location "todos.org" "Home")))
("o" (lambda () (interactive) (fk/org-refile-fixed-location "todos.org" "Other")))
;; Someday
("E" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Emacs")))
("P" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Emacs Presentation")))
("T" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Tech")))
("H" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Home")))
("W" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Watch")))
("R" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Read")))
("O" (lambda () (interactive) (fk/org-refile-fixed-location "someday.org" "Other")))
;; Archive
("d" fk/org-refile-done)
("x" fk/org-refile-trash)
;; General
("m" org-refile "Refile manually")
("s" save-buffer "Save buffer")
("q" nil "Quit" :color blue))
(set-face-attribute 'org-mode-line-clock nil :inherit 'default)
(defun fk/org-clock-update-mode-line (&optional _)
"Display org-clock info in echo area. See `fk/minibuffer-modeline-update'."
(if org-clock-effort
(org-clock-notify-once-if-expired)
(setq org-clock-task-overrun nil))
(setq fk/org-clock-string
(let ((clock-string (org-clock-get-clock-string)))
(if (and (> org-clock-string-limit 0)
(> (length clock-string) org-clock-string-limit))
(substring clock-string 0 org-clock-string-limit)
clock-string)))
(if (and org-clock-task-overrun org-clock-task-overrun-text)
(setq fk/org-clock-string
(concat (propertize
org-clock-task-overrun-text
'face 'org-mode-line-clock-overrun)
fk/org-clock-string))))
(advice-add 'org-clock-update-mode-line :override 'fk/org-clock-update-mode-line)
(add-hook 'org-clock-in-hook (lambda ()
(interactive)
(setq fk/org-clocking-buffer (org-clocking-buffer))
(save-buffer)))
(add-hook 'org-clock-out-hook (lambda ()
(interactive)
(with-current-buffer fk/org-clocking-buffer
(save-buffer))
(setq fk/org-clocking-buffer nil)
(setq fk/org-clock-string "")))
(add-hook 'org-clock-cancel-hook (lambda ()
(interactive)
(with-current-buffer fk/org-clocking-buffer
(save-buffer))
(setq fk/org-clocking-buffer nil)
(setq fk/org-clock-string "")))
(defun fk/org-clock-out-and-update-report (orig-func &rest args)
"Update org clock report table after clock out."
(interactive)
(let ((buffer (org-clocking-buffer)))
(apply orig-func args) ; clock out
;; `org-clock-report' updates the first table in the buffer
;; when called with a prefix argument
(save-excursion
(with-current-buffer buffer
(let ((current-prefix-arg 4))
(call-interactively 'org-clock-report)
(save-buffer))))))
(advice-add 'org-clock-out :around 'fk/org-clock-out-and-update-report)
;; Similar to `fk/org-clock-out-and-update-report',
;; I added this file local variable to my time tracking file to update report on save:
;; # Local Variables:
;; # eval: (add-hook 'before-save-hook (lambda () (save-excursion (let ((current-prefix-arg 4)) (call-interactively 'org-clock-report)))) :local t)
;; # End:
)
Org Super Agenda
(use-package org-super-agenda
:after org-agenda
:custom
(org-super-agenda-groups '(( :name "Calendar"
:time-grid t)
( :name "Deadlines"
:deadline t)
( :name "Scheduled"
:scheduled t)))
:config
(org-super-agenda-mode))
Org QL
(use-package org-ql
:commands org-ql-search org-ql-view
:bind
( :map org
("q" . org-ql-view))
:config
(with-eval-after-load 'org-ql-view
(define-key org-ql-view-map (kbd "q") 'quit-window)
(defmacro fk/org-ql-view (title query)
`'(,title
:title ,title
:buffers-files ,org-gtd-files
:query ,query
:super-groups ((:auto-parent))
:sort (priority)))
(setq org-ql-views
`(,(fk/org-ql-view "Effort <=15min" (effort "<=" "15min"))
,(fk/org-ql-view "Effort >15min <=30min" (and (effort ">" "15min") (effort "<=" "30min")))
,(fk/org-ql-view "Effort >30min <=1h" (and (effort ">" "30min") (effort "<=" "1h")))
,(fk/org-ql-view "Effort >1h <=2h" (and (effort ">" "1h") (effort "<=" "2h")))
,(fk/org-ql-view "Effort >2h <=4h" (and (effort ">" "2h") (effort "<=" "4h")))
,(fk/org-ql-view "Effort >4h" (effort ">" "4h"))))))
Custom Functions
org-screenshot
(defun fk/org-screenshot ()
;; fork from: https://delta.re/org-screenshot/
;; https://github.com/kadircancetin/.emacs.d
"Take a screenshot into a time stamped unique-named file in the
same directory as the org-buffer and insert a link to this file."
(interactive)
(when (eq major-mode 'org-mode)
(suspend-frame)
(run-at-time
"500 millisec" nil ; I have animation when minimize window
(lambda ()
(org-display-inline-images)
(setq filename
(concat
(make-temp-name
(concat (file-name-nondirectory (buffer-file-name))
"_imgs/"
(format-time-string "%Y%m%d_%H%M%S_")) ) ".png"))
(unless (file-exists-p (file-name-directory filename))
(make-directory (file-name-directory filename)))
;; take screenshot
(if (eq system-type 'darwin)
(call-process "screencapture" nil nil nil "-i" filename))
(if (eq system-type 'gnu/linux)
(call-process "import" nil nil nil filename))
;; insert into file if correctly taken
(if (file-exists-p filename)
(insert (concat "[[file:" filename "]]")))
(org-remove-inline-images)
(org-display-inline-images)
(other-frame 0)))))
org-indent-src-block
(defun fk/org-indent-src-block ()
(interactive)
(org-edit-special)
(fk/indent-buffer)
(org-edit-src-exit))
org-sort-by-priority
(defun fk/org-sort-by-priority ()
"Sort entries in level=2 by priority."
(interactive)
(org-map-entries (lambda () (condition-case nil
(org-sort-entries nil ?p)
(error nil)))
"LEVEL=1")
(org-set-startup-visibility))
org-agenda-posframe
(defun fk/org-agenda-posframe ()
"`org-agenda-list' in a posframe. Quit with 'q' as usual."
(interactive)
(save-window-excursion
(org-agenda-list)
(fk/darken-background))
(let ((frame (posframe-show org-agenda-buffer
:poshandler 'posframe-poshandler-frame-center
:border-width 30
:border-color fk/dark-color)))
(x-focus-frame frame)
(with-current-buffer org-agenda-buffer
(setq-local cursor-type 'box))))
Org Bullets
(use-package org-bullets
:custom
(org-bullets-bullet-list '("⁖"))
;;;; Alternatives
;; (org-bullets-bullet-list '("①" "②" "③" "④" "⑤" "⑥" "⑦" "⑧" "⑨"))
;; (org-bullets-bullet-list '("➀" "➁" "➂" "➃" "➄" "➅" "➆" "➇" "➈"))
;; (org-bullets-bullet-list '("❶" "❷" "❸" "❹" "❺" "❻" "❼" "❽" "❾"))
;; (org-bullets-bullet-list '("➊" "➋" "➌" "➍" "➎" "➏" "➐" "➑" "➒"))
;; (org-bullets-bullet-list '("⒈" "⒉" "⒊" "⒋" "⒌" "⒍" "⒎" "⒏" "⒐"))
:hook (org-mode . org-bullets-mode))
Toc Org
(use-package toc-org
:straight (:host github :repo "KaratasFurkan/toc-org" :branch "insert-silently")
:custom
(toc-org-max-depth 10)
(toc-org-insert-silently t)
:hook (org-mode . toc-org-mode))
Org Table Auto Align
;; TODO: make this snippet a package
;; (use-package org-table-auto-align-mode ; NOTE: breaks undo
;; :load-path (lambda () (concat user-emacs-directory "load/org-table-auto-align-mode"))
;; :hook org-mode)
ob-async
(use-package ob-async
:after org)
Org Pomodoro
(use-package org-pomodoro
:straight (:files ("*")) ; For sound files
:commands org-pomodoro
:custom
(org-pomodoro-audio-player "ffplay")
:config
;; Apply args for all sounds
(advice-add 'org-pomodoro-sound-args :override (lambda (_) "-volume 20 -nodisp -nostats -hide_banner")))
Org Roam
Org Roam
(use-package org-roam
:custom
(org-roam-directory "~/org/roam/")
:bind
( :map org
("o" . org-roam-find-file)))
Org Roam Server
;;(use-package org-roam-server
;; :after org-roam)
Company Org Roam
(use-package company-org-roam
:disabled
:after org-roam
:config
(add-to-list 'company-backends 'company-org-roam))
Org Fancy Priorities
(use-package org-fancy-priorities
:custom
(org-fancy-priorities-list '("[!!!]" "[!!] " "[!] ")) ; same length
(org-priority-faces '((?A . (:foreground "orangered2" :weight extrabold :height 1.3)) ; org-mode
(?B . (:foreground "orange" :weight extrabold :height 1.3))
(?C . (:foreground "Burlywood" :weight extrabold :height 1.3))))
:hook
(org-mode . org-fancy-priorities-mode)
(org-agenda-finalize . org-fancy-priorities-mode))
Org Tree Slide
(use-package org-tree-slide
:commands org-tree-slide-mode
:custom
(org-tree-slide-activate-message "")
(org-tree-slide-deactivate-message "")
(org-tree-slide-breadcrumbs " > ")
(org-tree-slide-heading-emphasis t)
(org-tree-slide-slide-in-waiting 0.025)
(org-tree-slide-content-margin-top 4)
:custom-face
(org-tree-slide-heading-level-1 ((t (:height 1.8 :weight bold))))
(org-tree-slide-heading-level-2 ((t (:height 1.5 :weight bold))))
(org-tree-slide-heading-level-3 ((t (:height 1.5 :weight bold))))
(org-tree-slide-heading-level-4 ((t (:height 1.5 :weight bold))))
:bind
( :map org
("s" . org-tree-slide-mode)
:map org-tree-slide-mode-map
("<f8>" . org-tree-slide-content)
("<f9>" . org-tree-slide-move-previous-tree)
("<f10>" . org-tree-slide-move-next-tree)
("<left>" . org-tree-slide-move-previous-tree)
("<right>" . org-tree-slide-move-next-tree)
("C-n" . (lambda () (interactive) (if cursor-type
(next-line)
(setq-local cursor-type t)
(next-line)))))
:hook
(org-tree-slide-before-narrow . (lambda () (setq-local cursor-type nil)))
(org-tree-slide-stop . (lambda () (setq-local cursor-type t)))
(org-tree-slide-play . variable-pitch-mode)
(org-tree-slide-stop . (lambda () (variable-pitch-mode -1)))
(org-tree-slide-play . fk/hide-org-metalines-toggle)
(org-tree-slide-stop . fk/hide-org-metalines-toggle)
(org-tree-slide-before-narrow . org-remove-inline-images)
(org-tree-slide-after-narrow . org-display-inline-images)
(org-tree-slide-play . fk/org-tree-slide-update-modeline)
(org-tree-slide-stop . fk/org-tree-slide-update-modeline)
(org-tree-slide-mode . (lambda () (fk/adjust-font-size 40)))
;; (org-tree-slide-stop . (lambda () (fk/adjust-font-size -40)))
;; (org-tree-slide-play . (lambda () (setq-local olivetti-body-width 95) (olivetti-mode 1)))
;; (org-tree-slide-stop . (lambda () (setq-local olivetti-body-width 120) (olivetti-mode 1)))
(org-tree-slide-mode . (lambda () (org-appear-mode -1)))
(org-tree-slide-mode . (lambda () (setq olivetti-enable-borders nil) (olivetti-mode 1)))
:config
(defun fk/buffer-contains-substring (string)
(save-excursion
(save-match-data
(goto-char (point-min))
(and-let* ((pos (search-forward string nil t))
(visible (not (outline-invisible-p pos))))))))
(setq fk/org-meta-line-hide-p nil)
(setq fk/org-meta-line-face-remap nil)
(defun fk/hide-org-metalines-toggle ()
"Hide or unhide meta lines starting with \"#+\" in org-mode."
(interactive)
(if fk/org-meta-line-hide-p
(face-remap-remove-relative fk/org-meta-line-face-remap)
(setq fk/org-meta-line-face-remap (face-remap-add-relative 'org-meta-line
:foreground fk/background-color)))
(setq fk/org-meta-line-hide-p (not fk/org-meta-line-hide-p)))
(defun fk/org-tree-slide-update-modeline ()
"Show current page in modeline."
(let ((slide-position '(:eval (format " %s " (org-tree-slide--count-slide (point))))))
(if (org-tree-slide--active-p)
(setq-local global-mode-string (append global-mode-string (list slide-position)))
(setq-local global-mode-string (delete slide-position global-mode-string))))))
;; Alternative
(use-package epresent
:commands epresent-run)
Org Export Twitter Bootstrap
(use-package ox-twbs
:after org)
Valign Mode
(use-package valign
:straight (:host github :repo "casouri/valign")
:commands valign-mode
:custom
(valign-fancy-bar t))
Org Appear
(use-package org-appear
:hook (org-mode . org-appear-mode))
Org Rainbow Tags
(use-package org-rainbow-tags
:straight (:host github :repo "KaratasFurkan/org-rainbow-tags")
:custom
(org-rainbow-tags-hash-start-index 9)
:hook
(org-mode . (lambda () (unless (daemonp) (org-rainbow-tags-mode)))))
Calendar
(use-package calendar
:commands calendar
:custom
(calendar-week-start-day 1) ; start at Monday
:hook
;; bigger calendar like OS calendars
(calendar-mode . (lambda () (text-scale-increase 8)))
(calendar-mode . delete-other-windows))
Version Control
Magit
Magit
(use-package magit
:commands magit
:custom
(magit-section-initial-visibility-alist '((stashes . show)
(unpushed . show)
(pullreqs . show)
(issues . show)))
(magit-display-buffer-function 'magit-display-buffer-same-window-except-diff-v1)
:bind*
( :map version-control
("v" . magit-status)
("s" . magit-status)
:map magit-mode-map
("o" . (lambda () (interactive)
(call-interactively 'magit-diff-visit-file-other-window)
(recenter-top-bottom)))
("C-c C-f" . magit-find-file))
:hook
(git-commit-setup . git-commit-turn-on-flyspell)
(magit-mode . hack-dir-local-variables-non-file-buffer))
Magit Todos
(use-package magit-todos
:commands magit-todos-list
:custom
(magit-todos-exclude-globs '("*jquery*.js" "*min.js" "*min.css" "*.mjml"))
(magit-todos-max-items 40) ; It's actually 20 but needed to set x2 for some reason
:bind*
( :map version-control
("T" . magit-todos-list))
:hook (magit-mode . magit-todos-mode))
Magit Forge
Pull Requests, Issues etc.
(use-package forge
:after magit
:config
(advice-add 'magit-pull-from-upstream :after
(lambda (&rest _) (call-interactively 'forge-pull)))
(advice-add 'magit-fetch-all :after
(lambda (&rest _) (call-interactively 'forge-pull)))
(defun fk/forge-create-pullreq--read-args ()
(let* ((source (magit-completing-read
"Source branch"
(magit-list-remote-branch-names)
;; `magit-get-current-branch' as initial input
nil t (concat "origin/" (magit-get-current-branch)) 'magit-revision-history
(or (and-let* ((d (magit-branch-at-point)))
(if (magit-remote-branch-p d)
d
(magit-get-push-branch d t)))
(and-let* ((d (magit-get-current-branch)))
(if (magit-remote-branch-p d)
d
(magit-get-push-branch d t))))))
(repo (forge-get-repository t))
(remote (oref repo remote))
(targets (delete source (magit-list-remote-branch-names remote)))
(target (magit-completing-read
;; TODO: show history at first
"Target branch" targets nil t nil 'magit-revision-history
(let* ((d (cdr (magit-split-branch-name source)))
(d (and (magit-branch-p d) d))
(d (and d (magit-get-upstream-branch d)))
(d (and d (if (magit-remote-branch-p d)
d
(magit-get-upstream-branch d))))
(d (or d (concat remote "/"
(or (oref repo default-branch)
"master")))))
(car (member d targets))))))
(list source target)))
(defun fk/forge-prepare-topic (source target)
"Prepare topic for `forge-create-pullreq'."
(if-let* ((target-name (upcase (string-remove-prefix "origin/" target)))
(source-name (string-remove-prefix "origin/" source))
(match (string-match "^[a-z]+-[0-9]+" source-name))
(match-string (match-string-no-properties 0 source-name))
(issue-id (when match (upcase match-string)))
;; Branch may not have issue-id
(issue-id-string (if issue-id (format "%s | " issue-id) ""))
(topic (string-remove-prefix (concat match-string "-") source-name))
(capitalized-topic (upcase-initials (string-replace "-" " " topic))))
(format "# %s | %s%s" target-name issue-id-string capitalized-topic)
(format "# %s | " target-name )))
(defun fk/forge--prepare-post-buffer (filename &optional header source target)
(let ((file (magit-git-dir
(convert-standard-filename
(concat "magit/posts/" filename)))))
(make-directory (file-name-directory file) t)
(let ((prevbuf (current-buffer))
(resume (and (file-exists-p file)
(> (file-attribute-size (file-attributes file)) 0)))
(buf (find-file-noselect file)))
(with-current-buffer buf
(forge-post-mode)
(when header
(magit-set-header-line-format header))
(setq forge--pre-post-buffer prevbuf)
(when resume
(forge--display-post-buffer buf)
(when (magit-read-char-case "A draft already exists. " nil
(?r "[r]esume editing existing draft")
(?d "[d]iscard draft and start over" t))
(erase-buffer)
(setq resume nil)))
(when (and (not resume) (string-prefix-p "new" filename))
(let-alist (forge--topic-template
(forge-get-repository t)
(if source 'forge-pullreq 'forge-issue))
(cond
(.url
(browse-url .url)
(forge-post-cancel)
(setq buf nil)
(message "Using browser to visit %s instead of opening an issue"
.url))
(.name
;; A Github issue with yaml frontmatter.
(save-excursion (insert .text))
(unless (re-search-forward "^title: " nil t)
(when (re-search-forward "^---" nil t 2)
(beginning-of-line)
(insert "title: \n")
(backward-char))))
(t
;; Custom part:
(insert (fk/forge-prepare-topic source target)))))))
buf)))
;; I just added `magit-get-current-branch' as initial input
(advice-add 'forge-create-pullreq--read-args :override 'fk/forge-create-pullreq--read-args)
;; I just added a custom dynamic topic template: `fk/forge-prepare-topic'
(advice-add 'forge--prepare-post-buffer :override 'fk/forge--prepare-post-buffer))
Magit Delta
;; (use-package magit-delta
;; :hook (magit-mode . magit-delta-mode))
diff-hl
(use-package diff-hl
:custom
(diff-hl-global-modes '(not org-mode))
(diff-hl-ask-before-revert-hunk nil)
:custom-face
(diff-hl-insert ((t (:background "#224022"))))
(diff-hl-change ((t (:background "#492949" :foreground "mediumpurple1"))))
(diff-hl-delete ((t (:background "#492929" :foreground "orangered2"))))
:bind
( :map version-control
("n" . diff-hl-next-hunk)
("p" . diff-hl-previous-hunk)
("r" . diff-hl-revert-hunk))
:hook
(dashboard-after-initialize . global-diff-hl-mode)
(diff-hl-mode . diff-hl-flydiff-mode)
(magit-pre-refresh . diff-hl-magit-pre-refresh)
(magit-post-refresh . diff-hl-magit-post-refresh))
Smerge
;; Source: https://github.com/alphapapa/unpackaged.el#smerge-mode
(use-package smerge-mode
:straight (:type built-in)
:after magit
:config
(defhydra smerge-hydra
(
:color red
:hint nil
;; :pre (progn
;; (setq-local global-hl-line-mode nil)
;; (when tree-sitter-hl-mode
;; (tree-sitter-hl-mode -1))
;; (smerge-mode))
;; :post (progn
;; (smerge-auto-leave)
;; (setq-local global-hl-line-mode t))
)
"
^Move^ ^Keep^ ^Diff^ ^Other^
^^-----------^^-------------------^^---------------------^^-------
_n_ext _b_ase _<_: upper/base _C_ombine
_p_rev _u_pper _=_: upper/lower _r_esolve
^^ _l_ower _>_: base/lower _k_ill current
^^ _a_ll _R_efine
^^ _RET_: current _E_diff
"
("n" (lambda () (interactive) (smerge-next) (recenter (round (* 0.2 (window-height))) t)))
("p" (lambda () (interactive) (smerge-prev) (recenter (round (* 0.2 (window-height))) t)))
("b" smerge-keep-base)
("u" smerge-keep-upper)
("l" smerge-keep-lower)
("a" smerge-keep-all)
("RET" smerge-keep-current)
("\C-m" smerge-keep-current)
("<" smerge-diff-base-upper)
("=" smerge-diff-upper-lower)
(">" smerge-diff-base-lower)
("R" smerge-refine)
("E" smerge-ediff)
("C" smerge-combine-with-next)
("r" smerge-resolve)
("k" smerge-kill-current)
("ZZ" (lambda ()
(interactive)
(save-buffer)
(bury-buffer))
"Save and bury buffer" :color blue)
("q" nil "cancel" :color blue))
:hook
(magit-diff-visit-file . (lambda ()
(when smerge-mode
(smerge-hydra/body)))))
Git Link
(use-package git-link
:commands git-link
:custom
(git-link-use-commit t)
:bind
( :map version-control
("l" . git-link)))
Git Timemachine
(use-package git-timemachine
:commands git-timemachine
:bind
( :map version-control
("t" . git-timemachine))
:hook
(git-timemachine-mode . fk/tree-sitter-hl-mode))
Git Blame (vc-msg)
(use-package vc-msg
:commands vc-msg-show
:bind
( :map version-control
("b" . vc-msg-show)))
Dired Git Info
;; (use-package dired-git-info
;; :hook (dired-after-readin . dired-git-info-auto-enable)
;; :config
;; (defun dired-subtree-toggle-advice (orig-fn &rest args)
;; "Source: https://github.com/clemera/dired-git-info/issues/9#issuecomment-1013520395"
;; (cond ((bound-and-true-p dired-git-info-mode)
;; (dired-git-info-mode -1)
;; (apply orig-fn args)
;; (dired-git-info-mode +1))
;; (t (apply orig-fn args))))
;; (advice-add 'dired-subtree-toggle :around 'dired-subtree-toggle-advice))
Terminal Emulation
Vterm
(use-package vterm
:load-path "~/.emacs.d/straight/repos/emacs-libvterm" ; manually cloned
:custom
(vterm-max-scrollback 100000)
(vterm-buffer-name "vterm")
:custom-face
;; match with fk/darken-background
(vterm-color-default ((t (:background ,fk/dark-color))))
:bind
( :map vterm-mode-map
("C-c C-e" . vterm-copy-mode)
("C-c C-n" . fk/vterm-next-prompt)
("C-c C-p" . fk/vterm-previous-prompt)
;; Disabled vterm keybindings: (in order to use their global values)
("M-m" . nil)
("M-u" . nil)
("M-j" . nil)
("<f1>" . nil)
("C-M-s" . nil)
:map vterm-copy-mode-map
("C-c C-e" . vterm-copy-mode)
("C-c C-c" . vterm-copy-mode)
("M-n" . fk/vterm-next-prompt)
("M-p" . fk/vterm-previous-prompt))
:hook
(vterm-mode . (lambda () (setq-local global-hl-line-mode nil
show-trailing-whitespace nil)))
(vterm-mode . fk/darken-background)
(vterm-mode . hack-dir-local-variables-non-file-buffer) ; TODO: doesn't work
(vterm-copy-mode . (lambda ()
(face-remap-add-relative 'hl-line :background fk/background-color)
(call-interactively 'hl-line-mode)))
:config
(defvar docker-container-prompt-regexp "^[\\^A-Z]*root@[A-z0-9-]*:/[^#]*# ")
(defvar python-pdb-prompt-regexp "^[\\^A-Z]*(Pdb) ") ; TODO: add next-prompt function for this one too
(defface docker-container-prompt-face
'((t (:foreground "green yellow")))
"Face for docker container prompt in vterm.")
;; NOTE: https://github.com/akermu/emacs-libvterm/pull/430 this PR is needed.
(font-lock-add-keywords
'vterm-mode
`((,docker-container-prompt-regexp 0 'docker-container-prompt-face t)
(,python-pdb-prompt-regexp 0 'docker-container-prompt-face t))
'set)
(defun fk/docker-container-next-prompt ()
"Move to end of next docker-container prompt in the buffer. According to the
`docker-container-prompt-regexp'."
(interactive)
(search-forward-regexp docker-container-prompt-regexp nil t))
(defun fk/docker-container-prev-prompt ()
"Move to end of previous docker-container prompt in the buffer. According to
the `docker-container-prompt-regexp'."
(interactive)
(beginning-of-line) ; not to catch same prompt
(when (search-backward-regexp docker-container-prompt-regexp nil t)
;; to go to the end of the prompt
(search-forward-regexp docker-container-prompt-regexp nil t)))
(defun fk/vterm-next-prompt ()
"Move to end of next prompt in the buffer. In addition to
`vterm-next-prompt', this catches docker containers prompts too."
(interactive)
(if-let*
((current-line (line-number-at-pos))
(prompt-by-regexp (save-excursion
(when (or (fk/docker-container-next-prompt)
;; to not return nil at the last prompt
(= (line-number-at-pos) (1- (line-number-at-pos (point-max)))))
(point))))
(prompt-by-vterm (save-excursion
(call-interactively 'vterm-next-prompt)
(point)))
(is-next-docker-prompt (or (< prompt-by-regexp prompt-by-vterm)
(> current-line (line-number-at-pos prompt-by-vterm))
(= current-line (line-number-at-pos prompt-by-vterm)))))
(fk/docker-container-next-prompt)
(call-interactively 'vterm-next-prompt)))
(defun fk/vterm-previous-prompt ()
"Move to end of previous prompt in the buffer. In addition to
`vterm-previous-prompt', this catches docker containers prompts too."
(interactive)
(if-let*
((current-line (line-number-at-pos))
(prompt-by-regexp (save-excursion
(when (fk/docker-container-prev-prompt)
(point))))
(prompt-by-vterm (save-excursion
(call-interactively 'vterm-previous-prompt)
(point)))
(is-prev-docker-prompt (or (> prompt-by-regexp prompt-by-vterm)
(< current-line (line-number-at-pos prompt-by-vterm))
(= current-line (line-number-at-pos prompt-by-vterm)))))
(fk/docker-container-prev-prompt)
(call-interactively 'vterm-previous-prompt))))
Shell Pop
(use-package shell-pop
:custom
(shell-pop-shell-type '("vterm" "*vterm*" (lambda () (vterm))))
(shell-pop-full-span t)
:bind*
(("M-t" . shell-pop)))
Restclient
Restclient
(use-package restclient
:mode ("\\.http\\'" . restclient-mode)
:custom
(restclient-log-request nil)
;;:config
;;(setcdr (assoc "application/json" restclient-content-type-modes) 'json-mode)
)
Company Restclient
(use-package company-restclient
:after restclient
:hook
(restclient-mode . (lambda ()
(add-to-list 'company-backends 'company-restclient))))
ob-restclient
(use-package ob-restclient
:after org
:config
(org-babel-do-load-languages 'org-babel-load-languages '((restclient . t))) ; TODO: this may slow down org load
(add-hook 'org-babel-after-execute-hook (lambda () (let ((lang (nth 0 (org-babel-get-src-block-info))))
(when (and buffer-file-name (string= lang "restclient"))
(save-buffer))))))
Password Mode
(use-package password-mode
:hook
(restclient-mode . password-mode)
(org-mode . (lambda ()
(when (buffer-file-name)
(let ((filename (file-name-nondirectory
(directory-file-name buffer-file-name))))
(when (or (string-match "rest" filename)
(string-match "api" filename))
(password-mode))))))
:config
(add-to-list 'password-mode-password-prefix-regexs "\"password\":?[[:space:]]+"))
EAF
(use-package eaf
:load-path "~/.emacs.d/straight/repos/emacs-application-framework" ; manually cloned
;; :straight
;; (:host github :repo "emacs-eaf/emacs-application-framework" :depth 1 :files ("*"))
:commands eaf-open-browser eaf-open-camera
:custom
(eaf-python-command "/usr/bin/python") ; don't use venv python
:config
(require 'eaf-browser)
(require 'eaf-camera))
Sudo Edit
(use-package sudo-edit
:commands sudo-edit)
Google Translate
(use-package go-translate
:straight (:host github :repo "lorniu/go-translate")
:custom
(go-translate-local-language "tr")
(go-translate-target-language "en")
(go-translate-inputs-function 'go-translate-inputs-current-or-prompt)
(go-translate-buffer-follow-p t)
(go-translate-token-current (cons 430675 2721866130)) ; Fix https://github.com/lorniu/go-translate/issues/7
:bind*
( :map text
:prefix-map google-translate
:prefix "g"
("g" . go-translate-popup-current)
("G" . go-translate)
("b" . go-translate)
("e" . go-translate-echo-area)))
PDF Tools
(use-package pdf-tools
:mode ("\\.pdf\\'" . pdf-view-mode)
:magic ("%PDF" . pdf-view-mode)
:custom
(pdf-view-display-size 'fit-page)
:bind
( :map pdf-view-mode-map
("O" . pdf-occur)
("d" . pdf-view-midnight-minor-mode)
("s a" . pdf-view-auto-slice-minor-mode)
("t" . (lambda (beg end) (interactive "r") (go-translate))))
:hook
(pdf-view-mode . pdf-links-minor-mode)
(pdf-view-mode . pdf-isearch-minor-mode)
(pdf-view-mode . pdf-outline-minor-mode)
(pdf-view-mode . pdf-history-minor-mode)
:config
(with-eval-after-load 'pdf-links
(define-key pdf-links-minor-mode-map (kbd "f") 'pdf-links-action-perform)))
Interleave
(use-package interleave
:commands interleave-mode
:custom
(interleave-disable-narrowing t))
PDF Continuous Scroll Mode
(use-package pdf-continuous-scroll-mode
:straight (:host github :repo "dalanicolai/pdf-continuous-scroll-mode.el")
;; M-x pdf-view-fit-width-to-window and disable olivetti before run this
:commands pdf-continuous-scroll-mode)
Emacs Screencast
(use-package gif-screencast
:straight (:host gitlab :repo "ambrevar/emacs-gif-screencast")
:bind
( :map gif-screencast-mode-map
("<f8>". gif-screencast-toggle-pause)
("<f9>". gif-screencast-stop)))
Slack
Slack
(use-package slack
:commands slack-start
:custom
(slack-buffer-function 'switch-to-buffer)
(slack-buffer-emojify t)
(slack-prefer-current-team t)
(slack-alert-icon (fk/expand-static-file-name "slack/icon.png"))
:custom-face
(slack-preview-face ((t (:inherit (fixed-pitch shadow org-block) :extend nil))))
:hook
(slack-message-buffer-mode . (lambda () (setq-local truncate-lines nil)))
(slack-message-buffer-mode . (lambda () (setq-local olivetti-body-width 80)))
:config
(slack-register-team
:name "hipo"
:default t
:token (auth-source-pick-first-password :host "slack")
:full-and-display-names t)
(defun fk/alert-with-sound (orig-func &rest args)
"Play sound with alert."
(apply orig-func args)
(when (eq (plist-get (cdr args) :category) 'slack)
(let* ((sound-file (fk/expand-static-file-name "slack/sound.mp3"))
(command (concat "ffplay -volume 20 -nodisp -nostats -hide_banner " sound-file)))
(when (file-exists-p sound-file)
(fk/async-process command)))))
(advice-add 'alert :around 'fk/alert-with-sound))
Emojify
(use-package emojify
:commands emojify-mode)
;; (use-package company-emoji
;; :after slack
;; :config
;; (add-to-list 'company-backends 'company-emoji))
Alert
(use-package alert
:commands alert
:custom
(alert-default-style 'libnotify))
Helm Slack
(use-package helm-slack
:straight (:host github :repo "yuya373/helm-slack")
:after slack)
PlantUML
(use-package plantuml-mode
:mode "\\.plantuml\\'"
:preface
(setq plantuml-jar-path (concat no-littering-etc-directory "plantuml.jar"))
:custom
(plantuml-default-exec-mode 'jar)
(plantuml-indent-level 4)
:init
(with-eval-after-load "org"
(add-to-list 'org-src-lang-modes '("plantuml" . plantuml))
(org-babel-do-load-languages 'org-babel-load-languages '((plantuml . t)))
(setq org-plantuml-jar-path plantuml-jar-path)))
EWW
Highlight Code Blocks
(use-package shr-tag-pre-highlight
:after shr
:config
(add-to-list 'shr-external-rendering-functions '(pre . shr-tag-pre-highlight)))
XWWP (Xwidget Webkit Enhancement)
(use-package xwidget
:straight (:type built-in)
:commands xwidget-webkit-browse-url)
(use-package xwwp
:commands (xwwp xwwp-browse-url-other-window)
:bind
( :map xwidget-webkit-mode-map
("f" . xwwp-follow-link)))
Screenshot
(use-package screenshot
:straight (:host github :repo "tecosaur/screenshot")
:commands screenshot
:custom
(screenshot-max-width 300)
:hook
(screenshot-buffer-creation . (lambda () (hl-line-mode -1))))
Emacs Everywhere
(use-package emacs-everywhere
:straight (:host github :repo "tecosaur/emacs-everywhere")
:commands emacs-everywhere
:bind
( :map emacs-everywhere-mode-map
("C-x C-c" . emacs-everywhere-abort))
:hook
(emacs-everywhere-mode . (lambda () (require 'turkish) (turkish-mode)))
;; Banish mouse to prevent focusing to company's childframe
(emacs-everywhere-mode . (lambda () (set-mouse-position (selected-frame) 0 0)))
:config
;; I disabled insert selection because it inserts from clipboard if there is
;; no selection.
(advice-add 'emacs-everywhere-insert-selection :override 'ignore))
Pomidor (Pomodoro)
(use-package pomidor
:commands pomidor
:custom
(pomidor-update-interval 30)
(pomidor-confirm-end-break nil)
(pomidor-breaks-before-long 3) ; work-b-work-b-work-b-work-longb
(pomidor-long-break-seconds (* 30 60)) ; 30 mins
(pomidor-sound-tick nil)
(pomidor-sound-tack nil)
(pomidor-save-session-file (expand-file-name "pomidor-session.json" no-littering-var-directory))
:custom-face
(pomidor-work-face ((t (:inherit success :width ultra-condensed))))
(pomidor-overwork-face ((t (:inherit warning :width ultra-condensed))))
(pomidor-break-face ((t (:inherit font-lock-keyword-face :width ultra-condensed))))
(pomidor-skip-face ((t (:inherit font-lock-comment-face :width ultra-condensed))))
:hook
(kill-emacs . fk/pomidor-save-session)
:config
(defun fk/pomidor-save-session ()
"Call `pomidor-save-session' if pomidor is active, without asking yes or no."
(interactive)
(when (and (featurep 'pomidor) (get-buffer pomidor-buffer-name))
(cl-letf (((symbol-function 'y-or-n-p) (lambda (_) t)))
(pomidor-save-session))))
;; Use a dedicated perspective for pomidor
(advice-add 'pomidor :before (lambda () (persp-switch "pomidor")))
(advice-add 'pomidor-quit :after (lambda () (persp-kill "pomidor")))
;; Unhold with space
(advice-add 'pomidor-break :around (lambda (orig-func &rest args)
(if pomidor--system-on-hold-p
(pomidor-unhold)
(apply orig-func args)))))
Speed Type
(use-package speed-type
:commands fk/speed-type-project
:config
(defun fk/speed-type-project ()
"Select example paragraphs from projects to exercise touch
typing with actual code."
(interactive)
(let* ((projects (directory-files "~/projects/python/" t "[^.][^zip]$"))
(project (nth (random (length projects)) projects))
(files (directory-files-recursively project "[^gunicorn][^__].py$" t
(lambda (path)
(let ((dir (file-name-base path)))
(not (or (string-prefix-p "." dir)
(string-prefix-p "__" dir)
(string= "migrations" dir)))))))
(file (nth (random (length files)) files)))
(speed-type--setup (with-temp-buffer
(insert-file-contents file)
(delete-whitespace-rectangle (point-min) (point-max))
(speed-type--pick-text-to-type)))
(setq-local show-paren-mode nil)))
(advice-add 'speed-type--play-next :override 'fk/speed-type-project))
Docker
(use-package docker
:commands docker)
Sozluk
(use-package sozluk
:straight (:host github :repo "isamert/sozluk.el")
:commands sozluk
:config
(with-eval-after-load 'shackle
(add-to-list 'shackle-rules '("\\`\\*sozluk.*?\\*\\'" :regexp t :align t :size 0.4))))
Epub
(use-package nov
:mode ("\\.epub\\'" . nov-mode))
File Modes
Markdown
(use-package markdown-mode
:mode "\\.md\\'"
:custom (markdown-header-scaling t)
:bind
( :map markdown-mode-map
("M-n" . markdown-next-visible-heading)
("M-p" . markdown-previous-visible-heading)
("C-M-j" . markdown-follow-thing-at-point))
:hook
(markdown-mode . emojify-mode))
Fish
(use-package fish-mode
:mode "\\.fish\\'")
Docker
Dockerfile
(use-package dockerfile-mode
:mode "Dockerfile\\'")
Docker Compose
(use-package docker-compose-mode
:mode "docker-compose\\'")
Yaml
(use-package yaml-mode
:mode "\\.ya?ml\\'"
:hook
(yaml-mode . highlight-indent-guides-mode)
(yaml-mode . display-line-numbers-mode))
requirements.txt (pip)
(use-package pip-requirements
:mode (("\\.pip\\'" . pip-requirements-mode)
("requirements[^z-a]*\\.txt\\'" . pip-requirements-mode)
("requirements\\.in" . pip-requirements-mode))
:config
;; Assign a non nil value to `pip-packages' to prevent fetching pip packages.
(setq pip-packages '("ipython")))
PDF-
Git Modes
(use-package git-modes
:mode (("/.gitignore\\'" . gitignore-mode)
("/.dockerignore\\'" . gitignore-mode)))
Csv
(use-package csv-mode
:mode "\\.csv\\'"
:custom
(csv-invisibility-default nil)
(csv-align-max-width 999))
Po
;; (use-package po-mode
;; :commands po-mode)
Terraform
(use-package terraform-mode
:mode "\\.tf\\'"
:hook
(terraform-mode . (lambda ()
(require 'eglot)
(add-to-list 'eglot-server-programs '(terraform-mode . ("terraform-ls" "serve")))
(eglot-ensure))))
Fun
Play Free Software Song
(defun fk/play-free-software-song ()
"Play Richard Stallman's free software song."
(interactive)
(call-process-shell-command
"youtube-dl -f 251 'https://www.youtube.com/watch?v=9sJUDx7iEJw' -o - | ffplay -nodisp -autoexit -i -" nil 0))
;;(add-hook 'after-init-hook 'play-free-software-song)
Selectric Mode
(use-package selectric-mode
:straight (:files ("*"))
:commands selectric-mode)
Fireplace
;; TODO: find mp3 file does not work with straight
(use-package fireplace
:straight (:files ("*")) ; Fix fireplace.mp3 not found issue
:commands fireplace
:custom
(fireplace-sound-on t))
Pacmacs
(use-package pacmacs
:commands pacmacs)
2048
(use-package 2048-game
:commands 2048-game)
Artist Mode
(use-package artist
:straight (:type built-in)
:commands artist-mode
:bind
( :map artist-mode-map
("C-c C-c" . 'artist-select-operation)))
Rubik’s Cube
(use-package rubik
:commands rubik)
Packages I almost never use but want to keep
Turkish Mode
(use-package turkish
:commands turkish-mode turkish-correct-region turkish-asciify-region
:bind
( :map text
("a" . turkish-asciify-region)
:map turkish-mode-map
("C-g" . fk/turkish-mode-C-g))
:hook
(turkish-mode . fk/company-turkish-mode)
:config
(defun fk/company-grab-word-turkish (orig-func)
"Convert the word company grabbed to Turkish with `turkish-mode' before
processing it."
(let ((word (funcall orig-func)))
(with-temp-buffer
(insert word)
(turkish-correct-buffer)
(buffer-string))))
(define-minor-mode fk/company-turkish-mode
"Suggest candidates by the turkish version of the word."
:global t
(if fk/company-turkish-mode
(progn
(require 'turkish)
(fk/company-wordfreq-mode 1)
(setq ispell-local-dictionary-backup ispell-local-dictionary)
(setq ispell-local-dictionary "turkish")
(advice-add 'company-grab-word :around 'fk/company-grab-word-turkish))
(fk/company-wordfreq-mode -1)
(setq ispell-local-dictionary ispell-local-dictionary-backup)
(advice-remove 'company-grab-word 'fk/company-grab-word-turkish)))
(defun fk/turkish-mode-C-g ()
"Toggle last word with `C-g' if didn't like last correctify."
(interactive)
(if (eq last-command 'turkish-correct-last-word)
(turkish-toggle-last-word)
(keyboard-quit))))
Minimap
(use-package minimap
:commands minimap-mode)
Helm System Packages
(use-package helm-system-packages
:commands helm-system-packages)
Dimmer
(use-package dimmer
:commands dimmer-mode
:custom
(dimmer-fraction 0.5)
:config
(dimmer-configure-company-box)
(dimmer-configure-which-key)
(dimmer-configure-helm)
(dimmer-configure-magit)
(dimmer-configure-posframe)
;; I tried to fix lsp-ui-doc but it seems did not work
(defun fk/dimmer-lsp-ui-doc-p ()
"Return non-nil if current buffer is a lsp-ui-doc buffer."
(string-prefix-p " *lsp-ui-doc-" (buffer-name)))
(defun fk/dimmer-configure-lsp-ui-doc ()
"Convenience setting for lsp-ui-doc users.
This predicate prevents dimming the buffer you are editing when
lsp-ui-doc pops up a documentation."
(add-to-list
'dimmer-prevent-dimming-predicates 'dimmer-lsp-ui-doc-p))
(fk/dimmer-configure-lsp-ui-doc))
Focus
(use-package focus
:commands focus-mode
:config
(add-to-list 'focus-mode-to-thing '(python-mode . paragraph)))
Command Log Mode
(use-package command-log-mode
:commands command-log-mode)
Keypression
(use-package keypression
:commands keypression-mode
:custom
(keypression-cast-command-name t)
(keypression-combine-same-keystrokes t)
;;(keypression-use-child-frame t) ; broken
(keypression-font-face-attribute '(:width normal :height 150 :weight bold)))
Literate Calc Mode
(use-package literate-calc-mode
:commands literate-calc-minor-mode)