;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-
;; For e.g. GPG configuration, email clients, file templates and snippets.
(setq user-full-name "Nopanun Laochunhanun"
user-mail-address "nopanun@pm.me")
(when-let (authinfo (and IS-MAC "~/.authinfo.gpg")) ; Ghub's preference.
(setq auth-sources (cons authinfo (remove authinfo auth-sources))))
- System
- Personalization
- Keyboard Shortcuts
- Apps
- Time & language
- Accessibility
- Privacy & security
- Autoload
- Modules
- Local Configures
;;
;;; System
(setq shell-command-switch "-ic"
<<split-windows>>
;; Unicode ellispis are nicer than "...", and also save precious space.
truncate-string-ellipsis "…")
(global-subword-mode 1) ; Iterate through CamelCase words
Automatically resize and arrange windows on your screen
window-combination-resize t ; take new window space from all other windows
When I split a window, show prompt for which buffer I want to see
(defadvice! prompt-for-buffer (&rest _)
:after '(evil-window-split evil-window-vsplit)
(consult-buffer))
(unless (equal "Battery status not available" (battery)) ; on laptops…
(display-battery-mode 1)) ; it's nice to know how much power you have
- doom:scrath
When I bring up Doom’s scratch buffer with SPC x, it’s often to play with elisp or note something down (that isn’t worth an entry in my org files). I can do both in `lisp-interaction-mode’. @hlissner
(setq doom-scratch-initial-major-mode 'lisp-interaction-mode)
(setq-default delete-by-moving-to-trash t) ; Delete files to trash
Troubleshoot by load Key-chain Environment Variables tarsius/keychain-environment: Loads keychain environment variables into emacs Captured On: [2021-11-12 Fri 23:05]
(when (featurep 'keychain-environment-autoloads)
(keychain-refresh-environment))
Some advice from @hlissner on Why is Emacs/Doom slow? - Discussion / Doom Emacs Discourse Captured On: [2021-12-11 Sat 14:35]
- Disable idle code-completion (i.e. completion-as-you-type).
company-idle-delay nil
Add the above sexp to make code completion manual(on
C-SPC
,by default) That way, you only need to suffer that slowness once, when it is needed. - Disable some of Doom’s slowest modules.
- Turn off line numbers:
display-line-numbers-type nil
It’s known to slow down scrolling, in particular. Line numbers are pretty slow all around. The performance boost of disabling them outweighs the utility of always keeping them on.
- Turn off the “prettification” of org-mode, by disabling the
+pretty
flag on the:lang org
module (if you have it on).You can go to an extreme by disabling more of its eye-candy:
(after! org (setq org-fontify-quote-and-verse-blocks nil org-fontify-whole-heading-line nil org-hide-leading-stars nil org-startup-indented nil))
- Change your font(s). Emacs struggles to display some unicode characters with certain fonts. It sometimes struggles to display multiple fonts at once – as is the case if your primary font doesn’t support certain symbols (Emacs will fall back to another font to try to display them, which appears to be more expensive on some systems than others – or with some fonts, more than others)
- Replace the
:ui modeline
module with:ui (modeline +light)
. The doom-modeline can be unpredictably slow in some edge cases. The+light
variant is a work-in-progress replacement aiming to provide a lighter (albeit less featureful) mode line. - Check out “Common configuration mistakes”.
UI stuff
;;
;;; UI
(setq <<line-numbers-setting>>
<<my-treemacs-settings>>
<<my-theme-settings>>
<<my-fonts-settings>>)
<<my-frame-settings>>
<<my-theme-script>>
Look at the options and choose your mode
(default 'doom-one)
(light ',(nth (random 7)
'(
alabaster doom-alabaster doom-alabaster-bg
doom-acario-light doom-github
almost-mono-white almost-mono-cream)))
(dark ',(nth (random 7)
'(
aj-dark+ quartz doom-ayu-mirage doom-dracula
ahungry almost-mono-black almost-mono-gray)))
(custom 'doom-dracula)
default, light, dark, custom
or
auto 'auto
auto
(light-theme-begin 6) ; Hour to turn on `light' theme
(light-theme-end 17) ; Hour to turn off `light' theme
auto
mode is the automatic theme switching. If auto
mode was
set, doom-theme
will be set to the light
or dark
theme according to
the hour in the current time, light-theme-begin
and light-theme-end
.
auto
aj-dark+-blue-modeline t
doom-acario-light-brighter-modeline t
doom-modeline-height 30
doom-themes-treemacs-theme 'doom-colors
(use-package straight
:commands straight--build-dir
:init
(add-to-list 'custom-theme-load-path (straight--build-dir "aj-dark+-theme")))
(let ((my-doom-color '<<mode>>)) ; theme varies to the value of `my-doom-color'
(eval `(let ((<<auto-dark-mode>>)
<<my-theme-modes>>)
(if (eq ,my-doom-color 'auto)
(run-with-timer
0 3600 ; check for every hour
(defun synchronize-theme (light dark)
"Sets the theme according to the hour in the current time.
If the hour is (both inclusive) in `light-hours' then
`light' theme is loaded, otherwise `dark' theme is loaded."
(let* ((hour (string-to-number
(substring (current-time-string) 11 13)))
<<auto-mode-settings>>
(light-hours (number-sequence
light-theme-begin light-theme-end))
(now (if (member hour light-hours) light dark)))
(unless (equal now doom-theme)
(setq doom-theme now) (doom-init-theme-h))))
light dark)
;; Specific color mode
(setq doom-theme ,my-doom-color) (doom-init-theme-h)))))
- JetBrainsMono
- DejaVu Sans
- meslo-lg
doom-font (let ((font-family `(font-spec
:family ,(format "JetBrains%sMono"
(if IS-MAC " " "")))))
(setq vertico-posframe-font (eval `(,@font-family :size 15)))
(eval `(,@font-family :size 12 :weight 'light)))
doom-serif-font (font-spec :family "DejaVu Sans" :size 13)
doom-unicode-font (font-spec :family "Meslo LG M")
doom-variable-pitch-font doom-serif-font
variable-pitch-serif-font (font-spec :family "Alegreya" :size 24)
display-line-numbers-type nil
(dolist (params '(<<frame-size>>
<<mouse-color>>
<<prevents-flickering>>
<<scroll-bar>>))
(add-to-list 'default-frame-alist params))
(height . 50) (width . 162)
(mouse-color . "red")
(inhibit-double-buffering . t)
(scroll-bar-width . 11)
Header line powered by minibuffer-header
(use-package minibuffer-header
:commands minibuffer-header-mode
:hook (minibuffer-setup . minibuffer-header-mode))
- Due to the development state, it may have some bugs. If that’s the case, here
is how to disable it:
(remove-hook! minibuffer-setup #'minibuffer-header-mode) (minibuffer-header-mode -1)
The feature of moving the mode line to the top is useful for MacBook M1 Pro.
(defun mode-line-enable-top ()
"Display mode line on the top side."
(interactive)
(when mode-line-format (setq header-line-format mode-line-format
mode-line-format nil)))
(defun mode-line-enable-bottom ()
"Display mode line on the bottom side."
(interactive)
(when header-line-format (setq mode-line-format header-line-format
header-line-format nil)))
Keybinds to the above commands with <leader> t prefix.
:desc "Up modeline" "6" #'mode-line-enable-top
:desc "Down modeline" "h" #'mode-line-enable-bottom
;; Roll the mouse wheel to scrolls the display pixel-by-pixel.
(when (fboundp #'pixel-scroll-precision-mode) ; EMACS29+
(pixel-scroll-precision-mode t))
For the speed of expressions
;;
;;; keybinds
(map! (:after dabbrev
<<Swap =M-/= and =C-M-/=>>)
:desc "Load doom-theme on the fly" "<f5>" (cmd! (doom-init-theme-h))
:desc "Org-capture bin" "s-X" #'+org-capture/open-frame
;;; C-c
(:prefix ("C-c" . "mode-specific-map")
(:when (modulep! :tools eval)
:desc "Evaluate line/region" "e" #'+eval/line-or-region
:desc "Evaluate & replace region" "E" #'+eval/region-and-replace)
(:when (modulep! :checkers grammar)
"g" #'writegood-mode
"C-g g" #'writegood-grade-level
"C-g e" #'writegood-reading-ease))
(:when IS-MAC
:desc "Next buffer" "s-]" #'next-buffer
:desc "Previous buffer" "s-[" #'previous-buffer)
;;
;;; evil
:when (modulep! :editor evil)
:desc "Next file" "M-]" #'+evil/next-file
:desc "Previous file" "M-[" #'+evil/previous-file
:n "g+" #'evil-numbers/inc-at-pt
:v "g+" #'evil-numbers/inc-at-pt-incremental
:nv "g=" #'er/expand-region
:gi "C-=" #'er/expand-region
:n "C-0" #'doom/reset-font-size
:n "C-+" #'text-scale-increase
:n "M-C-+" #'doom/increase-font-size
:ng "S-<left>" #'evil-window-left
:ng "S-<right>" #'evil-window-right
:ng "S-<up>" #'evil-window-up
:ng "S-<down>" #'evil-window-down
(:when (or IS-WINDOWS
(getenv "WSLENV"))
:n "C-SPC" #'just-one-space)
<<dashboard>>
;;; Org extra functionality
(:map org-mode-map
<<f12 --- Transclusion Add>>
;; <<C-c e --- Toggle Emphasis>>
:localleader
<<leader m O --- Outline Tree>>)
<<:ui hydra/Window Navigation>>
;;; :emacs dired
(:when (modulep! :emacs dired +dirvish)
<<Overide dired-mode-map>>
)
<<vterm-kbd>>
<<cape:bind global keys in normal mode>>
;;; C-x
(:prefix "C-x"
<<cape:prefix-map>>)
;;; <<default>>
(:when (modulep! <<default>>)
<<+default>>
;;; :ui
(:when (modulep! :ui popup)
:desc "Open this buffer in a popup" "C-x j" #'+popup/buffer)
(:when (modulep! :ui workspaces)
(:when IS-MAC
:desc "Next workspace" "s-}" #'+workspace/switch-right
:desc "Previous workspace" "s-{" #'+workspace/switch-left))
;;; <leader>
(:when (modulep! <<default>> +bindings)
(:leader
<<+hydra/text-zoom evil>>
;; <leader> f --- file
(:prefix-map ("f" . "file") :desc "Find dotfile" "." #'find-dotfile)
;; <leader> g --- git/version control
(:prefix-map ("g" . "git")
(:prefix ("l" . "list")
<<leader g l … --- gist commands>>))
;; <leader> n --- notes
(:prefix-map ("n" . "notes")
<<leader n T --- Org Transclusion Mode>>)
;; <leader> o --- open
(:prefix-map ("o" . "open")
<<doom-leader-open-map>>)
;; <leader> p --- project
(:prefix-map ("p" . "project")
:when (modulep! :tools prodigy)
:desc "services" "t" #'prodigy)
;; <leader> s --- search
(:prefix-map ("s" . "search")
(:prefix-map ("a" . "Search in applications")
<<leader s a d --- Dash>>))
;; <leader> t --- toggle
(:prefix-map ("t" . "toggle")
<<doom-leader-toggle-map>>)))))
Keyboard Shortcut to open apps with <leader> o prefix.
:desc "Calc" "c" #'calc
:desc "APP: rss" "," #'=rss
Open websites that can open in Emacs instead of a browser
Use nndiscourse
package as A Gnus
backend for Discourse. Access by M-x gnus
;; Applies to first-time Gnus users
(setq gnus-select-method '(nndiscourse "discourse.mozilla.org"
(nndiscourse-scheme "https")))
mastodon-instance-url "https://mstdn.io"
** Setting your default reddits
You can subscribe to different reddits by customizing your
~md4rd-subs-active~ variable.
#+begin_src emacs-lisp
(setq md4rd-subs-active
'(
<<your-subreddits>>))
#+end_src
(A cool feature of reddit is you can view multiple reddits combined with
the =+= concatenation)
** Re-authenticating your session on sign in and every hour
Add something like this into your config:
#+begin_src emacs-lisp :tangle yes
(after! md4rd
<<md4rd-conf>>)
#+end_src
emacs+doomemacs+orgmode lisp+Common_Lisp+prolog+clojure javascript
linux firefox ProgrammerHumor programming+learnprogramming webdev
guix+nixos BeMyReference hackernews graphql cscareerquestions
(let ((reddit-auth (lambda (type)
(funcall (plist-get (car (auth-source-search
:user type))
:secret)))))
(setq md4rd-subs-active
'(
<<your-subreddits>>)
md4rd--oauth-access-token (funcall
reddit-auth <<your-access-token-here>>)
md4rd--oauth-refresh-token (funcall
reddit-auth <<your-refresh-token-here>>)))
(run-with-timer 0 3540 #'md4rd-refresh-login)
"me^access-token"
"me^refresh-token"
Extra colors for Info-mode
Replace isearch
functions with more reliable browser-like experience.
A modern file manager based on dired-mode
, ranger but is more bare-bone.
(after! dired
;; Enable file preview when narrowing files in minibuffer(may navigate slow)
(dirvish-peek-mode))
"C-c f" #'dirvish-fd
:map dirvish-mode-map
;; left click for expand/collapse dir or open file
"<mouse-1>" #'dirvish-subtree-toggle-or-open
;; middle click for opening file / entering dir in other window
"<mouse-2>" #'dired-mouse-find-file-other-window
;; right click for opening file / entering dir
"<mouse-3>" #'dired-mouse-find-file
[remap dired-sort-toggle-or-edit] #'dirvish-quicksort ; o
[remap dired-do-redisplay] #'dirvish-ls-switches-menu ; r
[remap dired-do-copy] #'dirvish-yank-menu ; C
;;
;;; Time & language
(display-time-mode 1) ; Enable time in the mode-line
"M-/" #'dabbrev-completion ; Swap M-/ and C-M-/
"C-M-/" #'dabbrev-expand
;;
;;; Accessibility
;; Nice scrolling
(setq scroll-conservatively 100000
scroll-preserve-screen-position 1) ; Don't have `point' jump around
(setq-default x-stretch-cursor t) ; Stretch cursor to the glyph width
;;
;;; Security
(setq password-cache-expiry nil) ; I can trust my computers … can't I?
This section contains the code + autoload functions in autoload.el
or
autoload/*.el
to get the benefits of lazy loading.
- What “autoload” means?
From Emacs manual Concept:
Some commands are “autoloaded”; when you run them, Emacs automatically loads the associated library first. For instance, the ‘M-x compile’ command (*note Compilation::) is autoloaded; if you call it, Emacs automatically loads the ‘compile’ library first. In contrast, the command ‘M-x recompile’ is not autoloaded, so it is unavailable until you load the ‘compile’ library.
This also means that the Emacs load time can be saved by lazy load of libraries, it will loads only when it needs. It is available because it’s load path was indexed at
doom sync
time.
;;; autoload/random.el -*- lexical-binding: t; -*-
;;;###autoload
(defun one-or-another (one another)
"Return randomly chosen one from a given of 2 choices."
(interactive "s1: \ns2: ")
(message (if (eq 0 (random 2)) one another)))
;;;###autoload
(defun random-achar ()
"Random a key character on *Select Link* buffer"
(interactive)
(let* ((achar "abcdefghijklmnopqrstuvwxyz1234567890:;<=>?@")
(i (% (abs (random)) (length achar))))
(message (substring achar i (1+ i)))))
;;; autoload/string.el -*- lexical-binding: t; -*-
josh sam jed C.J. toby ----- Mark region then M-x arrayify RET enter Quote: " Join: ,
"josh", "sam", "jed", "C.J.", "toby"
;;;###autoload
(defun arrayify (start end quote join)
"Turn strings on newlines into a QUOTEd, comma-separated one-liner."
(interactive "r\nMQuote: \nMJoin: ")
(let ((insertion
(mapconcat
(lambda (x) (format "%s%s%s" quote x quote))
(split-string (buffer-substring start end)) join)))
(delete-region start end)
(insert insertion)))
;;
;;; Modules
#+TITLE: app/edit-server
#+DATE: January 13, 2022
#+SINCE: v3.0.0-alpha
#+STARTUP: inlineimages nofold
* Table of Contents :TOC_3:noexport:
- [[#description][Description]]
- [[#maintainers][Maintainers]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#prerequisites][Prerequisites]]
- [[#features][Features]]
- [[#configuration][Configuration]]
- [[#troubleshooting][Troubleshooting]]
* Description
Server that responds to edit requests from Chrome or Firefox
** Maintainers
+ [[https://github.com/thaenalpha][@thaenalpha]] (Author)
** Module Flags
This module provides no flags.
** Plugins
+ [[https://github.com/stsquad/emacs_chrome][edit-server]]
* Prerequisites
Install dependency: Edit with Emacs Chrome/Firefox extension
[[https://github.com/stsquad/emacs_chrome][stsquad/emacs_chrome: A Chromium "clone" of It's All Text for spawni...]]
* Features
# An in-depth list of features, how to use them, and their dependencies.
* Configuration
Set major mode for each site.
* Troubleshooting
# Common issues and their solution, or places to look for help.
;;; app/edit-server/config.el -*- lexical-binding: t; -*-
(use-package! edit-server
:hook (after-init . edit-server-start)
:config (setq edit-server-new-frame-alist
`((name . "Edit with Emacs FRAME")
(top . 200)
(left . 200)
(width . 80)
(height . 25)
(minibuffer . t)
(menu-bar-lines . t)))
(setq edit-server-url-major-mode-alist
'(("github\\.com" . markdown-mode))))
Installing edit-server from MELPA
install
as a doom :app module (You need to addedit-server
at :app ininit.el
)
;; -*- no-byte-compile: t; -*-
;;; app/edit-server/packages.el
(package! edit-server)
uninstall
by replace:tangle
value ininstall
block withno
and just press enter at the block below:rm -r modules/app/edit-server
;;; :app mastodon
(after! mastodon
(setq <<Mastodon Configuration>>))
#+TITLE: app/mastodon
#+DATE: January 13, 2022
#+SINCE: v3.0.0-alpha
#+STARTUP: inlineimages nofold
* Table of Contents :TOC_3:noexport:
* Description
Enjoy Mastodon from emacs.
+
** Maintainers
+ [[https://github.com/thaenalpha][@thaenalpha]] (Author)
** Module Flags
This module provides no flags.
** Plugins
+ [[https://git.blast.noho.st/mouse/mastodon.el][mastodon]]
** Hacks
# A list of internal modifications to included packages; omit if unneeded
* Prerequisites
This module has no prerequisites.
* Features
# An in-depth list of features, how to use them, and their dependencies.
* Configuration
# How to configure this module, including common problems and how to address them.
* Troubleshooting
# Common issues and their solution, or places to look for help.
(package! mastodon)
;;; :app reddit
(after! md4rd
<<md4rd-conf>>)
#+TITLE: app/reddit
#+DATE: January 31, 2022
#+SINCE: 3.0.0-alpha
#+STARTUP: inlineimages nofold
* Table of Contents :TOC_3:noexport:
- [[#description][Description]]
- [[#maintainers][Maintainers]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#prerequisites][Prerequisites]]
- [[#features][Features]]
- [[#configuration][Configuration]]
- [[#troubleshooting][Troubleshooting]]
* Description
Mode for reddit (browse it).
** Maintainers
+ [[https://github.com/thaenalpha][@thaenalpha]] (Author)
** Module Flags
This module provides no flags.
** Plugins
+ [[https://github.com/ahungry/md4rd][md4rd]]
* Prerequisites
This module has no prerequisites.
* Features
Please refer [[https://github.com/ahungry/md4rd][ahungry/md4rd's README]].
* Configuration
<<:app reddit's README Configuration>>
* Troubleshooting
# Common issues and their solution, or places to look for help.
;;; app/reddit/config.el -*- lexical-binding: t; -*-
(use-package! md4rd :hook (md4rd-mode . md4rd-indent-all-the-lines))
;;; :app rss +org
(after! elfeed
<<elfeed-conf>>)
Hook elfeed-update
to elfeed-search-mode-hook
(add-hook! elfeed-search-mode #'elfeed-update)
(setq rmh-elfeed-org-auto-ignore-invalid-feeds t)
;;; app/slack/config.el -*- lexical-binding: t; -*-
;; Slack
;; doc: https://github.com/yuya373/emacs-slack
;; To get a token:
;; - Open Chrome and sign into slack at https://my.slack.com/customize
;; - From the dev tools console type: TS.boot_data.api_token
;;
;; To get a cookie:
;; - Get cookie entry "d" with its default encoded value
(use-package! slack
:commands (slack-start)
:bind ("M-S" . slack-select-rooms)
:custom
(slack-buffer-emojify t)
(slack-prefer-current-team t)
:config
(slack-register-team
:default t
:name "C"
:token (+pass-get-secret "slack/c/token")
:cookie (+pass-get-secret "slack/c/cookie")
:full-and-display-names t)
(slack-register-team
:name "OR"
:token (+pass-get-secret "slack/or/token")
:cookie (+pass-get-secret "slack/or/cookie")
:full-and-display-names t)
(slack-register-team
:name "MA"
:token (+pass-get-secret "slack/ma/token")
:cookie (+pass-get-secret "slack/ma/cookie")
:full-and-display-names t)
(evil-define-key 'normal slack-mode-map
",ra" 'slack-message-add-reaction
",rr" 'slack-message-remove-reaction
",rs" 'slack-message-show-reaction-users
",me" 'slack-message-edit
",md" 'slack-message-delete
",mu" 'slack-message-embed-mention
",mc" 'slack-message-embed-channel)
(evil-define-key 'normal slack-edit-message-mode-map
",k" 'slack-message-cancel-edit
",mu" 'slack-message-embed-mention
",mc" 'slack-message-embed-channel))
(add-hook! 'slack-mode-hook 'variable-pitch-mode)
;; Trigger alerts
;; doc: https://github.com/jwiegley/alert
(use-package! alert
:commands (alert)
:custom (alert-default-style 'notifier))
;; Custom function from @noonker
;; src: https://github.com/noonker/doom-emacs/blob/main/config.org#slack-1
(defun +slack/slk ()
"start slack"
(interactive)
(slack-start)
(cl-defmethod slack-buffer-name ((_class (subclass slack-room-buffer))
room team)
(slack-if-let* ((room-name (slack-room-name room team)))
(format ":%s"
room-name)))
(slack-change-current-team))
telega depends on the visual-fill-column and rainbow-identifiers packages. This dependency automatically installs if you install telega from MELPA or GNU Guix. Otherwise, you will need to install these packages by hand.
telega is built on top of the official library provided by Telegram TDLib. Most distributions do not provide this package in their repositories, in which case you will have to install it manually by following the instructions.
GNU Guix, however, does have both telega and TDLib packaged. If you use GNU Guix you can skip directly to Installing from GNU Guix.
Look for all dependencies at Telega Manual (v0.7.018)
Guix usersguix install gperf
TDLib is the library for building Telegram clients. It requires a large amount of memory to be built. Make sure you are using TDLib version greater or equal to 1.7.0.
On MacOS you can install a pre-built TDLib package using Homebrew from brew.sh. Just run:
brew install tdlib
On Linux, you will need to build TDLib from source.
To get the source:
git clone https://github.com/tdlib/td.git
Move into the folder with $ cd ./td or wherever you checked out td.
Prepare a folder for building the library:
cd ./td && mkdir build && cd build && cmake ../
Build the sources:
cd ./td/build && make -jN # Change N first, read a description below.
With N number of cores that should be used for the compilation (the optimal value is the number of physical cores on the machine).
Finally, to install the library system-wide:
cd ./td/build && sudo make install
It will install headers to /usr/local/include
and library itself into
/usr/local/lib
.
If you have TDLib installed in other location, don’t forget to modify
telega-server-libs-prefix
before starting telega.
install
as a doom module (You need to addtelega
at :app ininit.el
)uninstall
by replace:tangle
value ininstall
block withno
and just press enter at the block below:rm -r modules/app/telega
;;; app/telega/config.el -*- lexical-binding: t; -*-
(use-package! telega-server
:defer t
:config
(cond ((and IS-MAC
;; Is user using home brew and Apple silicon Mac?
(file-directory-p "/opt/homebrew/Cellar/tdlib"))
(setq telega-server-libs-prefix "/opt/homebrew"))
(IS-LINUX (cond
;; Or a Guix user?
((file-directory-p "~/.guix-profile/lib/tdlib")
(setq telega-server-libs-prefix "~/.guix-profile"))
;; Or a Nix user?
((file-directory-p "~/.nix-profile/lib/tdlib")
(setq telega-server-libs-prefix "~/.nix-profile"))))))
;;; :checkers syntax
(after! flycheck
(flycheck-add-mode 'html-tidy 'web-mode)
(setq flycheck-tidyrc (expand-file-name "~/.tidyrc")
flycheck-javascript-eslint-executable "eslint_d"
flycheck-stylelintrc ".stylelintrc.json"
flycheck-global-modes '(not org-mode)))
- Features
- Key bindings (evil)
;;; :completion corfu (:when (modulep! :completion corfu) :i "C-@" #'completion-at-point :i "C-SPC" #'completion-at-point)
;; Omni-completion, Bind dedicated completion commands (:when (modulep! :completion corfu) :i "C-p" #'completion-at-point ; capf :i "C-l" #'cape-line :i "C-k" #'+cape/dict-or-keywords :i "C-a" #'cape-abbrev :i "s" #'cape-ispell (:unless (modulep! :completion company) :i "C-s" #'+cape/yasnippet) :i "C-d" #'cape-dabbrev :i "d" #'dabbrev-completion :i "C-f" #'cape-file :i "C-'" #'cape-symbol :i "C-]" #'complete-tag ; etags :i "C-\\" #'cape-tex :i "&" #'cape-sgml :i "C-r" #'cape-rfc1345)
- Key bindings (evil)
- Configuration
- From corfu/README.org
- From Corfu Wiki
- Additional movement commands
(defun corfu-beginning-of-prompt () "Move to beginning of completion input." (interactive) (corfu--goto -1) (goto-char (car completion-in-region--data))) (defun corfu-end-of-prompt () "Move to end of completion input." (interactive) (corfu--goto -1) (goto-char (cadr completion-in-region--data)))
- Transfer to the minibuffer
(defun corfu-move-to-minibuffer () (interactive) (let ((completion-extra-properties corfu--extra) completion-cycle-threshold completion-cycling) (apply #'consult-completion-in-region completion-in-region--data)))
- Additional movement commands
#+TITLE: completion/corfu
#+DATE: December 6, 2021
#+SINCE: v3.0.0-alpha
#+STARTUP: inlineimages nofold
* Table of Contents :TOC_3:noexport:
- [[#description][Description]]
- [[#maintainers][Maintainers]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#hacks][Hacks]]
- [[#prerequisites][Prerequisites]]
- [[#features][Features]]
- [[#configuration][Configuration]]
- [[#troubleshooting][Troubleshooting]]
* Description
[[https://github.com/minad/corfu][GitHub - minad/corfu: corfu.el - Completion Overlay Region FUnction]]
in the form of doom module by [[https://git.sr.ht/~gagbo][@Gerry_Agbobada]]. The primary packages are:
+ Corfu, which provides a completion overlay region function
+ Cape, which provides a bunch of Completion At Point Extensions
+ kind-icon, which provides an colorful icon/text prefix based on :company-kind
+ Company, which provides completion backends to be converted to Capf
** Maintainers
+ [[https://git.sr.ht/~gagbo][@Gerry_Agbobada]] (Author)
+ [[https://github.com/thaenalpha][@thaenalpha]]
** Module Flags
+ =+orderless= optionally use the [[https://github.com/oantolin/orderless][orderless]] completion style which provides
better filtering methods by filters through candidates those match space-
separated regular expressions.
+ =+tabnine= use Tabnine code completion for capf
** Plugins
+ [[https://github.com/minad/corfu][corfu]]
+ [[https://github.com/minad/cape][cape]]
+ [[https://github.com/jdtsmith/kind-icon][kind-icon]]
+ [[https://github.com/oantolin/orderless][orderless]] (=+orderless=)
+ [[https://github.com/company-mode/company-mode][company-mode]]
+ <<company-tabnine>>
+ [[https://github.com/galeo/corfu-doc][corfu-doc]] (=+childframe=)
** Hacks
# A list of internal modifications to included packages; omit if unneeded
* Prerequisites
This module has no prerequisites.
* Features
** TAB-and-Go completion
Pressing TAB moves to the next candidate and further input will then commit the
selection.
** Code completion
By default, completion is triggered after a short idle period or with the
=C-SPC= key. While the popup is visible, the following keys are available:
| Keybind | Description |
|---------+------------------------------------------|
| =C-n= | Go to next candidate |
| =C-p= | Go to previous candidate |
| =C-j= | (evil) Go to next candidate |
| =C-k= | (evil) Go to previous candidate |
| =C-h= | Display documentation (if available) |
| =C-u= | Move to previous page of candidates |
| =C-d= | Move to next page of candidates |
| =C-SPC= | Complete common |
| =TAB= | Complete common or select next candidate |
| =S-TAB= | Select previous candidate |
** Vim-esque omni-completion prefix (C-x)
In the spirit of Vim's omni-completion, the following insert mode keybinds are
available to evil users to access specific company backends:
| Keybind | Description |
|-----------+-----------------------------------|
| =C-x C-]= | Complete etags |
| =C-x C-f= | Complete file path |
| =C-x C-k= | Complete from dictionary/keyword |
| =C-x C-l= | Complete full line |
| =C-x C-p= | Invoke complete-at-point function |
| =C-x C-'= | Complete symbol at point |
| =C-x C-s= | Complete snippet |
| =C-x s= | Complete spelling suggestions |
| =C-x C-d= | Complete Corfu dabbrev at point |
| =C-x d= | dabbrev-completion at point |
* Configuration
#+begin_quote
Corfu is highly flexible and customizable via ~corfu-*~ customization variables.
For filtering I recommend to give Orderless completion a try, which is
different from the familiar prefix TAB completion. Corfu can be used with the
default completion styles, the use of Orderless is not a necessity. See also
the [[https://github.com/minad/corfu/wiki][Corfu Wiki]] for additional configuration tips. In particular the Lsp-mode
configuration is documented in the Wiki.
#+end_quote
--- @minad (Author of Cape, Corfu, Vertico)
Here is an example configuration:
** Optional customizations
#+begin_src emacs-lisp
(use-package corfu
:custom
(corfu-cycle t) ; Enable cycling for `corfu-next/previous'
(corfu-auto t) ; Enable auto completion
(corfu-commit-predicate nil) ; Do not commit selected candidates on
; next input
(corfu-quit-at-boundary t) ; Automatically quit at word boundary
(corfu-quit-no-match t) ; Automatically quit if there is no match
(corfu-preview-current nil) ; Disable current candidate preview
(corfu-preselect-first nil) ; Disable candidate preselection
(corfu-echo-documentation nil) ; Disable documentation in the echo area
(corfu-scroll-margin 5)) ; Use scroll margin
#+end_src
** Enable Corfu only for certain modes.
This module enable ~global-corfu-mode~ by default. You may want to enable
Corfu only for certain modes.
#+begin_src emacs-lisp
(use-package corfu
:hook ((prog-mode . corfu-mode)
(shell-mode . corfu-mode)
(eshell-mode . corfu-mode))
#+end_src
** Completion style
Tune the global completion style settings to your liking!
This affects the minibuffer and non-lsp completion at point.
#+begin_src emacs-lisp :noweb no-export
(use-package orderless
:defer t
:when (modulep! :completion corfu +orderless)
:init
<<optional>>
(setq completion-styles '(orderless)
completion-category-defaults nil
completion-category-overrides '(<<file-styles>>)))
#+end_src
The =+orderless= feature enable ~partial-completion~ for files to allow path
expansion by default. You may prefer to use ~initials~ instead.
#+name: file-styles
#+begin_src emacs-lisp :tangle no
(file (styles . (initials)))
#+end_src
See ~+orderless-dispatch~ in the Consult wiki for an advanced Orderless style
dispatcher.
#+name: optional
#+begin_src emacs-lisp :tangle no
(setq orderless-style-dispatchers '(+orderless-dispatch)
orderless-component-separator #'orderless-escapable-split-on-space)
#+end_src
** More additional
See also the [[https://github.com/minad/corfu/wiki][Corfu Wiki]] for additional configuration tips. For more general
documentation read the chapter about completion in the [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Completion.html][Emacs manual]]. If you
want to create your own Capfs, you can find documentation about completion in
the [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Completion.html][Elisp manual]].
* Troubleshooting
** Auto trigger commands
If Corfu is not triggering completion with ~corfu-auto~ it can be the case that the command was not called with ~self-insert-command~ or one of the other commands registered in the list ~corfu-auto-commands~. You can fix this by adding the commands you are missing to the list.
#+begin_src emacs-lisp
(add-to-list 'corfu-auto-commands 'some-special-insert-command)
#+end_src
;;; completion/company/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(defvar +completion-at-point-function-alist
'((text-mode cape-dabbrev +cape/yasnippet cape-ispell)
(prog-mode +cape/yasnippet)
(conf-mode company-dabbrev-code +cape/yasnippet))
"An alist matching modes to completion-at-point-functions. The capfs for any mode is
built from this.")
;;;###autodef
(defun set-completion-at-point-function! (modes &rest capfs)
"Prepends CAPFS (in order) to `completion-at-point-functions' in MODES.
MODES should be one symbol or a list of them, representing major or minor modes.
This will overwrite capfs for MODES on consecutive uses.
If the car of CAPFS is nil, unset the capfs for MODES.
Examples:
(set-completion-at-point-function! 'js2-mode
'company-tide 'company-yasnippet)
(set-completion-at-point-function! 'sh-mode
'(company-shell :with +cape/yasnippet))
(set-completion-at-point-function! '(c-mode c++-mode)
'(:separate company-irony-c-headers company-irony))
(set-completion-at-point-function! 'sh-mode nil) ; unsets capfs for sh-mode"
(declare (indent defun))
(dolist (mode (doom-enlist modes))
(if (null (car capfs))
(setq +completion-at-point-function-alist
(delq (assq mode +completion-at-point-function-alist)
+completion-at-point-function-alist))
(setf (alist-get mode +completion-at-point-function-alist)
capfs))))
;;;###autoload
(defun +cape/dict-or-keywords (&optional interactive)
"`corfu-mode' completion combining `cape-dict' and `cape-keywords'.
If INTERACTIVE is nil the function acts like a capf."
(interactive (list t))
(if interactive
(cape--interactive #'+cape/dict-or-keywords)
(funcall (cape-super-capf #'cape-keyword #'cape-dict))))
;;;###autoload
(defun +cape/yasnippet (&optional interactive)
"`corfu-mode' completion for `yasnippet'
If INTERACTIVE is nil the function acts like a capf."
(interactive (list t))
(if interactive
(cape--interactive #'+cape/yasnippet)
(funcall (cape-company-to-capf #'company-yasnippet))))
;;; completion/corfu/config.el -*- lexical-binding: t; -*-
;; Reset lsp-completion provider and optionally configure the cape-capf-buster.
(add-hook! 'doom-init-modules-hook
(after! lsp-mode
(setq lsp-completion-provider :none)
(setq-local completion-at-point-functions
(list #'+cape/yasnippet
(cape-capf-buster #'lsp-completion-at-point)
<<tabnine>>))))
;; Pad before lsp modeline error info
(add-hook! 'lsp-mode-hook
(setf (caadr (assq 'global-mode-string mode-line-misc-info)) " "))
;; Set orderless filtering for LSP-mode completions
(add-hook! 'lsp-completion-mode-hook
(setf (alist-get 'lsp-capf completion-category-defaults)
'((styles . (orderless)))))
;; Fallback cleanly to consult in TUI
(setq-default completion-in-region-function #'consult-completion-in-region)
(use-package! corfu
:commands corfu-complete
:hook (doom-first-input . global-corfu-mode)
:bind (:map corfu-map
("TAB" . corfu-next)
([tab] . corfu-next)
("S-TAB" . corfu-previous)
([backtab] . corfu-previous)
([remap move-beginning-of-line] . corfu-beginning-of-prompt)
([remap move-end-of-line] . corfu-end-of-prompt)
("M-m" . corfu-move-to-minibuffer)
([return] . corfu-insert))
:custom
(corfu-cycle t) ;; Enable cycling for `corfu-next/previous'
(corfu-auto t) ;; Enable auto completion
(corfu-auto-prefix 0)
(corfu-auto-delay 0.15)
(corfu-echo-documentation 0.3)
(corfu-preselect-first nil) ;; Disable candidate preselection
(corfu-on-exact-match 'quit)
:config
<<additional-move-cmds>>
<<to-minibuffer>>
(when (and (featurep 'evil) (modulep! :config default +bindings))
;; https://github.com/minad/corfu/issues/12#issuecomment-869037519
(advice-add #'corfu--setup :after #'evil-normalize-keymaps)
(advice-add #'corfu--teardown :after #'evil-normalize-keymaps)
(evil-make-intercept-map corfu-map)
;; Don't persist corfu popups when switching back to normal mode.
(add-hook! 'evil-normal-state-entry-hook
(when corfu--candidates (corfu-quit)))
(map! (:map corfu-map
"SPC" #'corfu-insert-separator
"C-n" #'corfu-next
"C-p" #'corfu-previous
"C-j" #'corfu-next
"C-k" #'corfu-previous
"C-u" #'corfu-scroll-down
"C-d" #'corfu-scroll-up
"C-v" #'corfu-scroll-up
"C-s" #'corfu-move-to-minibuffer
"C-`" #'corfu-move-to-minibuffer
"C-x j" #'corfu-move-to-minibuffer
"C-S-s" #'completion-at-point
"M-p" #'corfu-doc-scroll-down
"M-n" #'corfu-doc-scroll-up
"M-d" #'corfu-doc-toggle))))
;;
;;; Packages
(use-package! corfu-doc
:when (modulep! +childframe)
:hook (corfu-mode . corfu-doc-mode))
;; Enable `partial-completion' for files to allow path expansion.
;; You may prefer to use `initials' instead of `partial-completion'.
(use-package! orderless
:defer t
:when (modulep! +orderless)
:init
;; Optionally configure the first word as flex filtered.
(add-hook 'orderless-style-dispatchers
(defun my/orderless-dispatch-flex-first (_pattern index _total)
(and (eq index 0) 'orderless-flex))
nil 'local)
(setq completion-styles '(orderless partial-completion)
completion-category-defaults nil
completion-category-overrides
'((file (styles . (partial-completion))))))
(use-package! kind-icon
:after corfu
:custom
;; to compute blended backgrounds correctly
(kind-icon-default-face 'corfu-default)
:config
(add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))
(use-package! cape
:defer t
:init
(dolist (capf (list #'cape-file #'cape-dabbrev #'cape-keyword
#'cape-symbol #'+cape/yasnippet
<<tabnine>>))
(add-to-list 'completion-at-point-functions capf)))
;; TAB CYCLE if there are only a few candidates, otherwise show menu
(setq completion-cycle-threshold 1)
(when EMACS28+;hide commands in M-x which do not apply to the current mode.
;; Corfu commands are hidden, since they are not supposed to be used via M-x.
(setq read-extended-command-predicate
#'command-completion-default-include-p))
;; Enable indentation+completion using the TAB key.
;; `completion-at-point' is often bound to M-TAB.
(setq tab-always-indent 'complete)
;; Dirty hack to get c completion running
;; Discussion in https://github.com/minad/corfu/issues/34
(when (equal tab-always-indent 'complete)
(map! :map c-mode-base-map
:i [remap c-indent-line-or-region] #'completion-at-point))
(defun corfu-enable-always-in-minibuffer ()
"Enable Corfu in the minibuffer if Vertico/Mct are not active."
(unless (or (bound-and-true-p mct--active)
(bound-and-true-p vertico--input))
;; (setq-local corfu-auto nil) Enable/disable auto completion
(corfu-mode 1)))
(add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1)
no auto complete, quitting at boundary and quitting if there is no match.
(add-hook! eshell-mode
(setq-local corfu-quit-at-boundary t
corfu-quit-no-match t
corfu-auto nil)
(corfu-mode))
;; -*- no-byte-compile: t; -*-
;;; completion/corfu/packages.el
(package! corfu)
(when (modulep! +orderless)
(package! orderless))
(package! kind-icon)
(package! cape :recipe (:host github :repo "minad/cape" :branch "main"))
(when (modulep! +childframe)
(package! corfu-doc))
(unless (modulep! :completion company) (package! company))
- company-tabnine (
+tabnine
)
This configuration come as a flag but it isn’t bundled with company module. I put the flag for my convenient to enable/disable package.
Install package in packages.el
. Enable by add flag to :completion company
;;; :completion company +tabnine
(when (modulep! :completion company +tabnine)
(add-to-list 'company-backends #'company-tabnine)
(after! company
(setq <<company-idle-delay>>
+lsp-company-backends
'(company-tabnine :separate company-capf company-yasnippet)
company-show-numbers t
company-idle-delay 0)))
This configuration come as a flag and is bundled with corfu module.
Enable by add +tabnine
flag to :completion corfu
- tabnine/
[[https://github.com/tommyx12/company-tabnine][company-tabnine]] (=+tabnine=)
(when (modulep! +tabnine) (cape-company-to-capf #'company-tabnine))
(when (modulep! +tabnine) (package! company-tabnine :recipe (:host github :repo "tommyX12/company-tabnine")))
+childframe
To enable vertico-posframe.vertico-posframe is an vertico extension, which lets vertico use posframe to show its candidates menu.
;;; :completion vertico +childframe (when (modulep! :completion vertico +childframe) (setq vertico-posframe-border-width 10 vertico-posframe-parameters '(<<show fringe to vertico-posframe>> (min-width . 90)) vertico-posframe-poshandler #'posframe-poshandler-<<position>>))
(left-fringe . 8) (right-fringe . 8)
Let center and top of posframe(0.5, 0) align to a position, which x = center of frame(0.5, y) and y = y position under point(x, 1) by set <<position>> to
p0.5p0-to-f0.5p1
More info can be found in docstring of
posframe-show
.
:config default
More keybinds for literate configs
(:map help-map
(:when (featurep 'keychain-environment-autoloads)
"rk" #'keychain-refresh-environment)
:prefix "d"
:desc "init.org" "i" (cmd! (find-file
(expand-file-name "init.org" doom-private-dir)))
:desc "config.org" "o" (cmd! (find-file
(expand-file-name "config.org" doom-private-dir)))
:desc "packages.org" "po" (cmd! (find-file
(expand-file-name "packages.org" doom-private-dir))))
Specify the directory in which your notes are stored:
;;; ui: deft
(setq deft-directory "~/notes")
- Dashboard Quick Actions
The config idea is come from Tecosaur’s Emacs configuration.
When using the dashboard, there are often some actions I will take. As the dashboard is its own major mode, there is no need to suffer the tyranny of unnecessary keystrokes — we can simply bind common actions to a single key!
;;; :ui doom-dashboard (:when (modulep! :ui doom-dashboard) (:map doom-leader-open-map "0" #'+doom-dashboard/open) :map +doom-dashboard-mode-map :ne "h" #'+treemacs/toggle :ne "l" #'push-button :ne "u" #'doom/quickload-session :ne "a" #'org-agenda :ne "f" #'find-file :ne "e" #'eww :ne "r" #'consult-recent-file :ne "p" #'projectile-switch-project :ne "P" #'doom/open-private-config :ne "c" (cmd! (find-file (expand-file-name "config.org" doom-private-dir))) :ne "." (defun find-dotfile () (interactive) (doom-project-find-file "~/.config")) :ne "b" #'consult-buffer :ne "q" #'save-buffers-kill-terminal :ne "v" #'+vterm/here :ne "t" #'telega :ne "T" #'=twitter :ne "m" #'mu4e :ne "n" #'+default/find-in-notes :ne "d" #'+workspace/close-window-or-workspace :ne "x" #'org-capture)
#+TITLE: :ui fixmee
#+DATE: January 23, 2022
#+SINCE: v3.0.0-alpha
#+STARTUP: inlineimages nofold
* Table of Contents :TOC_3:noexport:
- [[#description][Description]]
- [[#maintainers][Maintainers]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#prerequisites][Prerequisites]]
- [[#features][Features]]
- [[#urgency-of-fixme-notices][Urgency of "fixme" notices]]
- [[#how-to-use-this-module][How to use this module]]
- [[#key-bindings][Key Bindings]]
- [[#configuration][Configuration]]
- [[#troubleshooting][Troubleshooting]]
* Description
This module tracks "fixme" notices in code comments, highlights
them, ranks them by urgency, and lets you navigate to them quickly.
** Maintainers
+ [[https://github.com/thaenalpha][@thaenalpha]] (Author)
** Module Flags
This module provides no flags.
** Plugins
+ [[https://github.com/rolandwalker/fixmee][fixmee]]
* Prerequisites
This module has no prerequisites.
* Features
# An in-depth list of features, how to use them, and their dependencies.
** Urgency of "fixme" notices
Is indicated by repetitions of the final character. For example,
one might write "FIXMEEEEEEEEE" for an important issue. The
~fixmee-goto-nextmost-urgent~ command will navigate to the longest notice first.
** How to use this module
open some buffers and right-click on the word "fixme" in a comment
or press
=C-c f=
or
=M-x fixmee RET=
or
roll the mouse wheel when hovering over the text "fixm"
in the modeline.
or
execute ~fixmee-view-listing~ to navigate using
`next-error' conventions.
** Key Bindings
| keybind | description |
|-------------+------------------------------------------------------------|
| =C-c f= | ~fixmee-goto-nextmost-urgent~ |
| =C-c F= | ~fixmee-goto-prevmost-urgent~ |
| =C-c v= | ~fixmee-view-listing~ |
| =M-n= | ~fixmee-goto-next-by-position~ only when the point is |
| =M-p= | ~fixmee-goto-previous-by-position~ inside a "fixme" notice |
| =C-u C-c f= | go to the nextmost urgent item in the current buffer |
| =C-u C-c F= | go to the previousmost urgent item in the current buffer |
* Configuration
# How to configure this module, including common problems and how to address them.
* Troubleshooting
# Common issues and their solution, or places to look for help.
;;; ui/fixmee/config.el -*- lexical-binding: t; -*-
(add-transient-hook! #'global-fixmee-mode (require 'button-lock))
When :ui hydra
is enabled, map keys to +hydra/window-nav/body~
and
+hydra/text-zoom/body
to control text/window with single key commands.
;;; :ui hydra
(:when (modulep! :ui hydra)
:desc "Interactive menu" "<menu>" #'+hydra/window-nav/body
:when (modulep! :completion vertico)
[remap +hydra/window-nav/idomenu] #'consult-imenu)
;; <leader> z --- zoom
(:when (modulep! :ui hydra)
:desc "Text zoom menu" "z" #'+hydra/text-zoom/body)
I want to use Nyan Mode with doom-modeline and I want to add +nyan
flag to
the modeline module. What I need to do is hack the ui/modeline/packages.el
file locates in .emacs.d/modules directory to add the nyan-mode package, add
+nyan.el
for config file and add some code into the module. (I’ve done this
in my doom-emacs fork, and I’m attempting to introduce it to upstream soon)
;;; :ui modeline
;; An evil mode indicator is redundant with cursor shape - @hlissner
(advice-add #'doom-modeline-segment--modals :override #'ignore)
;;; ui/modus/config.el -*- lexical-binding: t; -*-
(use-package! modus-themes
:init
(setq modus-themes-italic-constructs t
modus-themes-bold-constructs nil
modus-themes-mixed-fonts nil
modus-themes-subtle-line-numbers nil
modus-themes-intense-markup t
modus-themes-deuteranopia t
modus-themes-tabs-accented t
modus-themes-variable-pitch-ui nil
modus-themes-inhibit-reload t
modus-themes-fringes nil ; {nil,'subtle,'intense}
modus-themes-lang-checkers nil
modus-themes-mode-line '(4 accented borderless)
modus-themes-syntax nil
modus-themes-hl-line '(underline accented)
modus-themes-paren-match '(bold intense)
modus-themes-links '(neutral-underline background)
modus-themes-prompts '(intense bold)
modus-themes-completions 'moderate ; {nil,'moderate,'opinionated}
modus-themes-mail-citations nil ; {nil,'faint,'monochrome}
modus-themes-region '(bg-only no-extend)
modus-themes-diffs 'desaturated ; {nil,'desaturated,'bg-only}
modus-themes-org-blocks 'gray-background ; or {nil,'tinted-background}
modus-themes-org-agenda
'((header-block . (variable-pitch 1.3))
(header-date . (grayscale workaholic bold-today 1.1))
(event . (accented varied))
(scheduled . uniform)
(habit . traffic-light))
modus-themes-headings
'((1 . (overline background variable-pitch 1.3))
(2 . (rainbow overline 1.1))
(t . (semibold)))
doom-theme 'modus-operandi)
:bind ("<f5>" . modus-themes-toggle))
;; -*- no-byte-compile: t; -*-
;;; ui/modus/packages.el
(package! modus-themes :built-in 'prefer)
#+TITLE: ui/tab-workspaces
#+DATE: November 15, 2020
#+SINCE: 3.0
#+STARTUP: inlineimages nofold
* Table of Contents :TOC_3:noexport:
- [[#description][Description]]
- [[#maintainers][Maintainers]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#hacks][Hacks]]
- [[#prerequisites][Prerequisites]]
- [[#features][Features]]
- [[#configuration][Configuration]]
- [[#troubleshooting][Troubleshooting]]
* Description
Provide persistent workspaces using Emacs 27+ tab-bar feature
** Maintainers
+ @gagbo (Author)
** Module Flags
This module provides no flags.
** Plugins
+ [[https://github.com/alphapapa/burly.el][burly.el]]
+ [[https://github.com/alphapapa/bufler.el][bufler.el]]
** Hacks
The =bufler-tabs-mode= is very carefully avoided, because it uses tabs in a way
that is not compatible with this module's goal (changing tabs should change your
window configuration entirely)
* Prerequisites
This module has no prerequisites.
* Features
# An in-depth list of features, how to use them, and their dependencies.
A workspace is a group of buffers sharing the same bufler-workspace (which is
the =bufler-workspace-path= buffer-local variable).
Those workspaces are shown in tabs, whose name match the
=bufler-workspace-path=
* Configuration
# How to configure this module, including common problems and how to address them.
* Troubleshooting
# Common issues and their solution, or places to look for help.
(map! :n "C-t" #'+workspace/new
:n "C-S-t" #'+workspace/display
:g "M-1" #'+workspace/switch-to-0
:g "M-2" #'+workspace/switch-to-1
:g "M-3" #'+workspace/switch-to-2
:g "M-4" #'+workspace/switch-to-3
:g "M-5" #'+workspace/switch-to-4
:g "M-6" #'+workspace/switch-to-5
:g "M-7" #'+workspace/switch-to-6
:g "M-8" #'+workspace/switch-to-7
:g "M-9" #'+workspace/switch-to-8
:g "M-0" #'+workspace/switch-to-final
(:when IS-MAC
:g "s-t" #'+workspace/new
:g "s-T" #'+workspace/display
:n "s-1" #'+workspace/switch-to-0
:n "s-2" #'+workspace/switch-to-1
:n "s-3" #'+workspace/switch-to-2
:n "s-4" #'+workspace/switch-to-3
:n "s-5" #'+workspace/switch-to-4
:n "s-6" #'+workspace/switch-to-5
:n "s-7" #'+workspace/switch-to-6
:n "s-8" #'+workspace/switch-to-7
:n "s-9" #'+workspace/switch-to-8
:n "s-0" #'+workspace/switch-to-final))
(map! :leader
:desc "Switch workspace buffer" "," #'persp-switch-to-buffer
:desc "Switch buffer" "<" #'switch-to-buffer
(:prefix-map ("TAB" . "workspace")
:desc "Delete this workspace" "d" #'+workspace/delete
:desc "Switch workspace" "g" #'+workspace/switch-to
:desc "Restore workspace" "G" #'+workspace/restore
:desc "Load workspace from file" "l" #'+workspace/load
:desc "New workspace" "n" #'+workspace/new
:desc "Rename workspace" "r" #'+workspace/rename
:desc "Restore last session" "R" #'+workspace/restore-last-session
:desc "Save workspace to file" "s" #'+workspace/save
:desc "Delete session" "x" #'+workspace/kill-session
:desc "Display tab bar" "TAB" #'+workspace/display
:desc "Switch workspace" "." #'+workspace/switch-to
:desc "Switch to last workspace" "`" #'+workspace/other
:desc "Next workspace" "]" #'+workspace/switch-right
:desc "Previous workspace" "[" #'+workspace/switch-left
:desc "Switch to 1st workspace" "1" #'+workspace/switch-to-0
:desc "Switch to 2nd workspace" "2" #'+workspace/switch-to-1
:desc "Switch to 3rd workspace" "3" #'+workspace/switch-to-2
:desc "Switch to 4th workspace" "4" #'+workspace/switch-to-3
:desc "Switch to 5th workspace" "5" #'+workspace/switch-to-4
:desc "Switch to 6th workspace" "6" #'+workspace/switch-to-5
:desc "Switch to 7th workspace" "7" #'+workspace/switch-to-6
:desc "Switch to 8th workspace" "8" #'+workspace/switch-to-7
:desc "Switch to 9th workspace" "9" #'+workspace/switch-to-8
:desc "Switch to final workspace" "0" #'+workspace/switch-to-final)
(:prefix-map ("b" . "buffer")
:desc "Switch workspace buffer" "b" #'bufler-switch-buffer
:desc "Switch buffer" "B" #'switch-to-buffer))
;;; ui/tab-workspaces/config.el -*- lexical-binding: t; -*-
(load! "generic")
(load! "bindings")
(defvar +workspaces--bufler-path-prefix "Workspace: "
"The prefix in front of the workspace name, in the inner representation of a bufler path.
This prefix is used to correctly set the workspace context in the various hooks here,
using `doom-workspaces--workspace-frame-set-a'.")
(defadvice! doom-workspaces--workspace-frame-set-a (orig-fn &rest args)
"Set the workspace of current frame based on the given named workspace name"
:around #'bufler-workspace-frame-set
(apply orig-fn (list (list (concat +workspaces--bufler-path-prefix (caar args))))))
;; (defvar +workspaces-on-switch-project-behavior 'non-empty
;; "Controls the behavior of workspaces when switching to a new project.
;; Can be one of the following:
;; t Always create a new workspace for the project
;; 'non-empty Only create a new workspace if the current one already has buffers
;; associated with it.
;; nil Never create a new workspace on project switch.")
(defvar +workspaces-switch-project-function #'doom-project-find-file
"The function to run after `projectile-switch-project' or
`counsel-projectile-switch-project'. This function must take one argument: the
new project directory.")
;; We need to find the correct hook to change the bufler workspace
;; (which is frame-local and not tab-local) to the tab we are choosing.
(defun +workspaces-set-bufler-workspace-a (&rest _)
"Advice to set the workspace of the frame on tab changes"
(let ((tab-name (alist-get 'name (tab-bar--current-tab))))
(if (eq tab-name +workspaces-main)
(bufler-workspace-frame-set)
(bufler-workspace-frame-set (list tab-name)))))
(advice-add #'tab-bar-select-tab :after #'+workspaces-set-bufler-workspace-a)
(defun +doom-tab-bar--tab-name-function ()
"A Doom specific tab-bar-tab-name-function.
Unless the tab name has been specifically set through `tab-rename', return `+workspaces-main'.
Therefore this function only needs to return the default value."
+workspaces-main)
(setq tab-bar-tab-name-function #'+doom-tab-bar--tab-name-function)
(when EMACS28+
(setq tab-bar-format '(tab-bar-format-history
tab-bar-format-tabs
tab-bar-separator
tab-bar-format-add-tab
tab-bar-format-align-right
tab-bar-format-global)))
;; TODO: check that the switch-to-buffer action (when listing all buffers) correctly
;; goes through tabs first, before opening the buffer in the current tab ??
;; Add 'doom-switch-buffer-hook that calls `bufler-workspace-buffer-set
;; so that the buffers interactively opened are put in the workspace
;; The current workspace is `(frame-parameter nil 'bufler-workspace-path)`,
;; but this is actually also the tab name by convention now, and it is easier to fetch
(add-hook! '(doom-switch-buffer-hook server-visit-hook)
(defun +workspaces-add-current-buffer-h ()
"Add current buffer to focused workspace."
(when (or (not (+workspace-current-name))
(eq (+workspace-current-name) +workspaces-main))
(+workspaces-switch-to-project-h))
(bufler-workspace-buffer-name-workspace (+workspace-current-name))))
(defvar +workspaces--project-dir nil)
(defun +workspaces-set-project-action-fn ()
"A `projectile-switch-project-action' that sets the project directory for
`+workspaces-switch-to-project-h'."
(+workspaces-switch-to-project-h default-directory)
(funcall +workspaces-switch-project-function (or +workspaces--project-dir default-directory)))
;; TODO: properly reuse "main" if it's empty/free
(defun +workspaces-switch-to-project-h (&optional dir)
(unwind-protect
(progn
(when dir
(setq +workspaces--project-dir dir))
(let ((projectile-project-root)
(tab-name (doom-project-name +workspaces--project-dir)))
(if (string= tab-name "-")
(+workspace/switch-to-or-create +workspaces-main)
(+workspace/switch-to-or-create tab-name))))
(setq +workspaces--project-dir nil)))
(setq projectile-switch-project-action #'+workspaces-set-project-action-fn)
;; (when (modulep! :completion ivy)
;; (setq
;; counsel-projectile-switch-project-action
;; '(1 ("o" +workspaces-switch-to-project-h "open project in new workspace")
;; ("O" counsel-projectile-switch-project-action "jump to a project buffer or file")
;; ("f" counsel-projectile-switch-project-action-find-file "jump to a project file")
;; ("d" counsel-projectile-switch-project-action-find-dir "jump to a project directory")
;; ("D" counsel-projectile-switch-project-action-dired "open project in dired")
;; ("b" counsel-projectile-switch-project-action-switch-to-buffer "jump to a project buffer")
;; ("m" counsel-projectile-switch-project-action-find-file-manually "find file manually from project root")
;; ("w" counsel-projectile-switch-project-action-save-all-buffers "save all project buffers")
;; ("k" counsel-projectile-switch-project-action-kill-buffers "kill all project buffers")
;; ("r" counsel-projectile-switch-project-action-remove-known-project "remove project from known projects")
;; ("c" counsel-projectile-switch-project-action-compile "run project compilation command")
;; ("C" counsel-projectile-switch-project-action-configure "run project configure command")
;; ("e" counsel-projectile-switch-project-action-edit-dir-locals "edit project dir-locals")
;; ("v" counsel-projectile-switch-project-action-vc "open project in vc-dir / magit / monky")
;; ("s" (lambda (project)
;; (let ((projectile-switch-project-action
;; (lambda () (call-interactively #'+ivy/project-search))))
;; (counsel-projectile-switch-project-by-name project))) "search project")
;; ("xs" counsel-projectile-switch-project-action-run-shell "invoke shell from project root")
;; ("xe" counsel-projectile-switch-project-action-run-eshell "invoke eshell from project root")
;; ("xt" counsel-projectile-switch-project-action-run-term "invoke term from project root")
;; ("X" counsel-projectile-switch-project-action-org-capture "org-capture into project"))))
(use-package burly
:init
;; Add hook to fix https://github.com/alphapapa/burly.el/issues/21
(add-hook 'after-init-hook #'bookmark-maybe-load-default-file)
;; Blacklist a few frame-parameters
;; Discussion : https://github.com/alphapapa/burly.el/issues/23
(setq burly-frameset-filter-alist
'((name . nil)
(posframe-parent-buffer . :never)
(posframe-buffer . :never)))
;; Custom code to blacklist childframes from being saved in burly
;; Discussion : https://github.com/alphapapa/burly.el/issues/23
(defun doom--not-childframep (&optional frame)
"Return t if FRAME is a childframe. If FRAME is `nil', call for current frame."
(unless (frame-parameter frame 'parent-frame)
t))
(defvar doom-burly-frames-filter-predicate #'doom--not-childframep
"A predicate function to call of frames when saving them")
(defun doom--burly-bookmark-frames-a (name)
"Bookmark the current frames as NAME. Override of `burly-bookmark-frames' to filter frames with `doom-burly-frames-save-predicate'."
(interactive (let ((bookmark-names (cl-loop for bookmark in bookmark-alist
for (_name . params) = bookmark
when (equal #'burly-bookmark-handler (alist-get 'handler params))
collect (car bookmark))))
(list (completing-read "Save Burly bookmark: " bookmark-names nil nil burly-bookmark-prefix))))
(let ((record (list (cons 'url (burly-frames-url (cl-remove-if-not doom-burly-frames-filter-predicate (frame-list))))
(cons 'handler #'burly-bookmark-handler))))
(bookmark-store name record nil)))
(advice-add 'burly-bookmark-frames :override #'doom--burly-bookmark-frames-a))
(use-package bufler
:hook ((after-init . bufler-workspace-mode)) ; Set the frame name to the workspace name
:init
(setq tab-bar-show 1)
:config
;; disable tab-{bar,line}-mode in Company childframes
(after! company-box
(add-to-list 'company-box-frame-parameters '(tab-bar-lines . 0)))
;; Set the bufler grouping strategy
(setf bufler-groups
(bufler-defgroups
;; Grouping the named workspace first means that interactively
;; opening a special shared buffer like *Messages* will steal the
;; buffer from everyone else. Therefore all special-mode buffers are
;; handled first.
(group
;; Subgroup collecting all `help-mode' and `info-mode' buffers.
(group-or "*Help/Info*"
(mode-match "*Help*" (rx bos "help-"))
(mode-match "*Info*" (rx bos "info-"))))
;; TODO: some special buffers should not fall into this category,
;; like compilation buffers or interpreter buffers
(group
;; Subgroup collecting all special buffers (i.e. ones that are not
;; file-backed), except `magit-status-mode' buffers (which are allowed to fall
;; through to other groups, so they end up grouped with their project buffers).
(group-and "*Special*"
(lambda (buffer)
(unless (or (funcall (mode-match "Magit" (rx bos "magit-status"))
buffer)
(funcall (mode-match "Dired" (rx bos "dired"))
buffer)
(funcall (auto-file) buffer))
"*Special*")))
;; Subgroup collecting these "special special" buffers
;; separately for convenience.
(group
(name-match "**Special**"
(rx bos "*" (or "Messages" "Warnings" "scratch" "Backtrace") "*")))
;; TODO: Magit buffers should get to a project workspace before being put
;; in a magit catchall category
(group
;; Subgroup collecting all other Magit buffers, grouped by directory.
(mode-match "*Magit* (non-status)" (rx bos (or "magit" "forge") "-"))
(auto-directory))
;; Subgroup for Helm buffers.
(mode-match "*Helm*" (rx bos "helm-"))
;; Remaining special buffers are grouped automatically by mode.
(auto-mode))
;; Subgroup collecting all named workspaces.
(group
(auto-workspace))
;; NOTE: Past this line, we enter fallback territory
(group
;; Subgroup collecting buffers in `org-directory' (or "~/org" if
;; `org-directory' is not yet defined).
(dir (if (bound-and-true-p org-directory)
org-directory
"~/org"))
(group
;; Subgroup collecting indirect Org buffers, grouping them by file.
;; This is very useful when used with `org-tree-to-indirect-buffer'.
(auto-indirect)
(auto-file))
;; Group remaining buffers by whether they're file backed, then by mode.
(group-not "*special*" (auto-file))
(auto-mode))
(group
;; Subgroup collecting buffers in a projectile project.
(auto-projectile))
(group
;; Subgroup collecting buffers in a version-control project,
;; grouping them by directory.
(auto-project))
;; All buffers under "~/.emacs.d" (or wherever it is).
(dir user-emacs-directory)
;; All buffers under "~/.doom.d" (or wherever it is).
(dir doom-private-dir)
;; Group remaining buffers by directory, then major mode.
(auto-directory)
(auto-mode))))
;;; ui/tab-workspaces/generic.el -- Generic interface for workspaces -*- lexical-binding: t; -*-
(defvar +workspaces-main "main"
"The name of the primary and initial workspace, which cannot be deleted.")
;; (defun +workspaces-switch-to-project-h (&optional dir)
;; "Creates a workspace dedicated to a new project. If one already exists, switch
;; to it. If in the main workspace and it's empty, recycle that workspace, without
;; renaming it.
;; Afterwords, runs `+workspaces-switch-project-function'. By default, this prompts
;; the user to open a file in the new project.
;; This be hooked to `projectile-after-switch-project-hook'."
;; (when dir
;; (setq +workspaces--project-dir dir))
;; ;; HACK Clear projectile-project-root, otherwise cached roots may interfere
;; ;; with project switch (see #3166)
;; (let (projectile-project-root)
;; (when +workspaces--project-dir
;; (when projectile-before-switch-project-hook
;; (with-temp-buffer
;; ;; Load the project dir-local variables into the switch buffer, so the
;; ;; action can make use of them
;; (setq default-directory +workspaces--project-dir)
;; (hack-dir-local-variables-non-file-buffer)
;; (run-hooks 'projectile-before-switch-project-hook)))
;; (unwind-protect
;; (if (and (not (null +workspaces-on-switch-project-behavior))
;; (or (eq +workspaces-on-switch-project-behavior t)
;; (equal (alist-get 'name (tab-bar--current-tab)) +workspaces-main)))
;; (let* ((project-name (doom-project-name +workspaces--project-dir)))
;; (+workspace/switch-to-or-create project-name)
;; (with-current-buffer (doom-fallback-buffer)
;; (setq default-directory +workspaces--project-dir)
;; (hack-dir-local-variables-non-file-buffer))
;; (unless current-prefix-arg
;; (funcall +workspaces-switch-project-function +workspaces--project-dir))
;; (+workspace-message
;; (format "Switched to '%s' in new workspace" project-name)
;; 'success))
;; (with-current-buffer (doom-fallback-buffer)
;; (setq default-directory +workspaces--project-dir)
;; (hack-dir-local-variables-non-file-buffer)
;; (message "Switched to '%s'" (doom-project-name +workspaces--project-dir)))
;; (with-demoted-errors "Workspace error: %s"
;; (+workspace-rename (+workspace-current-name) (doom-project-name +workspaces--project-dir)))
;; (unless current-prefix-arg
;; (funcall +workspaces-switch-project-function +workspaces--project-dir)))
;; (run-hooks 'projectile-after-switch-project-hook)
;; (setq +workspaces--project-dir nil)))))
(defun +workspace--message-body (message &optional type)
(concat "Workspaces"
(propertize " | " 'face 'font-lock-comment-face)
(propertize (format "%s" message)
'face (pcase type
('error 'error)
('warn 'warning)
('success 'success)
('info 'font-lock-comment-face)))))
(defun +workspace-message (message &optional type)
"Show an 'elegant' message in the echo area next to a listing of workspaces."
(message "%s" (+workspace--message-body message type)))
(defun +workspace-error (message &optional noerror)
"Show an 'elegant' error in the echo area next to a listing of workspaces."
(funcall (if noerror #'message #'error)
"%s" (+workspace--message-body message 'error)))
;;; API from :ui workspaces readme
;;;; General functions
(defun +workspace-list ()
""
(error "+workspace-list is unimplemented"))
(defun +workspace-list-names ()
""
(error "+workspace-list-names is unimplemented"))
(defun +workspace-buffer-list (&optional ws-name)
""
(error "+workspace-buffer-list is unimplemented"))
(defun +workspace-p (obj)
""
(error "+workspace-buffer-list is unimplemented"))
(defun +workspace-exists-p (&optional ws-name)
""
(error "+workspace-exists-p is unimplemented"))
;;;; Accessors
(defun +workspace-get (name &optional noerror)
"Return the index of the tab-bar where workspace NAME lives."
(tab-bar--tab-index-by-name name))
(defun +workspace/get ()
""
(interactive)
;; TODO Use +workspace-list-names for the completing-read candidates
(error "+workspace/get is unimplemented"))
(defun +workspace-current (&optional frame window)
""
(error "+workspace-current is unimplemented"))
(defun +workspace-current-name ()
"Get the name of the current workspace."
(alist-get 'name (tab-bar--current-tab)))
;;;; Persistence
(defun +workspace-load (name)
""
(error "+workspace-load is unimplemented"))
(defun +workspace/load ()
"Load a previously saved workspace"
(interactive)
(error "+workspace/load is unimplemented"))
(defun +workspace/restore ()
(interactive)
(call-interactively #'burly-open-bookmark))
(defun +workspace-load-session (name)
""
(error "+workspace-load-session is unimplemented"))
(defun +workspace/restore-last-session ()
(interactive)
(error "+workspace/restore-last-session is unimplemented"))
(defun +workspace-save (name)
""
(error "+workspace-save is unimplemented"))
(defun +workspace/save ()
"Save the workspace configuration for later loading"
(interactive)
(call-interactively #'burly-bookmark-windows))
(defun +workspace-save-session (name)
""
(error "+workspace-save-session is unimplemented"))
;;;; Creation
(defalias #'+workspace-new #'+workspace-create)
(defun +workspace-create (name)
"Create a workspace with name NAME."
(+workspace/create-then-switch-to name)
(tab-recent))
(defun +workspace/new ()
(interactive)
(+workspace/create-then-switch-to +workspaces-main))
;;;; Renaming
(defun +workspace-rename (name new-name)
"Rename the current workspace named NAME to NEW-NAME. Returns old name on
success, nil otherwise."
(error "+workspace-rename is unimplemented"))
(defun +workspace/rename ()
(interactive)
(error "+workspace/rename is unimplemented"))
;;;; Deleting
(defun +workspace-delete (name &optional inhibit-kill-p)
""
(error "+workspace-delete is unimplemented"))
(defun +workspace/delete ()
"Kill all buffers associated to the workspace, and remove the workspace"
(interactive)
;; TODO: to implement this, we would need a bufler-remove-group-maybe-kill function,
;; and a bufler-buffer-list-by-workspace function (= bufler-workspace-buffers)
(error "+workspace/delete is unimplemented"))
;;;; Switching
(defun +workspace-switch (name &optional auto-create-p)
""
(error "+workspace-delete is unimplemented"))
(defun +workspace/switch-to-or-create (name)
"Get or create a workspace with name NAME."
(interactive
(list
(completing-read "Workspaces: "
(bufler-workspace-list-named-workspaces))))
(if-let ((index (tab-bar--tab-index-by-name name)))
(tab-select (1+ index))
(+workspace/create-then-switch-to name)))
(defun +workspace/create-then-switch-to (name)
"Create a workspace with name NAME."
(interactive "sWorkspace Name: ")
(tab-new)
;; FIXME: the "tab-new" did also switch tab, but the tab was nameless then
;; (and defaulted to +workspaces-main from tab-bar-tab-name-function)
;; so the tab switching hooks did not properly set the workspace for the frame
(bufler-workspace-frame-set (list name))
(tab-rename name))
;;;; Misc
(defun +workspace-protected-p (name)
""
(error "+workspace-protected-p is unimplemented"))
;;; Extra interactive functions
(defun +workspace/other ()
(interactive)
(call-interactively #'tab-recent))
(defun +workspace/kill-session ()
(interactive)
(error "+workspace/kill-session is unimplemented"))
(defun +workspace/switch-to-buffer ()
(interactive)
(call-interactively #'bufler-switch-buffer))
(defun +workspace/display ()
(interactive)
(error "+workspace/display is unimplemented"))
;;; Switch to a given workspace
(defun +workspace/switch-right ()
(interactive)
(tab-next))
(defun +workspace/switch-left ()
(interactive)
(tab-previous))
(defun +workspace/switch-to ()
(interactive)
(call-interactively #'+workspace/switch-to-or-create))
(defun +workspace/switch-to-0 ()
(interactive)
(error "+workspace/switch-to-0 is unimplemented"))
(defun +workspace/switch-to-1 ()
(interactive)
(error "+workspace/switch-to-1 is unimplemented"))
(defun +workspace/switch-to-2 ()
(interactive)
(error "+workspace/switch-to-2 is unimplemented"))
(defun +workspace/switch-to-3 ()
(interactive)
(error "+workspace/switch-to-3 is unimplemented"))
(defun +workspace/switch-to-4 ()
(interactive)
(error "+workspace/switch-to-4 is unimplemented"))
(defun +workspace/switch-to-5 ()
(interactive)
(error "+workspace/switch-to-5 is unimplemented"))
(defun +workspace/switch-to-6 ()
(interactive)
(error "+workspace/switch-to-6 is unimplemented"))
(defun +workspace/switch-to-7 ()
(interactive)
(error "+workspace/switch-to-7 is unimplemented"))
(defun +workspace/switch-to-8 ()
(interactive)
(error "+workspace/switch-to-8 is unimplemented"))
(defun +workspace/switch-to-final ()
(interactive)
(error "+workspace/switch-to-final is unimplemented"))
;; -*- no-byte-compile: t; -*-
;;; ui/tab-workspaces/packages.el
(package! bufler)
(package! burly)
Set theme, git-mode and width
treemacs-width 27
+treemacs-git-mode 'deferred
From the
:ui zen
module.
We’d like to use mixed pitch in certain modes. If we simply add a hook, when
directly opening a file with (a new) Emacs mixed-pitch-mode
runs before UI
initialisation, which is problematic. To resolve this, we create a hook that
runs after UI initialisation and both
- conditionally enables
mixed-pitch-mode
- sets up the mixed pitch hooks
(defvar mixed-pitch-modes '(org-mode LaTeX-mode markdown-mode gfm-mode Info-mode)
"Modes that `mixed-pitch-mode' should be enabled in, but only after UI initialisation.")
(defun init-mixed-pitch-h ()
"Hook `mixed-pitch-mode' into each mode in `mixed-pitch-modes'.
Also immediately enables `mixed-pitch-modes' if currently in one of the modes."
(when (memq major-mode mixed-pitch-modes)
(mixed-pitch-mode 1))
(dolist (hook mixed-pitch-modes)
(add-hook (intern (concat (symbol-name hook) "-hook")) #'mixed-pitch-mode)))
(add-hook 'doom-init-ui-hook #'init-mixed-pitch-h)
As mixed pitch uses the variable mixed-pitch-face
, we can create a new function
to apply mixed pitch with a serif face instead of the default. This was created
for writeroom mode.
(autoload #'mixed-pitch-serif-mode "mixed-pitch"
"Change the default face of the current buffer to a serifed variable pitch, while keeping some faces fixed pitch." t)
(after! mixed-pitch
(defface variable-pitch-serif
'((t (:family "serif")))
"A variable-pitch face with serifs."
:group 'basic-faces)
(setq mixed-pitch-set-height t)
(set-face-attribute 'variable-pitch-serif nil :font variable-pitch-serif-font)
(defun mixed-pitch-serif-mode (&optional arg)
"Change the default face of the current buffer to a serifed variable pitch, while keeping some faces fixed pitch."
(interactive)
(let ((mixed-pitch-face 'variable-pitch-serif))
(mixed-pitch-mode (or arg 'toggle)))))
Now, as Harfbuzz is currently used in Emacs, we’ll be missing out on the following Alegreya ligatures:
ff ff ffi ffi ffj ffj ffl ffl fft fft fi fi fj fj ft ft Th Th
Thankfully, it isn’t to hard to add these to the composition-function-table
.
(set-char-table-range composition-function-table ?f '(["\\(?:ff?[fijlt]\\)" 0 font-shape-gstring]))
(set-char-table-range composition-function-table ?T '(["\\(?:Th\\)" 0 font-shape-gstring]))
From the
:ui zen
module.
For starters, I think Doom is a bit over-zealous when zooming in
(setq +zen-text-scale 0.8)
Then, when using Org it would be nice to make a number of other aesthetic tweaks. Namely:
- Use a serifed variable-pitch font
- Hiding headline leading stars
- Using fleurons as headline bullets
- Hiding line numbers
- Removing outline indentation
- Centring the text
(defvar +zen-serif-p t
"Whether to use a serifed font with `mixed-pitch-mode'.")
(defvar +zen-org-starhide t
"The value `org-modern-hide-stars' is set to.")
(after! writeroom-mode
(defvar-local +zen--original-org-indent-mode-p nil)
(defvar-local +zen--original-mixed-pitch-mode-p nil)
(defun +zen-enable-mixed-pitch-mode-h ()
"Enable `mixed-pitch-mode' when in `+zen-mixed-pitch-modes'."
(when (apply #'derived-mode-p +zen-mixed-pitch-modes)
(if writeroom-mode
(progn
(setq +zen--original-mixed-pitch-mode-p mixed-pitch-mode)
(funcall (if +zen-serif-p #'mixed-pitch-serif-mode #'mixed-pitch-mode) 1))
(funcall #'mixed-pitch-mode (if +zen--original-mixed-pitch-mode-p 1 -1)))))
(pushnew! writeroom--local-variables
'display-line-numbers
'visual-fill-column-width
'org-adapt-indentation
'org-modern-mode
'org-modern-star
'org-modern-hide-stars)
(add-hook 'writeroom-mode-enable-hook
(defun +zen-prose-org-h ()
"Reformat the current Org buffer appearance for prose."
(when (eq major-mode 'org-mode)
(setq display-line-numbers nil
visual-fill-column-width 60
org-adapt-indentation nil)
(when (featurep 'org-modern)
(setq-local org-modern-star '("🙘" "🙙" "🙚" "🙛")
;; org-modern-star '("🙐" "🙑" "🙒" "🙓" "🙔" "🙕" "🙖" "🙗")
org-modern-hide-stars +zen-org-starhide)
(org-modern-mode -1)
(org-modern-mode 1))
(setq
+zen--original-org-indent-mode-p org-indent-mode)
(org-indent-mode -1)))
(add-hook 'writeroom-mode-disable-hook
(defun +zen-nonprose-org-h ()
"Reverse the effect of `+zen-prose-org'."
(when (eq major-mode 'org-mode)
(when (bound-and-true-p org-modern-mode)
(org-modern-mode -1)
(org-modern-mode 1))
(when +zen--original-org-indent-mode-p (org-indent-mode 1)))))))
;;; :editor evil
;; Focus new window after splitting
(setq evil-split-window-below t
evil-vsplit-window-right t
;; By default while in insert all changes are one big blob. Be more granular
evil-want-fine-undo t)
When changing surrounding pairs like from [hello-world] to (hello-world), using
a keyboard shortcut like cs[(
, it makes more sense to assume it’s formatted
correctly. Instead it will add a space after the substitute.
However This is not the case for programming modes which can auto balance space between pairs. Thus makes you need some extra work to get rid a space with a little effort except for Org-Mode source block.
(add-hook! 'evil-org-mode-hook
(setq-local evil-surround-pairs-alist
'((40 "(" . ")")
(91 "[" . "]")
(123 "{" . "}")
(41 "(" . ")")
(93 "[" . "]")
(125 "{" . "}")
(35 "#{" . "}")
(98 "(" . ")")
(66 "{" . "}")
(62 "<" . ">")
(116 . evil-surround-read-tag)
(60 . evil-surround-read-tag)
(102 . evil-surround-function))))
According to src_org{** Disabling the LSP formatter} in format
If you are in a buffer with
lsp-mode
enabled and a server that supportstextDocument/formatting
, it will be used instead of doom-package:format-all’s formatter.
- To disable this behavior on TS and JS modes in favor of prettier:
;;; :editor format (setq-hook! '(js-mode js2-mode rjsx-mode typescript-mode typescript-tsx-mode) +format-with-lsp nil)
Dictionary of rotations to use. The most part was taken from Parrot Mode.
;;; :editor rotate-text
(after! rotate-text
(pushnew! rotate-text-words
'("alpha" "beta")
'("yes" "no")
'("&" "|")
'("small" "medium" "large")
'("show" "hide")
'("up" "down")
'("stable" "unstable")
'("install" "uninstall")
'("add" "remove")
'("jan" "feb" "mar" "apr" "may" "jun"
"jul" "aug" "sep" "oct" "nov" "dec")
'("january" "february" "march" "april" "may" "june"
"july" "august" "september" "october" "november" "december")
'("mon" "tue" "wed" "thu" "fri" "sat" "sun")
'("monday" "tuesday" "wednesday" "thursday" "friday" "saturday"
"sunday")
;; Parrot Mode default dictionary starts here ('v')
'("begin" "end")
'("enter" "exit")
'("forward" "backward")
'("front" "rear" "back")
'("get" "set")
'("high" "low")
'("in" "out")
'("min" "max")
'("on" "off")
'("prev" "next")
'("start" "stop")
'("&&" "||")
'("==" "!=")
'("." "->")
'("if" "else" "elif")
'("ifdef" "ifndef")
'("int8_t" "int16_t" "int32_t" "int64_t")
'("uint8_t" "uint16_t" "uint32_t" "uint64_t")
'("1" "2" "3" "4" "5" "6" "7" "8" "9" "10")
'("1st" "2nd" "3rd" "4th" "5th" "6th" "7th" "8th" "9th" "10th")))
#Substitute (substitute.el)
Efficiently replace targets in the buffer or context.
Video demo: https://protesilaos.com/codelog/2023-01-16-emacs-substitute-package-demo/
;;; editor/substitute/config.el -*- lexical-binding: t; -*-
(use-package! substitute
:config (map! "M-# s" #'substitute-target-below-point
"M-# r" #'substitute-target-above-point
"M-# d" #'substitute-target-in-defun
"M-# b" #'substitute-target-in-buffer))
;;; emacs/ctrlf/config.el -*- lexical-binding: t; -*-
(use-package! ctrlf :hook (doom-first-input . ctrlf-mode))
;;; :emacs dired +dirvish
(when (modulep! :emacs dired +dirvish)
<<dirvish Configuration>>)
;;; emacs/info/config.el -*- lexical-binding: t; -*-
(use-package! info-colors :hook (Info-selection . info-colors-fontify-node))
This is my personal configuration.
I use msmtp
to send mail:
;;; :email mu4e
(after! mu4e
(setq sendmail-program (executable-find "msmtp")
send-mail-function #'smtpmail-send-it
message-send-mail-function #'message-send-mail-with-sendmail
message-sendmail-f-is-evil t
message-sendmail-extra-arguments '("--read-envelope-from")
<<mu4e-policies>>
mu4e-maildir-shortcuts '(("/thaenalpha@gmail.com/Job Applying".?j)))
<<mu4e-email-accounts>>
(mapc
(lambda (bookmark) (add-to-list 'mu4e-bookmarks bookmark))
`( ; create bookmarks to show merged views of folders across accounts:
<<mu4e-bookmarks>>)))
I have multiple email addresses set like these
;; Each path is relative to the path of the maildir you passed to mu
(set-email-account!
"boliden@gmail.com"
'((mu4e-sent-folder . "/boliden@gmail.com/[Gmail]/Sent Mail")
(mu4e-drafts-folder . "/boliden@gmail.com/[Gmail]/Drafts")
(mu4e-spam-folder . "/boliden@gmail.com/[Gmail]/Spam")
(mu4e-trash-folder . "/boliden@gmail.com/[Gmail]/Trash")
(mu4e-refile-folder . "/boliden@gmail.com/[Gmail]/All Mail")
(smtpmail-smtp-user . "boliden@gmail.com")
(mu4e-compose-signature . "---\nNopanun Laochunhanun"))
t)
(set-email-account!
"thaenalpha@gmail.com"
'((mu4e-sent-folder . "/thaenalpha@gmail.com/[Gmail]/Sent Mail")
(mu4e-drafts-folder . "/thaenalpha@gmail.com/[Gmail]/Drafts")
(mu4e-spam-folder . "/thaenalpha@gmail.com/[Gmail]/Spam")
(mu4e-trash-folder . "/thaenalpha@gmail.com/[Gmail]/Trash")
(mu4e-refile-folder . "/thaenalpha@gmail.com/[Gmail]/All Mail")
(smtpmail-smtp-user . "thaenalpha@gmail.com")
(mu4e-compose-signature . "---\nNopanun Laochunhanun"))
t)
(set-email-account!
"bolidenx@hotmail.com"
'((mu4e-sent-folder . "/bolidenx@hotmail.com/Sent")
(mu4e-drafts-folder . "/bolidenx@hotmail.com/Drafts")
(mu4e-spam-folder . "/bolidenx@hotmail.com/Junk")
(mu4e-trash-folder . "/bolidenx@hotmail.com/Deleted")
(mu4e-refile-folder . "/bolidenx@hotmail.com/Archive")
(smtpmail-smtp-user . "bolidenx@hotmail.com")
(mu4e-compose-signature . "---\nNopanun Laochunhanun"))
t)
(set-email-account!
"nopanun@live.com"
'((mu4e-sent-folder . "/nopanun@live.com/Sent")
(mu4e-drafts-folder . "/nopanun@live.com/Drafts")
(mu4e-spam-folder . "/nopanun@live.com/Junk")
(mu4e-trash-folder . "/nopanun@live.com/Deleted")
(mu4e-refile-folder . "/nopanun@live.com/Archive")
(smtpmail-smtp-user . "nopanun@live.com")
(mu4e-compose-signature . "---\nNopanun Laochunhanun"))
t)
(set-email-account!
"tannarin26@yahoo.com"
'((mu4e-sent-folder . "/tannarin26@yahoo.com/Sent")
(mu4e-drafts-folder . "/tannarin26@yahoo.com/Draft")
(mu4e-spam-folder . "/tannarin26@yahoo.com/Bulk Mail")
(mu4e-trash-folder . "/tannarin26@yahoo.com/Trash")
(mu4e-refile-folder . "/tannarin26@yahoo.com/Archive")
(smtpmail-smtp-user . "tannarin26@yahoo.com")
(mu4e-compose-signature . "---\nNopanun Laochunhanun"))
t)
Change context behavior when opening mu4e and composing email with:
mu4e-context-policy
and mu4e-compose-context-policy
mu4e-context-policy 'ask-if-none
mu4e-compose-context-policy 'always-ask
How to use contexts
Examples:
- Compose new mail in a context
- Archive a message in a context (show which folder it goes to)
- Reply to a message in a merged search
You can create bookmarks to show merged views of folders across accounts:
(,(concat
"m:/boliden@gmail.com/INBOX or m:/bolidenx@hotmail.com/Inbox or "
"m:/nopanun@live.com/Inbox or m:/tannarin26@yahoo.com/Inbox or "
"m:/thaenalpha@gmail.com/INBOX or m:/nopanun@live.com/IT Demands")
"All Inboxes" ?i)
(,(concat
"m:/boliden@gmail.com/[Gmail]/Sent Mail or m:/bolidenx@hotmail.com/Sent "
"m:/thaenalpha@gmail.com/[Gmail]/Sent Mail or m:/nopanun@live.com/Sent or"
" m:/tannarin26@yahoo.com/Sent") "All Sent" ?s)
(,(concat
"m:/boliden@gmail.com/[Gmail]/Drafts or m:/bolidenx@hotmail.com/Drafts "
"m:/thaenalpha@gmail.com/[Gmail]/Drafts or m:/nopanun@live.com/Drafts or "
"m:/tannarin26@yahoo.com/Draft") "All Drafts" ?d)
(,(concat
"m:/boliden@gmail.com/[Gmail]/All Mail or m:/bolidenx@hotmail.com/Archive"
" m:/thaenalpha@gmail.com/[Gmail]/All Mail or m:/nopanun@live.com/Archive"
" or m:/tannarin26@yahoo.com/Archive") "All Archives" ?a)
(,(concat
"m:/boliden@gmail.com/[Gmail]/Spam or m:/bolidenx@hotmail.com/Junk or "
"m:/thaenalpha@gmail.com/[Gmail]/Spam or m:/nopanun@live.com/Junk or "
"m:/tannarin26@yahoo.com/Bulk Mail") "All Spams" ?p)
(,(concat
"m:/boliden@gmail.com/[Gmail]/Trash or m:/bolidenx@hotmail.com/Deleted or"
" m:/thaenalpha@gmail.com/[Gmail]/Trash or m:/nopanun@live.com/Deleted or"
" m:/tannarin26@yahoo.com/Trash") "All Trashes" ?t)
This is your e-mail client to build!
This section adds packages and configuration on top of Doom Lang modules
turn on paredit-mode (minor) after Clojure-mode was loaded (major)
;;; :lang clojure
(when (modulep! :lang clojure)
(add-hook 'clojure-mode-hook #'paredit-mode)
(require 'clj-deps-new))
My Doom Emacs private JavaScript module with Add-on plugins. This was made by doing relative symbolic links to Doom built-in JavaScript Module except for packages.el.
;; -*- no-byte-compile: t; -*-
;;; lang/javascript/packages.el
coffee-mode js2-mode rjsx-mode typescript-mode
;; Major modes
(package! rjsx-mode)
(package! typescript-mode)
;; Tools
(package! js2-refactor)
(package! npm-mode)
(package! add-node-modules-path)
(package! pnpm-mode)
(package! import-js)
(package! yarn :recipe (:host github :repo "thaenalpha/yarn.el"))
;; Eval
(package! nodejs-repl)
(package! skewer-mode)
;; Programming environment
(package! tide)
(when (modulep! :tools lookup)
(package! xref-js2))
symbolic | description |
---|---|
PACKAGE | Doom original |
+ PACKAGE | Add-on plugin |
- automatically import dependencies in your JavaScript project.
- Minor mode for working with pnpm projects
- Minor mode for working with yarn projects
You must open this doc in Emacs and run this code block by press enter: (this works only if your Emacs and Doom configs path are defaults)
ln -s ../../../../.emacs.d/modules/lang/javascript/README.org modules/lang/javascript/README.org
ln -s ../../../../.emacs.d/modules/lang/javascript/autoload.el modules/lang/javascript/autoload.el
ln -s ../../../../.emacs.d/modules/lang/javascript/config.el modules/lang/javascript/config.el
ln -s ../../../../.emacs.d/modules/lang/javascript/doctor.el modules/lang/javascript/doctor.el
For XDG convention:
ln -s ../../../../emacs/modules/lang/javascript/README.org modules/lang/javascript/README.org
ln -s ../../../../emacs/modules/lang/javascript/autoload.el modules/lang/javascript/autoload.el
ln -s ../../../../emacs/modules/lang/javascript/config.el modules/lang/javascript/config.el
ln -s ../../../../emacs/modules/lang/javascript/doctor.el modules/lang/javascript/doctor.el
- Install the importjs binary with
npm
from a root shell:npm install import-js -g
or with
pnpm
:pnpm add import-js -g # root shell
pnpm add import-js -g # user shell
- Configure Import-Js
- Install Watchman as an performance booster to import-js daemon
- macOS or Linux
brew update && brew install watchman
- macOS or Linux
These configs live inside the +config.el
file and some are located in
autoload/*.el
files.
As the +config.el
is an Additional file,
This can be loaded with the load!
macro, which will load an elisp file
relative to the file it’s used from which is init.el
in our case.
;; Omitting the file extension allows Emacs to load the byte-compiled version,
;; if it is available:
(load! "+config")
- Run the import-js daemon
M-x
run-import-js
- The daemon will use watchman if installed to improve performance
- Configure
run-import-js
to run on open a JavaScript oriented buffer:;;; lang/javascript/+config.el -*- lexical-binding: t; -*- ;; ;;; Tools (add-hook! '(typescript-mode-local-vars-hook typescript-tsx-mode-local-vars-hook web-mode-local-vars-hook rjsx-mode-local-vars-hook) (defun import-js-setup () "Start `import-js' in the current buffer." (let ((buffer-file-name (buffer-file-name (buffer-base-buffer)))) (when (derived-mode-p 'js-mode 'typescript-mode 'typescript-tsx-mode) (if (null buffer-file-name) ;; necessary because `tide-setup' and `lsp' will error if not a ;; file-visiting buffer (add-hook 'after-save-hook #'import-js-setup nil 'local) (if (executable-find "node") (and (require 'import-js nil t) (progn (import-js-mode 1) (run-import-js) import-js-mode)) (ignore (doom-log "Couldn't start the import-js daemon because 'node' is missing"))) (remove-hook 'after-save-hook #'import-js-setup 'local))))))
- Import a file!
- You can use something like
M-x
import-js-import
with your cursor over the desired module - It will be helpful to bind
import-js-import
to an easy-to-use binding, such as:;;; lang/javascript/autoload/import-js.el -*- lexical-binding: t; -*- (defvar import-js-map (make-sparse-keymap) "Keymap for `import-js'.") (map! (:map import-js-map <<import-js-goto>> "C-c i" (define-prefix-command 'import-js) :localleader "i" #'import-js) (:map import-js "f" #'import-js-fix "g" #'import-js-goto "i" #'import-js-import "k" #'kill-import-js "r" #'run-import-js))
- You can use something like
- Go directly to a file
- The ImportJS goto interface allows us to jump to a package
M-x
import-js-goto
will jump to the appropriate file found by ImportJS- This should also be bound to something useful
:nv "g <f4>" #'import-js-goto
- Fix your imports
- Optionally, you can configure ImportJS to fix your imports for you, adding unknown variables and removing unused imports. ImportJS uses eslint to find these variables.
- `eslint` must be in your PATH.
- eslint plugins must be installed for that specific version of eslint (if eslint is a global eslint, you may need to install the plugins globally)
- Run with
M-x
import-js-fix
- Configure
import-js-fix
to run on save:;;;###autoload (define-minor-mode import-js-mode "Minor mode for automatically Import Javascript dependencies." :lighter " importjs" :keymap import-js-map (if import-js-mode (add-hook 'after-save-hook #'import-js-fix nil t) (remove-hook 'after-save-hook #'import-js-fix t))) ;; Hooks ;;;###autoload (defun +javascript-cleanup-import-js-processes-h () "Clean up dangling import-js daemon process if there are no more buffers with `import-js-mode' active." (when import-js-mode (unless (cl-loop for buf in (delq (current-buffer) (buffer-list)) if (buffer-local-value 'import-js-mode buf) return buf) (kill-import-js))))
- Cleanup importjsd process when no import-js buffers are left
;;;###package import-js (use-package! import-js :config (add-hook! 'import-js-mode-hook (add-hook 'kill-buffer-hook #'+javascript-cleanup-import-js-processes-h nil 'local)))
;;;###package pnpm-mode
(use-package! pnpm-mode
:config
(map! :localleader
:map pnpm-mode-keymap
"n" pnpm-mode-command-keymap)
;; Disable npm-mode when enabling pnpm
(add-hook 'pnpm-mode-hook
(defun turn-off-npm-mode () (npm-mode -1))))
By adding .dir-locals.el
file on the root directory of a project containing:
((nil . ((mode . pnpm))))
This package doesn’t provide keyboard bindings, so, I have to add them to my own module scripts.
;;; lang/javascript/autoload/yarn.el -*- lexical-binding: t; -*-
(defvar yarn--project-file-name "package.json"
"The name of yarn project files.")
(defun yarn--project-file ()
"Return path to the project file, or nil.
If project file exists in the current working directory, or a
parent directory recursively, return its path. Otherwise, return
nil."
(let ((dir (locate-dominating-file default-directory yarn--project-file-name)))
(unless dir
(error (concat "Error: cannot find " yarn--project-file-name)))
(concat dir yarn--project-file-name)))
(defun yarn--get-project-property (prop)
"Get the given PROP from the current project file."
(let* ((project-file (yarn--project-file))
(json-object-type 'hash-table)
(json-contents (with-temp-buffer
(insert-file-contents project-file)
(buffer-string)))
(json-hash (json-read-from-string json-contents))
(value (gethash prop json-hash))
(commands (list)))
(cond ((hash-table-p value)
(maphash (lambda (key value)
(setq commands
(append commands
(list (list key (format "%s %s" "yarn" key))))))
value)
commands)
(t value))))
(defun yarn--exec-process (cmd &optional comint)
"Execute a process running CMD.
Optional argument COMINT ."
(let ((compilation-buffer-name-function
(lambda (mode)
(format "*yarn:%s - %s*"
(yarn--get-project-property "name") cmd))))
(message (concat "Running " cmd))
(compile cmd comint)))
(defun yarn-list ()
"Run the 'yarn list' command."
(interactive)
(yarn--exec-process "yarn list --depth=0"))
(defun yarn-visit-project-file ()
"Visit the project file."
(interactive)
(find-file (yarn--project-file)))
(defvar yarn-map (make-sparse-keymap)
"Keymap for the `Yarn' package manager.")
(map! (:map yarn-map
"C-c n" (define-prefix-command 'yarn)
(:localleader "n" #'yarn))
(:map yarn
"i" #'yarn-install
"n" #'yarn-init
"a" #'yarn-add
"d" #'yarn-add-dev
"r" #'yarn-run
"p" #'yarn-publish
"t" #'yarn-test
"v" #'yarn-visit-project-file
"V" #'yarn-version
"g" #'yarn-upgrade
"u" #'yarn-update
"U" #'yarn-remove
"l" #'yarn-list))
;;;###autoload
(define-minor-mode yarn-mode
"Minor mode for working with yarn projects."
:lighter " yarn"
:keymap yarn-map
:group 'yarn)
;;;###autoload
(define-globalized-minor-mode yarn-global-mode
yarn-mode
yarn-mode)
;;;###package yarn
(use-package! yarn
:config
;; Disable npm-mode when enabling yarn-mode
(add-hook 'yarn-mode-hook #'turn-off-npm-mode))
By adding .dir-locals.el
file on the root directory of a project containing:
((nil . ((mode . yarn))))
After you M-x run-import-js
command if there is an error about void symbol definition.
This likely your import-js npm installation is not succeed. You can test by running:
importjsd start
If it succeed it will return this
If not it will throw an error, the one I got is about it cannot find node_sqlite3 module.
This case is come from node-pre-gyp
cannot find the pre-compiled binary in the first place.
Then it uses `node-gyp` to build the extension but it failed without messages you anything.
doom-modules:lang/nix extended configuration.
- Git executable cannot be found on remote TRAMP while it should be.
This is because by default,
eshell
will not adopt the remote PATH settings.That’s why lang/nix/config.el has included
/run/current-system/sw/bin
which is the root’sPATH
for Nix(OS), but it’s still not enough since we also need ~~/.nix-profile/bin~ for packages in user profile (usinghome-manager
).The solution was provided by rekado (thanks!) (as a reply to another more general question about
eshell
):;;; :lang nix (when (modulep! :lang nix) ;; You can configure TRAMP to respect the PATH variable on the remote ;; machine (for remote eshell sessions) by adding ;; `tramp-own-remote-path' to the list `tramp-remote-path': (after! tramp (add-to-list 'tramp-remote-path 'tramp-own-remote-path)))
- Configuration
Put any basic configuration here
;;; :lang org (after! org (setq org-hide-emphasis-markers t org-support-shift-select t ;; use g{h,j,k} to traverse headings and TAB to toggle their ;; visibility and leave C-left/C-right to . org-tree-slide-skip-outline-level 2 org-display-remote-inline-images 'cache org-startup-indented nil) <<org-conf>>)
- Org Modern
(add-hook 'org-mode-hook #'org-modern-mode) (after! org-modern (add-to-list 'org-modern-star "❯") (setq org-modern-list '((43 . "➤") (45 . "–") (42 . "✓"))))
- Emphasis markers
Doom Emacs already has set to hide emphasis markers in the org-tree-slide-mode.
This is just good to known for hiding/showing its.
(defun org-toggle-emphasis () "Toggle hiding/showing of org emphasis markers." (interactive) (if org-hide-emphasis-markers (setq org-hide-emphasis-markers nil) (setq org-hide-emphasis-markers t)))
"C-c e" #'org-toggle-emphasis
Source While
org-hide-emphasis-markers
is very nice, it can sometimes make edits which occur at the border a bit more fiddley. We can improve this situation without sacrificing visual amenities with theorg-appear
package.(add-hook! org-mode #'org-appear-mode) (after! org-appear (setq org-appear-autoemphasis t org-appear-autosubmarkers t org-appear-autolinks nil) ;; for proper first-time setup, `org-appear--set-elements' ;; needs to be run after other hooks have acted. (run-at-time nil nil #'org-appear--set-elements))
- Heading structure
(after! org-ol-tree (defadvice! org-ol-tree-system--graphical-frame-p--pgtk () :override #'org-ol-tree-system--graphical-frame-p (memq window-system '(pgtk x w32 ns))))
:desc "Outline" "O" #'org-ol-tree
- Transclusion
"<f12>" #'org-transclusion-add
:desc "Org Transclusion Mode" "T" #'org-transclusion-mode
- Capture
From the @Tecosaur’s Emacs configuration.
doct
(Declarative Org Capture Templates) seems to be a nicer way to set up org-capture.(after! org-capture <<prettify-capture>> (defun +doct-icon-declaration-to-icon (declaration) "Convert :icon declaration to icon" (let ((name (pop declaration)) (set (intern (concat "all-the-icons-" (plist-get declaration :set)))) (face (intern (concat "all-the-icons-" (plist-get declaration :color)))) (v-adjust (or (plist-get declaration :v-adjust) 0.01))) (apply set `(,name :face ,face :v-adjust ,v-adjust)))) (defun +doct-iconify-capture-templates (groups) "Add declaration's :icon to each template group in GROUPS." (let ((templates (doct-flatten-lists-in groups))) (setq doct-templates (mapcar (lambda (template) (when-let* ((props (nthcdr (if (= (length template) 4) 2 5) template)) (spec (plist-get (plist-get props :doct) :icon))) (setf (nth 1 template) (concat (+doct-icon-declaration-to-icon spec) "\t" (nth 1 template)))) template) templates)))) (setq doct-after-conversion-functions '(+doct-iconify-capture-templates)) (defvar +org-capture-recipes (concat org-directory "/recipes.org")) (defun set-org-capture-templates () (setq org-capture-templates (doct `(("Personal todo" :keys "t" :icon ("checklist" :set "octicon" :color "green") :file +org-capture-todo-file :prepend t :headline "Inbox" :type entry :template ("* TODO %?" "%i %a")) ("Personal note" :keys "n" :icon ("sticky-note-o" :set "faicon" :color "green") :file +org-capture-todo-file :prepend t :headline "Inbox" :type entry :template ("* %?" "%i %a")) ("Email" :keys "e" :icon ("envelope" :set "faicon" :color "blue") :file +org-capture-todo-file :prepend t :headline "Inbox" :type entry :template ("* TODO %^{type|reply to|contact} %\\3 %? :email:" "Send an email %^{urgancy|soon|ASAP|anon|at some point|eventually} to %^{recipiant}" "about %^{topic}" "%U %i %a")) ("Interesting" :keys "i" :icon ("eye" :set "faicon" :color "lcyan") :file +org-capture-todo-file :prepend t :headline "Interesting" :type entry :template ("* [ ] %{desc}%? :%{i-type}:" "%i %a") :children (("Webpage" :keys "w" :icon ("globe" :set "faicon" :color "green") :desc "%(org-cliplink-capture) " :i-type "read:web") ("Article" :keys "a" :icon ("file-text" :set "octicon" :color "yellow") :desc "" :i-type "read:reaserch") ("\tRecipe" :keys "r" :icon ("spoon" :set "faicon" :color "dorange") :file +org-capture-recipes :headline "Unsorted" :template "%(org-chef-get-recipe-from-url)") ("Information" :keys "i" :icon ("info-circle" :set "faicon" :color "blue") :desc "" :i-type "read:info") ("Idea" :keys "I" :icon ("bubble_chart" :set "material" :color "silver") :desc "" :i-type "idea"))) ("Tasks" :keys "k" :icon ("inbox" :set "octicon" :color "yellow") :file +org-capture-todo-file :prepend t :headline "Tasks" :type entry :template ("* TODO %? %^G%{extra}" "%i %a") :children (("General Task" :keys "k" :icon ("inbox" :set "octicon" :color "yellow") :extra "") ("Task with deadline" :keys "d" :icon ("timer" :set "material" :color "orange" :v-adjust -0.1) :extra "\nDEADLINE: %^{Deadline:}t") ("Scheduled Task" :keys "s" :icon ("calendar" :set "octicon" :color "orange") :extra "\nSCHEDULED: %^{Start time:}t"))) ("Project" :keys "p" :icon ("repo" :set "octicon" :color "silver") :prepend t :type entry :headline "Inbox" :template ("* %{time-or-todo} %?" "%i" "%a") :file "" :custom (:time-or-todo "") :children (("Project-local todo" :keys "t" :icon ("checklist" :set "octicon" :color "green") :time-or-todo "TODO" :file +org-capture-project-todo-file) ("Project-local note" :keys "n" :icon ("sticky-note" :set "faicon" :color "yellow") :time-or-todo "%U" :file +org-capture-project-notes-file) ("Project-local changelog" :keys "c" :icon ("list" :set "faicon" :color "blue") :time-or-todo "%U" :heading "Unreleased" :file +org-capture-project-changelog-file))) ("\tCentralised project templates" :keys "o" :type entry :prepend t :template ("* %{time-or-todo} %?" "%i" "%a") :children (("Project todo" :keys "t" :prepend nil :time-or-todo "TODO" :heading "Tasks" :file +org-capture-central-project-todo-file) ("Project note" :keys "n" :time-or-todo "%U" :heading "Notes" :file +org-capture-central-project-notes-file) ("Project changelog" :keys "c" :time-or-todo "%U" :heading "Unreleased" :file +org-capture-central-project-changelog-file))))))) (set-org-capture-templates) (unless (display-graphic-p) (add-hook 'server-after-make-frame-hook (defun org-capture-reinitialise-hook () (when (display-graphic-p) (set-org-capture-templates) (remove-hook 'server-after-make-frame-hook #'org-capture-reinitialise-hook))))))
It would also be nice to improve how the capture dialogue looks
(defun org-capture-select-template-prettier (&optional keys) "Select a capture template, in a prettier way than default Lisp programs can force the template by setting KEYS to a string." (let ((org-capture-templates (or (org-contextualize-keys (org-capture-upgrade-templates org-capture-templates) org-capture-templates-contexts) '(("t" "Task" entry (file+headline "" "Tasks") "* TODO %?\n %u\n %a"))))) (if keys (or (assoc keys org-capture-templates) (error "No capture template referred to by \"%s\" keys" keys)) (org-mks org-capture-templates "Select a capture template\n━━━━━━━━━━━━━━━━━━━━━━━━━" "Template key: " `(("q" ,(concat (all-the-icons-octicon "stop" :face 'all-the-icons-red :v-adjust 0.01) "\tAbort"))))))) (advice-add 'org-capture-select-template :override #'org-capture-select-template-prettier) (defun org-mks-pretty (table title &optional prompt specials) "Select a member of an alist with multiple keys. Prettified. TABLE is the alist which should contain entries where the car is a string. There should be two types of entries. 1. prefix descriptions like (\"a\" \"Description\") This indicates that `a' is a prefix key for multi-letter selection, and that there are entries following with keys like \"ab\", \"ax\"… 2. Select-able members must have more than two elements, with the first being the string of keys that lead to selecting it, and the second a short description string of the item. The command will then make a temporary buffer listing all entries that can be selected with a single key, and all the single key prefixes. When you press the key for a single-letter entry, it is selected. When you press a prefix key, the commands (and maybe further prefixes) under this key will be shown and offered for selection. TITLE will be placed over the selection in the temporary buffer, PROMPT will be used when prompting for a key. SPECIALS is an alist with (\"key\" \"description\") entries. When one of these is selected, only the bare key is returned." (save-window-excursion (let ((inhibit-quit t) (buffer (org-switch-to-buffer-other-window "*Org Select*")) (prompt (or prompt "Select: ")) case-fold-search current) (unwind-protect (catch 'exit (while t (setq-local evil-normal-state-cursor (list nil)) (erase-buffer) (insert title "\n\n") (let ((des-keys nil) (allowed-keys '("\C-g")) (tab-alternatives '("\s" "\t" "\r")) (cursor-type nil)) ;; Populate allowed keys and descriptions keys ;; available with CURRENT selector. (let ((re (format "\\`%s\\(.\\)\\'" (if current (regexp-quote current) ""))) (prefix (if current (concat current " ") ""))) (dolist (entry table) (pcase entry ;; Description. (`(,(and key (pred (string-match re))) ,desc) (let ((k (match-string 1 key))) (push k des-keys) ;; Keys ending in tab, space or RET are equivalent. (if (member k tab-alternatives) (push "\t" allowed-keys) (push k allowed-keys)) (insert (propertize prefix 'face 'font-lock-comment-face) (propertize k 'face 'bold) (propertize "›" 'face 'font-lock-comment-face) " " desc "…" "\n"))) ;; Usable entry. (`(,(and key (pred (string-match re))) ,desc . ,_) (let ((k (match-string 1 key))) (insert (propertize prefix 'face 'font-lock-comment-face) (propertize k 'face 'bold) " " desc "\n") (push k allowed-keys))) (_ nil)))) ;; Insert special entries, if any. (when specials (insert "─────────────────────────\n") (pcase-dolist (`(,key ,description) specials) (insert (format "%s %s\n" (propertize key 'face '(bold all-the-icons-red)) description)) (push key allowed-keys))) ;; Display UI and let user select an entry or ;; a sub-level prefix. (goto-char (point-min)) (unless (pos-visible-in-window-p (point-max)) (org-fit-window-to-buffer)) (let ((pressed (org--mks-read-key allowed-keys prompt (not (pos-visible-in-window-p (1- (point-max))))))) (setq current (concat current pressed)) (cond ((equal pressed "\C-g") (user-error "Abort")) ;; Selection is a prefix: open a new menu. ((member pressed des-keys)) ;; Selection matches an association: return it. ((let ((entry (assoc current table))) (and entry (throw 'exit entry)))) ;; Selection matches a special entry: return the ;; selection prefix. ((assoc current specials) (throw 'exit current)) (t (error "No entry available"))))))) (when buffer (kill-buffer buffer)))))) (advice-add 'org-mks :override #'org-mks-pretty)
The org-capture bin is rather nice, but it’d be nicer with a smaller frame, and no modeline.
(setf (alist-get 'height +org-capture-frame-parameters) 15) ;; (alist-get 'name +org-capture-frame-parameters) "❖ Capture") ;; ATM hardcoded in other places, so changing breaks stuff (setq +org-capture-fn (lambda () (interactive) (set-window-parameter nil 'mode-line-format 'none) (org-capture)))
- Display inline image for url that not end with ext.(like shields.io)
image-type-file-name-regexps is used to detect the image type. It’s a list of regexps. The first one that matches the file name in org-link is used to determine the image type to be displayed as inline image in org-mode buffer with org-display-user-inline-images if that link type have a
:image-data-fun
parameter (See org-link-set-parameters.)By default, the image type is determined by the file name extension at the end of the url (
\.ext\'
). So, if the url is , the image type issvg
and can be displayed as inline image but when it is , type cannot be determined. So we have to add a regexp to the list of regexps.;; To display inline image for url that not end with ext.(like shields.io), we ;; still need a file extenstion but not strict it at the end of the url. (add-to-list 'image-type-file-name-regexps '("\\.svgz?" . svg)) (advice-add #'+org-http-image-data-fn :override #'+org-http-with-desc-image-data-fn) (dolist (scheme '("id" "elisp" "mailto")) (org-link-set-parameters scheme :image-data-fun #'+org-http-image-data-fn))
;;; autoload/org-link.el -*- lexical-binding: t; -*- ;;;###autoload (defun +org-http-with-desc-image-data-fn (protocol link description) "Return the image data for URL." (when-let (image-url (and (not (eq org-display-remote-inline-images 'skip)) (or (and description (or (and (image-type-from-file-name description) (string-trim description "\\[\\[")) (and (length> description 11) (equal (substring description 0 3) "img") (setq protocol "img" description (substring description 4))))) (and (image-type-from-file-name link) (concat protocol ":" link))))) (if (equal protocol "img") (base64-decode-string image-url) (if-let ((buf (url-retrieve-synchronously image-url))) (with-current-buffer buf (goto-char (point-min)) (re-search-forward "\r?\n\r?\n" nil t) (buffer-substring-no-properties (point) (point-max))) (message "Download of image \"%s\" failed" image-url) nil))))
- Org Capture from Web browser
To let this configuration works,
Org Capture
browser’s extension andorg-protocol
handler is required.If you’re a WSL user, you can run the one that matches how your emacs launches:
powershell.exe ./org-protocol.reg
powershell.exe ./org-protocol-omz-emacs-plugin.reg
REGEDIT4 [HKEY_CLASSES_ROOT\org-protocol] @="URL:Org Protocol" "URL Protocol"="" [HKEY_CLASSES_ROOT\org-protocol\shell] [HKEY_CLASSES_ROOT\org-protocol\shell\open] [HKEY_CLASSES_ROOT\org-protocol\shell\open\command] @="\"C:\\Windows\\System32\\wsl.exe\" emacsclient \"%1\""
REGEDIT4 [HKEY_CLASSES_ROOT\org-protocol] @="URL:Org Protocol" "URL Protocol"="" [HKEY_CLASSES_ROOT\org-protocol\shell] [HKEY_CLASSES_ROOT\org-protocol\shell\open] [HKEY_CLASSES_ROOT\org-protocol\shell\open\command] @="\"C:\\Windows\\System32\\wsl.exe\" zsh -l ~/.oh-my-zsh/plugins/emacs/emacsclient.sh --no-wait \"%1\""
Then set keys for
Selected Template
andUnselected Template
in extension options to P&L to match with org-capture-templates fororg-protocol
set below.(defun transform-square-brackets-to-round-ones(string-to-transform) "Transforms [ into ( and ] into ), other chars left unchanged." (concat (mapcar #'(lambda (c) (if (equal c ?\[) ?\( (if (equal c ?\]) ?\) c))) string-to-transform)))
(setq org-capture-templates (append org-capture-templates `(("P" "Protocol" entry (file+headline +org-capture-notes-file "Inbox") "* %^{Title}\nSource: %u, %c\n #+BEGIN_QUOTE\n%i\n#+END_QUOTE\n\n\n%?") ("L" "Protocol Link" entry (file+headline +org-capture-notes-file "Inbox") "* %? [[%:link][%(transform-square-brackets-to-round-ones \"%:description\")]] \nCaptured On: %U") ("l" "Web site" entry (file+headline "webnotes.org" "Inbox") "* %a\nCaptured On: %U\nWebsite: %l\n\n%i\n%?") ("m" "meetup" entry (file "~/org/caldav.org") "* %?%:description \n%i\n%l") ("w" "Web site" entry (file+olp "~/org/inbox.org" "Web") "* %c :website:\n%U %?%:initial"))))
- Extend
org-edit-special
to support quote, verse, and comment blocks(add-hook 'org-mode-hook #'org-edit-indirect-mode)
(setq org-jira-working-dir "~/org/jira"
jiralib-url "https://cenergy.atlassian.net/")
This part is taken from @hlissner
(after! org-roam
(setq
<<Org-roam capture templates>>)
<<org-roam-conf>>)
- Extend org-roam-capture-templates to include the new templates.
org-roam-capture-templates `(("n" "note" plain ,(format "#+title: ${title}\n%%[%s/template/note.org]" org-roam-directory) :target (file "note/%<%Y%m%d%H%M%S>-${slug}.org") :unnarrowed t) ("r" "thought" plain ,(format "#+title: ${title}\n%%[%s/template/thought.org]" org-roam-directory) :target (file "thought/%<%Y%m%d%H%M%S>-${slug}.org") :unnarrowed t) ("t" "topic" plain ,(format "#+title: ${title}\n%%[%s/template/topic.org]" org-roam-directory) :target (file "topic/%<%Y%m%d%H%M%S>-${slug}.org") :unnarrowed t) ("c" "contact" plain ,(format "#+title: ${title}\n%%[%s/template/contact.org]" org-roam-directory) :target (file "contact/%<%Y%m%d%H%M%S>-${slug}.org") :unnarrowed t) ("p" "project" plain ,(format "#+title: ${title}\n%%[%s/template/project.org]" org-roam-directory) :target (file "project/%<%Y%m%d>-${slug}.org") :unnarrowed t) ("i" "invoice" plain ,(format "#+title: %%<%%Y%%m%%d>-${title}\n%%[%s/template/invoice.org]" org-roam-directory) :target (file "invoice/%<%Y%m%d>-${slug}.org") :unnarrowed t) ("f" "ref" plain ,(format "#+title: ${title}\n#+header: :var name='${title}' %%[%s/template/ref.org]" org-roam-directory) :target (file "ref/%<%Y%m%d%H%M%S>-${slug}.org") :unnarrowed t) ("w" "works" plain ,(format "#+title: ${title}\n%%[%s/template/works.org]" org-roam-directory) :target (file "works/%<%Y%m%d%H%M%S>-${slug}.org") :unnarrowed t) ("s" "secret" plain "#+title: ${title}\n\n" :target (file "secret/%<%Y%m%d%H%M%S>-${slug}.org.gpg") :unnarrowed t) ("h" "howdoyou" plain ,(format "#+title: ${title}\n%%[%s/template/howdoyou.org]" org-roam-directory) :target (file "howdoyou/%<%Y%m%d%H%M%S>-${slug}.org") :unnarrowed t)) ;; Use human readable dates for dailies titles org-roam-dailies-capture-templates '(("d" "default" entry "* %?" :target (file+head "%<%Y-%m-%d>.org" "#+title: %<%B %d, %Y>\n\n"))) org-roam-capture-ref-templates '(("l" "Web site" plain (function org-roam-capture--get-point) "${body}\n%?" :file-name "%<%Y%m%d>-${slug}" :head "#+title: ${title}\n#+CREATED: %U\n#+roam_key: ${ref}\n\n" :unnarrowed t))
Offer completion for #tags
and @areas
separately from notes.
(add-to-list 'org-roam-completion-functions
#'org-roam-complete-tag-at-point)
Automatically update the slug in the filename when #+title: has changed.
(add-hook 'org-roam-find-file-hook #'org-roam-update-slug-on-save-h)
Make the backlinks buffer easier to peruse by folding leaves by default.
(add-hook 'org-roam-buffer-postrender-functions
#'magit-section-show-level-2)
List dailies and zettels separately in the backlinks buffer.
(advice-add #'org-roam-backlinks-section :override
#'org-roam-grouped-backlinks-section)
Open in focused buffer, despite popups
(advice-add #'org-roam-node-visit :around #'+popup-save-a)
Make sure tags in vertico are sorted by insertion order, instead of arbitrarily (due to the use of group_concat in the underlying SQL query).
;; (advice-add #'org-roam-node-list :filter-return
;; #'org-roam-restore-insertion-order-for-tags-a)
;; (advice-remove #'org-roam-node-list
;; #'org-roam-restore-insertion-order-for-tags-a) ; hotfix
Add ID, Type, Tags, and Aliases to top of backlinks buffer.
(advice-add #'org-roam-buffer-set-header-line-format :after
#'org-roam-add-preamble-a)
(when (featurep 'sql-indent-autoloads) (add-hook! sql-mode #'sqlind-minor-mode))
;; MySQL
(require 'emacsql-mysql)
- Tailwind LSP :+tailwind:
Provide tailwind class name completions as an extra LSP package.
Install the lsp-tailwindcss emacs package: Packages in Use
Set add-on mode. This ensures both tailwind-lsp and the js or ts lsp servers run in parallel. Set
lsp-tailwindcss-add-on-mode
before the package loads.;;; :lang web (use-package! lsp-tailwindcss :when (and (modulep! :tools lsp) (modulep! :lang web +tailwind)) :init (setq lsp-tailwindcss-add-on-mode t lsp-tailwindcss-major-modes '(rjsx-mode web-mode html-mode css-mode typescript-mode typescript-tsx-mode)))
Lastly associate various template language files with html so that lsp-tailwindcss runs when editing .liquid files for example.
(add-to-list 'lsp-language-id-configuration '(".*\\.liquid" . "html"))
Make sure to add the
+tailwind
flag to the:lang web
module in init.el!
WSL-specific setup
;;; os/wsl/config.el -*- lexical-binding: t; -*-
(when (and IS-LINUX (getenv "WSLENV"))
;; pgtk is only available in Emacs 29+
;; without it Emacs fonts don't scale properly on HiDPI display
(unless EMACS29+
(set-frame-font "Cascadia Code 28"))
;; Teach Emacs how to open links in your default Windows browser
(let ((cmd-exe "/mnt/c/Windows/System32/cmd.exe")
(cmd-args '("/c" "start")))
(when (file-exists-p cmd-exe)
(setq browse-url-generic-program cmd-exe
browse-url-generic-args cmd-args
browse-url-browser-function 'browse-url-generic
search-web-default-browser 'browse-url-generic
org-clock-sound "/mnt/c/Windows/Media/Alarm06.wav"))))
In Vterm I prefer ESC
to be a vterm--self-insert
;;; :term vterm
(add-hook 'vterm-mode-hook #'evil-collection-vterm-toggle-send-escape)
Other keybinds these should be…
(:when (modulep! :term vterm)
:map vterm-mode-map
:i "C-j" #'vterm--self-insert
"C-c C-x" #'vterm--self-insert)
#+TITLE: tools/brief
#+DATE: January 13, 2022
#+SINCE: v3.0.0-alpha
#+STARTUP: inlineimages nofold
* Table of Contents :TOC_3:noexport:
- [[#description][Description]]
- [[#maintainers][Maintainers]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#prerequisites][Prerequisites]]
- [[#features][Features]]
- [[#configuration][Configuration]]
- [[#troubleshooting][Troubleshooting]]
* Description
Module for brief help
+ If possible, include a brief list of feature highlights here
+ Like code completion, syntax checking or available snippets
+ Include links to packages & external things where possible
** Maintainers
+ [[https://github.com/thaenalpha][@thaenalpha]] (Author)
** Module Flags
This module provides no flags.
** Plugins
+ [[https://github.com/davep/cheat-sh.el][cheat-sh]]
+ [[https://github.com/thanhvg/emacs-howdoyou][howdoyou]]
+ [[https://github.com/kuanyui/tldr.el][tldr]]
+ [[https://github.com/astoff/devdocs.el][devdocs]]
* Prerequisites
This module has no prerequisites.
* Features
# An in-depth list of features, how to use them, and their dependencies.
* Configuration
# How to configure this module, including common problems and how to address them.
* Troubleshooting
;;; tools/brief/config.el -*- lexical-binding: t; -*-
(use-package! howdoyou
:commands (howdoyou-query aj/howdoyou-hydra/body)
:config
(set-popup-rule! "*How Do You" :vslot 3 :size 0.5 :side 'top
:select t :ttl nil :modeline t :autosave t :quit t)
(add-hook! 'howdoyou-mode-hook
(doom-mark-buffer-as-real-h)
(persp-add-buffer (current-buffer)) (solaire-mode +1)
(setq-local org-src-fontify-natively nil
buffer-file-name (concat org-roam-directory "howdoyou/latest.org"))))
(use-package! tldr
:commands tldr
:config
(setq tldr-directory-path (expand-file-name "tldr" doom-cache-dir))
(add-hook! tldr-mode #'doom-mark-buffer-as-real-h #'mixed-pitch-mode
#'+popup/buffer)
(set-popup-rule! "*tldr"
:vslot 1 :size 82 :side 'left :select t :ttl nil :modeline t))
(map! :map search-map
"M-a" #'howdoyou-query
"M-s" #'cheat-sh
"M-d" #'tldr
"d" (define-prefix-command 'devdocs))
(defun corfu-next-or-copilot-completion ()
(interactive)
(or (copilot-accept-completion)
(corfu-next)))
;; complete by copilot first, then corfu-mode
(use-package! copilot
:after corfu
:hook ((prog-mode text-mode) . copilot-mode)
:bind (([remap aya-expand] . copilot-accept-completion-by-word) ; C-TAB
:map corfu-map
([tab] . corfu-next-or-copilot-completion)
("TAB" . corfu-next-or-copilot-completion)))
Command-line fuzzy finder written in Go
;;; tools/fzf/config.el -*- lexical-binding: t; -*-
(after! evil
(evil-define-key 'insert fzf-mode-map (kbd "ESC") #'term-kill-subjob))
(define-minor-mode fzf-mode
"Minor mode for the FZF buffer"
:init-value nil
:lighter " FZF"
:keymap '(("C-c" . term-kill-subjob)))
(defadvice! doom-fzf--override-start-args-a (original-fn &rest args)
"Set the FZF minor mode with the fzf buffer."
:around #'fzf/start
(message "called with args %S" args)
(apply original-fn args)
;; set the FZF buffer to fzf-mode so we can hook ctrl+c
(set-buffer "*fzf*")
(fzf-mode))
(defvar fzf/args
"-x --print-query -m --tiebreak=index --expect=ctrl-v,ctrl-x,ctrl-t")
(use-package! fzf
:commands (fzf fzf-projectile fzf-hg fzf-git fzf-git-files fzf-directory
fzf-git-grep)
:bind ("C-c t" . fzf-projectile))
;; -*- no-byte-compile: t; -*-
;;; tools/fzf/packages.el
(package! fzf)
Troubleshooting for the conflict of gist-star
and evil-snipe-s
key.
;;; :tools gist
(add-hook! gist-list-mode #'turn-off-evil-snipe-mode)
(:when (modulep! :tools gist)
:desc "List other user's gists" "u" #'gist-list-user
:desc "List your starred gists" "M-s" #'gist-list-starred)
+devdocs
+docsets
Search Dash GUI from Emacs. only works in MacOS.
(:when (and IS-MAC (modulep! :tools lookup +docsets))
:desc "dash at point" "d" #'dash-at-point)
;;; :tools LSP -- https://git.sr.ht/~gagbo/doom-config
(unless (modulep! :checkers syntax)
(setq lsp-diagnostics-provider :flymake))
(after! lsp-mode
(setq
lsp-auto-guess-root t
lsp-enable-semantic-tokens-enable nil
lsp-progress-via-spinner nil
lsp-idle-delay 0.47
lsp-completion-enable-additional-text-edit nil
lsp-signature-render-documentation t
lsp-signature-auto-activate t
lsp-signature-doc-lines 5
lsp-eldoc-enable-hover t
lsp-headerline-breadcrumb-enable t
lsp-enable-indentation nil
lsp-enable-on-type-formatting nil
lsp-enable-symbol-highlighting nil
lsp-enable-links nil
lsp-lens-enable nil
lsp-log-io nil))
(setq +lsp-company-backends '(company-capf :with company-yasnippet))
(after! lsp-ui
(setq lsp-ui-sideline-enable t
lsp-ui-sideline-show-code-actions t
lsp-ui-sideline-show-symbol nil
lsp-ui-sideline-show-hover nil
lsp-ui-sideline-show-diagnostics nil
lsp-ui-peek-enable nil
lsp-ui-doc-enable t
lsp-ui-doc-position 'top
lsp-ui-doc-delay 0.73
lsp-ui-doc-max-width 50
lsp-ui-doc-max-height 15
lsp-ui-doc-include-signature t
lsp-ui-doc-header t)
(add-hook! 'lsp-ui-mode-hook
(run-hooks (intern (format "%s-lsp-ui-hook" major-mode)))))
Fastest LSP client for Emacs
#+TITLE: tools/lsp-bridge
#+DATE: June 2, 2022
#+,SINCE: v3.0.0-dev
#+,STARTUP: inlineimages nofold
* Table of Contents :TOC_3:noexport:
- [[#description][Description]]
- [[#maintainers][Maintainers]]
- [[#module-flags][Module Flags]]
- [[#plugins][Plugins]]
- [[#hacks][Hacks]]
- [[#prerequisites-python-epc][Prerequisites: python-epc]]
- [[#features][Features]]
- [[#configuration][Configuration]]
- [[#troubleshooting][Troubleshooting]]
* Description
Fastest LSP client for Emacs.
+ Fast & smooth code completion
** Maintainers
+ [[https://github.com/evanmeek][@evanmeek]] (Author)
+ [[https://github.com/jadestrong][@jadestrong]]
+ [[https://github.com/thaenalpha][@thaenalpha]]
** Module Flags
This module provides no flags.
** Plugins
+ [[https://github.com/tumashu/posframe][posframe]]
+ [[https://github.com/jrblevin/markdown-mode][markdown-mode]]
+ [[https://github.com/joaotavora/yasnippet][yasnippet]]
** Hacks
# A list of internal modifications to included packages; omit if unneeded
* Prerequisites: [[https://github.com/tkf/python-epc][python-epc]]
#+begin_src shell
pip install epc
#+end_src
* Features
# An in-depth list of features, how to use them, and their dependencies.
* Configuration
# How to configure this module, including common problems and how to address them.
* Troubleshooting
# Common issues and their solution, or places to look for help.
(use-package! lsp-bridge
:init
(defun lsp-bridge-mix-multi-backends ()
(setq-local completion-category-defaults nil)
(setq-local completion-at-point-functions
(list
(cape-capf-buster
(cape-super-capf
#'cape-file
#'cape-dabbrev)
'equal))))
(defun lsp-bridge-jump ()
(interactive)
(cond
((eq major-mode 'emacs-lisp-mode)
(let ((symb (function-called-at-point)))
(when symb
(find-function symb))))
(lsp-bridge-mode
(lsp-bridge-find-def))
(t
(require 'dumb-jump)
(dumb-jump-go))))
(defun lsp-bridge-jum
(interactive)
(cond
(lsp-bridge-mode
(lsp-bridge-return-from-def))
(t
(require 'dumb-jump)
(dumb-jump-back))))
:config
(setq acm-enable-dabbrev t)
(dolist (hook (-remove-item nil lsp-bridge-default-mode-hooks))
(add-hook hook (lambda ()
(setq-local corfu-auto nil)
(lsp-bridge-mode 1)
(lsp-bridge-mix-multi-backends)
(setq-local +lookup-definition-functions
'(lsp-bridge-jump)
+lookup-references-functions
'(lsp-bridge-find-references)
+lookup-implementations-functions
'(lsp-bridge-find-impl))
(map! :map doom-leader-code-map
:desc "Rename" "r" #'lsp-bridge-rename)
;; (map! :map evil-normal-state-local-map
;; :desc "Go to definition" "gd" #'lsp-bridge-jump)
)))
(dolist (command '("save-buffer" "evil-normal-state"))
(add-to-list 'lsp-bridge-completion-stop-commands command 'append))
(defadvice! ++javascript-init-lsp-or-tide-maybe-h ()
:override #'+javascript-init-lsp-or-tide-maybe-h
nil)
(global-lsp-bridge-mode)
;; :custom
;; (lsp-bridge-lang-server-extension-list
;; '((("vue") . "/home/evanmeek/.asdf/shims/vue-language-server")))
)
(dolist (project
'([tailwindcss "package.json"] ; `lsp-bridge--tailwindcss-project-p'
[vue "package.json"])) ; `lsp-bridge--vue-project-p'
(let ((name (elt project 0))
(dominating-file (elt project 1)))
(defalias (make-symbol (format "lsp-bridge--%s-project-p" name))
(lambda (project-path)
(if-let ((project-conf (f-join project-path dominating-file))
(exist (f-file-p project-conf))
(config (json-read-file project-conf))
(dependencies (alist-get 'dependencies config)))
(alist-get name dependencies)
nil)))))
(dolist (list-name '(clojure-project-list
javascript-project-list
php-project-list
rust-project-list
tailwindcss-project-list
vue-project-list))
(eval `(defvar ,list-name nil)))
(defconst lsp-bridge-directory (file-name-directory lsp-bridge-python-file))
(setq lsp-bridge-get-lang-server-by-project
(lambda (project-path file-path)
(cond
((or (member project-path clojure-project-list)
(locate-dominating-file project-path "deps.edn")
(locate-dominating-file project-path "project.clj"))
(expand-file-name "clojure-lsp.json" lsp-bridge-directory))
((or (member project-path javascript-project-list)
(locate-dominating-file project-path "package.json"))
(expand-file-name "javascript.json" lsp-bridge-directory))
((or (member project-path php-project-list)
(locate-dominating-file project-path "composer.json"))
(expand-file-name "intelephense.json" lsp-bridge-directory))
((or (member project-path rust-project-list)
(locate-dominating-file project-path "cargo.toml"))
(expand-file-name "rust-analyzer.json" lsp-bridge-directory))
((or (member project-path tailwindcss-project-list)
(lsp-bridge--tailwindcss-project-p project-path))
(expand-file-name (if IS-MAC
"tailwindcss_darwin.json"
"tailwindcss.json") lsp-bridge-directory))
((or (member project-path vue-project-list)
(lsp-bridge--vue-project-p project-path))
(expand-file-name (if IS-MAC
"volar_darwin.json"
"volar.json") lsp-bridge-directory)))))
;; -*- no-byte-compile: t; -*-
;;; tools/lsp-bridge/packages.el
(package! lsp-bridge
:recipe (:host github :repo "manateelazycat/lsp-bridge"
:files ("*.el" "*.py" "core/*.py" "langserver/*.json" "acm/*")))
;;; :tools magit
(setq magit-repository-directories `((,doom-emacs-dir . 0)
(,doom-private-dir . 0)
("~/projects/" . 1)
("~/www/" . 1)
("~/downloads/emacs-config/" . 1))
;; Don't restore the wconf after quitting magit, it's jarring
magit-inhibit-save-previous-winconf t
transient-values
'((magit-commit "--gpg-sign=5750461731047101")
(magit-rebase "--autosquash" "--autostash"
"--gpg-sign=5750461731047101")
(magit-pull "--rebase" "--autostash" "--gpg-sign=5750461731047101")
(magit-revert "--autostash")))
(after! forge
(when EMACS29+ ; sqlite buitin support
(setq forge-database-connector 'sqlite-builtin))
(setq forge-owned-accounts '(("thaenalpha" remote-name "my-fork")))
;; https://github.com/yqrashawn/yqdotfiles/blob/6ef183fb30d9f2030104b51988820b1d745f9203/.doom.d/version-control.el#L112-L115
(defadvice! +forge-topic-setup-buffer (topic)
"Refresh topic on open"
:after #'forge-topic-setup-buffer
(forge-pull-topic topic)))
(when (featurep 'magit-cz-autoloads) ; magit commitizen support
(add-hook 'git-commit-setup-hook
#'magit-cz-git-commit-message-setup-function))
This will enable gravatars when viewing commits. The service used by default is Libravatar.
magit-revision-show-gravatars '("^Author: " . "^Commit: ")
Take this extra config from Package praise: Prodigy and fancy-dabbrev : emacs
;; Custom status to indicate that the service is doing something
;; that means it's not ready yet (compiling, etc)
(prodigy-define-status :id 'working :face 'prodigy-yellow-face)
;; Keep track of how many services are in each status
(defvar my-prodigy-service-counts nil "Map of status ID to number of
Prodigy processes with that status")
;; Poor man's hook: when a service's status changes, update the counts
;; in our alist
(advice-add
'prodigy-set-status
:before
(lambda (service new-status)
(let* ((old-status (plist-get service :status))
(old-status-count
(or 0 (alist-get old-status my-prodigy-service-counts)))
(new-status-count
(or 0 (alist-get new-status my-prodigy-service-counts))))
(when old-status
(setf (alist-get old-status my-prodigy-service-counts)
(max 0 (- old-status-count 1))))
(setf (alist-get new-status my-prodigy-service-counts)
(+ 1 new-status-count))
(force-mode-line-update t))))
(defun my-prodigy-working-count ()
"Number of services with the 'working' status."
(let ((count (alist-get 'working my-prodigy-service-counts 0)))
(when (> count 0)
(format "W:%d" count))))
(defun my-prodigy-failed-count ()
"Number of services with the 'failed' status."
(let ((count (alist-get 'failed my-prodigy-service-counts 0)))
(if (> count 0)
(format "F:%d" count)
"")))
;; Show some status counts in the mode line so I can easily see when a
;; service is compiling or failed
(setq-default mode-line-format ; TODO Change this to doom-modeline
(append mode-line-format
'((:eval (my-prodigy-working-count))
(:eval (propertize (my-prodigy-failed-count)
'face 'prodigy-red-face)))))
;; Compiling via yarn can take a long time, so detect when it
;; starts compiling and set the status to "working"
(prodigy-define-tag
:name 'yarn
:on-output (lambda (&rest args)
(let ((output (plist-get args :output))
(service (plist-get args :service)))
(when (or (s-matches? "Already up to date." output)
(s-matches? "remote" output))
(prodigy-set-status service 'working)))))
;; Define a service that compiles a frontend app, using Yarn
(prodigy-define-service
:name "my-frontend-app"
:command "fish"
:args '(".local/bin/run-fe.fish")
:cwd (getenv "HOME")
:ready-message "Server start at "
:tags '(work yarn)
:stop-signal 'sigkill
:kill-process-buffer-on-stop t)
;; (define more services here...)
My current development external services & code builders:
- PHP FPM
(prodigy-define-service :name "PHP-FPM" :command "php-fpm" :args '("--nodaemonize") :tags '(work lang) :stop-signal 'kill :kill-process-buffer-on-stop t)
- MySQL
(prodigy-define-service :name "mysqld daemon" :command "mysqld_safe" :args '("--datadir=/opt/homebrew/var/mysql") :tags '(work dbms) :stop-signal 'kill :kill-process-buffer-on-stop t)
- Redis
(prodigy-define-service :name "Redis server" :command "redis-server" :tags '(work cache) :stop-signal 'kill :kill-process-buffer-on-stop t)
- Nginx
(prodigy-define-service :name "nginx daemon" :command "/opt/homebrew/opt/nginx/bin/nginx" :args '("-g" "daemon off;") :tags '(work www) :stop-signal 'kill :kill-process-buffer-on-stop t)
(load! "+local" nil t)
SPC p e
projectile-edit-dir-locals
This command let you custom project variables in .dir-locals.el
For more information see (info “(emacs) Directory Variables”)
((nil . ((projectile-project-name . "oh-my-system") ; Edit project name .doom.d
(indent-tabs-mode . nil)
(git-commit-major-mode . git-commit-elisp-text-mode)
(fill-column . 80)
(sentence-end-double-space . t)
(emacs-lisp-docstring-fill-column . 75)
(project-vc-merge-submodules . nil)))
(org-mode . ((flycheck-textlint-executable . "node_modules/.bin/textlint"))))
To makes README.org buffer read-only
((org-mode (buffer-read-only . t)))
Emacs treat some variable as a risky-local-variable and will request your
permission to apply these variables every time when accessing a file within a
directory that contains settings for these variable in .dir-locals.el
file.
;;
;;; Local Variables
(put 'flycheck-textlint-executable 'safe-local-variable #'stringp)
(put 'quickrun-option-command 'safe-local-variable #'stringp)
projectile-project-root-files-bottom-up
A list of files considered marking the root of a project.
(after! projectile
(setq projectile-project-root-files-bottom-up
(remove ".git" projectile-project-root-files-bottom-up)))
#+Original Value:
.projectile | .git | .hg | .fslckout | FOSSIL | .bzr | _darcs |