Skip to content

thaenalpha/.doom.d

Repository files navigation

Doom Emacs configuration

;;; $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 "[email protected]")

(when-let (authinfo (and IS-MAC "~/.authinfo.gpg")) ; Ghub's preference.
  (setq auth-sources (cons authinfo (remove authinfo auth-sources))))

Table of Contents

System

;;
;;; System

Emacs

(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

Multitasking

Split windows

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

Power

Battery usage

(unless (equal "Battery status not available" (battery)) ; on laptops…
  (display-battery-mode 1)) ; it's nice to know how much power you have

Startup

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

Trash

(setq-default delete-by-moving-to-trash t) ; Delete files to trash

Troubleshoot

Github remote SSH always ask for passphrase

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

Why is Emacs/Doom slow?

Some advice from @hlissner on Why is Emacs/Doom slow? - Discussion / Doom Emacs Discourse Captured On: [2021-12-11 Sat 14:35]

  1. 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.

  2. Disable some of Doom’s slowest modules.
  3. 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.

  4. 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))
        
  5. 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)
  6. 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.
  7. Check out “Common configuration mistakes”.

Personalization

UI stuff

;;
;;; UI

(setq <<line-numbers-setting>>
      <<my-treemacs-settings>>
      <<my-theme-settings>>
      <<my-fonts-settings>>)

<<my-frame-settings>>

<<my-theme-script>>

Colors

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.

Choose your mode:

auto

Themes

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

Fonts

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

Line numbers

display-line-numbers-type  nil

Frame

(dolist (params '(<<frame-size>>
                  <<mouse-color>>
                  <<prevents-flickering>>
                  <<scroll-bar>>))
  (add-to-list 'default-frame-alist params))

Frame sizing

(height . 50) (width . 162)

Mouse color

(mouse-color . "red")

Prevents some cases of Emacs flickering.

(inhibit-double-buffering . t)

Scroll bar

(scroll-bar-width . 11)

Minibuffer

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)
        

Mode Line

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

Miscellaneous

Scrolling

;; Roll the mouse wheel to scrolls the display pixel-by-pixel.
(when (fboundp #'pixel-scroll-precision-mode) ; EMACS29+
  (pixel-scroll-precision-mode t))

Keyboard Shortcuts

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

Leader SPC

Open o

Keyboard Shortcut to open apps with <leader> o prefix.

:desc "Calc"              "c"     #'calc
:desc "APP: rss"          ","     #'=rss

Apps

Apps for websites

Open websites that can open in Emacs instead of a browser

Discourse

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

mastodon-instance-url "https://mstdn.io"

Md4rd

** 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"

Better defaults packages

info-colors

Extra colors for Info-mode

ctrlf

Replace isearch functions with more reliable browser-like experience.

dirvish

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

;;
;;; Time & language

Date & time

(display-time-mode 1)                   ; Enable time in the mode-line

Typing

Dabbrev

"M-/"   #'dabbrev-completion   ; Swap M-/ and C-M-/
"C-M-/" #'dabbrev-expand

Accessibility

;;
;;; Accessibility

Visual effects

;; Nice scrolling
(setq scroll-conservatively 100000
      scroll-preserve-screen-position 1) ; Don't have `point' jump around

Text cursor

(setq-default x-stretch-cursor t)       ; Stretch cursor to the glyph width

Privacy & security

;;
;;; Security

(setq password-cache-expiry nil)        ; I can trust my computers … can't I?

Autoload

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.

Random

;;; autoload/random.el -*- lexical-binding: t; -*-

One or Another

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

Random a character

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

String Manipulation

;;; autoload/string.el -*- lexical-binding: t; -*-

Arrayify

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

;;
;;; Modules

App

Edit-Server

README.org

#+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.

config.el

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

packages.el

Installing edit-server from MELPA

  • install as a doom :app module (You need to add edit-server at :app in init.el)
;; -*- no-byte-compile: t; -*-
;;; app/edit-server/packages.el

(package! edit-server)
  • uninstall by replace :tangle value in install block with no and just press enter at the block below:
    rm -r modules/app/edit-server
        

Mastodon

;;; :app mastodon
(after! mastodon
  (setq <<Mastodon Configuration>>))

README.org

#+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.

packages.el

(package! mastodon)

Reddit

;;; :app reddit
(after! md4rd
  <<md4rd-conf>>)

README.org

#+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.

config.el

;;; app/reddit/config.el -*- lexical-binding: t; -*-

(use-package! md4rd :hook (md4rd-mode . md4rd-indent-all-the-lines))

rss (+org)

;;; :app rss +org
(after! elfeed
  <<elfeed-conf>>)

Elfeed configuration elfeed-conf

Automatically updating feed when opening elfeed

Hook elfeed-update to elfeed-search-mode-hook

(add-hook! elfeed-search-mode #'elfeed-update)
Automatically tagging feed as “ignore” on invalid feeds
(setq rmh-elfeed-org-auto-ignore-invalid-feeds t)

Slack

config.el

 ;;; 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 - full featured unofficial client for Telegram platform for GNU Emacs.

README.org

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.

Dependencies

Look for all dependencies at Telega Manual (v0.7.018)

GNU gperf (for building TDLib)
Guix users
guix install gperf
Building TDLib

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.

Installing telega from MELPA
  • install as a doom module (You need to add telega at :app in init.el)
  • uninstall by replace :tangle value in install block with no and just press enter at the block below:
    rm -r modules/app/telega
        

config.el

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

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

Completion

Corfu

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

README.org

#+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

autoload.el

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

config.el

;;; 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))
Completing with Corfu in the minibuffer
(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)
Completing with Corfu in the Shell or Eshell

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

