Compare commits

...

135 Commits

Author SHA1 Message Date
114553abb1 Haskell stuff 2025-11-17 07:21:16 -08:00
bf53c657a8 Better C-style indentation 2025-11-15 02:33:11 -08:00
00b905fad1 Prevent ehsell-starship extensions from applying to hidden files 2025-11-02 20:19:02 -08:00
8191a92a8c Change auto-format for lisp files 2025-11-02 01:42:50 -07:00
64d229236f Update woman settings 2025-11-01 17:53:07 -07:00
b70db29a1a Change page break plugin 2025-10-25 21:42:00 -07:00
a310e12771 Add arduino stuff 2025-10-22 01:39:43 -07:00
12d8cc0e3e Add stuff for waybar 2025-10-10 02:38:46 -07:00
a0c7697440 Fix jsonc major mode 2025-10-09 03:55:14 -07:00
2367b15a9c Fix the last commit 2025-10-08 19:41:19 -07:00
18b804680d Fix being unable to set the mark in the sly fonification buffer 2025-10-08 19:14:45 -07:00
de7e77c1ea eshell-starship.el - Don't show Emacs for .dir-locals(2).el 2025-09-27 00:54:19 -07:00
450e951914 Org babel stuff 2025-09-26 22:49:26 -07:00
d1f90eaef1 Use with editor in eshell 2025-09-05 20:10:54 -07:00
f5bcc7e5c1 Change completion preview keybinds again 2025-09-05 18:37:58 -07:00
2bfd574504 Add new binding for completion preview mode 2025-09-05 18:24:24 -07:00
1445beb3d0 Change indentation in c-ts-mode 2025-08-27 14:04:29 -07:00
e517cd0ab8 Fix the last commit 2025-08-27 02:02:46 -07:00
f14d35b79a set ff-ignore-include to t 2025-08-27 01:18:29 -07:00
cf71e268df Fix type in init.el 2025-07-20 14:38:52 +09:00
72c9dc87a5 Fix mines-mode 2025-07-02 20:53:49 +09:00
2d85457f5d Fix circular list 2025-06-30 20:25:58 +09:00
a23464a17a Attempt to fix dape info modes 2025-06-29 19:00:16 +09:00
b7dd3010a0 Fix completion-preview 2025-06-24 20:44:57 +09:00
73530f887f Fix bug in eshell-starship 2025-06-21 19:50:23 +09:00
19d559d626 Disable forge 2025-05-19 22:48:27 +09:00
5bb3c77e3c Update mu4e stuff 2025-04-29 22:54:18 +09:00
b036ee2a32 Add consult-history binding 2025-04-22 00:32:34 +09:00
00bdf1e8eb Fix some magit stuff I seem to have broken 2025-04-17 23:51:39 +09:00
8112f6b1dc Fix trusted-content-p 2025-04-01 20:54:20 +09:00
e0c8453dfe Remove ros2 stuff 2025-03-19 08:27:36 +09:00
78ad4f0ba6 Update variable name 2025-02-28 21:57:13 -08:00
95fe0861c9 Fix trusted-content for remote directories 2025-02-28 19:55:07 -08:00
b723dc961c Fix mozc in remove files 2025-02-24 22:29:57 -08:00
3883a90da3 Disable tramp-direct-async-process due to hangs 2025-02-24 19:11:04 -08:00
b374fc57e2 Fix tramp eglot stuff 2025-02-22 00:41:04 -08:00
89e010474f Emacs 30.1!!! 2025-02-21 17:43:40 -08:00
226ea54105 Make C-y yank in eat 2025-02-20 21:43:52 -08:00
819d0eda4f Make .clangd files yaml mode 2025-02-20 20:38:28 -08:00
997be323f5 Add my/always-use-eat 2025-02-20 20:33:00 -08:00
2d072241a7 Update elisp/arch-ros2.el 2025-02-20 20:28:42 -08:00
0fd70fb03c Add arch-ros2.el to init.el 2025-02-20 20:10:54 -08:00
90fbbda854 Readd arch-ros2.el 2025-02-20 20:10:04 -08:00
a5e3bd4c11 A bunch of changes 2025-02-18 06:24:16 -08:00
2a2264be9f Add local-init.el (support) 2025-02-18 01:22:00 -08:00
dabf480e7d Change back to eshell for remote connections 2025-02-17 21:46:40 -08:00
b1d77b0f5d Better distrobox and sudo tramp support 2025-02-17 21:21:52 -08:00
6ea87de1b5 (Hopefully) fix elisp/inferior-cc.el 2025-02-16 03:33:33 -08:00
386e65c0f4 Some (more) jupyter stuff 2025-02-14 03:31:10 -08:00
e158df3fd1 Add repl for javascript 2025-02-13 05:44:08 -08:00
816e696f47 Many changes 2025-02-13 00:13:43 -08:00
e2db4e1193 Add jshell stuff 2025-02-12 03:00:19 -08:00
3ebc12ddc9 Modify eglot stuff 2025-02-11 21:41:51 -08:00
173178313e Disable the mozc fixes 2025-02-11 14:10:08 -08:00
2313ad1b25 Add arch-ros2 2025-02-11 05:37:03 -08:00
a3c1ccb6c7 Fix warning message on startup 2025-02-11 03:21:59 -08:00
de206d7b93 Fix eat in terminal and eat char mode with evil 2025-02-11 01:15:55 -08:00
3f23480cb9 Add some evil fixes to eat 2025-02-10 23:26:17 -08:00
d65948ca41 Add pyenv support 2025-02-09 14:05:12 -08:00
655eb827e1 Change to a different method for fixing kkp and map-y-or-n-p 2025-02-09 03:43:36 -08:00
87ec1690ee Temporarily fix mozc.el 2025-02-09 01:20:21 -08:00
a6a712ea99 Change some add-to-list calls to add-hook calls 2025-02-08 18:02:56 -08:00
427b70f347 Clean up my/bibtex-in-entry-p 2025-02-08 17:33:34 -08:00
641aa325dc Fix kkp for the latest update 2025-02-08 15:40:52 -08:00
4282129190 Enable rainbow-delimiters-mode in the sly mrepl 2025-02-08 15:07:40 -08:00
58b6608cbb Fontify the sly mrepl 2025-02-08 04:30:49 -08:00
91a54013b9 Make sure Jupyter syntax highlighting works on first run 2025-02-08 00:27:16 -08:00
9c413aaa38 Add jupyter stuff 2025-02-08 00:09:42 -08:00
c8ba0ce0ca Change the world clock list 2025-02-07 00:27:06 -08:00
9a6a9fcbf8 Disable apheleia auto-format in awk buffers 2025-02-06 17:05:01 -08:00
fd942c275f Make which-key not show evil operator maps 2025-02-06 16:49:54 -08:00
6a1d358548 Refresh the eww message count whenever a message is updated in mu4e 2025-02-06 15:41:39 -08:00
719a2ffac7 Prevent awk from being auto-formatted 2025-02-06 15:13:19 -08:00
2af97af4dd Clean up eww code 2025-02-05 20:54:55 -08:00
9611655fa0 Link fcitx and emacs's input method 2025-02-04 20:59:13 -08:00
44c3cde2c5 Some json stuff 2025-02-04 18:10:31 -08:00
5a3735644d Make which-key show evil operator maps 2025-01-27 05:02:40 -08:00
b33937f50b Pyvenv support 2025-01-27 03:31:38 -08:00
4dfd389998 Fix hyperspec 2025-01-21 05:22:45 -08:00
7b03b977ac Fix hyperspec 2025-01-12 01:42:58 -08:00
21d861dbd0 Disable org-mu4e 2025-01-06 15:23:34 -08:00
9a9a707a10 Add migemo 2025-01-06 01:47:38 -08:00
14467fb9f8 Make the scratch buffer trusted 2025-01-03 20:16:54 -08:00
96c175e0bb Make eshell buffers always trusted 2025-01-03 15:12:19 -08:00
966c3392aa A few more changes 2025-01-01 04:39:28 -08:00
7ef055bc51 Fix the last commit 2025-01-01 03:29:45 -08:00
6d3b19fe46 Hopefully finish trusted-files.el 2025-01-01 03:17:50 -08:00
bebd49f14a Work on trusted-dirs.el 2024-12-30 06:00:19 -08:00
af17d6e0dc Complete the "env" directive in firejail-mode.el 2024-12-29 15:41:24 -08:00
c49caf7a25 Rewrite firejail-mode.el 2024-12-29 15:30:26 -08:00
54e58aca7e Take another crack at making sure WAYLAND_DISPLAY is set 2024-12-26 18:19:52 -08:00
0d1d4e10c1 Update firejail-mode.el 2024-12-26 00:11:10 -08:00
02122f979b More changes!!! 2024-12-25 23:43:36 -08:00
83d40e3713 Fix TypeScript tree-sitter 2024-12-25 22:05:40 -08:00
100fe208e2 Add some new magit stuff 2024-12-25 20:23:52 -08:00
101342c5e3 TypeScript stuff 2024-12-25 19:26:48 -08:00
459705d05a Random JS changes 2024-12-25 19:06:19 -08:00
ed237a2e03 Don't auto-revert dired buffers 2024-12-25 17:02:02 -08:00
82e2f5d753 Fix dirvish tty rendering 2024-12-25 04:27:45 -08:00
e1b18eeefe Fix last commit 2024-12-25 03:22:27 -08:00
80a0d4aefe Auto-format added to init.el 2024-12-25 02:41:17 -08:00
b307a21e11 Dirvish, easy-escape, and devdocs 2024-12-24 03:50:06 -08:00
96b64a144e Some more small changes 2024-12-22 15:19:28 -08:00
dc789627c0 Fix bug in eshell-starship.el 2024-12-22 05:33:07 -08:00
09914fc3a9 Make eshell-starship.el more efficient 2024-12-22 05:22:45 -08:00
c2001ae2b3 Fix previous commit 2024-12-22 00:18:50 -08:00
b6ddcd03c0 All ability to set icon face to eshell-starship.el 2024-12-22 00:16:15 -08:00
f81f0c6a15 Rewrite eshell-starship.el 2024-12-21 21:38:15 -08:00
39efc3d5ba Add more eshell-starship modules 2024-12-20 20:22:23 -08:00
b794eebeb7 Add src path and fix dashboard image 2024-12-20 18:59:40 -08:00
6cdd4f6aa3 Add GLSL mode 2024-12-20 17:16:10 -08:00
190627d982 Better bibtex URL highlighting and matching 2024-12-20 16:11:38 -08:00
b148423914 Fix some bugs and random things 2024-12-20 15:16:44 -08:00
5c3c492fd8 Undo kkp tab and return decoding 2024-12-09 23:09:36 -08:00
535dc0313e Fix sly mrepl thing 2024-12-05 08:41:44 -08:00
8d7aba02d3 Add some more kkp stuff 2024-12-05 08:00:20 -08:00
19e2d6fd59 Update init.el 2024-12-04 14:09:05 -08:00
f6b37f1b10 Add visual-regexp 2024-12-02 16:37:47 -08:00
32b3042418 Fix kkp, again 2024-11-19 19:25:21 -08:00
4e94728235 Some more kkp stuff 2024-11-19 18:13:37 -08:00
bf1f2a7bfa Remove lualatex stuff (because I read the docs) 2024-11-13 08:58:52 -08:00
e7392c6c09 Modify my/kill-some-buffers 2024-11-07 22:06:44 -08:00
a0249716b6 Disable agressive-indent-mode 2024-11-07 10:18:48 -08:00
f9f7badd76 Fix wl-copy (again) 2024-11-06 18:04:13 -08:00
2cd476d2b1 Fix xclip-mode to not print an error when there is no selection 2024-11-06 16:51:48 -08:00
738cd67f00 Fix latex-help.el 2024-11-06 08:42:48 -08:00
222fcacfeb Add sly stuff to my/diagnostic-at-point 2024-11-05 21:20:57 -08:00
04fa288627 Fix some more kkp stuff 2024-11-05 10:06:00 -08:00
998d5cf3fa Fix error handling in crofu-terminal-popupinfo.el 2024-11-05 08:50:06 -08:00
ac07328aca Make sure my/kill-some-buffers asks about all buffers on tty frames 2024-11-04 13:41:54 -08:00
2ef42f86dc Fix kkp not being enabled in the first tty frame 2024-11-03 07:03:25 -08:00
4dc28f50d7 Properly handle tab and backtab in kkp-enabled terminals 2024-11-03 05:47:25 -08:00
71fb77f758 Fix last commit 2024-11-01 09:48:16 -07:00
5d09db86a0 Add some Embark stuff 2024-11-01 09:40:43 -07:00
a5e9144d63 User newer key binding functions 2024-10-31 18:19:54 -07:00
11 changed files with 4253 additions and 593 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@
/tramp /tramp
/dape-breakpoints /dape-breakpoints
flycheck_init.el flycheck_init.el
local-init.el*

