aboutsummaryrefslogtreecommitdiff
path: root/tw/services/files/emacs-packages
diff options
context:
space:
mode:
authorTimo Wilken2024-03-09 14:52:56 +0100
committerTimo Wilken2024-03-10 16:19:00 +0100
commitde20fc8d904643ffe6957febfc6a24e57c12b512 (patch)
tree8177459e40786bd432a37c5833f26350fb689356 /tw/services/files/emacs-packages
parentda5e9d5ee98dfc216eb7e3b1559c09f4bf868bf6 (diff)
Separate home service into PIM, dev env and graphical parts
This means we only instantiate Shepherd and mcron services if we really need them, to avoid annoyance on servers.
Diffstat (limited to 'tw/services/files/emacs-packages')
-rw-r--r--tw/services/files/emacs-packages/actionlint.el147
-rw-r--r--tw/services/files/emacs-packages/alidist-mode.el170
-rw-r--r--tw/services/files/emacs-packages/bemscript-mode.el92
-rw-r--r--tw/services/files/emacs-packages/environmentd-mode.el46
-rw-r--r--tw/services/files/emacs-packages/flymake-guile.el123
-rw-r--r--tw/services/files/emacs-packages/ifm-mode.el18
-rw-r--r--tw/services/files/emacs-packages/pam-env-mode.el45
-rw-r--r--tw/services/files/emacs-packages/vcard-mode.el56
8 files changed, 697 insertions, 0 deletions
diff --git a/tw/services/files/emacs-packages/actionlint.el b/tw/services/files/emacs-packages/actionlint.el
new file mode 100644
index 00000000..68a25c57
--- /dev/null
+++ b/tw/services/files/emacs-packages/actionlint.el
@@ -0,0 +1,147 @@
+;;; actionlint.el --- Flycheck checker for GitHub Actions. -*- lexical-binding: t -*-
+;;; Commentary:
+;; GitHub Actions are defined using mostly plain YAML files.
+;; Actionlint is a linter catching GitHub Action-specific mistakes, and also
+;; checks Shell and Python code embedded in Actions (using shellcheck and
+;; pyflakes, respectively).
+;;; Code:
+
+(require 'custom)
+(require 'flymake)
+
+(defgroup actionlint nil
+ "Actionlint-related options."
+ :group 'languages
+ :prefix "actionlint-")
+
+(defcustom actionlint-executable "actionlint"
+ "The alidistlint executable to use. This will be looked up in $PATH."
+ :type '(string)
+ :risky t
+ :group 'actionlint)
+
+(defvar actionlint--message-regexp
+ (rx bol "<stdin>:" ; filename
+ (group-n 2 (+ digit)) ":" ; line
+ (group-n 3 (+ digit)) ": " ; column
+ (? (or (seq "pyflakes reported issue in this script: "
+ (group-n 4 (+ digit)) ":" ; inner line
+ (group-n 5 (+ digit)) " ") ; inner column
+ (seq "shellcheck reported issue in this script: "
+ (group-n 8 "SC" (+ digit)) ":" ; shellcheck code
+ (group-n 6 (or "info" "warning" "error")) ":" ; type
+ (group-n 4 (+ digit)) ":" ; inner line
+ (group-n 5 (+ digit)) ": "))) ; inner column
+ (group-n 1 (+? not-newline)) " " ; message
+ "[" (group-n 7 (+ (not ?\]))) "]" eol) ; backend/error name
+ "Regular expression matching messages reported by actionlint.
+
+The following convention for match groups is used:
+
+ 1. free-form message
+ 2. outer line number
+ 3. outer column number
+ 4. (optional) inner line number
+ 5. (optional) inner column number
+ 6. (optional) error level/type
+ 7. backend/error name (e.g. syntax-check or pyflakes)
+ 8. (optional) backend-specific error code
+
+The outer line/column numbers are always present and refer to the location of
+the key where the error is, normally. If the message was passed through from
+another linter (e.g. shellcheck), it may have an inner line/column, which will
+be relative to the contents of the key instead.")
+
+(defun actionlint--next-message (source)
+ "Return the next message according to REGEXP for buffer SOURCE, if any."
+ (when-let* ((match (search-forward-regexp actionlint--message-regexp nil t))
+ (inner-line (if-let ((match (match-string 4)))
+ ;; 1-based; don't subtract 1 since we assume
+ ;; that the script actually starts on the next
+ ;; line.
+ (string-to-number match)
+ 0))
+ (inner-column (if-let ((match (match-string 5)))
+ ;; 1-based; add 1 (assuming 2-space indents)
+ ;; to pick the right place inside the string.
+ (1+ (string-to-number match))
+ 0))
+ (region (flymake-diag-region
+ source
+ (+ (string-to-number (match-string 2)) inner-line)
+ (+ (string-to-number (match-string 3)) inner-column)))
+ (type (pcase (match-string 6)
+ ("info" :note)
+ ("warning" :warning)
+ ("error" :error)
+ ('nil :error)))
+ (message (if-let ((code (match-string 8)))
+ (concat (match-string 1) " (" (match-string 7) " " code ")")
+ (concat (match-string 1) " (" (match-string 7) ")"))))
+ (flymake-make-diagnostic source (car region) (cdr region) type message)))
+
+(defvar-local actionlint--flymake-proc nil
+ "The latest invocation of actionlint.")
+
+;; See info node: (flymake)An annotated example backend.
+(defun actionlint-flymake (report-fn &rest _args)
+ "Run actionlint and report diagnostics from it using REPORT-FN.
+Any running invocations are killed before running another one."
+ (unless (executable-find actionlint-executable)
+ (funcall report-fn :panic
+ :explanation "Cannot find `actionlint-executable' program")
+ (error "Cannot find actionlint executable"))
+
+ ;; Kill previous check, if it's still running.
+ (when (process-live-p actionlint--flymake-proc)
+ (kill-process actionlint--flymake-proc))
+
+ ;; This needs `lexical-binding'.
+ (let ((source (current-buffer)))
+ (save-restriction
+ (widen)
+ (setq actionlint--flymake-proc
+ (make-process
+ :name "actionlint-flymake" :noquery t :connection-type 'pipe
+ ;; Direct output to a temporary buffer.
+ :buffer (generate-new-buffer " *actionlint-flymake*")
+ :command (list actionlint-executable "-oneline" "-no-color" "-")
+ :sentinel
+ (lambda (proc _event)
+ "Parse diagnostic messages once the process PROC has exited."
+ ;; Check the process has actually exited, not just been suspended.
+ (when (memq (process-status proc) '(exit signal))
+ (unwind-protect
+ ;; Only proceed if we've got the "latest" process.
+ (if (with-current-buffer source (eq proc actionlint--flymake-proc))
+ (with-current-buffer (process-buffer proc)
+ (goto-char (point-min))
+ (cl-do (diags
+ (msg (actionlint--next-message source)
+ (actionlint--next-message source)))
+ ((null msg)
+ (funcall report-fn diags))
+ (push msg diags)))
+ (flymake-log :warning "Canceling obsolete check %s" proc))
+ ;; Clean up temporary buffer.
+ (kill-buffer (process-buffer proc)))))))
+ ;; Send the buffer to actionlint on stdin.
+ (process-send-region actionlint--flymake-proc (point-min) (point-max))
+ (process-send-eof actionlint--flymake-proc))))
+
+(defun actionlint-github-workflow-p ()
+ "Does the current buffer contain a GitHub Action?"
+ (let ((name (buffer-file-name)))
+ (and name (string-match-p
+ (rx ".github/workflows/" (+ (not ?\/)) ".yml" eos) name))))
+
+(defun actionlint-setup ()
+ "Set up actionlint in this buffer, if it is recognised as a workflow file."
+ (when (actionlint-github-workflow-p)
+ (add-hook 'flymake-diagnostic-functions #'actionlint-flymake nil t)))
+
+(add-hook 'yaml-mode-hook #'actionlint-setup)
+(add-hook 'yaml-ts-mode-hook #'actionlint-setup)
+
+(provide 'actionlint)
+;;; actionlint.el ends here
diff --git a/tw/services/files/emacs-packages/alidist-mode.el b/tw/services/files/emacs-packages/alidist-mode.el
new file mode 100644
index 00000000..fbcef7e5
--- /dev/null
+++ b/tw/services/files/emacs-packages/alidist-mode.el
@@ -0,0 +1,170 @@
+;;; alidist-mode.el --- Major mode for alidist recipes -*- lexical-binding: t -*-
+;;; Commentary:
+;;; alidist recipes are shell scripts with a YAML header in front. We
+;;; want both these parts highlighted properly, and to lint the whole
+;;; thing with a custom script that glues together yamllint and
+;;; shellcheck with a few custom checks.
+;;; Code:
+
+(require 'custom)
+(require 'flymake)
+(require 'mmm-mode)
+(require 'mmm-cmds)
+(require 'mmm-vars)
+(require 'sh-script)
+(require 'yaml-mode)
+
+(defgroup alidist-mode nil
+ "Alidist-related options."
+ :group 'languages
+ :prefix "alidist-mode-")
+
+(defcustom alidist-mode-alidistlint-executable "alidistlint"
+ "The alidistlint executable to use. This will be looked up in $PATH."
+ :type '(string)
+ :risky t
+ :group 'alidist-mode)
+
+(defvar alidist-mode--message-regexp
+ (rx bol "<stdin>:" ; filename
+ (group (+ digit)) ":" ; line
+ (group (+ digit)) ": " ; column
+ (group (or "note" "warning" "error")) ": " ; type
+ (group (+ not-newline)) eol) ; message
+ "Regular expression matching messages from alidistlint.
+`alidist-flymake' expects the following capturing groups in this
+regexp: (1) line number; (2) column number; (3) error type; (4)
+message.")
+
+(defvar-local alidist-mode--flymake-proc nil
+ "The latest invocation of alidistlint.")
+
+;; See info node: (flymake)An annotated example backend.
+(defun alidist-flymake (report-fn &rest _args)
+ "Run alidistlint and report diagnostics from it using REPORT-FN.
+Any running invocations are killed before running another one."
+ (unless (executable-find alidist-mode-alidistlint-executable)
+ (funcall report-fn :panic
+ :explanation "Cannot find `alidist-mode-alidistlint-executable' program")
+ (error "Cannot find alidistlint executable"))
+
+ ;; Kill previous check, if it's still running.
+ (when (process-live-p alidist-mode--flymake-proc)
+ (kill-process alidist-mode--flymake-proc))
+
+ ;; This needs `lexical-binding'.
+ (let ((source (current-buffer)))
+ (save-restriction
+ (widen)
+ (setq alidist-mode--flymake-proc
+ (make-process
+ :name "alidistlint-flymake" :noquery t :connection-type 'pipe
+ ;; Direct output to a temporary buffer.
+ :buffer (generate-new-buffer " *alidistlint-flymake*")
+ :command (list alidist-mode-alidistlint-executable "-f" "gcc" "-")
+ :sentinel
+ (lambda (proc _event)
+ "Parse diagnostic messages once the process PROC has exited."
+ ;; Check the process has actually exited, not just been suspended.
+ (when (memq (process-status proc) '(exit signal))
+ (unwind-protect
+ ;; Only proceed if we've got the "latest" process.
+ (if (with-current-buffer source (eq proc alidist-mode--flymake-proc))
+ (with-current-buffer (process-buffer proc)
+ (goto-char (point-min))
+ (cl-loop
+ while (search-forward-regexp alidist-mode--message-regexp nil t)
+ for (beg . end) = (flymake-diag-region
+ source
+ (string-to-number (match-string 1))
+ (string-to-number (match-string 2)))
+ for type = (pcase (match-string 3)
+ ("note" :note)
+ ("warning" :warning)
+ ("error" :error)
+ (type (error "Unknown alidistlint error type %s" type)))
+ collect (flymake-make-diagnostic source beg end type (match-string 4))
+ into diags
+ finally (funcall report-fn diags)))
+ (flymake-log :warning "Canceling obsolete check %s" proc))
+ ;; Clean up temporary buffer.
+ (kill-buffer (process-buffer proc)))))))
+ ;; Send the buffer to alidistlint on stdin.
+ (process-send-region alidist-mode--flymake-proc (point-min) (point-max))
+ (process-send-eof alidist-mode--flymake-proc))))
+
+(defvar-local alidist-mode--mmm-refresh-timer nil
+ "An idle timer for the current buffer, to make `mmm-mode' reparse it.")
+(put 'alidist-mode--mmm-refresh-timer 'risky-local-variable t)
+
+(defun alidist-mode--cancel-refresh-timer ()
+ "Cancel and delete the timer that reparses the buffer.
+It is stored in `alidist-mode--mmm-refresh-timer'."
+ (when alidist-mode--mmm-refresh-timer
+ (cancel-timer alidist-mode--mmm-refresh-timer)
+ (setq alidist-mode--mmm-refresh-timer nil)))
+
+(define-derived-mode alidist-mode yaml-mode "alidist"
+ "An outer mode for alidist recipes, handling the metadata."
+ (mmm-mode)
+ ;; `mmm-mode' doesn't refresh its submodes when the buffer changes
+ ;; (e.g. when a *_recipe key is added to the YAML header), so
+ ;; refresh manually when idle.
+ (alidist-mode--cancel-refresh-timer)
+ (add-hook 'kill-buffer-hook #'alidist-mode--cancel-refresh-timer 0 t)
+ (setq alidist-mode--mmm-refresh-timer
+ (run-with-idle-timer
+ 2 t (lambda (original-buffer)
+ (when (eq original-buffer (current-buffer))
+ ;; Silence `mmm-parse-buffer''s annoying message.
+ (let ((inhibit-message t))
+ (mmm-parse-buffer))))
+ ;; Idle timers are global, so make sure we only run the timer
+ ;; in the right buffer. Save the buffer now to enable this,
+ ;; and compare every time the timer ticks over.
+ (current-buffer)))
+ ;; Set up `flymake-mode'.
+ (add-hook 'flymake-diagnostic-functions #'alidist-flymake nil t)
+ (flymake-mode))
+
+(define-derived-mode alidist-script-ts-mode bash-ts-mode "Script"
+ "A mode for scripts in alidist recipes, using tree-sitter.")
+
+(define-derived-mode alidist-script-mode sh-mode "Script"
+ "A mode for scripts in alidist recipes with some default settings."
+ (sh-set-shell "bash"))
+
+(mmm-add-group
+ 'alidist-recipe
+ `((alidist-main-script
+ :submode alidist-script-mode
+ :face mmm-default-submode-face
+ :front ,(rx line-start "---\n")
+ :back ,(rx buffer-end))
+ (alidist-option-script
+ :submode alidist-script-mode
+ :face mmm-default-submode-face
+ ;; Any *_recipe key with a multiline string value is probably a script.
+ :front ,(rx line-start (* whitespace)
+ (or "recipe" ; for recipes under prefer_system_replacement_specs
+ (seq (1+ (any alnum ?\_))
+ (or "_recipe" "_check")))
+ ": |\n")
+ ;; End of YAML header, or another YAML key.
+ :back ,(rx line-start
+ (or "---\n"
+ (seq (* whitespace) (+ (any alnum ?\_)) ":"
+ (or line-end whitespace)))))))
+
+;; Make `mmm-mode' remember `sh-mode'/`bash-ts-mode' indentation variables.
+(cl-dolist (var sh-var-list)
+ (cl-pushnew `(,var region (sh-mode bash-ts-mode))
+ mmm-save-local-variables :test 'equal))
+
+(mmm-add-mode-ext-class 'alidist-mode nil 'alidist-recipe)
+(add-to-list 'auto-mode-alist
+ (cons (rx (or bot "/") "alidist/" (1+ (not ?\/)) ".sh" eot)
+ #'alidist-mode))
+
+(provide 'alidist-mode)
+;;; alidist-mode.el ends here
diff --git a/tw/services/files/emacs-packages/bemscript-mode.el b/tw/services/files/emacs-packages/bemscript-mode.el
new file mode 100644
index 00000000..f46c858b
--- /dev/null
+++ b/tw/services/files/emacs-packages/bemscript-mode.el
@@ -0,0 +1,92 @@
+;;; bemscript-mode.el --- Syntax highlighting for MERRILL BEMScript files.
+;;; Commentary:
+;;; Based on the MERRILL manual. Some commands may have been missed.
+;;; Code:
+
+(defconst bemscript-mode-keywords
+ '("set" "setsubdomain" "magnetite" "iron" "tm54" "resize" "cubic anisotropy"
+ "uniaxial anisotropy" "cubicrotation" "easy axis" "external field strength"
+ "external field direction" "readmesh" "loadmesh" "readmagnetization"
+ "uniform magnetization" "randomize magnetization" "randomize all moments"
+ "remesh" "conjugategradient" "steepestdescent" "minimize" "energylog"
+ "closelogfile" "writeloopdata" "writemagnetization" "writedemag" "writehyst"
+ "writeboxdata" "appenddemagzone" "magnetizationtopath" "pathtomagnetization"
+ "renewpath" "refinepathto" "writetecplotpath" "readtecplotpath"
+ "readtecplotzone" "keypause" "makeinitialpath" "pathminimize" "pathlogfile"
+ "systemcommand" "pathstructureenergies" "reportenergy" "stop" "end" "loop"
+ "endloop" "define" "addto" "undefine" "generatecubemesh" "zonename")
+ "List of keywords for BEMScript mode. Intended for case folding.")
+
+(defconst bemscript-mode-builtins
+ '("Aex" "CurvatureWeight" "ExchangeCalculator" "K1" "K2" "Ls" "Ms" "mu"
+ "MaxEnergyEvaluations" "MaxPathEvaluations" "MaxRestart" "MaxMeshNumber"
+ "NEBSpring" "PathN" "Zone" "ZoneIncrement")
+ "List of built-in variable names for BEMScript.")
+
+(defconst bemscript-mode-special
+ '("patran" "tecplot" "POINT" "BLOCK" "SD" "muT" "mT" "T" "C")
+ "Variables with special meanings and units in BEMScript.")
+
+;; Available font-lock-*-faces: doc type string builtin comment keyword warning
+;; constant (reference) preprocessor syntactic-function function-name
+;; negation-char variable-name comment-delimiter
+(defconst bemscript-mode-font-lock-defaults
+ `((;; See font-lock-keywords docs. Earlier lines seem to override later ones,
+ ;; except if both have OVERRIDE? t.
+ ;; Format: (REGEXP (GROUPNUM FACENAME OVERRIDE? LAXMATCH?)...)
+ ;; Comments
+ ("!\\(.*\\)" 1 font-lock-comment-face t t)
+ ("!" 0 font-lock-comment-delimiter-face t)
+ ("!!.*$" 0 font-lock-doc-face t)
+
+ ;; Equals signs need spaces around them.
+ ("\\s-=\\s-" 0 font-lock-type-face t) ; there is no "operator" etc face
+ ("=" . font-lock-warning-face)
+
+ ;; Numbers and variables
+ ("\\<[0-9]*\\.?[0-9]+\\(e[-+]?\\)?[0-9]*\\>" . font-lock-constant-face)
+ ("\\(\\<[#%][A-Z_a-z][0-9A-Z_a-z]*\\>\\|\\$[A-Z_a-z][0-9A-Za-z_]*\\$\\)"
+ . font-lock-variable-name-face)
+
+ ;; Preprocessor (&-substitution)
+ (,(concat "\\([^&]\\|^\\)\\(&&\\)*" ; && escapes &
+ "\\(&\\([_a-zA-Z][_a-zA-Z0-9]*\\|{[_a-zA-Z][_a-zA-Z0-9]*}\\)\\)")
+ 3 font-lock-preprocessor-face)
+ (,(concat "\\([^&]\\|^\\)\\(&&\\)*" ; && escapes &
+ "\\(&\\($" ; bare &
+ "\\|[^&{_a-zA-Z]\\|{[^_a-zA-Z]" ; invalid char following & or &{
+ ; invalid name or unclosed {
+ "\\|{[_a-zA-Z][_0-9a-zA-Z]*\\([^_0-9a-zA-Z}]\\|$\\)\\)\\)")
+ 3 font-lock-warning-face t)
+
+ ;; Variable definitions
+ ("\\<\\(loop\\|define\\)\\s-+\\([_a-zA-Z][_a-zA-Z0-9]*\\)\\>"
+ 2 font-lock-function-name-face)
+ ("\\<\\(addto\\|undefine\\)\\s-+\\([_a-zA-Z][_a-zA-Z0-9]*\\)\\>"
+ 2 font-lock-variable-name-face)
+
+ ;; Keywords
+ (,(regexp-opt bemscript-mode-special 'words) . font-lock-string-face)
+ (,(regexp-opt bemscript-mode-keywords 'words) . font-lock-keyword-face)
+ (,(regexp-opt bemscript-mode-builtins 'words) . font-lock-builtin-face))
+
+ ;; KEYWORDS-ONLY: if t, no syntactic fontification (strings and comments)
+ nil
+ ;; CASE-FOLD: if t, make keywords case-insensitive.
+ t)
+ "Font lock settings for BEMScript mode.")
+
+(define-derived-mode bemscript-mode prog-mode "BEMScript"
+ "BEMScript-mode is used for editing MERRILL scripts."
+ (setq comment-start "!"
+ comment-end ""
+ tab-width 2
+ font-lock-defaults bemscript-mode-font-lock-defaults)
+ ;; Only word syntax entries are highlighted; add needed chars.
+ (modify-syntax-entry ?# "w")
+ ;; Strings in BEMScript are not quoted.
+ (modify-syntax-entry ?\" "w"))
+
+(add-to-list 'auto-mode-alist '("\\.bem\\'" . bemscript-mode))
+(provide 'bemscript-mode)
+;;; bemscript-mode.el ends here
diff --git a/tw/services/files/emacs-packages/environmentd-mode.el b/tw/services/files/emacs-packages/environmentd-mode.el
new file mode 100644
index 00000000..4bb8812e
--- /dev/null
+++ b/tw/services/files/emacs-packages/environmentd-mode.el
@@ -0,0 +1,46 @@
+;;; environmentd-mode.el --- Major mode for environment.d(5) files.
+
+;;; Commentary:
+
+;; This major mode font-locks files including /etc/environment and
+;; ~/.config/environment.d/*.conf. Their format is specified by the
+;; environment.d(5) man page.
+
+;;; Code:
+
+(defconst environmentd-mode/font-lock-defaults
+ '((("^[[:blank:]]+[^[:blank:]]+" . font-lock-warning-face) ; stray leading whitespace
+ ("^#+[[:blank:]]*" . font-lock-comment-delimiter-face)
+ ("^#+[[:blank:]]*\\(.*\\)$" 1 font-lock-comment-face)
+ ("\\\\[$\\]" . font-lock-string-face) ; escaped $ \
+ ("^\\([A-Za-z_][A-Za-z0-9_]*\\)\\(=\\)"
+ (1 font-lock-variable-name-face)
+ (2 font-lock-keyword-face))
+ ("\\(\\${\\)\\([A-Za-z_][A-Za-z0-9_]*\\)\\(:[+-]\\)[^}]*\\(}\\)"
+ (1 font-lock-keyword-face)
+ (2 font-lock-variable-name-face)
+ (3 font-lock-keyword-face)
+ (4 font-lock-keyword-face)) ; ${X:-default}-variable references
+ ("\\(\\${\\)\\([A-Za-z_][A-Za-z0-9_]*\\)\\(}\\)"
+ (1 font-lock-keyword-face)
+ (2 font-lock-variable-name-face)
+ (3 font-lock-keyword-face)) ; ${X}-variable references
+ ("\\(\\$\\)\\([A-Za-z_][A-Za-z0-9_]*\\)"
+ (1 font-lock-keyword-face)
+ (2 font-lock-variable-name-face))) ; $X-variable references
+ t nil ((?\' . "w") (?\" . "w")))
+ "Font lock settings for Environment.d mode. See `font-lock-defaults' for documentation.")
+
+(define-derived-mode environmentd-mode prog-mode "Environment.d"
+ "Environment.d mode is used for environment.d(5) files."
+ (setq-local comment-start "#"
+ comment-start-skip "#"
+ comment-end ""
+ font-lock-defaults environmentd-mode/font-lock-defaults))
+
+(add-to-list 'auto-mode-alist
+ '("/environment\\.d/[^/]+\\.conf\\'\\|\\`/etc/environment\\'"
+ . environmentd-mode))
+
+(provide 'environmentd-mode)
+;;; environmentd-mode.el ends here
diff --git a/tw/services/files/emacs-packages/flymake-guile.el b/tw/services/files/emacs-packages/flymake-guile.el
new file mode 100644
index 00000000..edfbce82
--- /dev/null
+++ b/tw/services/files/emacs-packages/flymake-guile.el
@@ -0,0 +1,123 @@
+;;; flymake-guile.el --- Flymake checker using `guild compile' -*- lexical-binding: t -*-
+;;; Commentary:
+;;; "guild compile" compiles Guile code to bytecode and can output a few basic
+;;; warnings. Let's use this as a linter!
+;;; Code:
+
+(require 'custom)
+(require 'flymake)
+(require 'geiser-impl) ; for `geiser-active-implementations'
+
+(defcustom flymake-guile-guild-executable "guild"
+ "The guild executable to use. This will be looked up in $PATH."
+ :type '(string)
+ :risky t
+ :group 'flymake-guile)
+
+(defvar-local flymake-guile--flymake-proc nil
+ "The latest invocation of guild compile.")
+
+(defvar-local flymake-guile--temp-file nil
+ "The temporary file name to pass to guild.")
+
+(defun flymake-guile--encode-filename (buffer-name)
+ "Create a safe temporary file name from BUFFER-NAME."
+ (concat "/tmp/flymake-guile-"
+ (string-replace
+ "/" "!" ; we don't want to create subdirs under /tmp
+ (or buffer-name
+ (format "temp-%s.scm"
+ (random most-positive-fixnum))))))
+
+;; See info node: (flymake)An annotated example backend.
+(defun flymake-guile (report-fn &rest _args)
+ "Run guild compile and report diagnostics from it using REPORT-FN.
+Any running invocations are killed before running another one."
+ (unless (executable-find flymake-guile-guild-executable)
+ (funcall report-fn :panic
+ :explanation "Cannot find `flymake-guile-guild-executable' program")
+ (error "Cannot find guild executable"))
+
+ (unless flymake-guile--temp-file
+ (setq-local flymake-guile--temp-file (flymake-guile--encode-filename (buffer-file-name))))
+
+ ;; Kill previous check, if it's still running.
+ (when (process-live-p flymake-guile--flymake-proc)
+ (kill-process flymake-guile--flymake-proc))
+
+ ;; This needs `lexical-binding'.
+ (let ((source (current-buffer))
+ ;; Copy `flymake-guile--temp-file' to a local var so that we can refer to it in the `lambda' below.
+ (temp-file flymake-guile--temp-file))
+ (save-restriction
+ (widen)
+ ;; Send the buffer to guild on stdin.
+ (with-temp-file flymake-guile--temp-file
+ (insert-buffer-substring-no-properties source))
+ (setq flymake-guile--flymake-proc
+ (make-process
+ :name "flymake-guild" :noquery t :connection-type 'pipe
+ ;; Direct output to a temporary buffer.
+ :buffer (generate-new-buffer " *flymake-guile*")
+ ;; Guild can't read from stdin; it needs a file.
+ :command (list flymake-guile-guild-executable "compile"
+ ;; See "guild --warn=help" for details.
+ ;; "--warn=unsupported-warning" ; ignore unsupported warning types
+ ;; "--warn=unused-variable" ; too many false positives from macros
+ "--warn=unused-toplevel"
+ "--warn=shadowed-toplevel"
+ "--warn=unbound-variable"
+ "--warn=macro-use-before-definition"
+ "--warn=use-before-definition"
+ "--warn=non-idempotent-definition"
+ "--warn=arity-mismatch"
+ "--warn=duplicate-case-datum"
+ "--warn=bad-case-datum"
+ "--warn=format"
+ "-L" (expand-file-name
+ (project-root (project-current nil (file-name-directory
+ (buffer-file-name source)))))
+ flymake-guile--temp-file)
+ :sentinel
+ (lambda (proc _event)
+ "Parse diagnostic messages once the process PROC has exited."
+ ;; Check the process has actually exited, not just been suspended.
+ (when (memq (process-status proc) '(exit signal))
+ (unwind-protect
+ ;; Only proceed if we've got the "latest" process.
+ (if (with-current-buffer source (not (eq proc flymake-guile--flymake-proc)))
+ (flymake-log :warning "Canceling obsolete check %s" proc)
+ (with-current-buffer (process-buffer proc)
+ (goto-char (point-min))
+ (cl-loop
+ with msg-regexp = (rx bol (literal temp-file) ":" ; filename
+ (group (+ digit)) ":" ; line
+ (group (+ digit)) ": " ; column
+ (group (or "warning" "error")) ": " ; type
+ (group (+ not-newline)) eol) ; message
+ while (search-forward-regexp msg-regexp nil t)
+ for (beg . end) = (flymake-diag-region
+ source ; we filter for messages matching our buffer in the regexp
+ (string-to-number (match-string 1))
+ ;; guild outputs 0-based column numbers
+ (1+ (string-to-number (match-string 2))))
+ for type = (pcase (match-string 3)
+ ("warning" :warning)
+ ("error" :error)
+ (type (error "Unknown guild error type %s" type)))
+ collect (flymake-make-diagnostic source beg end type (match-string 4))
+ into diags
+ finally (funcall report-fn diags))))
+ ;; Clean up temporary buffer.
+ (kill-buffer (process-buffer proc))
+ (delete-file temp-file)))))))))
+
+(defun flymake-guile-enable ()
+ "Set up the Guile checker for flymake, if in a Guile buffer."
+ (when (memq 'guile geiser-active-implementations)
+ (add-hook 'flymake-diagnostic-functions #'flymake-guile nil t)))
+
+(add-hook 'scheme-mode-hook #'flymake-guile-enable)
+
+(provide 'flymake-guile)
+;;; flymake-guile.el ends here
diff --git a/tw/services/files/emacs-packages/ifm-mode.el b/tw/services/files/emacs-packages/ifm-mode.el
new file mode 100644
index 00000000..7416588b
--- /dev/null
+++ b/tw/services/files/emacs-packages/ifm-mode.el
@@ -0,0 +1,18 @@
+(define-generic-mode 'ifm-mode
+ '("#")
+ '("title" "map" "require" "room" "join" "to" "dir" "exit" "go" "oneway"
+ "tag" "from" "link" "nolink" "item" "in" "note" "score" "need" "after"
+ "before" "leave" "all" "except" "cmd" "length" "start" "finish" "nodrop"
+ "nopath" "style" "hidden" "keep" "with" "until" "ignore" "give" "lost"
+ "do" "get" "drop" "until" "safe" "ignore" "goto" "endstyle")
+ '(("\\<\\(\\(north\\|south\\)\\(east\\|west\\)?\\|[ns][ew]?\\|east\\|west\\|[ew]\\)\\>"
+ . 'font-lock-builtin-face)
+ ("\\<\\([du]\\|down\\|up\\|in\\|out\\|last\\|it\\|them\\)\\>"
+ . 'font-lock-builtin-face)
+ ("\\<[0-9]+" . 'font-lock-constant-face)
+ ("\\<[_a-zA-Z][_0-9A-Za-z]*\\>" . 'font-lock-variable-name-face))
+ '("\\.ifm\\'")
+ nil
+ "A mode for interactive fiction manager files")
+
+(provide 'ifm-mode)
diff --git a/tw/services/files/emacs-packages/pam-env-mode.el b/tw/services/files/emacs-packages/pam-env-mode.el
new file mode 100644
index 00000000..75b0bf94
--- /dev/null
+++ b/tw/services/files/emacs-packages/pam-env-mode.el
@@ -0,0 +1,45 @@
+;;; pam-env.el --- Major mode for pam_env.conf(5) files.
+
+;;; Commentary:
+
+;; This major mode font-locks files including ~/.pam_environment and
+;; /etc/security/pam_env.conf, but notably not /etc/environment. Their format is
+;; specified by the pam_env.conf(5) man page.
+
+;; TODO: Only apply font-lock-variable-name-face to variable declarations if
+;; the previous line didn't end with a backslash. The following case didn't
+;; work (some declarations that should've been font-locked weren't):
+;; '("\\(?:^$\\|[^\\\\]\\)[\r\n]\\([^[:blank:]]+\\)"
+;; 1 font-lock-variable-name-face keep)
+
+;; pam_env does not support escaped double quotes ("). Single-quoted strings are
+;; not used as string delimiters. We can only match against word chars in
+;; `pam-env-mode/font-lock-defaults', so make double quotes word chars.
+
+;;; Code:
+
+(defconst pam-env-mode/font-lock-defaults
+ '((("^#+" . font-lock-comment-delimiter-face)
+ ("^#+[[:blank:]]*\\(.*\\)$" 1 font-lock-comment-face)
+ ("\\\\[@$\\]" . font-lock-string-face) ; escaped $ @ \
+ ("@{[^}]+}" . font-lock-builtin-face) ; @{}-variable references
+ ("\\${[^}]+}" . font-lock-variable-name-face) ; ${}-variable references
+ ("\"[^\"]*\"" 0 font-lock-string-face keep) ; double-quoted strings; escaped " not supported
+ ("\\<\\(DEFAULT\\|OVERRIDE\\)=" . font-lock-keyword-face) ; DEFAULT= and OVERRIDE=
+ ("^[^[:blank:]]+" . font-lock-variable-name-face) ; variable declarations
+ ("[[:blank:]]+[^[:blank:]]+" . font-lock-warning-face)) ; stray whitespace
+ t nil ((?\' . "w") (?\" . "w")))
+ "Font lock settings for PAM-Environment mode. See `font-lock-defaults' for documentation.")
+
+(define-derived-mode pam-env-mode prog-mode "PAM-Environment"
+ "PAM-environment mode is used for pam_env.conf(5) files."
+ (set (make-local-variable 'comment-start) "#")
+ (set (make-local-variable 'comment-start-skip) "^#+[[:blank:]]*")
+ (set (make-local-variable 'comment-end) "")
+ (set (make-local-variable 'font-lock-defaults) pam-env-mode/font-lock-defaults))
+
+(let ((regexp "\\(\\`\\|/\\)\\(pam_env\\.conf\\|\\.pam_environment\\)\\'"))
+ (add-to-list 'auto-mode-alist `(,regexp . pam-env-mode)))
+
+(provide 'pam-env-mode)
+;;; pam-env.el ends here
diff --git a/tw/services/files/emacs-packages/vcard-mode.el b/tw/services/files/emacs-packages/vcard-mode.el
new file mode 100644
index 00000000..a932477a
--- /dev/null
+++ b/tw/services/files/emacs-packages/vcard-mode.el
@@ -0,0 +1,56 @@
+;;; vcard-mode.el --- Major mode for vCard files.
+
+;; Copyright (C) 2012 Desmond O. Chang
+
+;; Author: Desmond O. Chang <dochang@gmail.com>
+;; Version: 0.1.0
+;; Keywords: files
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package provides a major mode to edit vCard files.
+
+;; To install it, put this file under your load path. Then add the
+;; following to your .emacs file:
+
+;; (require 'vcard-mode)
+
+;; Or if you don't want to load it until editing a vCard file:
+
+;; (autoload 'vcard-mode "vcard-mode" "Major mode for vCard files" t)
+;; (add-to-list 'auto-mode-alist '("\\.vc\\(f\\|ard\\)\\'" . vcard-mode))
+
+;;; Code:
+
+(require 'generic)
+
+(defun vcard-mode-init ()
+ (set (make-local-variable 'paragraph-start) "BEGIN:VCARD"))
+
+;;;###autoload
+(define-generic-mode vcard-mode
+ '()
+ nil
+ '(("^BEGIN:VCARD" . font-lock-function-name-face)
+ (";[^:\n]+:" . font-lock-type-face)
+ ("^\\([^;:\n]+\\):?" . font-lock-keyword-face))
+ '("\\.\\(vcf\\|vcard\\)\\'")
+ '(vcard-mode-init)
+ "Generic mode for vCard files.")
+
+(provide 'vcard-mode)
+
+;;; vcard-mode.el ends here