packages.el

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

Tabnine

With Company

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

Configuration code
;;; :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)))

With Corfu

This configuration come as a flag and is bundled with corfu module. Enable by add +tabnine flag to :completion corfu

Tabnine part in Doom’s Corfu module source code
  • 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")))
        

Vertico

  • +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.

Configure

literate

default

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

UI

Deft

Specify the directory in which your notes are stored:

;;; ui: deft
(setq deft-directory "~/notes")

Discover

Doom-Dashboard

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

fixmee

README.org

#+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.

config.el

;;; ui/fixmee/config.el -*- lexical-binding: t; -*-

(add-transient-hook! #'global-fixmee-mode (require 'button-lock))

packages.el

Fixmee

Hydra

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)

Modeline

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)

modus

config.el

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

packages.el

;; -*- no-byte-compile: t; -*-
;;; ui/modus/packages.el

(package! modus-themes :built-in 'prefer)

Tab-Workspaces (work in progress)

README.org

#+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.

bindings.el

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

config.el

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

generic.el

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

packages.el

;; -*- no-byte-compile: t; -*-
;;; ui/tab-workspaces/packages.el

(package! bufler)
(package! burly)

Treemacs

Set theme, git-mode and width

treemacs-width             27
+treemacs-git-mode         'deferred

Mixed pitch

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

Writeroom

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

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

Change evil surround pairs alist

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

Format

Disabling the LSP formatter

According to src_org{** Disabling the LSP formatter} in format

If you are in a buffer with lsp-mode enabled and a server that supports textDocument/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)
        

Rotate text

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

;;; emacs/ctrlf/config.el -*- lexical-binding: t; -*-

(use-package! ctrlf :hook (doom-first-input . ctrlf-mode))

dired

;;; :emacs dired +dirvish
(when (modulep! :emacs dired +dirvish)
  <<dirvish Configuration>>)

info

;;; emacs/info/config.el -*- lexical-binding: t; -*-

(use-package! info-colors :hook (Info-selection . info-colors-fontify-node))

Email 📧

mu4e

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 '(("/[email protected]/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!
 "[email protected]"
 '((mu4e-sent-folder       . "/[email protected]/[Gmail]/Sent Mail")
   (mu4e-drafts-folder     . "/[email protected]/[Gmail]/Drafts")
   (mu4e-spam-folder       . "/[email protected]/[Gmail]/Spam")
   (mu4e-trash-folder      . "/[email protected]/[Gmail]/Trash")
   (mu4e-refile-folder     . "/[email protected]/[Gmail]/All Mail")
   (smtpmail-smtp-user     . "[email protected]")
   (mu4e-compose-signature . "---\nNopanun Laochunhanun"))
 t)