View File

@ -172,3 +172,185 @@
;; flyspell-issue-welcome-flag nil) ;; flyspell-issue-welcome-flag nil)
;; (define-key flyspell-mode-map (kbd "C-;") nil t) ;; (define-key flyspell-mode-map (kbd "C-;") nil t)
;; (define-key flyspell-mode-map (kbd "C-,") nil t)) ;; (define-key flyspell-mode-map (kbd "C-,") nil t))
;; (use-package aggressive-indent
;; :hook (prog-mode . aggressive-indent-mode)
;; :config
;; (add-to-list 'aggressive-indent-protected-commands
;; #'evil-undo))
;; ;; easier identification of local variables
;; (use-package color-identifiers-mode
;; :hook (prog-mode . color-identifiers-mode-maybe)
;; :init
;; (setq color-identifiers:num-colors 10
;; color-identifiers:recoloring-delay 0.5)
;; :config
;; ;; make sure that remapped treesitter modes are handled
;; (defun my/color-identifiers-mode-remap-ts-modes ()
;; (dolist (entry color-identifiers:modes-alist)
;; (cl-destructuring-bind (mode &rest props) entry
;; (when-let ((remapped-mode (alist-get mode major-mode-remap-alist))
;; ((string-match-p "-ts-" (symbol-name remapped-mode)))
;; ((not (assq remapped-mode color-identifiers:modes-alist))))
;; ;; no need to test with `add-to-list'
;; (push (cons remapped-mode props) color-identifiers:modes-alist)))))
;; (my/color-identifiers-mode-remap-ts-modes)
;; (setf (alist-get 'lisp-interaction-mode color-identifiers:modes-alist)
;; (alist-get 'emacs-lisp-mode color-identifiers:modes-alist))
;; (defun my/-color-identifiers-elisp-handle-let-like (sexp output)
;; (cl-destructuring-bind (_name &optional vars &rest body) sexp
;; (dolist (entry vars body)
;; (cond
;; ((and entry (symbolp entry)) (puthash entry t output))
;; ((and (car entry) (symbolp (car entry)))
;; (puthash (car entry) t output))))))
;; (defun my/-color-identifiers-parse-lambda-list (list output)
;; (dolist (entry list)
;; (cond
;; ((and entry (symbolp entry)
;; (not (string-prefix-p ":" (symbol-name entry)))
;; (not (string-prefix-p "&" (symbol-name entry))))
;; (puthash entry t output))
;; ((and (car-safe entry) (symbolp (car entry)))
;; (puthash (car entry) t output)))))
;; (defun my/-color-identifiers-elisp-handle-destructing-bind-like
;; (sexp output)
;; (cl-destructuring-bind (_name &optional vars &rest expr-and-body) sexp
;; (my/-color-identifiers-parse-lambda-list vars output)
;; expr-and-body))
;; (defun my/-color-identifiers-elisp-handle-defun-like
;; (sexp output)
;; (cl-destructuring-bind (_name _func &optional vars &rest body) sexp
;; (my/-color-identifiers-parse-lambda-list vars output)
;; body))
;; (defun my/-color-identifiers-elisp-handle-dolist-like
;; (sexp output)
;; (cl-destructuring-bind (_name &optional spec &rest body) sexp
;; (cl-destructuring-bind (&optional var &rest forms) spec
;; (when (symbolp var)
;; (puthash var t output))
;; (append body forms))))
;; (defun my/-color-identifiers-elisp-handle-loop (sexp output)
;; (let (body-forms)
;; (cl-maplist
;; (lambda (kwds)
;; (cl-case (car kwds)
;; (for ;; this could be a dotted list
;; (let ((tail (ensure-list (cadr kwds))))
;; (while tail
;; (when (and (consp tail) (symbolp (car tail)))
;; (puthash (car tail) t output))
;; (when (and (consp tail) (symbolp (cdr tail)))
;; (puthash (cdr tail) t output))
;; (setq tail (cdr-safe tail)))))
;; (using
;; (when (and (listp (cdr kwds))
;; (symbolp (cl-second (cdr kwds))))
;; (puthash (cl-second (cdr kwds)) t output)))
;; ((with into)
;; (when (symbolp (cadr kwds))
;; (puthash (cadr kwds) t output)))
;; (t
;; (unless (atom (car kwds))
;; (push (car kwds) body-forms)))))
;; (cdr sexp))
;; body-forms))
;; (defun my/-color-identifiers-elisp-handle-do-like (sexp output)
;; (let ((eval-forms))
;; (cl-destructuring-bind (name &optional vars test-forms &rest body) sexp
;; (dolist (entry vars (append eval-forms test-forms body))
;; (cl-destructuring-bind (&optional var init step &rest _)
;; entry
;; (when (symbolp var)
;; (puthash var t output)
;; (cl-callf nconc eval-forms (list init step))))))))
;; (defvar my/-color-identifiers-eslip-handlers
;; (let ((table (make-hash-table)))
;; (puthash 'quote #'ignore table)
;; (puthash 'function #'ignore table)
;; (puthash 'let #'my/-color-identifiers-elisp-handle-let-like table)
;; (puthash 'let* #'my/-color-identifiers-elisp-handle-let-like table)
;; (puthash 'cl-destructuring-bind
;; #'my/-color-identifiers-elisp-handle-destructing-bind-like table)
;; (puthash 'with-slots
;; #'my/-color-identifiers-elisp-handle-destructing-bind-like table)
;; (puthash 'lambda
;; #'my/-color-identifiers-elisp-handle-destructing-bind-like table)
;; (puthash 'cl-function
;; #'my/-color-identifiers-elisp-handle-destructing-bind-like table)
;; (puthash 'defun
;; #'my/-color-identifiers-elisp-handle-defun-like table)
;; (puthash 'cl-defun
;; #'my/-color-identifiers-elisp-handle-defun-like table)
;; (puthash 'defmacro
;; #'my/-color-identifiers-elisp-handle-defun-like table)
;; (puthash 'cl-defmacro
;; #'my/-color-identifiers-elisp-handle-defun-like table)
;; (puthash 'cl-defmacro
;; #'my/-color-identifiers-elisp-handle-defun-like table)
;; (puthash 'cl-loop
;; #'my/-color-identifiers-elisp-handle-loop table)
;; (puthash 'dolist
;; #'my/-color-identifiers-elisp-handle-dolist-like table)
;; (puthash 'dotimes
;; #'my/-color-identifiers-elisp-handle-dolist-like table)
;; (puthash 'cl-dolist
;; #'my/-color-identifiers-elisp-handle-dolist-like table)
;; (puthash 'cl-dotimes
;; #'my/-color-identifiers-elisp-handle-dolist-like table)
;; (puthash 'cl-do
;; #'my/-color-identifiers-elisp-handle-do-like table)
;; table)
;; "A list of functions that find declarations in variables.
;; This is used in `my/-color-identifiers-elisp-declarations-in-sexp'. It is a
;; hash table of function (or macro) names and a function that handles them. The
;; functions should be of two arguments. The first is the sexp to parse. The
;; second is a hash table with the keys being the symbols of local variables. The
;; function should return a list of the forms that it contains that should be
;; recursively searched.")
;; (defun my/-color-identifiers-lisp-declarations-in-sexp (sexp output table)
;; "Get all of the variable declarations in SEXP and place them in OUTPUT.
;; OUTPUT is a hash table. TABLE is a table like
;; `my/-color-identifiers-elisp-declarations-in-sexp'."
;; (let ((stack (list sexp)))
;; (while (and stack (not (input-pending-p)))
;; (let ((entry (pop stack)))
;; (when (proper-list-p entry)
;; (if-let ((handler (gethash (car entry) table)))
;; (cl-callf nconc stack
;; (copy-sequence (funcall handler entry output)))
;; (cl-callf nconc stack
;; (copy-sequence (cdr entry)))))))))
;; (defun my/-color-identifiers-lisp-declarations-in-buffer (&optional buffer)
;; (let ((result (make-hash-table)))
;; (save-excursion
;; (goto-char (point-min))
;; (condition-case nil
;; (while t
;; (condition-case nil
;; (let ((sexp (read (or buffer (current-buffer)))))
;; (my/-color-identifiers-lisp-declarations-in-sexp
;; sexp result my/-color-identifiers-eslip-handlers))
;; (invalid-read-syntax nil)))
;; (end-of-file nil))
;; (let ((names))
;; (maphash (lambda (k _v)
;; (unless (or (eq k t) (not k) (boundp k))
;; (push (symbol-name k) names)))
;; result)
;; names))))
;; (color-identifiers:set-declaration-scan-fn
;; 'emacs-lisp-mode
;; 'my/-color-identifiers-lisp-declarations-in-buffer)
;; (color-identifiers:set-declaration-scan-fn
;; 'lisp-interaction-mode
;; 'my/-color-identifiers-lisp-declarations-in-buffer))
;; page break lines
;; (use-package page-break-lines
;; :config
;; (global-page-break-lines-mode 1)
;; (add-to-list 'page-break-lines-modes 'prog-mode)
;; (add-to-list 'page-break-lines-modes 'text-mode)
;; (add-to-list 'page-break-lines-modes 'helpful-mode))

64
elisp/c-comments.el Normal file
View File

@ -0,0 +1,64 @@
;;; c-comments.el --- Functions for working with C-style comments.-*- lexical-binding: t -*-
;;; Commentary:
;;; Code:
(defun c-comments-bounds-at-point ()
"Return the bounds of the multi-line comment around point."
(let* ((syntax-info (syntax-ppss))
(in-comment (nth 4 syntax-info))
(is-multiline (and in-comment
(not (nth 7 syntax-info)))))
(cond
(is-multiline
(let ((start (save-excursion
(end-of-line)
(search-backward "/*" nil t)))
(end (save-excursion
(beginning-of-line)
(search-forward "*/" nil t))))
(when (and start
(>= (point) start)
(or (not end)
(<= (point) (- end 2))))
(cons start end))))
(in-comment
(let ((start (save-excursion
(beginning-of-line)
(search-forward "//" (pos-eol) t))))
(and (>= (point) start)
(cons start (pos-eol))))))))
(defun c-comments--need-comment-terminator (bounds)
"Return non-nil if a comment terminator needs to be inserted given BOUNDS."
(and (or (not (cdr bounds))
(save-excursion
(search-forward "/*" (cdr bounds) t)))))
(defun c-comments-newline (&optional arg always-continue)
"Insert ARG newlines and indent, automatically closing multi-line comments.
Also, insert \"* \" at the beginning of every line when executed from inside a
multi-line comment. If ALWAYS-CONTINUE is true, also insert \"//\" if called
from inside a C++-style single-line comment."
(interactive "*Pi")
(when (and arg (< (prefix-numeric-value arg) 0))
(user-error "Count cannot be negative"))
(if-let ((bounds (c-comments-bounds-at-point))
((or always-continue (not (nth 7 (syntax-ppss))))))
(progn
(when (c-comments--need-comment-terminator bounds)
(save-excursion
(insert "\n*/")
(indent-region (pos-bol) (pos-eol))))
(dotimes (_ (prefix-numeric-value arg))
(default-indent-new-line))
(when (save-excursion
(forward-char -1)
(looking-at-p (regexp-opt '("*" "/"))))
(insert " ")))
(newline arg t)))
(defun c-comments-newline-always-continue (&optional arg)
"Like calling `c-comments-newline', but always continue comments.
That is, this is equivalent to (c-comments-newline ARG t)."
(interactive "*P")
(c-comments-newline arg t))
(provide 'c-comments)
;;; c-comments.el ends here

View File

@ -87,7 +87,9 @@ As this is :around advice, OLDFUN is the real (advised) function to call."
(goto-char (point-min)) (goto-char (point-min))
(put-text-property (point-min) (point-max) 'face 'corfu-popupinfo) (put-text-property (point-min) (point-max) 'face 'corfu-popupinfo)
(when-let ((m (memq 'corfu-default (alist-get 'default face-remapping-alist)))) (when-let ((m (memq 'corfu-default (alist-get 'default face-remapping-alist))))
(setcar m 'corfu-popupinfo))))) (setcar m 'corfu-popupinfo)))
;; We succeeded in loading the data
t))
(defun ctp--popon-position (buffer) (defun ctp--popon-position (buffer)
"Find a good position to open the popon for BUFFER's content. "Find a good position to open the popon for BUFFER's content.
@ -201,10 +203,12 @@ CANDIDATE is the same as for `corfu-popupinfo--show'. As this is meant to be
(not (corfu--equal-including-properties (not (corfu--equal-including-properties
candidate corfu-popupinfo--candidate))) candidate corfu-popupinfo--candidate)))
(let ((buf (ctp--get-buffer))) (let ((buf (ctp--get-buffer)))
(ctp--load-content candidate buf) (if (ctp--load-content candidate buf)
(ctp--display-buffer buf)) (progn
(setq corfu-popupinfo--candidate candidate (ctp--display-buffer buf)
corfu-popupinfo--toggle t)))) (setq corfu-popupinfo--candidate candidate
corfu-popupinfo--toggle t))
(corfu-popupinfo--hide))))))
(defun ctp--move-away-from-eob () (defun ctp--move-away-from-eob ()
"Ensure the point isn't too close to the end of the buffer." "Ensure the point isn't too close to the end of the buffer."

File diff suppressed because it is too large Load Diff

View File

@ -1,136 +1,862 @@
;;; firejail-mode --- Major mode for editing firejail profiles -*- lexical-binding: t -*- ;;; firejail-mode --- Major mode for editing firejail profiles -*- lexical-binding: t -*-
;;; Commentary: ;;; Commentary:
;;; Code: ;;; Code:
(require 'find-file)
(require 'custom)
(require 'thingatpt)
(require 'man)
(eval-when-compile
(require 'rx))
(defgroup firejail-mode ()
"Major mode for editing Firejail profiles."
:group 'programming
:prefix "firejail-")
(defcustom firejail-executable "firejail"
"Executable to use when calling firejail."
:tag "Executable"
:group 'firejail-mode
:type 'string)
(defcustom firejail-include-search-directories
'("./" "~/.config/firejail/" "/etc/firejail/" "/usr/local/etc/firejail/")
"List of directories to search for include files."
:tag "Include Search Directories"
:group 'firejail-mode
:type '(repeat string))
(defcustom firejail-include-search-suffixes
'("inc" "local" "profile")
"List of file suffixes to use when searching for include files.
These should _NOT_ have a leading period."
:tag "Include Search Suffixes"
:group 'firejail-mode
:type '(repeat string))
(defcustom firejail-include-ignored-files
'(".git/")
"List of file names that should be ignored when searching for include files.
These should end with a slash (/) if their are a directory."
:tag "Include Ignored Files"
:group 'firejail-mode
:type '(repeat string))
(defface firejail-error-face
'((t :background "red"))
"Face for reporting Firejail syntax errors."
:tag "Error Face"
:group 'firejail-mode)
(defun firejail--debug-output-to-list (&rest args)
"Convert the output from one of Firejail's --debug-* commands to a list.
ARGS are passed uncaged to Firejail and should include the proper debug command."
(ignore-error file-missing
(mapcan (lambda (line)
(when (string-match (rx "- " (group (+ any)) eol) line)
(list (match-string 1 line))))
(apply 'process-lines firejail-executable args))))
(defconst firejail--known-caps
(firejail--debug-output-to-list "--debug-caps")
"A list of known Linux capabilities.
This will probably be empty on anything but Linux.")
(defconst firejail--known-syscalls64
(firejail--debug-output-to-list "--debug-syscalls")
"A list of known 64 bit system calls.
This will probably be empty on anything by Linux.")
(defconst firejail--known-syscalls32
(firejail--debug-output-to-list "--debug-syscalls32")
"A list of known system 32 bit calls.
This will probably be empty on anything by Linux.")
(defconst firejail--known-errnos
(firejail--debug-output-to-list "--debug-errnos")
"A list of known system 32 bit calls.
This will probably be empty on anything by Linux.")
(defconst firejail--known-conditionals
'("HAS_APPIMAGE" "HAS_NET" "HAS_NODBUS" "HAS_NOSOUND" "HAS_PRIVATE"
"HAS_X11" "ALLOW_TRAY" "BROWSER_DISABLE_U2F" "BROWSER_ALLOW_DRM")
"List of conditionals known to Firejail.")
(defun firejail--list-dbus-services (bus)
"List all DBus services on BUS.
BUS is one of `:system' or `:session'."
(ignore-errors
(require 'dbus nil t)
(when (fboundp 'dbus-call-method) ;; silence byte compiler
(dbus-call-method bus "org.freedesktop.DBus" "/org/freedesktop/DBus"
"org.freedesktop.DBus" "ListNames"))))
(defun firejail--insert-entire-special-file (file)
"Insert all of FILE (e.g. /proc/cpuinfo), even if it's special."
(while (>= (cl-second (insert-file-contents file nil (1- (point))
(+ (point) 9999)))
10000)
(goto-char (point-max))))
(defvar-local firejail--num-cpus-cache nil
"The number of CPUs the current system has.
This might be nil on platforms other than Linux.")
(defun firejail--get-num-cpus ()
"Return the number of CPUs the current system has."
(if (local-variable-p 'firejail--num-cpus-cache)
firejail--num-cpus-cache
(ignore-error file-missing
(with-temp-buffer
(firejail--insert-entire-special-file "/proc/cpuinfo")
(goto-char (point-max))
(when (re-search-backward (rx bol "processor" blank ":" blank
(group (+ digit)) eol))
(setq firejail--num-cpus-cache
(string-to-number (match-string-no-properties 1))))))))
(defun firejail--find-next-glob-char (limit)
"Find the next glob char between point and LIMIT."
(let ((max-lisp-eval-depth 10000))
(when (search-forward "*" limit t)
(backward-char)
(if (not (eq t (nth 5 (syntax-ppss))))
(progn
(looking-at (regexp-quote "*"))
(forward-char)
t)
(forward-char)
(firejail--find-next-glob-char limit)))))
(defun firejail--generate-documentation-table ()
"Parse the firejail-profile(5) man page to get a documentation table."
(ignore-error file-missing
(let ((path (car (process-lines-handling-status
manual-program (lambda (status)
(when (not (zerop status))
(signal 'file-missing "")))
"-w" "firejail-profile")))
(ht (make-hash-table)))
(with-temp-buffer
;; Emacs will auto unzip this if needed
(insert-file-contents path)
(when (re-search-forward (rx bol ".TP\n"
bol "\\fBinclude other.profile" eol)
nil t)
(forward-line -1)
(while (and (not (looking-at-p (rx bol ".SH FILES" eol)))
(re-search-forward (rx bol ".TP\n" bol
"\\fB" (group
(+ (not (any "\n" blank)))))
nil t))
(let ((name (intern (match-string-no-properties 1)))
(start (+ 3 (pos-bol))))
(when (re-search-forward (rx bol ".TP" eol) nil t)
(forward-line -1)
(when (looking-at-p (rx bol eol))
(forward-line -1))
(let* ((raw-doc (buffer-substring-no-properties
start (pos-eol)))
(new-doc (replace-regexp-in-string (rx bol ".br" eol)
"\n" raw-doc))
(cur-doc (gethash name ht)))
(puthash name (concat cur-doc
(when cur-doc "\n\n")
new-doc)
ht)))))))
;; some manual fixing
(cl-macrolet ((summary (dir text)
`(let ((old-val (gethash ',dir ht)))
(puthash ',dir (concat (symbol-name ',dir) "\n"
,text (when old-val "\n\n")
old-val)
ht))))
(summary net "Enable a new network namespace.")
(summary bind "Mount bind directories or files."))
ht)))
(defvar-local firejail--documentation-table nil
"Table mapping Firejail directives to their documentation.")
(defun firejail--documentation-for (dir)
"Lookup the documentation for DIR."
(unless firejail--documentation-table
(setq firejail--documentation-table
(firejail--generate-documentation-table)))
(gethash (intern-soft dir) firejail--documentation-table))
(defconst firejail-profile-font-lock-keywords (defconst firejail-profile-font-lock-keywords
(let* ((normal '("quiet" "include" "noblacklist" "nowhitelist" (let* ((cond-rx (rx (* space) "?" (group (* (any alnum "_"))) (? ":")))
"blacklist" "blacklist-nolog" "bind" "disable-mnt" (ignore-rx (rx (group (+ (* space) bow "ignore"))))
"keep-config-pulse" "keep-dev-shm" "keep-var-tmp" (prefix-rx (rx bol (? (regexp cond-rx)) (? (regexp ignore-rx))
"mkdir" "mkfile" "noexec" "private" "private-bin" (* space)))
"private-cache" "private-cwd" "private-dev" kwds)
"private-etc" "private-home" "private-lib" (cl-flet ((add (dirs &optional opts (face 'font-lock-keyword-face))
"private-opt" "private-srv" "private-tmp" (push (list
"read-only" "read-write" "tmpfs" "tracelog" (rx (regexp prefix-rx)
"whitelist" "whitelist-ro" "writable-etc" bow (regexp (regexp-opt (ensure-list dirs) t)) eow
"writable-run-user" "writable-var" (* space)
"writable-var-log" "allow-debuggers" "apparmor" (? (regexp (regexp-opt (ensure-list opts) t)) eow))
"caps" "caps.keep" "caps.drop" '(1 font-lock-builtin-face nil t)
"memory-deny-write-execute" "nonewprivs" '(2 font-lock-keyword-face nil t)
"noprinters" "noroot" "restrict-namespaces" '(3 font-lock-keyword-face)
"seccomp" "seccomp.32" "seccomp.drop" `(4 ,face nil t))
"seccomp.32.drop" "seccomp.keep" "seccomp.32.keep" kwds))
"protocol" "xephyr-screen" "dbus-system.own" (add-many (dirs opts &optional (face 'font-lock-keyword-face))
"dbus-system.talk" "dbus-system.see" (push (list
"dbus-system.call" "dbus-system.broadcast" (rx (regexp prefix-rx)
"dbus-user.own" "dbus-user.talk" "dbus-user.see" bow (regexp (regexp-opt (ensure-list dirs) t)) eow)
"dbus-user.call" "dbus-user.broadcast" "nodbus" '(1 font-lock-builtin-face nil t)
"cpu" "nice" "rlimit-as" "rlimit-cpu" '(2 font-lock-keyword-face nil t)
"rlimit-fsize" "rlimit-nproc" "rlimit-nofile" '(3 font-lock-keyword-face)
"rlimit-sigpending" "timeout" "allusers" "env" `(,(rx bow (regexp (regexp-opt opts t)) eow)
"ipc-namespace" "keep-fd" "name" "no3d" nil nil (0 ,face)))
"noautopulse" "nodvd" "nogroups" "noinput" kwds)))
"nosound" "notv" "nou2f" "novideo" "machine-id" ;; NOTE the order below matters
"defaultgw" "dns" "hostname" "hosts-file" "x11" ;; glob asterisk
"dbus-system" "dbus-user" "ip" "ip6" "iprange" (push '("*" 0 'bold append) kwds)
"mac" "mtu" "net" "netfilter" "netfilter" "netlock" ;; invalid characters
"netmask" "netns" "veth-name" (push `(,(rx (or "\"" "\\")) 0 'firejail-error-face t) kwds)
"deterministic-exit-code" "deterministic-shutdown" ;; variables
"join-or-start")) (push (list (rx "${" (+ (any alnum "_")) "}") 0
(take-all-list '("caps.drop")) 'font-lock-variable-name-face t)
(take-none-list '("shell" "net")) kwds)
(comment-rx '("^.*\\(#.*\\)$" 1 font-lock-comment-face)) ;; ignore
(dbus-system-user-rx '("^ *\\(\\?[A-Z_]+: +\\)?\ (push (list (rx bol (? (regexp cond-rx)) (regexp ignore-rx) eow)
\\(\\(ignore +\\)?\ 2 'font-lock-keyword-face)
dbus-\\(system\\|user\\) +\\(none\\|filter\\)?\\)" . 2)) kwds)
(x11-rx '("^ *\\(?:\\?[A-Z_]+: +\\)?\ ;; conditional
\\(\\(?:ignore +\\)?x11 +\\(?:none\\|xephyr\\|xorg\\|xpra\\|xvfb\\)?\\)" . 1)) (push (list (rx bol (regexp cond-rx) eow) 1 'font-lock-builtin-face) kwds)
(ip-ip6-rx '("^ *\\(\\?[A-Z_]+: +\\)?\ ;; can't have a conditional include or quiet
\\(\\(ignore +\\)?ip6? +\\(none\\|dhcp\\)\\)" . 2)) (push (list (rx bol (? (regexp ignore-rx)) (* space)
(take-all `(,(concat (regexp-opt take-all-list "^ *\\(\\?[A-Z_]+: +\\)?\ bow (group (or "include" "quiet")) eow)
\\(\\(ignore +\\)?\\<\\(") "\\>\\)") 2 'font-lock-keyword-face)
(2 font-lock-keyword-face) kwds)
("\\<all\\>" nil nil (0 font-lock-keyword-face)))) ;; directives
(take-none `(,(concat (regexp-opt take-none-list "^ *\\(\\?[A-Z_]+: +\\)?\ (add '("noblacklist" "nowhitelist" "blacklist" "blacklist-nolog" "bind"
\\(\\(ignore +\\)?\\<\\(") "\\>\\)") "disable-mnt" "keep-config-pulse" "keep-dev-shm" "keep-var-tmp"
(2 font-lock-keyword-face) "mkdir" "mkfile" "noexec" "private" "private-bin" "private-cache"
("\\<none\\>" nil nil (0 font-lock-keyword-face)))) "private-cwd" "private-dev" "private-etc" "private-home"
(protocol '("^ *\\(\\?A+: +\\)?\ "private-lib" "private-opt" "private-srv" "private-tmp" "read-only"
\\(\\(ignore +\\)?\\<protocol\\>\\)" (2 font-lock-keyword-face) "read-write" "tmpfs" "tracelog" "whitelist" "whitelist-ro"
("\\<unix\\>" nil nil (0 font-lock-keyword-face)) "writable-etc" "writable-run-user" "writable-var"
("\\<inet\\>" nil nil (0 font-lock-keyword-face)) "writable-var-log" "allow-debuggers" "apparmor" "caps" "caps.keep"
("\\<inet6\\>" nil nil (0 font-lock-keyword-face)) "caps.drop" "memory-deny-write-execute" "nonewprivs" "noprinters"
("\\<netlink\\>" nil nil (0 font-lock-keyword-face)) "noroot" "restrict-namespaces" "seccomp" "seccomp.32"
("\\<packet\\>" nil nil (0 font-lock-keyword-face)) "seccomp.drop" "seccomp.32.drop" "seccomp.keep" "seccomp.32.keep"
("\\<bluetooth\\>" nil nil (0 font-lock-keyword-face)))) "seccomp.block-secondary" "protocol" "xephyr-screen"
(variable-rx '("\\${[A-Za-z_]*}" 0 font-lock-variable-name-face)) "dbus-system.own" "dbus-system.talk" "dbus-system.see"
(normal-rx `(,(concat (regexp-opt normal "^ *\\(\\?[A-Z_]+: +\\)?\ "dbus-system.call" "dbus-system.broadcast" "dbus-user.own"
\\(\\(ignore +\\)?\\<\\(") "\\>\\)") . 2))) "dbus-user.talk" "dbus-user.see" "dbus-user.call"
(list comment-rx x11-rx ip-ip6-rx take-all take-none protocol "dbus-user.broadcast" "nodbus" "cpu" "nice" "rlimit-as"
dbus-system-user-rx normal-rx variable-rx "rlimit-cpu" "rlimit-fsize" "rlimit-nproc" "rlimit-nofile"
'("^ *\\(\\?[A-Z_]+: +\\)?\\(\\<ignore\\>\\)" . 2))) "rlimit-sigpending" "timeout" "allusers" "env" "ipc-namespace"
"keep-fd" "name" "no3d" "noautopulse" "nodvd" "nogroups" "noinput"
"nosound" "notv" "nou2f" "novideo" "machine-id" "defaultgw" "dns"
"hostname" "hosts-file" "x11" "dbus-system" "dbus-user" "ip" "ip6"
"iprange" "mac" "mtu" "net" "netfilter" "netfilter" "netlock"
"netmask" "netns" "veth-name" "deterministic-exit-code"
"deterministic-shutdown" "join-or-start"))
(add "caps.drop" "all")
(add '("net" "shell") "none")
(add '("dbus-system" "dbus-user") '("none" "filter"))
(add '("ip" "ip6") '("none" "dhcp"))
(add "x11" '("none" "xephyr" "xorg" "xpra" "xvfb"))
(add-many "restrict-namespaces" '("cgroup" "ipc" "net" "mnt"
"time" "user" "uts"))
(add-many "protocol" '("unix" "inet" "inet6" "netlink"
"packet" "bluetooth"))
(add-many '("caps.drop" "caps.keep")
firejail--known-caps 'font-lock-builtin-face)
(add-many '("seccomp" "seccomp.drop" "seccomp.keep")
firejail--known-syscalls64 'font-lock-builtin-face)
(add-many '("seccomp.32" "seccomp.32.drop" "seccomp.32.keep")
firejail--known-syscalls32 'font-lock-builtin-face)
(add "seccomp-error-action" '("kill" "log"))
(add "seccomp-error-action" firejail--known-errnos
'font-lock-builtin-face)
kwds))
"Highlight keywords for `firejail-profile-mode'.") "Highlight keywords for `firejail-profile-mode'.")
(defvar firejail-profile-syntax-table (defvar firejail-profile-syntax-table
(let ((syn-table (make-syntax-table))) (let ((syn-table (make-syntax-table)))
(modify-syntax-entry ?# "<" syn-table) (modify-syntax-entry ?# "<" syn-table)
(modify-syntax-entry ?\n ">" syn-table) (modify-syntax-entry ?\n ">" syn-table)
(modify-syntax-entry ?\" "." syn-table)
(modify-syntax-entry ?\( "." syn-table)
(modify-syntax-entry ?\) "." syn-table)
(modify-syntax-entry ?\[ "." syn-table)
(modify-syntax-entry ?\] "." syn-table)
syn-table) syn-table)
"Syntax table for `firejail-profile-mode'.") "Syntax table for `firejail-profile-mode'.")
(defconst firejail-profile--keyword-list (defconst firejail-profile--keyword-list
'("quiet" "include" "noblacklist" "nowhitelist" "blacklist" '("ignore" "include" "noblacklist" "nowhitelist" "blacklist" "blacklist-nolog"
"blacklist-nolog" "bind" "disable-mnt" "keep-config-pulse" "bind" "disable-mnt" "keep-config-pulse" "keep-dev-shm" "keep-var-tmp"
"keep-dev-shm" "keep-var-tmp" "mkdir" "mkfile" "noexec" "private" "mkdir" "mkfile" "noexec" "private" "private-bin" "private-cache"
"private-bin" "private-cache" "private-cwd" "private-dev" "private-cwd" "private-dev" "private-etc" "private-home" "private-lib"
"private-etc" "private-home" "private-lib" "private-opt" "private-opt" "private-srv" "private-tmp" "read-only" "read-write" "tmpfs"
"private-srv" "private-tmp" "read-only" "read-write" "tmpfs" "tracelog" "whitelist" "whitelist-ro" "writable-etc" "writable-run-user"
"tracelog" "whitelist" "whitelist-ro" "writable-etc" "writable-var" "writable-var-log" "allow-debuggers" "apparmor" "caps"
"writable-run-user" "writable-var" "writable-var-log" "caps.keep" "caps.drop" "memory-deny-write-execute" "nonewprivs"
"allow-debuggers" "apparmor" "caps" "caps.keep" "caps.drop" "noprinters" "noroot" "restrict-namespaces" "seccomp" "seccomp.32"
"memory-deny-write-execute" "nonewprivs" "noprinters" "noroot" "seccomp.drop" "seccomp.32.drop" "seccomp.keep" "seccomp.32.keep"
"restrict-namespaces" "seccomp" "seccomp.32" "seccomp.drop" "seccomp.block-secondary" "seccomp-error-action" "protocol" "xephyr-screen"
"seccomp.32.drop" "seccomp.keep" "seccomp.32.keep" "protocol" "dbus-system.own" "dbus-system.talk" "dbus-system.see" "dbus-system.call"
"xephyr-screen" "dbus-system.own" "dbus-system.talk" "dbus-system.broadcast" "dbus-user.own" "dbus-user.talk" "dbus-user.see"
"dbus-system.see" "dbus-system.call" "dbus-system.broadcast" "dbus-user.call" "dbus-user.broadcast" "nodbus" "cpu" "nice" "rlimit-as"
"dbus-user.own" "dbus-user.talk" "dbus-user.see" "dbus-user.call"
"dbus-user.broadcast" "nodbus" "cpu" "nice" "rlimit-as"
"rlimit-cpu" "rlimit-fsize" "rlimit-nproc" "rlimit-nofile" "rlimit-cpu" "rlimit-fsize" "rlimit-nproc" "rlimit-nofile"
"rlimit-sigpending" "timeout" "allusers" "env" "ipc-namespace" "rlimit-sigpending" "timeout" "allusers" "env" "ipc-namespace" "keep-fd"
"keep-fd" "name" "no3d" "noautopulse" "nodvd" "nogroups" "noinput" "name" "no3d" "noautopulse" "nodvd" "nogroups" "noinput" "nosound" "notv"
"nosound" "notv" "nou2f" "novideo" "machine-id" "defaultgw" "dns" "nou2f" "novideo" "machine-id" "defaultgw" "dns" "hostname" "hosts-file"
"hostname" "hosts-file" "x11" "dbus-system" "dbus-user" "ip" "ip6" "x11" "dbus-system" "dbus-user" "ip" "ip6" "iprange" "mac" "mtu" "net"
"iprange" "mac" "mtu" "net" "netfilter" "netfilter" "netlock" "netfilter" "netfilter" "netlock" "netmask" "netns" "veth-name"
"netmask" "netns" "veth-name" "deterministic-exit-code" "ignore" "deterministic-exit-code" "deterministic-shutdown" "join-or-start" "net"
"deterministic-shutdown" "join-or-start" "net" "shell" "protocol") "shell" "protocol")
"List of keywords used for `firejail-profile-capf'.") "List of keywords used for `firejail-profile-capf'.")
(defun firejail--symlink-directory-p (symlink)
"Return non-nil if SYMLINK has a directory at the end of its chain."
(file-directory-p (file-truename symlink)))
(defun firejail--collect-includes (&optional relative-to)
"Return a list of files that the user is likely to want to include.
With RELATIVE-TO, return a list of files relative to each directory in it."
(let ((pat (concat "\\." (regexp-opt firejail-include-search-suffixes) "\\'"))
(buffer-file (file-name-nondirectory
(directory-file-name (buffer-file-name)))))
(seq-difference
(mapcan (lambda (dir)
(ignore-error file-missing
(cl-loop for (name type) in (directory-files-and-attributes dir)
when (or (and (eq t type)
(not (member name (list "." ".."))))
(and (stringp type)
(firejail--symlink-directory-p type)))
collect (concat name "/")
when (and (string-match-p pat name)
(not (equal name buffer-file))
(not (auto-save-file-name-p name))
(not (backup-file-name-p name)))
collect name)))
(or (ensure-list relative-to) firejail-include-search-directories))
firejail-include-ignored-files)))
(defun firejail--include-completion-table (current-input)
"Return completion table for file name based on CURRENT-INPUT.
The completion table contains just the last component. Therefore, the capf
should specify the START position of this table to be the first character after
the last slash (/) on the line. If none of that made sense, see the
documentation for `completion-at-point-functions'."
(if-let ((last-slash (cl-position ?/ current-input :from-end t))
(base (file-truename
(substring current-input 0 (1+ last-slash)))))
(let ((default-directory base))
(firejail--collect-includes default-directory))
(firejail--collect-includes)))
(defun firejail--guess-system-cfg-directory ()
"Guess the system config directory.
The return value will have a trailing slash."
(or (cl-find-if 'file-directory-p
'("/etc/firejail/" "/usr/local/etc/firejail/"))
"/etc/firejail/"))
(defun firejail--exec-path ()
"Parse the PATH environment variable.
Return a list of files."
(cl-loop for (dir . rest) = exec-path then rest
while rest ;; ignore last element
collect (file-name-as-directory dir)))
(defun firejail--parse-file-argument (arg)
"Parse ARG by resolving variables.
This will return a list. This is because the PATH variable has many directories
in it."
(if (string-match (rx "${" (group (or "HOME" "CFG" "PATH"
"RUNUSER")) "}" (? "/")) arg)
(let ((var (match-string 1 arg))
(rest (substring arg (match-end 0))))
(cond
((equal var "HOME")
(list (concat (expand-file-name "~/") rest)))
((equal var "CFG")
(list (concat (firejail--guess-system-cfg-directory) rest)))
((equal var "RUNUSER")
(list (concat (file-name-as-directory (getenv "XDG_RUNTIME_DIR"))
rest)))
((equal var "PATH")
(mapcar (lambda (elt)
(concat elt rest))
(firejail--exec-path)))))
(list arg)))
(defun firejail--file-completion-table (current-input &optional dir-only)
"Generate a completion table for files.
CURRENT-INPUT is the current text of the argument to complete. With DIR-ONLY,
only report directory completions."
(ignore-error file-missing
(let ((dir (if-let ((last-idx (cl-position ?/ current-input
:from-end t)))
(substring current-input 0 (1+ last-idx))
current-input)))
(cl-loop for (name type) in (directory-files-and-attributes dir)
when (or (and (eq t type)
(not (member name '("." ".."))))
(and (stringp type)
(firejail--symlink-directory-p type)))
collect (concat name "/")
unless (or type dir-only)
collect name))))
(defun firejail--move-over-string-chars (count)
"Move over COUNT characters, assuming the point is inside a string.
This may move over more than COUNT characters if the string contains escapes."
(cl-loop repeat count
do (cl-loop with read-buf = (string (char-after))
for read-val = (condition-case nil
(read (concat "\"" read-buf "\""))
(end-of-file))
until read-val
do (forward-char) and
do (setq read-buf (concat read-buf (string
(char-after))))
finally (forward-char)
finally return read-val)))
(defun firejail--complete-file-from-table (table-fn index args)
"Complete INDEX of ARGS using TABLE-FN.
TABLE-FN should be a function of one argument that takes the current arg and
returns a completion table for it."
(cl-destructuring-bind (start _end text) (nth index args)
(let* ((base (or (file-name-directory text) ""))
(table (funcall table-fn base)))
(list (+ start (length base)) (+ start (length text)) table))))
(defun firejail--complete-include (index args _directive)
"Complete an include directive's arg numbered INDEX of ARGS."
(firejail--complete-file-from-table #'firejail--include-completion-table
index args))
(defun firejail--complete-file (index args _directive)
"Complete file taking directive's arg numbered INDEX of ARGS."
(firejail--complete-file-from-table #'firejail--file-completion-table
index args))
(defun firejail--complete-directory (index args _directive)
"Complete directory taking directive's arg numbered INDEX of ARGS."
(firejail--complete-file-from-table #'(lambda (base)
(firejail--file-completion-table
base 'dironly))
index args))
(defvar-local firejail--relative-to-cache nil
"Cache for `firejail--complete-relative-to'.")
(defmacro firejail--complete-relative-to (dirs &optional no-absolute)
"Return a function that completes relative to DIRS.
With NO-ABSOLUTE, don't complete absolute file names."
(let ((index (make-symbol "index"))
(args (make-symbol "args"))
(directive (make-symbol "directive"))
(out (make-symbol "out"))
(idirs (make-symbol "dirs"))
(dir (make-symbol "dir"))
(adirname (make-symbol "adirname"))
(evaled-dirs (eval dirs t)))
`(lambda (,index ,args ,directive)
(unless firejail--relative-to-cache
(setq firejail--relative-to-cache (make-hash-table :test 'equal)))
(let ((,idirs (cl-remove-if-not #'file-directory-p
(ensure-list ',evaled-dirs)))
(,adirname (file-name-directory (cl-third (nth ,index ,args)))))
(if-let ((cache (gethash (cons ,adirname ,dirs)
firejail--relative-to-cache)))
cache
(let (,out)
(dolist (,dir ,idirs)
,(let ((stmt
`(let ((default-directory ,dir))
(push (firejail--complete-file ,index ,args
,directive)
,out))))
(if no-absolute
`(unless (file-name-absolute-p
(cl-third (nth ,index ,args)))
,stmt)
stmt)))
(puthash (cons ,adirname ,idirs)
(append (seq-take (car ,out) 2)
(list (seq-uniq (mapcan 'cl-third ,out))))
firejail--relative-to-cache)))))))
(defmacro firejail--complete-many-from-set (vals)
"Return a function to complete a multi-arg directive from VALS."
(let ((index (make-symbol "index"))
(args (make-symbol "args"))
(directive (make-symbol "directive"))
(i (make-symbol "i"))
(arg (make-symbol "arg"))
(present (make-symbol "present"))
(evaled-vals (eval vals t)))
`(lambda (,index ,args ,directive)
(let ((,present (cl-loop for ,i upfrom 0
for ,arg in ,args
unless (= ,i ,index)
collect (cl-third ,arg))))
(append (seq-take (nth ,index ,args) 2)
(list (seq-difference ,evaled-vals ,present)))))))
(defun firejail--get-all-env-keys ()
"Return the name of every current environment variable."
(mapcar (lambda (elt)
(if-let ((sep (cl-position ?= elt)))
(substring elt 0 sep)
elt))
process-environment))
(defun firejail--complete-env (index args _directive)
"Complete the arg numbered INDEX in ARGS for an \"env\" directive."
(cl-destructuring-bind (start _end text) (nth index args)
(let ((sep-pos (or (cl-position ?= text) (length text))))
(when (<= (point) (+ start sep-pos))
(list start (+ start sep-pos) (firejail--get-all-env-keys))))))
(defconst firejail-profile--keyword-handlers
(let ((ht (make-hash-table :test 'equal)))
(cl-flet* ((complete (args fun dirs)
(dolist (arg (ensure-list (or args (list nil))))
(dolist (dir (ensure-list dirs))
(puthash (cons dir arg) fun ht))))
(complete-all (fun dirs)
(complete nil fun dirs)))
(complete 1 #'firejail--complete-include "include")
(complete 1 #'firejail--complete-file
'("whitelist" "nowhitelist" "blacklist" "noblacklist"
"blacklist-nolog" "noexec" "read-only" "read-write"
"whitelist-ro" "hosts-file"))
(complete 1 #'firejail--complete-directory
'("mkdir" "mkfile" "private" "private-cwd" "tmpfs"))
(complete '(1 2) #'firejail--complete-file "bind")
(complete-all (firejail--complete-relative-to
'("/bin" "/sbin" "/usr/bin" "/usr/sbin" "/usr/local/bin")
t)
"private-bin")
(complete-all (firejail--complete-relative-to '(getenv "HOME") t)
"private-home")
(complete-all (firejail--complete-relative-to "/lib" t)
"private-lib")
(complete-all (firejail--complete-relative-to "/etc" t)
"private-etc")
(complete-all (firejail--complete-relative-to "/opt" t)
"private-opt")
(complete-all (firejail--complete-relative-to "/srv" t)
"private-srv")
(complete-all (firejail--complete-many-from-set
;; evaluate at runtime
'firejail--known-caps)
"caps.keep")
(complete-all (firejail--complete-many-from-set
;; evaluate at runtime
'(cons "all" firejail--known-caps))
"caps.drop")
(complete-all (firejail--complete-many-from-set
''("unix" "inet" "inet6" "netlink" "packet" "bluetooth"))
"protocol")
(complete-all (firejail--complete-many-from-set
''("cgroup" "ipc" "mnt" "pid" "time" "user" "uts"))
"restrict-namespaces")
(complete-all (firejail--complete-many-from-set
'firejail--known-syscalls64)
'("seccomp" "seccomp.drop" "seccomp.keep" ))
(complete-all (firejail--complete-many-from-set
'firejail--known-syscalls32)
'("seccomp.32" "seccomp.32.drop" "seccomp.32.keep"))
(complete 1 (firejail--complete-many-from-set
'(firejail--list-dbus-services :system))
'("dbus-system" "dbus-system.own" "dbus-system.talk"
"dbus-system.see"))
(complete 1 (firejail--complete-many-from-set
'(firejail--list-dbus-services :session))
'("dbus-user" "dbus-user.own" "dbus-user.talk" "dbus-user.see"))
(complete 1 (firejail--complete-many-from-set
'(append '("kill" "log") firejail--known-errnos))
"seccomp-error-action")
(complete 1 (firejail--complete-many-from-set
''("none" "xephyr" "xorg" "xpra" "xvfb"))
"x11")
(complete 1 (firejail--complete-many-from-set
''("none" "filter"))
'("dbus-system" "dbus-user"))
(complete 1 (firejail--complete-many-from-set
''("none" "dhcp"))
'("ip" "ip6"))
(complete 1 (firejail--complete-many-from-set
''("none"))
'("net" "shell"))
(complete-all (firejail--complete-many-from-set
'(mapcar 'number-to-string
(number-sequence 0 (firejail--get-num-cpus))))
"cpu")
(complete 1 #'firejail--complete-env "env"))
ht)
"Hash table mapping firejail profile directives to their handler.
Each handler is a function of three arguments. The first is the index of the
current argument, the second is a list of the arguments, the third the
directive. These functions mustn't move the point. The point will be on the
first character of the argument. The keys of this table are a cons of a
directive and its argument number. The values are the completion functions.")
(defun firejail--quiet-allowed-p ()
"Return non-nil if the \"quiet\" directive is allowed on line under point."
(save-excursion
(let ((orig-line (line-number-at-pos)))
(goto-char (point-min))
(while (forward-comment 1))
(>= (line-number-at-pos) orig-line))))
(defun firejail--ignored-line-p ()
"Return non-nil if the line under point is an \"ignore\" directive.
Actually, return the position of the first character of the \"real\" directive."
(save-excursion
(beginning-of-line)
(when (looking-at (rx bol (* space) (? "?" (* (any alnum "_")) (? ":"))
(+ (* space) "ignore" eow) (or eol (+ space))
(group (* nonl)) eol))
(match-beginning 1))))
(defun firejail--read-next-arg ()
"Return the bounds of the next argument from the buffer starting at point.
This returns a list of four things, the first two are the start and end of the
current argument. The third is the text of the argument."
(skip-syntax-forward "-")
(looking-at (rx (group (* (not (any "\n" "," "#"))))
(* space) (or eol "," "#")))
(goto-char (match-end 0))
(when (eql ?# (char-before))
(backward-char))
(list (match-beginning 1) (match-end 1)
(match-string-no-properties 1)))
(defun firejail--has-more-args-p ()
"Return non-nil if there are probably more args beyond point on this line."
(save-excursion
(skip-syntax-forward "-")
(not (or (eobp) (memql (char-after) '(?\n ?#))))))
(defun firejail--multi-arg-directive-p (name)
"Return non-nil if NAME is a multi-argument directive."
(member name '("bind" "private-bin" "private-etc" "private-home"
"private-lib" "private-opt" "private-srv" "caps.drop"
"caps.keep" "protocol" "restrict-namespaces"
"seccomp" "seccomp.32" "seccomp.drop" "seccomp.32.drop"
"seccomp.keep" "seccomp.32.keep" "cpu" "iprange")))
(defun firejail--current-args (dir arg-start)
"Return a list of the text of each argument in the directive DIR under point.
ARG-START is the first character of the list of arguments."
(if (firejail--multi-arg-directive-p dir)
(append (save-excursion
(goto-char arg-start)
(cl-loop while (firejail--has-more-args-p)
collect (firejail--read-next-arg)))
(list (list (point) (point) "")))
(save-excursion
(goto-char arg-start)
(skip-syntax-forward "-")
(let ((eol (pos-eol)))
(list (list (point) eol
(buffer-substring-no-properties
(point) eol)))))))
(defun firejail--count-args (start end)
"Return the number of arguments between START and END."
(1+ (how-many "," start end)))
(defun firejail--complete-arguments (directive arg-start)
"Generate completions for the argument that the point is currently in.
DIRECTIVE is the directive to generate completions for. ARG-START is the first
argument character on the current line."
(let* ((cur-arg (if (firejail--multi-arg-directive-p directive)
(firejail--count-args arg-start (point))
1)))
(when-let ((handler (or (gethash (cons directive nil)
firejail-profile--keyword-handlers)
(gethash (cons directive cur-arg)
firejail-profile--keyword-handlers))))
(funcall handler (1- cur-arg)
(firejail--current-args directive arg-start)
directive))))
(defun firejail--line-conditional-p ()
"Return non-nil if the line under point begins with a conditional.
Actually, return a list of its bounds and the bounds of its name."
(save-excursion
(beginning-of-line)
(skip-syntax-forward "-")
(when (looking-at (rx (group "?" (group (* (any "_" alnum))) (? ":"))
(or eol (+ space) "#")))
(list (match-beginning 1) (match-end 1) (match-beginning 2)
(match-end 2)))))
(defun firejail--complete-conditional (start end)
"Complete the conditional around point.
START and END are the bounds of the name of the conditional."
(list start end '()))
(defun firejail-profile-capf () (defun firejail-profile-capf ()
"Complete the firejail profile directive at point." "Complete the Firejail profile directive at point."
(if-let ((word-bounds (bounds-of-thing-at-point 'word))) (save-excursion
(cl-loop for kwd in firejail-profile--keyword-list ;; don't complete comments
with word-at-point = (buffer-substring-no-properties (unless (nth 4 (syntax-ppss (point)))
(car word-bounds) (let ((start-pos (point)))
(cdr word-bounds)) (back-to-indentation)
when (string-prefix-p word-at-point kwd) (let ((condition (firejail--line-conditional-p))
collect kwd into candidates (ignored (firejail--ignored-line-p)))
finally return (list (car word-bounds) (if (and condition (>= start-pos (cl-first condition))
(cdr word-bounds) (<= start-pos (cl-second condition)))
candidates)) (list (cl-third condition) (cl-fourth condition)
(list (point) ;; is there already a '?'
(point) (if (= (cl-second condition) (cl-fourth condition))
firejail-profile--keyword-list))) (mapcar (lambda (elt)
(concat elt ":"))
firejail--known-conditionals)
firejail--known-conditionals))
(cond
(ignored (goto-char ignored))
(condition
(goto-char (1+ (cl-second condition)))
(skip-syntax-forward "-")))
;; read the directive name
(looking-at (rx (group (* (not (any space "#" "\n"))))
(? (group space))))
(let ((directive-start (match-beginning 1))
(directive-end (match-end 1))
(arg-start (match-end 2)))
(if (and arg-start (>= start-pos arg-start))
(progn
(goto-char start-pos)
(firejail--complete-arguments
(buffer-substring-no-properties directive-start
directive-end)
arg-start))
(cond
((= directive-start directive-end)
(setq directive-start start-pos
directive-end start-pos))
((and (< start-pos directive-start)
(eql 2 (syntax-class (syntax-after (1- start-pos)))))
(save-excursion
(goto-char start-pos)
(forward-word -1)
(setq directive-start (point)
directive-end start-pos)))
((< start-pos directive-start)
(setq directive-start start-pos
directive-end start-pos)))
(list
directive-start directive-end
(append (when (and (not condition) (not ignored)
(firejail--quiet-allowed-p))
'("quiet"))
firejail-profile--keyword-list))))))))))
(defun firejail--directive-at-point ()
"Return the name of the directive at point."
(save-excursion
(beginning-of-line)
(when (looking-at (rx bol (* space)
(? "?" (* (any alnum "_")) (? ":")
(+ space))
(* "ignore" (+ space))
(group (+ (not (any space "\n" "#"))))))
(let ((name (match-string-no-properties 1)))
(unless (or (equal name "ignore")
(string-prefix-p "?" name)
(string-suffix-p ":" name))
name)))))
(defun firejail--read-next-sentence ()
"Return from point up to the next sentance end."
(let ((start (point))
(end (or (re-search-forward (rx eow "." (or " " eol))
nil t)
(point-max))))
(when (eql (char-before end) ? )
(cl-decf end)
(backward-char))
(cl-substitute ? ?\n (buffer-substring-no-properties
start end))))
(defun firejail--format-doc-string-and-get-summary (dir doc)
"Format DOC and get a summary for DIR.
Return a list of the formatted doc and a summary."
(with-temp-buffer
(insert doc)
(goto-char (point-min))
(forward-line)
(let ((summary (save-excursion
(firejail--read-next-sentence))))
(cl-loop for start = (point)
until (eobp) do
(forward-paragraph)
(fill-region-as-paragraph start (point))
(forward-line)
when (looking-at-p (rx bol (literal dir) (or eol " ")))
do (forward-line))
(goto-char (point-min))
(replace-regexp-in-region (rx (>= 3 "\n")) "\n\n")
(replace-regexp-in-region (rx eow "." (+ blank)) ". ")
(while (re-search-forward (rx ":" eol) nil t)
(forward-line)
(while (and (not (eobp))
(not (char-uppercase-p (char-after))))
(if (= (pos-bol) (pos-eol))
(delete-char 1)
(insert " ")
(forward-line)))
(unless (eobp)
(insert "\n")))
(list (buffer-string) summary))))
(defun firejail-eldoc-documentation-function (callback &rest _args)
"Call CALLBACK with the documentation of the directive under point."
(save-excursion
(when-let ((name (firejail--directive-at-point))
(doc (firejail--documentation-for name)))
(cl-destructuring-bind (clean-doc summary)
(firejail--format-doc-string-and-get-summary name doc)
(funcall callback clean-doc `(:thing ,name
:echo ,summary))))))
(defvar-keymap firejail-profile-mode-map
:doc "Keymap for `firejail-profile-mode'."
:parent prog-mode-map
"C-c C-o" #'ff-find-other-file)
(define-derived-mode firejail-profile-mode prog-mode "Firejail-Profile" (define-derived-mode firejail-profile-mode prog-mode "Firejail-Profile"
"Major mode for editing firejail profiles." "Major mode for editing firejail profiles."
(add-to-list (make-local-variable 'completion-at-point-functions) :group 'firejail-mode
#'firejail-profile-capf) :syntax-table firejail-profile-syntax-table
(add-hook 'completion-at-point-functions #'firejail-profile-capf nil t)
(setq-local font-lock-defaults '(firejail-profile-font-lock-keywords) (setq-local font-lock-defaults '(firejail-profile-font-lock-keywords)
comment-start "#" comment-start "#"
comment-end "") comment-end ""
(set-syntax-table firejail-profile-syntax-table)) electric-pair-pairs '((?{ . ?}))
ff-search-directories firejail-include-search-directories
ff-other-file-alist '(("\\.local\\'" (".profile"))
("\\.profile\\'" (".local")))
eldoc-documentation-functions
'(firejail-eldoc-documentation-function
t)))
(add-to-list 'auto-mode-alist (add-to-list 'auto-mode-alist
'("\\.\\(firejail\\|profile\\|local\\)$" . firejail-profile-mode)) '("\\.\\(firejail\\|profile\\|local\\|inc\\)\\'" . firejail-profile-mode))
(provide 'firejail-mode) (provide 'firejail-mode)
;;; firejail-mode.el ends here ;;; firejail-mode.el ends here
;; Local Variables:
;; jinx-local-words: "Firejail Firejail's"
;; End:

499
elisp/inferior-cc.el Normal file
View File

@ -0,0 +1,499 @@
;;; inferior-cc.el --- Run interpreters for cc-mode languages -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'comint)
(require 'cl-lib)
(require 'cc-mode)
(require 'treesit)
(require 'shell)
(eval-when-compile (require 'rx))
(defgroup inferior-cc ()
"Run interpreters for `cc-mode' languages."
:group 'comint)
(defclass inferior-cc-interpreter ()
((name :type string
:initarg :name
:accessor inf-cc-name
:doc "The name of this interpreter.")
(command :type string
:initarg :command
:accessor inf-cc-command
:doc "The command (program) for this interpreter.")
(args :type (list-of string)
:initarg :args
:accessor inf-cc-args
:initform nil
:doc "Command-line arguments to pass to the interpreter.")
(font-lock-mode :type (or null function)
:initarg :font-lock-mode
:accessor inf-cc-font-lock-mode
:initform nil
:doc "Major mode to use for font locking of the interpreter's
input. A value of nil means don't do font locking.")
(modes :type (list-of function)
:initarg :modes
:accessor inf-cc-modes
:initform nil
:doc "The major modes that this interpreter corresponds to.")
(exp-at-point-func :type (or function null)
:initarg :exp-at-point-func
:accessor inf-cc-exp-at-point-func
:initform nil
:doc "Function to retrieve the expression at point for
languages supported by this interpreter."))
(:documentation "An interpreter for a `cc-mode'-like language."))
(define-widget 'inferior-cc-interpreter 'lazy
"Interpreter for `cc-mode'-like languages."
:offset 4
:tag "Interpreter"
:type '(list (string :tag "Name")
(repeat :tag "Command line" (string :tag "Argument"))
(choice :tag "Font lock mode"
(function :tag "Major mode")
(const :tag "None" nil))
(repeat :tag "Major modes" (function :tag "Major mode"))
(choice :tag "Expression at point function"
(function :tag "Function")
(const :tag "None" nil))))
(defun inf-cc--interpreter-list-to-obj (list)
"Return LIST as a proper `inferior-cc-interpreter' object."
(cl-destructuring-bind (name (command &rest args) font-lock-mode modes
exp-at-point-func)
list
(inferior-cc-interpreter :name name :command command
:args args :font-lock-mode font-lock-mode
:modes modes :exp-at-point-func exp-at-point-func)))
(defun inf-cc--interpreter-obj-to-list (obj)
"Return OBJ, a proper `inferior-cc-interpreter', object as a list."
(with-slots (name command args font-lock-mode modes exp-at-point-func) obj
(list name (cons command args) font-lock-mode modes exp-at-point-func)))
(defun inf-cc--remove-trailing-semicolon (str)
"Remove a trailing semicolon and whitespace from STR."
(if (string-match (rx (* (syntax whitespace))
";"
(* (syntax whitespace)) eos)
str)
(substring str 0 (match-beginning 0))
str))
(defun inf-cc--remove-surrounding-parens (str)
"Remove surrounding parenthesis from STR."
(if (string-match (rx bos (* (syntax whitespace)) "("
(group (* any))
")" (* (syntax whitespace)) eos)
str)
(match-string 1 str)
str))
(defun inf-cc--c-c++-ts-exp-at-point ()
"Return the expression at point in `c-ts-mode' and `c++-ts-mode' buffers."
(unless (or (derived-mode-p 'c-ts-mode 'c++-ts-mode))
(user-error "Major mode does not support find expressions: %s" major-mode))
(save-excursion
(let ((start (point)))
(back-to-indentation)
(unless (> (point) start)
(goto-char start)))
(when-let ((thing (treesit-thing-at-point "_" 'nested)))
(inf-cc--remove-trailing-semicolon (treesit-node-text thing)))))
(defun inf-cc--java-ts-exp-at-point ()
"Return the expression at point in `java-ts-mode' buffers."
(unless (or (derived-mode-p 'java-ts-mode))
(user-error "Major mode does not support find expressions: %s" major-mode))
(save-excursion
(let ((start (point)))
(back-to-indentation)
(unless (> (point) start)
(goto-char start)))
(let ((root (treesit-buffer-root-node)))
(let ((node (car (or (treesit-query-range
root '([(expression_statement)
(field_declaration)
(local_variable_declaration)
(import_declaration)]
@exp)
(point) (1+ (point)))
(treesit-query-range
root '([(parenthesized_expression)
(binary_expression)
(update_expression)
(unary_expression)]
@exp)
(point) (1+ (point)))))))
(inf-cc--remove-surrounding-parens
(inf-cc--remove-trailing-semicolon
(buffer-substring-no-properties (car node) (cdr node))))))))
(defcustom inferior-cc-interpreters
(list (inferior-cc-interpreter :name "jshell"
:command "jshell"
:font-lock-mode 'java-mode
:modes '(java-mode java-ts-mode)
:exp-at-point-func
'inf-cc--java-ts-exp-at-point)
(inferior-cc-interpreter :name "root"
:command "root"
:font-lock-mode 'c++-mode
:modes '(c-mode c-ts-mode c++-mode c++-ts-mode)
:exp-at-point-func
'inf-cc--c-c++-ts-exp-at-point))
"List of inferior-cc interpreters."
:type '(repeat inferior-cc-interpreter)
:get (lambda (sym)
(mapcar 'inf-cc--interpreter-obj-to-list (default-toplevel-value sym)))
:set (lambda (sym newval)
(set-default-toplevel-value
sym (mapcar #'(lambda (elt)
(if (inferior-cc-interpreter-p elt)
elt
(inf-cc--interpreter-list-to-obj elt)))
newval)))
:group 'inferior-cc)
(defvar-local inf-cc--obj nil
"The current buffer's interpreter object.")
(put 'inf-cc--obj 'permanent-local t)
(defvar-local inf-cc--fontification-buffer nil
"The fontification buffer for the current buffer.")
(defvar-local inf-cc--skip-next-lines 0
"Number of lines of output to skip.")
(defun inf-cc--preoutput-filter-function (output)
"Preoutput filter function for inferior cc buffers.
OUTPUT is the new text to be inserted."
(if (<= inf-cc--skip-next-lines 0)
output
(let* ((lines (string-lines output))
(cnt (length lines)))
(if (> cnt inf-cc--skip-next-lines)
(prog1
(string-join (nthcdr inf-cc--skip-next-lines lines) "\n")
(setq inf-cc--skip-next-lines 0))
(cl-decf inf-cc--skip-next-lines cnt)
(when (and (not (string-empty-p output))
(/= ?\n (elt output (1- (length output)))))
(cl-incf inf-cc--skip-next-lines))
""))))
(defun inf-cc--get-fontification-buffer ()
"Return or create the current buffer's fontification buffer."
(if (buffer-live-p inf-cc--fontification-buffer)
inf-cc--fontification-buffer
(let ((buffer (generate-new-buffer
(format " %s-fontification-buffer" (buffer-name))))
(obj inf-cc--obj))
(with-current-buffer buffer
(setq-local inf-cc--obj obj)
(unless (and (inf-cc-font-lock-mode inf-cc--obj)
(derived-mode-p (inf-cc-font-lock-mode inf-cc--obj)))
(let ((delayed-mode-hooks nil))
(delay-mode-hooks
(funcall (inf-cc-font-lock-mode inf-cc--obj)))))
(when (eq c-basic-offset 'set-from-style)
(setq-local c-basic-offset standard-indent))
(let ((inhibit-message t))
(indent-tabs-mode -1))
(unless font-lock-mode
(font-lock-mode 1)))
(setq-local inf-cc--fontification-buffer buffer))))
(defmacro inf-cc--with-font-lock-buffer (&rest body)
"Execute BODY in the current buffer's fortification buffer.
Note that this erases the buffer before doing anything."
`(with-current-buffer (inf-cc--get-fontification-buffer)
(erase-buffer)
,@body))
(defun inf-cc--fontify-current-input ()
"Function called from `post-command-hook' to fontify the current input."
(when-let (((inf-cc-font-lock-mode inf-cc--obj))
(proc (get-buffer-process (current-buffer)))
(start (process-mark proc))
(end (point-max))
(input (buffer-substring-no-properties start end))
(fontified (inf-cc--with-font-lock-buffer
(insert input)
(font-lock-ensure)
(buffer-string)))
(len (length fontified))
(i 0))
;; mostly from:
;; `python-shell-font-lock-post-command-hook'
(while (not (= i len))
(let* ((props (text-properties-at i fontified))
(change-i (or (next-property-change i fontified)
len)))
(when-let ((face (plist-get props 'face)))
(setf (plist-get props 'face) nil
(plist-get props 'font-lock-face) face))
(set-text-properties (+ start i) (+ start change-i) props)
(setq i change-i)))))
(defun inf-cc--bounds-of-last-prompt ()
"Return the bounds of the last prompt.
This returns a cons."
(save-excursion
(let ((end (process-mark (get-buffer-process (current-buffer)))))
(goto-char end)
(cons (pos-bol) end))))
(defun inf-cc--remove-extra-indentation (count)
"Remove COUNT spaces from the start of each line."
(save-excursion
(goto-char (point-min))
(while (not (eobp))
(back-to-indentation)
(let ((indent (- (point) (pos-bol))))
(when (> indent count)
(delete-char (- count))))
(forward-line))))
(defun inf-cc--indent-line-function ()
"`indent-line-function' for inferior cc comint buffers."
(when (inf-cc-font-lock-mode inf-cc--obj)
(let* ((start (process-mark (get-buffer-process (current-buffer)))))
;; don't indent the first line
(unless (= (pos-bol) (save-excursion (goto-char start) (pos-bol)))
(let* ((input (buffer-substring-no-properties start (pos-eol)))
(prompt-size (let ((bound (inf-cc--bounds-of-last-prompt)))
(- (cdr bound) (car bound))))
(col (inf-cc--with-font-lock-buffer
(insert input)
(inf-cc--remove-extra-indentation prompt-size)
(c-indent-line nil t)
(back-to-indentation)
(- (point) (pos-bol)))))
(save-excursion
(indent-line-to (+ prompt-size col)))
(skip-syntax-forward "-"))))))
(defun inferior-cc-send-input ()
"Like `comint-send-input', but with some extra stuff for inferior cc."
(interactive)
(let ((pmark (process-mark (get-buffer-process (current-buffer))))
(end (if comint-eol-on-send (pos-eol) (point))))
(with-restriction pmark end
(let ((res (syntax-ppss (point-max))))
(without-restriction
(cond
;; open string
((cl-fourth res)
(message "Unterminated string"))
;; unmatched blocks or comment
((or (numberp (cl-fifth res))
(not (zerop (cl-first res)))
;; trailing . character
(save-excursion
(end-of-line)
(skip-syntax-backward "-")
(eql (char-before) ?.)))
(newline-and-indent))
(t
;; ignore the interpreter echoing back our lines
(setq-local inf-cc--skip-next-lines (count-lines pmark end))
(when (= pmark end)
(cl-incf inf-cc--skip-next-lines))
;; also, methods add a bunch of extra newlines
(when (>= inf-cc--skip-next-lines 2)
(cl-incf inf-cc--skip-next-lines (- inf-cc--skip-next-lines 2)))
(comint-send-input))))))))
(defvar-keymap inferior-cc-shell-mode-map
:doc "Keymap for `inferior-cc-shell-mode'."
:parent comint-mode-map
"RET" #'inferior-cc-send-input)
(defun inf-cc--kill-fontification-buffer ()
"Kill the current `inf-cc--fontification-buffer'."
(ignore-errors
(kill-buffer inf-cc--fontification-buffer)))
(define-derived-mode inferior-cc-shell-mode comint-mode ""
"Major mode for buffers running inferior cc interpreters.
You MUST set `inf-cc--obj' before activating this major mode."
:interactive nil
:group 'inferior-jshell
:syntax-table nil
(with-slots (name font-lock-mode) inf-cc--obj
(setq-local comint-highlight-input nil
indent-line-function #'inf-cc--indent-line-function
electric-indent-chars '(?\n ?})
mode-name (concat "Inferior " (upcase-initials name)))
(when-let ((font-lock-mode)
(sym (intern-soft (format "%s-syntax-table" font-lock-mode)))
(syntax-table (symbol-value sym)))
(set-syntax-table syntax-table)))
(add-hook 'comint-preoutput-filter-functions
#'inf-cc--preoutput-filter-function
nil t)
(add-hook 'post-command-hook
#'inf-cc--fontify-current-input
nil t)
(add-hook 'kill-buffer-hook
#'inf-cc--kill-fontification-buffer
nil t))
(cl-defun inf-cc--find-buffer ()
"Find and return a live inferior cc buffer for the current major mode."
(let ((target-mode major-mode))
(dolist (buffer (buffer-list))
(with-current-buffer buffer
(when (and (process-live-p (get-buffer-process buffer))
inf-cc--obj
(member target-mode (inf-cc-modes inf-cc--obj)))
(cl-return-from inf-cc--find-buffer buffer))))))
(defun inferior-cc-eval (code)
"Evaluate CODE in a live inferior cc buffer."
(interactive "sEval: " inferior-cc-shell-mode)
(let ((buffer (inf-cc--find-buffer)))
(unless buffer
(user-error "No live inferior cc buffer found"))
(with-current-buffer buffer
(let* ((start (process-mark (get-buffer-process buffer)))
(end (point-max))
(old (buffer-substring-no-properties start end)))
(delete-region start end)
(goto-char (point-max))
(insert code)
(goto-char (point-max))
;; don't save history
(let ((comint-input-filter #'ignore))
(inferior-cc-send-input))
(goto-char (point-max))
(insert old)
(goto-char (point-max))))))
(defun inferior-cc-eval-region (start end)
"Evaluate the current buffer from START to END in a live inferior cc buffer.
START and END default to the current region."
(interactive "r" inferior-cc-shell-mode)
(inferior-cc-eval (buffer-substring-no-properties start end))
(message "Evaluated %s lines" (count-lines start end)))
(defun inferior-cc-eval-buffer ()
"Send the current buffer to a live inferior cc buffer."
(interactive nil inferior-cc-shell-mode)
(inferior-cc-eval-region (point-min) (point-max))
(message "Evaluated buffer %s" (current-buffer)))
(defun inferior-cc-eval-defun ()
"Send the defun under point to a live inferior cc buffer."
(interactive nil inferior-cc-shell-mode)
(let ((bounds (bounds-of-thing-at-point 'defun)))
(unless bounds
(user-error "No defun under point"))
(inferior-cc-eval-region (car bounds) (cdr bounds))
(message "Evaluated defun (%s lines)" (count-lines (car bounds)
(cdr bounds)))))
(defun inferior-cc-eval-line ()
"Send the line under point to a live inferior cc buffer."
(interactive nil inferior-cc-shell-mode)
(inferior-cc-eval-region (pos-bol) (pos-eol))
(message "Evaluated %s" (buffer-substring (pos-bol) (pos-eol))))
(defun inferior-cc-eval-expression ()
"Evaluate the expression under point in a live inferior cc buffer.
This only works in modes that have defined an \\=:exp-at-point-func."
(interactive nil inferior-cc-shell-mode)
(let ((obj (inf-cc--find-interpreter-for-mode)))
(unless obj
(user-error "Cannot get expression for major mode: %s" major-mode))
(with-slots ((func exp-at-point-func)) obj
(unless func
(user-error "Cannot get expression for major mode: %s" major-mode))
(let ((code (funcall func)))
(unless code
(user-error "No expression under point"))
(inferior-cc-eval code)
(message "Evaluated expression (%s lines)"
(1+ (cl-count ?\n code)))))))
(defun inf-cc--find-interpreter-for-mode (&optional mode)
"Find a suitable interpreter for MODE, defaulting to `major-mode'."
(unless mode (setq mode major-mode))
(cl-find-if (lambda (elt)
(with-slots (modes) elt
(member mode modes)))
inferior-cc-interpreters))
(defun inf-cc--interpreter-by-name (name)
"Find the interpreter named NAME."
(cl-find-if (lambda (elt)
(equal (inf-cc-name elt) name))
inferior-cc-interpreters))
(defun inf-cc--prompt-for-interpreter ()
"Prompt for an inferior cc interpreter."
(inf-cc--interpreter-by-name
(completing-read "Interpreter: "
(mapcar 'inf-cc-name inferior-cc-interpreters) nil t)))
(defun inf-cc--prompt-for-command (int)
"Prompt for a command line for INT."
(with-slots (command args) int
(let* ((def-cmd (string-join (mapcar 'shell-quote-argument
(cons command args))
" "))
(choice (read-shell-command "Command: " def-cmd)))
(split-string-shell-command choice))))
(defun run-cc-interpreter (int &optional command)
"Run the `cc-mode'-like interpreter INT.
Interactively, INT will be an interpreter suitable for the current
`major-mode'. With a prefix argument, prompt for an interpreter.
If COMMAND is non-nil, it should be a list with the first element being the
program to execute and the rest of the elements being the arguments to pass to
the interpreter. This overrides the default settings in INT. Interactively,
prompt for COMMAND with two prefix arguments."
(interactive (let ((int (if current-prefix-arg
(inf-cc--prompt-for-interpreter)
(or (inf-cc--find-interpreter-for-mode)
(inf-cc--prompt-for-interpreter)))))
(list int
(when (>= (prefix-numeric-value current-prefix-arg) 16)
(inf-cc--prompt-for-command int)))))
(with-slots (name (def-cmd command) args) int
(unless command
(setq command (cons def-cmd args)))
(pop-to-buffer
(with-current-buffer (get-buffer-create (format "*%s*" name))
(prog1 (current-buffer)
(unless (process-live-p (get-buffer-process (current-buffer)))
(setq-local inf-cc--obj int)
(inferior-cc-shell-mode)
(comint-exec (current-buffer)
(format "Inferior %s" (upcase-initials name))
(car command) nil (cdr command))))))))
(defun run-jshell (command)
"Run JShell in a comint buffer.
COMMAND is the same as for `run-cc-interpreter', except that any prefix arg
causes the user to be prompted."
(interactive (list (when current-prefix-arg
(inf-cc--prompt-for-command
(inf-cc--interpreter-by-name "jshell")))))
(run-cc-interpreter (inf-cc--interpreter-by-name "jshell") command))
(defun run-root (command)
"Run CERN root in a comint buffer.
COMMAND is the same as for `run-cc-interpreter', except that any prefix arg
causes the user to be prompted."
(interactive (list (when current-prefix-arg
(inf-cc--prompt-for-command
(inf-cc--interpreter-by-name "root")))))
(run-cc-interpreter (inf-cc--interpreter-by-name "root") command))
(provide 'inferior-cc)
;;; inferior-cc.el ends here

View File

@ -225,7 +225,7 @@ The following are some examples:
(looking-at (rx "\\" (looking-at (rx "\\"
(group (+ (not (any " " "\n" "(" (group (+ (not (any " " "\n" "("
"{" "[" "|" "{" "[" "|"
"}" "]" ")"))))))) "}" "]" ")" "%")))))))
(let ((cmd (match-string-no-properties 1))) (let ((cmd (match-string-no-properties 1)))
(if (> (match-end 1) orig-point) (if (> (match-end 1) orig-point)
(cons cmd 'command) (cons cmd 'command)

Binary file not shown.

1863
init.el

File diff suppressed because it is too large Load Diff

View File

@ -29,10 +29,10 @@
(defvar kdl-ts-mode--syntax-table (defvar kdl-ts-mode--syntax-table
(let ((table (make-syntax-table))) (let ((table (make-syntax-table)))
(modify-syntax-entry ?= ".") (modify-syntax-entry ?= "." table)
(modify-syntax-entry ?/ ". 124") (modify-syntax-entry ?/ ". 124" table)
(modify-syntax-entry ?* ". 23b") (modify-syntax-entry ?* ". 23b" table)
(modify-syntax-entry ?\n ">") (modify-syntax-entry ?\n ">" table)
table) table)
"Syntax table for `kdl-ts-mode'.") "Syntax table for `kdl-ts-mode'.")