Compare commits
135 Commits
6708db3bdc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
114553abb1
|
|||
|
bf53c657a8
|
|||
|
00b905fad1
|
|||
|
8191a92a8c
|
|||
|
64d229236f
|
|||
|
b70db29a1a
|
|||
|
a310e12771
|
|||
|
12d8cc0e3e
|
|||
|
a0c7697440
|
|||
|
2367b15a9c
|
|||
|
18b804680d
|
|||
|
de7e77c1ea
|
|||
|
450e951914
|
|||
|
d1f90eaef1
|
|||
|
f5bcc7e5c1
|
|||
|
2bfd574504
|
|||
|
1445beb3d0
|
|||
|
e517cd0ab8
|
|||
|
f14d35b79a
|
|||
|
cf71e268df
|
|||
|
72c9dc87a5
|
|||
|
2d85457f5d
|
|||
|
a23464a17a
|
|||
|
b7dd3010a0
|
|||
|
73530f887f
|
|||
|
19d559d626
|
|||
|
5bb3c77e3c
|
|||
|
b036ee2a32
|
|||
|
00bdf1e8eb
|
|||
|
8112f6b1dc
|
|||
|
e0c8453dfe
|
|||
|
78ad4f0ba6
|
|||
|
95fe0861c9
|
|||
|
b723dc961c
|
|||
|
3883a90da3
|
|||
|
b374fc57e2
|
|||
|
89e010474f
|
|||
|
226ea54105
|
|||
|
819d0eda4f
|
|||
|
997be323f5
|
|||
|
2d072241a7
|
|||
|
0fd70fb03c
|
|||
|
90fbbda854
|
|||
|
a5e3bd4c11
|
|||
|
2a2264be9f
|
|||
|
dabf480e7d
|
|||
|
b1d77b0f5d
|
|||
|
6ea87de1b5
|
|||
|
386e65c0f4
|
|||
|
e158df3fd1
|
|||
|
816e696f47
|
|||
|
e2db4e1193
|
|||
|
3ebc12ddc9
|
|||
|
173178313e
|
|||
|
2313ad1b25
|
|||
|
a3c1ccb6c7
|
|||
|
de206d7b93
|
|||
|
3f23480cb9
|
|||
|
d65948ca41
|
|||
|
655eb827e1
|
|||
|
87ec1690ee
|
|||
|
a6a712ea99
|
|||
|
427b70f347
|
|||
|
641aa325dc
|
|||
|
4282129190
|
|||
|
58b6608cbb
|
|||
|
91a54013b9
|
|||
|
9c413aaa38
|
|||
|
c8ba0ce0ca
|
|||
|
9a6a9fcbf8
|
|||
|
fd942c275f
|
|||
|
6a1d358548
|
|||
|
719a2ffac7
|
|||
|
2af97af4dd
|
|||
|
9611655fa0
|
|||
|
44c3cde2c5
|
|||
|
5a3735644d
|
|||
|
b33937f50b
|
|||
|
4dfd389998
|
|||
|
7b03b977ac
|
|||
|
21d861dbd0
|
|||
|
9a9a707a10
|
|||
|
14467fb9f8
|
|||
|
96c175e0bb
|
|||
|
966c3392aa
|
|||
|
7ef055bc51
|
|||
|
6d3b19fe46
|
|||
|
bebd49f14a
|
|||
|
af17d6e0dc
|
|||
|
c49caf7a25
|
|||
|
54e58aca7e
|
|||
|
0d1d4e10c1
|
|||
|
02122f979b
|
|||
|
83d40e3713
|
|||
|
100fe208e2
|
|||
|
101342c5e3
|
|||
|
459705d05a
|
|||
|
ed237a2e03
|
|||
|
82e2f5d753
|
|||
|
e1b18eeefe
|
|||
|
80a0d4aefe
|
|||
|
b307a21e11
|
|||
|
96b64a144e
|
|||
|
dc789627c0
|
|||
|
09914fc3a9
|
|||
|
c2001ae2b3
|
|||
|
b6ddcd03c0
|
|||
|
f81f0c6a15
|
|||
|
39efc3d5ba
|
|||
|
b794eebeb7
|
|||
|
6cdd4f6aa3
|
|||
|
190627d982
|
|||
|
b148423914
|
|||
|
5c3c492fd8
|
|||
|
535dc0313e
|
|||
|
8d7aba02d3
|
|||
|
19e2d6fd59
|
|||
|
f6b37f1b10
|
|||
|
32b3042418
|
|||
|
4e94728235
|
|||
|
bf1f2a7bfa
|
|||
|
e7392c6c09
|
|||
|
a0249716b6
|
|||
|
f9f7badd76
|
|||
|
2cd476d2b1
|
|||
|
738cd67f00
|
|||
|
222fcacfeb
|
|||
|
04fa288627
|
|||
|
998d5cf3fa
|
|||
|
ac07328aca
|
|||
|
2ef42f86dc
|
|||
|
4dc28f50d7
|
|||
|
71fb77f758
|
|||
|
5d09db86a0
|
|||
|
a5e9144d63
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@
|
|||||||
/tramp
|
/tramp
|
||||||
/dape-breakpoints
|
/dape-breakpoints
|
||||||
flycheck_init.el
|
flycheck_init.el
|
||||||
|
local-init.el*
|
||||||
|
|||||||
182
disabled.el
182
disabled.el
@ -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
64
elisp/c-comments.el
Normal 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
|
||||||
@ -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
@ -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
499
elisp/inferior-cc.el
Normal 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
|
||||||
@ -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)
|
||||||
|
|||||||
BIN
elisp/private.el
BIN
elisp/private.el
Binary file not shown.
@ -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'.")
|
||||||
|
|
||||||
Reference in New Issue
Block a user