(set-email-account!
 "[email protected]"
 '((mu4e-sent-folder       . "/[email protected]/[Gmail]/Sent Mail")
   (mu4e-drafts-folder     . "/[email protected]/[Gmail]/Drafts")
   (mu4e-spam-folder       . "/[email protected]/[Gmail]/Spam")
   (mu4e-trash-folder      . "/[email protected]/[Gmail]/Trash")
   (mu4e-refile-folder     . "/[email protected]/[Gmail]/All Mail")
   (smtpmail-smtp-user     . "[email protected]")
   (mu4e-compose-signature . "---\nNopanun Laochunhanun"))
 t)
(set-email-account!
 "[email protected]"
 '((mu4e-sent-folder       . "/[email protected]/Sent")
   (mu4e-drafts-folder     . "/[email protected]/Drafts")
   (mu4e-spam-folder       . "/[email protected]/Junk")
   (mu4e-trash-folder      . "/[email protected]/Deleted")
   (mu4e-refile-folder     . "/[email protected]/Archive")
   (smtpmail-smtp-user     . "[email protected]")
   (mu4e-compose-signature . "---\nNopanun Laochunhanun"))
 t)
(set-email-account!
 "[email protected]"
 '((mu4e-sent-folder       . "/[email protected]/Sent")
   (mu4e-drafts-folder     . "/[email protected]/Drafts")
   (mu4e-spam-folder       . "/[email protected]/Junk")
   (mu4e-trash-folder      . "/[email protected]/Deleted")
   (mu4e-refile-folder     . "/[email protected]/Archive")
   (smtpmail-smtp-user     . "[email protected]")
   (mu4e-compose-signature . "---\nNopanun Laochunhanun"))
 t)
(set-email-account!
 "[email protected]"
 '((mu4e-sent-folder       . "/[email protected]/Sent")
   (mu4e-drafts-folder     . "/[email protected]/Draft")
   (mu4e-spam-folder       . "/[email protected]/Bulk Mail")
   (mu4e-trash-folder      . "/[email protected]/Trash")
   (mu4e-refile-folder     . "/[email protected]/Archive")
   (smtpmail-smtp-user     . "[email protected]")
   (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:/[email protected]/INBOX or m:/[email protected]/Inbox or "
   "m:/[email protected]/Inbox or m:/[email protected]/Inbox or "
   "m:/[email protected]/INBOX or m:/[email protected]/IT Demands")
 "All Inboxes" ?i)
(,(concat
   "m:/[email protected]/[Gmail]/Sent Mail or m:/[email protected]/Sent "
   "m:/[email protected]/[Gmail]/Sent Mail or m:/[email protected]/Sent or"
   " m:/[email protected]/Sent") "All Sent" ?s)
(,(concat
   "m:/[email protected]/[Gmail]/Drafts or m:/[email protected]/Drafts "
   "m:/[email protected]/[Gmail]/Drafts or m:/[email protected]/Drafts or "
   "m:/[email protected]/Draft") "All Drafts" ?d)
(,(concat
   "m:/[email protected]/[Gmail]/All Mail or m:/[email protected]/Archive"
   " m:/[email protected]/[Gmail]/All Mail or m:/[email protected]/Archive"
   " or m:/[email protected]/Archive") "All Archives" ?a)
(,(concat
   "m:/[email protected]/[Gmail]/Spam or m:/[email protected]/Junk or "
   "m:/[email protected]/[Gmail]/Spam or m:/[email protected]/Junk or "
   "m:/[email protected]/Bulk Mail") "All Spams" ?p)
(,(concat
   "m:/[email protected]/[Gmail]/Trash or m:/[email protected]/Deleted or"
   " m:/[email protected]/[Gmail]/Trash or m:/[email protected]/Deleted or"
   " m:/[email protected]/Trash") "All Trashes" ?t)

