-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathsudo-edit.el
More file actions
234 lines (201 loc) · 9.2 KB
/
sudo-edit.el
File metadata and controls
234 lines (201 loc) · 9.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
;;; sudo-edit.el --- Open files as another user -*- lexical-binding: t -*-
;; Copyright (C) 2014 Nathaniel Flath <flat0103@gmail.com>
;; Author: Nathaniel Flath <flat0103@gmail.com>
;; URL: https://github.com/nflath/sudo-edit
;; Keywords: convenience
;; Version: 0.1.1
;; Package-Requires: ((emacs "24") (cl-lib "0.5"))
;; This file is not part of GNU Emacs.
;;; License:
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License
;; as published by the Free Software Foundation; either version 3
;; of the License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;; This package allows to open files as another user, by default "root":
;;
;; `sudo-edit'
;;
;; Suggested keybinding:
;;
;; (global-set-key (kbd "C-c C-r") 'sudo-edit)
;;
;; Installation:
;;
;; To use this mode, put the following in your init.el:
;;
;; (require 'sudo-edit)
;;; Code:
(eval-when-compile
(require 'cl-lib)
(require 'subr-x nil 'no-error))
(require 'tramp)
;; Compatibility for Emacs 24.3 and earlier
(eval-and-compile
(unless (fboundp 'string-prefix-p)
(defun string-prefix-p (prefix string &optional ignore-case)
"Return non-nil if PREFIX is a prefix of STRING.
If IGNORE-CASE is non-nil, the comparison is done without paying attention
to case differences."
(let ((prefix-length (length prefix)))
(if (> prefix-length (length string)) nil
(eq t (compare-strings prefix 0 prefix-length string
0 prefix-length ignore-case))))))
(unless (fboundp 'string-suffix-p)
(defun string-suffix-p (suffix string &optional ignore-case)
"Return non-nil if SUFFIX is a suffix of STRING.
If IGNORE-CASE is non-nil, the comparison is done without paying
attention to case differences."
(let ((start-pos (- (length string) (length suffix))))
(and (>= start-pos 0)
(eq t (compare-strings suffix nil nil
string start-pos nil ignore-case))))))
(unless (featurep 'subr-x)
(defsubst string-remove-prefix (prefix string)
"Remove PREFIX from STRING if present."
(if (string-prefix-p prefix string)
(substring string (length prefix))
string))
(defsubst string-remove-suffix (suffix string)
"Remove SUFFIX from STRING if present."
(if (string-suffix-p suffix string)
(substring string 0 (- (length string) (length suffix)))
string))
(defsubst string-blank-p (string)
"Check whether STRING is either empty or only whitespace."
(string-match-p "\\`[ \t\n\r]*\\'" string))))
(defgroup sudo-edit nil
"Edit files as another user."
:prefix "sudo-edit-"
:group 'convenience)
(defcustom sudo-edit-user "root"
"Default user to edit a file with `sudo-edit'."
:type 'string
:group 'sudo-edit)
(defcustom sudo-edit-local-method "sudo"
"Tramp method to use with `sudo-edit' for local files."
:type '(choice
(const "sudo")
(const "doas")
(const "su"))
:group 'sudo-edit)
(defcustom sudo-edit-remote-method nil
"Tramp method to use with `sudo-edit' for remote files."
:type '(choice
(const :tag "Use local method" nil)
(const "sudo")
(const "doas")
(const "su"))
:group 'sudo-edit)
(defface sudo-edit-header-face
'((t (:foreground "white" :background "red3")))
"*Face use to display header-lines for files opened as root."
:group 'sudo-edit)
;;;###autoload
(defun sudo-edit-set-header ()
"*Display a warning in header line of the current buffer.
This function is suitable to add to `find-file-hook' and `dired-file-hook'."
(when (string-equal
(file-remote-p (or buffer-file-name default-directory) 'user)
"root")
(setq header-line-format
(propertize "--- WARNING: EDITING FILE AS ROOT! %-"
'face 'sudo-edit-header-face))))
;;;###autoload
(define-minor-mode sudo-edit-indicator-mode
"Indicates editing as root by displaying a message in the header line."
:global t
:lighter nil
:group 'sudo-edit
(if sudo-edit-indicator-mode
(progn
(add-hook 'find-file-hook #'sudo-edit-set-header)
(add-hook 'dired-mode-hook #'sudo-edit-set-header))
(remove-hook 'find-file-hook #'sudo-edit-set-header)
(remove-hook 'dired-mode-hook #'sudo-edit-set-header)))
(defvar sudo-edit-user-history nil)
;; NB: TRAMP 2.3.2 introduced `tramp-file-name' struct which offers these
;; functions to access the slots.
(or (fboundp 'tramp-file-name-domain) (defalias 'tramp-file-name-domain #'ignore))
(or (fboundp 'tramp-file-name-port) (defalias 'tramp-file-name-port #'ignore))
(defalias 'sudo-edit-make-tramp-file-name
(if (version< tramp-version "2.3.2")
(with-no-warnings
(lambda (method user _domain host _port localname &optional hop)
(tramp-make-tramp-file-name method user host localname hop)))
#'tramp-make-tramp-file-name))
(defun sudo-edit-tramp-get-parameter (vec param)
"Return from tramp VEC a parameter PARAM."
(or (tramp-get-method-parameter vec param)
;; NB: Compatibility old versions of TRAMP 2.2.x shipped with Emacs 24.3
;; and earlier. Ignore errors when the method doesn't have the
;; parameter as static value defined in `tramp-methods'.
(ignore-errors (tramp-get-method-parameter (tramp-file-name-method vec) param))))
(defun sudo-edit-out-of-band-ssh-p (filename)
"Check if tramp FILENAME is a out-of-band method and use ssh."
(or (let ((vec (tramp-dissect-file-name filename)))
(and (sudo-edit-tramp-get-parameter vec 'tramp-copy-program)
(string-equal (sudo-edit-tramp-get-parameter vec 'tramp-login-program) "ssh")))
(tramp-gvfs-file-name-p filename)))
(defun sudo-edit-filename (filename user)
"Return a tramp edit name for a FILENAME as USER."
(if (file-remote-p filename)
(let* ((vec (tramp-dissect-file-name filename))
;; XXX: Change to ssh method on out-of-band ssh based methods
(method (if (sudo-edit-out-of-band-ssh-p filename)
"ssh"
(tramp-file-name-method vec)))
(hop (sudo-edit-make-tramp-file-name
method
(tramp-file-name-user vec)
(tramp-file-name-domain vec)
(tramp-file-name-host vec)
(tramp-file-name-port vec)
""
(tramp-file-name-hop vec)))
(remote-method (or sudo-edit-remote-method sudo-edit-local-method)))
(setq hop (string-remove-prefix (if (fboundp 'tramp-prefix-format) (tramp-prefix-format) (bound-and-true-p tramp-prefix-format)) hop))
(setq hop (string-remove-suffix (if (fboundp 'tramp-postfix-host-format) (tramp-postfix-host-format) (bound-and-true-p tramp-postfix-host-format)) hop))
(setq hop (concat hop tramp-postfix-hop-format))
(if (and (string= user (tramp-file-name-user vec))
(string-match tramp-local-host-regexp (tramp-file-name-host vec)))
(tramp-file-name-localname vec)
(sudo-edit-make-tramp-file-name remote-method user (tramp-file-name-domain vec) (tramp-file-name-host vec) (tramp-file-name-port vec) (tramp-file-name-localname vec) hop)))
(sudo-edit-make-tramp-file-name sudo-edit-local-method user nil "localhost" nil (expand-file-name filename))))
;;;###autoload
(defun sudo-edit (&optional arg)
"Edit currently visited file as another user, by default `sudo-edit-user'.
With a prefix ARG prompt for a file to visit. Will also prompt
for a file to visit if current buffer is not visiting a file."
(interactive "P")
(let ((user (if arg
(completing-read "User: " (and (fboundp 'system-users) (system-users)) nil nil nil 'sudo-edit-user-history sudo-edit-user)
sudo-edit-user))
(filename (or buffer-file-name
(and (derived-mode-p 'dired-mode) default-directory))))
(cl-assert (not (string-blank-p user)) nil "User must not be a empty string")
(if (or arg (not filename))
(find-file (sudo-edit-filename (read-file-name (format "Find file (as \"%s\"): " user)) user))
(let ((position (point)))
(find-alternate-file (sudo-edit-filename filename user))
(goto-char position)))))
;;;###autoload
(defun sudo-edit-find-file (filename)
"Edit FILENAME as another user, by default `sudo-edit-user'."
(interactive
(list (read-file-name (format "Find file (as \"%s\"): " sudo-edit-user))))
(cl-assert (not (string-blank-p sudo-edit-user)) nil "User must not be a empty string")
(find-file (sudo-edit-filename filename sudo-edit-user)))
(define-obsolete-function-alias 'sudo-edit-current-file 'sudo-edit "2016-09-05")
(provide 'sudo-edit)
;;; sudo-edit.el ends here