├── .dir-locals.el ├── README.md ├── TODO.md ├── LICENSE └── nix-buffer.el /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((emacs-lisp-mode . ((indent-tabs-mode . t) 2 | (fill-column . 80)))) 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nix-buffer 2 | ============ 3 | 4 | [![MELPA](https://melpa.org/packages/nix-buffer-badge.svg)](https://melpa.org/#/nix-buffer) 5 | [![MELPA-STABLE](https://stable.melpa.org/packages/nix-buffer-badge.svg)](https://stable.melpa.org/#/nix-buffer) 6 | 7 | Adds an emacs command to modify the buffer environment according to a 8 | Lisp expression built by nix, like `nix-shell` but for emacs. See 9 | [nix-buffer.el][1] for more details. 10 | 11 | [1]: nix-buffer.el 12 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | Future work 2 | ============ 3 | 4 | Clean up state dir 5 | -------------------- 6 | 7 | Currently the list of trusted expressions grows indefinitely, and we 8 | keep gc roots to the latest build for every file we ever 9 | `nix-buffer` into. This can be manually cleaned up by nuking the state 10 | dir and clearing `nix-buffer--trusted-exprs`, but this should be 11 | handled more automatically. 12 | 13 | Handle remote files 14 | -------------------- 15 | 16 | Right now `nix-buffer` will almost certainly fail for remote 17 | files, unless `f.el` and `call-process` somehow conspire to do the 18 | right thing here. 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, 2017 Shea Levy 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /nix-buffer.el: -------------------------------------------------------------------------------- 1 | ;;; nix-buffer.el --- Set up buffer environments with nix 2 | 3 | ;; Copyright (C) 2016, 2017 Shea Levy 4 | 5 | ;; Author: Shea Levy 6 | ;; URL: https://github.com/shlevy/nix-buffer/tree/master/ 7 | ;; Version: 3.1.1 8 | ;; Package-Requires: ((f "0.17.3") (emacs "24.4")) 9 | 10 | ;;; Commentary: 11 | 12 | ;; This package provides 'nix-buffer', to modify your buffer 13 | ;; according to a directory-local nix expression. Think of it as 14 | ;; nix-shell for Emacs. See the documentation for 'nix-buffer' for 15 | ;; more details. 16 | 17 | ;; It may be desirable to run 'nix-buffer' before 'normal-mode' is 18 | ;; called so it affects all modes. 19 | 20 | ;;; Code: 21 | 22 | (require 'f) 23 | (require 'subr-x) 24 | 25 | (defgroup nix-buffer nil "Customization for nix-buffer." 26 | :prefix "nix-buffer-" 27 | :group 'environment 28 | :package-version '('nix-buffer . "3.1.1")) 29 | 30 | (defun nix-buffer--directory-name-setter (opt val) 31 | "Defcustom setter for ‘nix-buffer-directory-name’. 32 | OPT The option we're setting. 33 | 34 | VAL The value it's being set to." 35 | (nix-buffer-update-directory-name val)) 36 | 37 | (defcustom nix-buffer-directory-name 38 | (locate-user-emacs-file "nix-buffer") 39 | "Path where ‘nix-buffer’ keeps its data. 40 | To update this variable outside of Customize, please use 41 | 'nix-buffer-update-directory-name'." 42 | :group 'nix-buffer 43 | :type '(directory) 44 | :set 'nix-buffer--directory-name-setter 45 | :initialize 'custom-initialize-default 46 | :risky t) 47 | 48 | (defvar nix-buffer--trust-exprs-file 49 | (f-join nix-buffer-directory-name "trusted-exprs")) 50 | 51 | (defun nix-buffer--load-trusted-exprs () 52 | "Load the trusted ‘nix-buffer’ exprs." 53 | (let ((tbl (ignore-errors 54 | (with-temp-buffer 55 | (insert-file-contents-literally 56 | nix-buffer--trust-exprs-file) 57 | (read (current-buffer)))))) 58 | (if (hash-table-p tbl) 59 | tbl 60 | (make-hash-table :test 'equal)))) 61 | 62 | (defvar nix-buffer--trusted-exprs (nix-buffer--load-trusted-exprs)) 63 | 64 | (defun nix-buffer-update-directory-name (path) 65 | "Update the ‘nix-buffer’ state directory. 66 | PATH The path to store the ‘nix-buffer’ state." 67 | (setq nix-buffer-directory-name path) 68 | (setq nix-buffer--trust-exprs-file 69 | (f-join nix-buffer-directory-name "trusted-exprs")) 70 | (setq nix-buffer--trusted-exprs (nix-buffer--load-trusted-exprs))) 71 | 72 | (defun nix-buffer-unload-function () 73 | "Save state on unload." 74 | (ignore-errors (make-directory nix-buffer-directory-name t)) 75 | (with-temp-buffer 76 | (prin1 nix-buffer--trusted-exprs (current-buffer)) 77 | (write-region nil nil nix-buffer--trust-exprs-file)) 78 | nil) 79 | 80 | (defun nix-buffer--unique-filename (path) 81 | "Create a unix-safe filename from an entire path. 82 | PATH the path to generate the name from." 83 | (replace-regexp-in-string "[|\\/]" 84 | (lambda (str) 85 | (if (equal str "/") 86 | "|" 87 | (concat "\\\\" str))) 88 | path)) 89 | 90 | (defun nix-buffer--query-safety (expr-file lisp-file) 91 | "Ask the user whether to trust a Lisp file. 92 | EXPR-FILE The nix expression leading to this file. 93 | 94 | LISP-FILE The file in question." 95 | (let ((res (yes-or-no-p (concat expr-file 96 | " resulted in unknown Lisp file " 97 | lisp-file 98 | "; trust it? ")))) 99 | (puthash lisp-file res nix-buffer--trusted-exprs) 100 | res)) 101 | 102 | (defvar nix-buffer-after-load-hook nil 103 | "Hook run after ‘nix-buffer’ loads an expression.") 104 | 105 | (defun nix-buffer--load-result (expr-file out) 106 | "Load the result of a ‘nix-buffer’ build, checking for safety. 107 | EXPR-FILE The nix expression being built. 108 | 109 | OUT The build result." 110 | (when (or (gethash out nix-buffer--trusted-exprs) 111 | (nix-buffer--query-safety expr-file out)) 112 | (load out t t nil t) 113 | (run-hooks 'nix-buffer-after-load-hook))) 114 | 115 | (defun nix-buffer--sentinel 116 | (out-link last-out expr-file user-buf err-buf process event) 117 | "Handle the results of the nix build. 118 | OUT-LINK The path to the output symlink. 119 | 120 | LAST-OUT The previous build result, if any. 121 | 122 | EXPR-FILE The nix expression being built. 123 | 124 | USER-BUF The buffer to apply the results to. 125 | 126 | ERR-BUF The standard error buffer of the nix-build 127 | 128 | PROCESS The process whose status changed. 129 | 130 | EVENT The process status change event string." 131 | (unless (process-live-p process) 132 | (let ((out-buf (process-buffer process))) 133 | (progn 134 | (if (= (process-exit-status process) 0) 135 | (let ((cur-out (with-current-buffer out-buf 136 | (string-trim-right (buffer-string))))) 137 | (if (string= "" cur-out) 138 | (ignore-errors (delete-file out-link)) 139 | (unless (string= last-out cur-out) 140 | (with-current-buffer user-buf 141 | (nix-buffer--load-result expr-file cur-out))))) 142 | (with-current-buffer 143 | (get-buffer-create "*nix-buffer errors*") 144 | (insert "nix-build for nix-buffer for " 145 | (buffer-name user-buf) 146 | " " 147 | (string-trim-right event) 148 | " with error output: \n") 149 | (insert-buffer-substring err-buf) 150 | (pop-to-buffer (current-buffer)))) 151 | (let ((kill-buffer-query-functions nil)) 152 | (kill-buffer out-buf) 153 | (kill-buffer err-buf)))))) 154 | 155 | (defun nix-buffer--nix-build (root expr-file) 156 | "Start the nix build. 157 | ROOT The path we started from. 158 | 159 | EXPR-FILE The file containing the nix expression to build." 160 | (let* ((state-dir (f-join nix-buffer-directory-name 161 | (nix-buffer--unique-filename root))) 162 | (out-link (f-join state-dir "result")) 163 | (current-out (file-symlink-p out-link)) 164 | (err (generate-new-buffer " nix-buffer-nix-build-stderr"))) 165 | (ignore-errors (make-directory state-dir t)) 166 | (make-process 167 | :name "nix-buffer-nix-build" 168 | :buffer (generate-new-buffer " nix-buffer-nix-build-stdout") 169 | :command (list 170 | "nix-build" 171 | "--arg" "root" root 172 | "--out-link" out-link 173 | expr-file 174 | ) 175 | :noquery t 176 | :sentinel (apply-partially 'nix-buffer--sentinel 177 | out-link 178 | current-out 179 | expr-file 180 | (current-buffer) 181 | err) 182 | :stderr err) 183 | (when current-out 184 | (nix-buffer--load-result expr-file current-out)))) 185 | 186 | (defcustom nix-buffer-root-file "dir-locals.nix" 187 | "File name to use for determining Nix expression to use." 188 | :group 'nix-buffer 189 | :type '(string)) 190 | 191 | ;;;###autoload 192 | (defun nix-buffer () 193 | "Set up the buffer according to the directory-local nix expression. 194 | Looks for dir-locals.nix upward from the current directory. If found, 195 | asynchronously builds the derivation defined there with the 'root' arg 196 | set to the current buffer file name or directory and evaluates the 197 | resulting elisp if safe to do so. 'nix-buffer-after-load-hook' can be 198 | used to detect when the elisp load occurs. 199 | 200 | If we have previously built dir-locals.nix for the current file or 201 | directory, the elisp corresponding to the last build is evaluated 202 | synchronously and the new elisp is evaluated when the build completes, 203 | unless the newly-built file is identical. As such, the elisp 204 | generated by dir-locals.nix should be written with multiple 205 | evaluations in mind. 206 | 207 | Because in practice dir-locals.nix will always want to do things that 208 | are unsafe in dir-locals.el (e.g. append to 'exec-path'), we don't 209 | reuse that mechanism and instead just load the file as elisp. Because 210 | this allows arbitrary code execution, the first time we're asked to 211 | load a particular store path we query the user to verify if it's safe 212 | to load beforehand. 213 | 214 | The Lisp code generated by dir-locals.nix should limit itself to 215 | modifying buffer-local variables, but there is no actual enforcement 216 | of this. 'setq-local' is your friend. 217 | 218 | If dir-locals.nix does not evaluate to any derivations (e.g. it 219 | evaluates to {}), then nothing is loaded and the cached result, if any, 220 | is removed." 221 | (interactive) 222 | (let* ((root (f-expand (directory-file-name 223 | (or (buffer-file-name) default-directory)))) 224 | (expr-dir (locate-dominating-file root nix-buffer-root-file))) 225 | (when expr-dir 226 | (let ((expr-file (f-expand nix-buffer-root-file expr-dir))) 227 | (nix-buffer--nix-build root expr-file))))) 228 | 229 | (add-hook 'kill-emacs-hook 'nix-buffer-unload-function) 230 | 231 | (provide 'nix-buffer) 232 | 233 | ;;; nix-buffer.el ends here 234 | 235 | ;; Local Variables: 236 | ;; tab-width: 8 237 | ;; End: 238 | --------------------------------------------------------------------------------