This is your e-mail client to build!

Programming Language

This section adds packages and configuration on top of Doom Lang modules

Clojure

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

JavaScript

Description

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.

Plugins
;; -*- no-byte-compile: t; -*-
;;; lang/javascript/packages.el

coffee-mode js2-mode rjsx-mode typescript-mode

;; Major modes
(package! rjsx-mode)
(package! typescript-mode)

js2-refactor npm-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"))

nodejs-repl skewer-mode

;; Eval
(package! nodejs-repl)
(package! skewer-mode)

tide xref-js2*

;; Programming environment
(package! tide)
(when (modulep! :tools lookup)
  (package! xref-js2))
Appendix
symbolicdescription
PACKAGEDoom original
+ PACKAGEAdd-on plugin

New features

  • automatically import dependencies in your JavaScript project.
  • Minor mode for working with pnpm projects
  • Minor mode for working with yarn projects

Prerequisites for

This module

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
Running ImportJS in Emacs
  1. 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
        
  2. Configure Import-Js
  3. Install Watchman as an performance booster to import-js daemon
    • macOS or Linux
      brew update && brew install watchman
              

Configuration

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")
import-js
  1. 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))))))
              
  2. 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))
              
  3. 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
              
  4. 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))))
              
  5. 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)))
        
pnpm-mode
;;;###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))))
Project Activation

By adding .dir-locals.el file on the root directory of a project containing:

((nil . ((mode . pnpm))))
For more detail: rajasegar/pnpm-mode: An Emacs minor mode for working with NPM projects. Captured On: [2021-10-21 Thu 15:08]
yarn
keyboard bindings

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

By adding .dir-locals.el file on the root directory of a project containing:

((nil . ((mode . yarn))))
For more detail: rajasegar/pnpm-mode: An Emacs minor mode for working with NPM projects. Captured On: [2021-10-21 Thu 15:08]

Troubleshooting

import-js-daemon not running

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.

Nix

doom-modules:lang/nix extended configuration.

Troubleshooting

  • 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’s PATH for Nix(OS), but it’s still not enough since we also need ~~/.nix-profile/bin~ for packages in user profile (using home-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)))
        

Org

  • 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 the org-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 [[image-url:https://shields.io/badge/foo-bar-baz.svg][https://shields.io/badge/foo-bar-baz.svg]], the image type is ~svg and can be displayed as inline image https://shields.io/badge/foo-bar-baz.svg but when it is https://shields.io/badge/foo-bar-baz.svg?color=blue, 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 and org-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 and Unselected Template in extension options to P&L to match with org-capture-templates for org-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)
        

Jira

(setq org-jira-working-dir "~/org/jira"
      jiralib-url "https://cenergy.atlassian.net/")

Roam +roam2

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)

SQL

(when (featurep 'sql-indent-autoloads) (add-hook! sql-mode #'sqlind-minor-mode))
;; MySQL
(require 'emacsql-mysql)

Web

  • 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!

OS

Windows Subsystem Linux

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

Terminal

vterm

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)

Tools :tools:

Brief

README.org

#+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

config.el

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

packages.el

Brief

Copilot

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

fzf

Description

Command-line fuzzy finder written in Go

Plugins

config.el

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

packages.el

;; -*- no-byte-compile: t; -*-
;;; tools/fzf/packages.el

(package! fzf)

Gist

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)

Lookup

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

LSP

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

lsp-bridge

Fastest LSP client for Emacs

README.org

#+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.

config.el

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

packages.el

;; -*- 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/*")))

magit +forge

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

Enable Gravatars (not works)

This will enable gravatars when viewing commits. The service used by default is Libravatar.

magit-revision-show-gravatars '("^Author:     " . "^Commit:     ")

Make Prodigy sudo-able

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)
        

Tree-sitter

Local Configures

(load! "+local" nil t)

Directory Local Variables

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

safe-local-variables

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)

Marking the root of a project

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.fslckoutFOSSIL.bzr_darcs

About

My Doom Emacs configuration with extra modules like Nyan Modeline, full features JavaScript (yarn, pnpm, import-js), apps.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published