-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathauto-sudoedit.el
More file actions
169 lines (152 loc) · 6.54 KB
/
auto-sudoedit.el
File metadata and controls
169 lines (152 loc) · 6.54 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
;;; auto-sudoedit.el --- Auto sudo edit by tramp -*- lexical-binding: t -*-
;; Author: ncaq <ncaq@ncaq.net>
;; Version: 1.1.2
;; Package-Requires: ((emacs "26.1")(f "0.19.0"))
;; URL: https://github.com/ncaq/auto-sudoedit
;;; Commentary:
;; when find-file-hook and dired-mode-hook, and current path not writable
;; re-open tramp sudo edit automatic
;;; Code:
(require 'dired)
(require 'f)
(require 'recentf)
(require 'tramp)
(require 'tramp-sh)
(defcustom auto-sudoedit-ask nil
"Ask for user confirmation when reopening?"
:group 'auto-sudoedit
:type 'boolean)
(defun auto-sudoedit-path (curr-path)
"To convert path to tramp using sudo path.
Argument CURR-PATH is current path.
The result is a cons cell in the format \\='(USER . TRAMP-PATH).
USER is nil, when we cannot open via sudo."
;; trampのpathに変換します
(let* ((file-owner (auto-sudoedit-file-owner curr-path))
(tramp-path
(if (tramp-tramp-file-p curr-path)
(auto-sudoedit-path-from-tramp-ssh-like curr-path file-owner)
(concat "/sudo::" curr-path))))
(if (and
;; We must know the file owner's login name
;; If we can't, we don't know which user to sudo as
file-owner
;; The file owner must be different from our current user so that the sudo makes sense
(not (string= file-owner (auto-sudoedit-current-user curr-path)))
;; For local files, the file must be writable by its owner,
;; otherwise sudo cannot help (e.g. read-only files on NixOS /nix/store)
;; Skip this check for tramp paths to avoid triggering a remote connection
(or (tramp-tramp-file-p curr-path) (auto-sudoedit-owner-writable-p curr-path))
;; 変換前のパスと同じでなく(2回めの変換はしない)
(not (equal curr-path tramp-path)))
(cons file-owner tramp-path)
(cons nil curr-path))))
(defun auto-sudoedit-file-owner (path)
"Determine the login name of the user PATH belongs to."
(file-attribute-user-id (file-attributes path 'string)))
(defun auto-sudoedit-current-user (path)
"Determine the user name visiting PATH. E.g. local Emacs user or ssh login."
(if (tramp-tramp-file-p path)
;; We can't just go by the user in the tramp filename, because it may have been omitted
(tramp-get-remote-uid (tramp-dissect-file-name path) 'string)
(user-login-name)))
(defun auto-sudoedit-owner-writable-p (path)
"Check if the owner of PATH has write permission.
If the file modes cannot be determined, assume writable."
(let ((modes (file-modes path)))
(or (null modes) (not (zerop (logand modes #o200))))))
(defun auto-sudoedit-path-from-tramp-ssh-like (curr-path new-user)
"Argument CURR-PATH is tramp path(that use protocols such as ssh).
NEW-USER is the user for sudo."
(let* ((file-name (tramp-dissect-file-name curr-path))
(method (tramp-file-name-method file-name))
(user (tramp-file-name-user file-name))
(host (tramp-file-name-host file-name))
(port (tramp-file-name-port file-name))
(localname (tramp-file-name-localname file-name))
(hop (tramp-file-name-hop file-name))
(new-method "sudo")
(new-host host)
(new-port port)
(new-localname localname)
(new-hop
(format "%s%s:%s%s%s|"
(or hop "") method
(if user
(concat user "@")
"")
host
(if port
(concat "#" port)
""))))
;; 最終メソッドがsudoである場合それ以上の変換は無意味なので行わない。
(if (equal method "sudo")
curr-path
(tramp-make-tramp-file-name
(make-tramp-file-name
:method new-method
:user new-user
:host new-host
:port new-port
:localname new-localname
:hop new-hop)))))
(defun auto-sudoedit-current-path ()
"Current path file or dir."
(or (buffer-file-name) list-buffers-directory))
(defun auto-sudoedit-sudoedit (curr-path)
"Open sudoedit. Argument CURR-PATH is path."
(interactive (list (auto-sudoedit-current-path)))
(find-file (cdr (auto-sudoedit-path curr-path))))
(defun auto-sudoedit ()
"`auto-sudoedit' hook for `find-file'.
Reopen the buffer via tramp with sudo method."
(let* ((curr-path (auto-sudoedit-current-path))
(remote-info (auto-sudoedit-path curr-path))
(user (car remote-info))
(tramp-path (cdr remote-info)))
(when (and curr-path
user tramp-path (not (and (tramp-tramp-file-p curr-path) (tramp-sh-handle-file-writable-p curr-path)))
(or (not auto-sudoedit-ask)
(y-or-n-p (format "This buffer belongs to user %s. Reopen this buffer as user %s? " user user))))
;; We have to tell emacs that this buffer now visits another file (actually the same one, just via tramp sudo)
;; We have to do things differently for normal files and for dired
(when buffer-file-name
(set-visited-file-name tramp-path t))
(when dired-directory
;; Remove the buffer as displaying the old directory path in dired's active buffer list
(dired-unadvertise dired-directory)
(setq list-buffers-directory tramp-path)
(setq dired-directory tramp-path)
(setq default-directory tramp-path)
;; Insert the new directory path in dired's active buffer list
(dired-advertise))
;; Remove the old filename from the recentf-list
;; TODO: Is this a good idea? Could this break something?
(when (string= (car recentf-list) curr-path)
(pop recentf-list))
;; We have changed the way emacs edits the file
;; Therefore we have to reinitialize the buffer (read-only, etc.)
;; Also the file may have not been readable before
;; Revert buffer fixes this for us.
;; Use the arguments to prevent user confirmation
;; (There are no changes that could be discarded in the buffer anyways, it was just opened)
(revert-buffer t t))))
;;;###autoload
(define-minor-mode auto-sudoedit-mode
"When sudo is required, it automatically reopens in tramp."
:group 'auto-sudoedit
:global t
:init-value 0
:lighter
" ASE"
(if auto-sudoedit-mode
(progn
(add-hook 'find-file-hook #'auto-sudoedit)
(add-hook 'dired-mode-hook #'auto-sudoedit))
(remove-hook 'find-file-hook #'auto-sudoedit)
(remove-hook 'dired-mode-hook #'auto-sudoedit)))
(provide 'auto-sudoedit)
;; Local Variables:
;; fill-column: 120
;; End:
;;; auto-sudoedit.el ends here