emacs/init.el

793 lines
26 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; init.el -*- lexical-binding: t; coding: utf-8-unix -*-
;; Author: Case Duckworth <acdw@acdw.net>
;; Created: Sometime during Covid-19, 2020
;; Keywords: configuration
;; URL: https://tildegit.org/acdw/emacs
;; Bankruptcy: 7
;; This file is NOT part of GNU Emacs.
;;;- License:
;; Everyone is permitted to do whatever with this software, without
;; limitation. This software comes without any warranty whatsoever,
;; but with two pieces of advice:
;; - Don't hurt yourself.
;; - Make good choices.
;;;- Code:
;; Necessary packages
;; `setup'
(progn
(straight-use-package '(setup :host nil
:repo "https://git.sr.ht/~zge/setup"))
(require 'setup))
(setup-define :straight
(lambda (recipe)
`(straight-use-package ',recipe))
:documenation "Install RECIPE with `straight-use-package'."
:repeatable t
:shorthand (lambda (sexp)
(let ((recipe (cadr sexp)))
(if (consp recipe)
(car recipe)
recipe))))
(setup-define :leader
(lambda (key command)
`(progn
(autoload #',command (symbol-name setup-name))
(define-key acdw/leader
,(if (stringp key) (kbd key) key)
#',command))))
;; `no-littering'
(setup (:straight no-littering)
(:option no-littering-etc-directory (acdw/dir)
no-littering-var-directory (acdw/dir))
(require 'no-littering))
;; Good defaults
(defmacro setc (&rest args)
"Customize user options using ARGS like `setq'."
(declare (debug setq))
`(setup (:option ,@args)))
(setup emacs
;; Me
(:option user-full-name "Case Duckworth"
user-mail-address "acdw@acdw.net"
calendar-location-name "Baton Rouge, LA"
calendar-latitude 30.4
calendar-longitude -91.1)
;; Lines
(:option fill-column 79
word-wrap t
truncate-lines nil)
(global-display-fill-column-indicator-mode +1)
(global-so-long-mode +1)
;; Whitespace
(:option whitespace-style
'(empty indentation space-before-tab space-after-tab)
indent-tabs-mode nil
tab-width 4
backward-delete-char-untabify-method 'hungry)
(add-hook 'before-save-hook #'whitespace-cleanup)
;; Killing and yanking
(:option save-interprogram-paste-before-kill t
yank-pop-change-selection t
x-select-enable-clipboard t
x-select-enable-primary t
mouse-drag-copy-region t
kill-do-not-save-duplicates t)
(delete-selection-mode +1)
;; Encoding
(:option local-coding-system 'utf-8-unix
coding-system-for-read 'utf-8-unix
coding-system-for-write 'utf-8-unix
buffer-file-coding-system 'utf-8-unix
org-export-coding-system 'utf-8-unix
org-html-coding-system 'utf-8-unix
default-process-coding-system '(utf-8-unix . utf-8-unix)
x-select-request-type '(UTF8_STRING
COMPOUND_TEXT
TEXT
STRING))
(set-charset-priority 'unicode)
(set-language-environment "UTF-8")
(prefer-coding-system 'utf-8-unix)
(set-default-coding-systems 'utf-8-unix)
(set-terminal-coding-system 'utf-8-unix)
(set-keyboard-coding-system 'utf-8-unix)
(set-selection-coding-system 'utf-8-unix)
;; Cursor
(:option cursor-type 'bar
cursor-in-non-selected-windows 'hollow
blink-cursor-blinks 1)
(blink-cursor-mode +1)
;; Scrolling
(:option auto-window-vscroll nil
fast-but-imprecise-scrolling t
scroll-margin 0
scroll-conservatively 101
scroll-preserve-screen-position 1)
;; Minibuffer
(:option minibuffer-prompt-properties
'(read-only t cursor-intangible t face minibuffer-prompt)
enable-recursive-minibuffers t
file-name-shadow-properties '(invisible t intangible t)
read-answer-short t)
(add-hook 'minibuffer-setup-hook #'acdw/gc-disable)
(add-hook 'minibuffer-exit-hook #'acdw/gc-enable)
(minibuffer-depth-indicate-mode +1)
(file-name-shadow-mode +1)
(fset 'yes-or-no-p #'y-or-n-p)
;; Completion
(:option completion-ignore-case t
read-buffer-completion-ignore-case t
icomplete-delay-completions-threshold 0
icomplete-max-delay-chars 0
icomplete-compute-delay 0
icomplete-show-matches-on-no-input t
icomplete-with-buffer-completion-tables t
icomplete-in-buffer t
completion-styles '(partial-completion substring flex)
completion-category-defaults nil
completion-category-overrides
'((file (styles . (partial-completion)))))
;; Etc.
(:option custom-file (acdw/dir "custom.el")
inhibit-startup-screen t
initial-buffer-choice t
initial-scratch-message (concat ";; Howdy, "
(nth 0 (split-string
user-full-name))
"! "
"Welcome to GNU Emacs.\n\n")
disabled-command-function nil
load-prefer-newer t
comp-async-report-warnings-errors nil
frame-title-format '("%b %+%* GNU Emacs"
(:eval (when (frame-parameter
nil 'client)
" Client")))
tab-bar-show 1
use-dialog-box nil
use-file-dialog nil
echo-keystrokes 0.25
recenter-positions '(top middle bottom)
attempt-stack-overflow-recovery nil
attempt-orderly-shutdown-on-fatal-signal nil
window-resize-pixelwise t
find-function-C-source-directory
(pcase acdw/system
(:work (expand-file-name (concat "~/src/emacs-"
emacs-version
"/src")))
(:home (expand-file-name
"~/src/pkg/emacs/src/emacs-git/src"))
(:other nil))
w32-allow-system-shell t
w32-pass-lwindow-to-system nil
w32-lwindow-modifier 'super
w32-pass-rwindow-to-system nil
w32-rwindow-modifier 'super
w32-pass-apps-to-system nil
w32-apps-modifier 'hyper)
(when-unfocused garbage-collect
(garbage-collect))
(tooltip-mode -1)
(winner-mode +1)
;; Bindings
(:global "M-SPC" cycle-spacing
"M-/" hippie-expand
"M-=" count-words
"C-x C-b" ibuffer
"C-c i" acdw/find-emacs-dotfiles))
;; Regular modes (`text-mode', `prog-mode', etc.)
(defun acdw/setup-regular-modes ()
(setq-local indicate-empty-lines t
indicate-buffer-boundaries '((top . right)
(bottom . right))))
(setup text
(:hook turn-on-auto-fill
acdw/setup-regular-modes))
(setup prog
(:option smie-indent-basic tab-width)
(hook-defun auto-fill-prog-mode prog-mode-hook
(setq-local comment-auto-fill-only-comments t)
(turn-on-auto-fill))
(:option show-paren-delay 0
show-paren-style 'mixed
show-paren-when-point-inside-paren t
show-paren-when-point-in-periphery t)
(:hook show-paren-mode
electric-pair-local-mode
acdw/setup-regular-modes)
(add-hook 'after-save-hook
#'executable-make-buffer-file-executable-if-script-p))
(setup uniquify
(:option uniquify-buffer-name-style 'forward
uniquify-separator path-separator
uniquify-after-kill-buffer-p t
uniquify-ignore-buffers-re "^\\*"))
(setup files
(:option backup-directory-alist `((".*" . ,(acdw/dir "backup/" t)))
tramp-backup-directory-alist backup-directory-alist
auto-save-file-name-transforms `((".*" ,(acdw/dir "auto-save/" t) t))
auto-save-list-file-prefix (acdw/dir "auto-save-list/.saves-" t)
backup-by-copying t
delete-old-versions t
version-control t
vc-make-backup-files t)
(auto-save-visited-mode +1)
(when-unfocused save-some-buffers
(save-some-buffers t)))
(setup autorevert
(global-auto-revert-mode +1))
(setup saveplace
(:option save-place-file (acdw/dir "places.el")
save-place-forget-unreadable-files (eq acdw/system :home))
(save-place-mode +1))
(setup (:require recentf)
(:option recentf-save-file (acdw/dir "recentf.el")
recentf-max-menu-items 100
recentf-max-saved-items nil
recentf-auto-cleanup 60
(append recentf-exclude) (acdw/dir))
(recentf-mode +1))
(setup (:require savehist)
(:option (append savehist-additional-variables) 'kill-ring
(append savehist-additional-variables) 'search-ring
(append savehist-additional-variables) 'regexp-search-ring
history-length t
history-delete-duplicates t
savehist-autosave-interval 6
savehist-file (acdw/dir "savehist.el"))
(savehist-mode +1))
(setup imenu
(:option imenu-auto-rescan t))
(setup debugger
(:hook visual-line-mode)
(:leader "d" toggle-debug-on-error))
(setup eldoc
(:option eldoc-idle-delay 0.1
eldoc-echo-area-use-multiline-p nil))
(setup flyspell
(:option ispell-program-name "hunspell"
ispell-dictionary "en_US"
ispell-personal-dictionary "~/.hunspell_personal")
(:needs ispell-program-name) ; don't proceed if not installed
(ispell-set-spellchecker-params)
(unless (file-exists-p ispell-personal-dictionary)
(write-region "" nil ispell-personal-dictionary nil 0))
(add-hook 'text-mode-hook #'flyspell-mode)
(add-hook 'prog-mode-hook #'flyspell-prog-mode))
(setup scratch
(hook-defun immortal-scratch kill-buffer-query-functions
(if (eq (current-buffer) (get-buffer "*scratch*"))
(progn (bury-buffer)
nil)
t)))
;; Applications
(setup (:straight (org :host nil
:repo "https://code.orgmode.org/bzg/org-mode.git"))
(require 'acdw-org)
(:option org-directory "~/org"
org-hide-emphasis-markers t
org-fontify-whole-heading-line t
org-fontify-done-headline t
org-fontify-quote-and-verse-blocks t
org-src-fontify-natively t
org-pretty-entities t
org-tags-column (- 0 fill-column -3)
org-src-tab-acts-natively t
org-src-window-setup 'current-window
org-confirm-babel-evaluate nil
org-adapt-indentation nil
org-catch-invisible-edits 'smart
org-special-ctrl-a/e t
org-special-ctrl-k t
org-imenu-depth 3
org-export-headline-levels 8
org-export-with-smart-quotes t
org-export-with-sub-superscripts t)
(:bind "RET" unpackaged/org-return-dwim)
(add-hook 'before-save-hook #'acdw/hook--org-mode-fix-blank-lines))
(setup eshell
(:option eshell-directory-name (acdw/dir "eshell/" t)
eshell-aliases-file (acdw/dir "eshell/aliases" t))
(defun eshell-quit-or-delete-char (arg)
"Delete the character to the right, or quit eshell on an empty line."
(interactive "p")
(if (and (eolp) (looking-back eshell-prompt-regexp))
(eshell-life-is-too-much)
(delete-forward-char arg)))
(hook-defun eshell-setup 'eshell-mode-hook
(define-key eshell-mode-map (kbd "C-d")
#'eshell-quit-or-delete-char)
(when (fboundp simple-modeline--mode-line)
(setq mode-line-format '(:eval simple-modeline--mode-line)))))
(setup ediff
(:option ediff-window-setup-function 'ediff-setup-windows-plain
ediff-split-window-function 'split-window-horizontally))
(setup browse-url
(:option browse-url-browser-function 'eww-browse-url
browse-url-secondary-browser-function
(if (executable-find "firefox")
'browse-url-firefox
'browse-url-default-browser)
browse-url-new-window-flag t
browse-url-firefox-new-window-is-tab t)
(when (eq acdw/system :work)
(add-to-list 'exec-path "C:/Program Files/Mozilla Firefox")))
(setup shr
(:option shr-width fill-column
shr-max-width fill-column
shr-max-image-proportion 0.6
shr-image-animate t
shr-discard-aria-hidden t))
(setup eww
(:hook acdw/reading-mode))
(setup (:straight (elpher :host nil
:repo "git://thelambdalab.xyz/elpher.git"))
(:option elpher-ipv4-always t
elpher-certificate-directory (acdw/dir "elpher/")
elpher-gemini-max-fill-width fill-column)
(:bind "n" elpher-next-link
"p" elpher-prev-link
"o" elpher-follow-current-link
"G" elpher-go-current)
(:hook acdw/reading-mode)
;; Make `eww' gemini/gopher aware. From Emacswiki.
(advice-add 'eww-browse-url :around
(defun elpher:eww-browse-url (original url &optional new-window)
"Handle gemini and gopher links."
(cond ((string-match-p "\\`\\(gemini\\|gopher\\)://" url)
(require 'elpher)
(elpher-go url))
(t (funcall original url new-window))))))
(setup (:straight (gemini-mode
:host nil
:repo "https://git.carcosa.net/jmcbray/gemini.el.git"))
(:option (append auto-mode-alist)
'("\\.\\(gemini\\|gmi\\)\\'" . gemini-mode)))
(setup dired
(:option dired-recursive-copies 'always
dired-recursive-deletes 'always
delete-by-moving-to-trash t
dired-listing-switches "-Al"
ls-lisp-dirs-first t
dired-ls-F-marks-symlinks t
dired-no-confirm '(byte-compile
chgrp chmod chown copy
hardlink load move
shell touch symlink)
dired-dwim-target t)
(:also-load dired-x)
(:hook dired-hide-details-mode
hl-line-mode)
(:global "C-x C-j" dired-jump)
(:bind "RET" dired-find-alternate-file)
(with-eval-after-load 'dired
(setup (:straight dired-subtree)
(:with-map dired-mode-map
(:bind "i" dired-subtree-toggle
"TAB" dired-subtree-cycle)))
(setup (:straight dired-collapse)
(:hook-into dired-mode))
(setup (:straight trashed)
(:option trashed-action-confirmer 'y-or-n-p))))
(setup (:straight magit)
(:leader "g" magit-status)
(:option magit-display-buffer-function
(defun magit-display-buffer-same-window (buffer)
"Display BUFFER in the selected window like God intended."
(display-buffer buffer '(display-buffer-same-window)))
magit-popup-display-buffer-action '((display-buffer-same-window))))
(setup (:straight nov)
(:option nov-text-width fill-column
(append auto-mode-alist) '("\\.epub\\'" . nov-mode)))
(when (eq acdw/system :home)
(setup (:straight pdf-tools)
(pdf-loader-install)))
(when (eq acdw/system :home)
(setup (:straight vterm)))
;; Extra packages
(setup (:straight beginend)
(beginend-global-mode +1))
(setup (:straight mwim)
(:global "C-a" mwim-beginning
"C-e" mwim-end))
(setup (:straight expand-region)
(:global "C-=" er/expand-region))
(setup (:straight crux)
(:global "M-o" crux-other-window-or-switch-buffer
"C-k" crux-kill-and-join-forward
"C-o" crux-smart-open-line-above
"C-S-o" crux-smart-open-line
"C-M-\\" crux-cleanup-buffer-or-region)
(crux-reopen-as-root-mode +1))
(setup (:straight avy)
(:global "C-:" avy-goto-char
"C-'" avy-goto-char-timer
"M-g f" avy-goto-line
"M-g w" avy-goto-word-1
"C-c C-j" avy-resume)
(eval-after-load "isearch"
'(define-key isearch-mode-map (kbd "C-'") #'avy-isearch)))
(setup (:straight zzz-to-char)
(defun acdw/zzz-up-to-char (prefix)
"Call `zzz-up-to-char', unless issued a PREFIX, in which case
call `zzz-to-char'."
(interactive "P")
(if prefix
(call-interactively #'zzz-to-char)
(call-interactively #'zzz-up-to-char)))
(:global "M-z" acdw/zzz-up-to-char))
(setup (:straight anzu)
(:option anzu-replace-to-string-separator ""
anzu-cons-mode-line-p nil)
(add-to-list 'mode-line-misc-info '(:eval (anzu--update-mode-line)))
(:global [remap query-replace] anzu-query-replace
[remap query-replace-regexp] anzu-query-replace-regexp)
(:with-map isearch-mode-map
(:bind [remap isearch-query-replace] anzu-isearch-query-replace
[remap isearch-query-replace-regexp]
anzu-isearch-query-replace-regexp))
(global-anzu-mode +1))
(setup (:straight async)
(autoload 'dired-async-mode "dired-async.el" nil t)
(dired-async-mode +1))
(setup (:straight undo-fu)
(:global "C-/" undo-fu-only-undo
"C-?" undo-fu-only-redo))
(setup (:straight undo-fu-session)
(:option undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'"
"/git-rebase-todo\\'")
undo-fu-session-directory (acdw/dir "undo/" t)
undo-fu-session-compression (eq acdw/system :home))
(global-undo-fu-session-mode +1))
(setup (:straight (vertico
:host github
:repo "minad/vertico"))
(advice-add #'completing-read-multiple :filter-args
(defun crm-indicator (args)
(cons (concat "[CRM] " (car args)) (cdr args))))
(vertico-mode +1))
(setup (:straight orderless)
(:option (prepend completion-styles) 'orderless))
(setup (:straight consult)
;; "Sensible" functions
(defun consult-sensible-grep ()
"Perform `consult-git-grep' if in a git project, otherwise `consult-ripgrep'
if ripgrep is installed, otherwise `consult-grep'."
(interactive "P")
(cond ((= (vc-backend buffer-file-name) "Git")
(call-interactively #'consult-git-grep))
((executable-find "rg")
(call-interactively #'consult-ripgrep))
(t (call-interactively #'consult-grep))))
(defun consult-sensible-find ()
"Peform `consult-locate' if locate is installed, otehrwise `consult-find'."
(interactive "P")
(cond ((executable-find "locate") (call-interactively #'consult-locate))
(t (call-interactively #'consult-find))))
;; Bindings
(:global
;; C-c bindings (`mode-specific-map')
"C-c h" consult-history
"C-c m" consult-mode-command
"C-c b" consult-bookmark
"C-c k" consult-kmacro
;; C-x bindings (`ctl-x-map')
"C-x M-:" consult-complex-command
"C-x b" consult-buffer
"C-x 4 b" consult-buffer-other-window
"C-x 5 b" consult-buffer-other-frame
;; Custom M-# bindings for fast register access
"M-#" consult-register-load
"M-'" consult-register-store
"C-M-#" consult-register
;; M-g bindings (`goto-map')
"M-g e" consult-compile-error
"M-g g" consult-goto-line
"M-g M-g" consult-goto-line
"M-g o" consult-outline
"M-g m" consult-mark
"M-g k" consult-global-mark
"M-g i" consult-imenu
"M-g I" consult-project-imenu
;; M-s bindings (`search-map')
"M-s g" consult-sensible-grep
"M-s f" consult-sensible-find
"M-s l" consult-line
"M-s m" consult-multi-occur
"M-s k" consult-keep-lines
"M-s u" consult-focus-lines
;; Other bindings
"M-y" consult-yank-pop
"<help> a" consult-apropos
;; Isearch integration
"M-s e" consult-isearch)
(:with-map isearch-mode-map
(:bind "M-e" consult-isearch
"M-s e" consult-isearch
"M-s l" consult-line))
;; Registers
(autoload 'consult-register-preview "consult")
(:option register-preview-delay 0
register-preview-function #'consult-register-format)
(:advise register-preview :override #'consult-register-window)
;; Xref
(:option xref-show-xrefs-function #'consult-xref
xref-show-definitions-function #'consult-xref)
;; Projects
(:option consult-project-root-function #'vc-root-dir))
(setup (:straight marginalia)
(:option marginalia-annotators '(marginalia-annotators-heavy
marginalia-annotators-light))
(marginalia-mode +1))
(setup (:straight (modus-themes
:host gitlab
:repo "protesilaos/modus-themes"))
(:option modus-themes-slanted-constructs t
modus-themes-bold-constructs t
modus-themes-region 'bg-only
modus-themes-org-blocks 'grayscale
modus-themes-headings '((1 . section)
(t . no-color))
modus-themes-mode-line nil)
(acdw/sunrise-sunset #'modus-themes-load-operandi
#'modus-themes-load-vivendi))
(setup (:straight simple-modeline)
(setup (:straight minions))
(:option simple-modeline-segments
'((acdw-modeline/modified
acdw-modeline/buffer-name
acdw-modeline/vc-branch
simple-modeline-segment-position
simple-modeline-segment-word-count)
(simple-modeline-segment-misc-info
simple-modeline-segment-process
acdw-modeline/god-mode-indicator
acdw-modeline/minions
simple-modeline-segment-major-mode)))
(require 'acdw-modeline)
(simple-modeline-mode +1))
(setup (:straight olivetti)
(:option olivetti-body-width (+ fill-column 4)
olivetti-minimum-body-width fill-column))
(setup (:straight form-feed)
(global-form-feed-mode +1))
(setup (:straight which-key)
(:option which-key-show-early-on-C-h t
which-key-idle-delay 10000
which-key-idle-secondary-delay 0.05)
(which-key-setup-side-window-bottom)
(which-key-mode +1))
(setup (:straight helpful)
(:global "<help> f" helpful-callable
"<help> v" helpful-variable
"<help> k" helpful-key
"<help> o" helpful-symbol
"C-c C-d" helpful-at-point))
(setup (:straight (0x0 :host nil
:repo "https://git.sr.ht/~zge/nullpointer-emacs"))
(:option 0x0-default-host 'ttm))
(with-eval-after-load 'flyspell
(setup (:straight flyspell-correct)
(define-key flyspell-mode-map (kbd "C-;") #'flyspell-correct-wrapper)))
(when (eq acdw/system :home)
(setup (:straight pkgbuild-mode)))
(setup (:straight (apheleia :host github
:repo "raxod502/apheleia"))
(apheleia-global-mode +1)
;; Use a dumb formatter on modes that `apheleia' doesn't work for.
(hook-defun dumb-auto-format before-save-hook
(unless (and (fboundp 'apheleia--get-formatter-command)
(apheleia--get-formatter-command))
(indent-region (point-min) (point-max)))))
(setup (:straight paren-face)
(global-paren-face-mode +1))
(setup (:straight paredit)
(autoload 'enable-paredit-mode "paredit" nil t)
(:hook-into emacs-lisp-mode
eval-expression-minibuffer
ielm-mode
lisp-mode
lisp-interaction-mode
scheme-mode)
(defun disable-electric-pair-local-mode ()
(electric-pair-local-mode -1))
(:hook disable-electric-pair-local-mode)
(require 'eldoc)
(eldoc-add-command 'paredit-backward-delete 'paredit-close-round))
(setup elisp-mode
(:option eval-expression-print-length nil
eval-expression-print-level nil
lisp-indent-function #'lisp-indent-function)
(defun acdw/eval-region-or-buffer ()
(interactive)
(if (region-active-p)
(eval-region (region-beginning) (region-end))
(eval-buffer)))
(:with-map emacs-lisp-mode-map
(:bind "C-c C-c" acdw/eval-region-or-buffer
"C-c C-z" ielm))
(add-hook 'emacs-lisp-mode-hook 'turn-on-eldoc-mode)
(add-hook 'ielm-mode-hook 'turn-on-eldoc-mode)
(setup (:straight macrostep)
(define-key emacs-lisp-mode-map (kbd "C-c e") #'macrostep-expand))
(setup (:straight eros)
(:hook-into emacs-lisp-mode)))
(setup (:straight geiser))
(when-let (lisp-bin (or (executable-find "clisp")
(executable-find "sbcl")))
(setup (:straight slime)
(require 'slime-autoloads)
(:option inferior-lisp-program lisp-bin)))
(when (executable-find "fennel")
(setup (:straight fennel-mode)
(autoload 'fennel-mode "fennel-mode" nil t)
(autoload 'fennel-repl "fennel-mode" nil t)
(:option (append auto-mode-alist) '("\\.fnl\\'" . fennel-mode))))
(setup (:straight lua-mode)
(:option (append auto-mode-alist) '("\\.lua\\'" . lua-mode)))
(setup sh-mode
(:option sh-basic-offset tab-width
sh-indent-after-case 0
sh-indent-for-case-alt '+
sh-indent-for-case-label 0)
(:local-set indent-tabs-mode t)
(when (executable-find "shfmt")
(with-eval-after-load 'apheleia
(:option (append apheleia-formatters) '(shfmt . ("shfmt"))
(append apheleia-mode-alist) '(sh-mode . shfmt))))
(when (executable-find "shellcheck")
(straight-use-package 'flymake-shellcheck)
(:hook flymake-mode
flymake-shellcheck-load)))
(setup (:straight web-mode)
(:option css-level-offset 2
js-indent-level 2
sgml-indent-offset 2)
(dolist (extension '("\\(p\\|dj\\)?html"
"html?"
"\\(tpl\\.\\)?php"
"[agj]sp"
"as[cp]x"
"erb"
"mustache"))
(add-to-list 'auto-mode-alist
`(,(concat "\\." extension "\\'") . web-mode))))
(when (locate-library "gforth")
(autoload 'forth-mode "gforth")
(add-to-list 'auto-mode-alist '("\\.fs\\'" . forth-mode))
(autoload 'forth-block-mode "gforth")
(add-to-list 'auto-mode-alist '("\\.fb\\'" . forth-block-mode)))