Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

414 lines
12 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. ;;; ssh-config-mode.el --- Mode for fontification of ~/.ssh/config
  2. ;;
  3. ;; ssh-config-mode-el/ssh-config-mode.el ---
  4. ;;
  5. ;; $Id: ssh-config-mode.el,v 1.14 2012/05/14 05:29:26 harley Exp $
  6. ;;
  7. ;; Author: Harley Gorrell <harley@panix.com>
  8. ;; URL: https://github.com/jhgorrell/ssh-config-mode-el
  9. ;; Github: https://raw.github.com/jhgorrell/ssh-config-mode-el/master/ssh-config-mode.el
  10. ;; License: GPL v3+ (https://www.gnu.org/licenses/gpl-3.0.txt)
  11. ;; Keywords: ssh, config, emacs
  12. ;; Version: $Revision: 1.14 $
  13. ;; Tag: 20170413T0010
  14. ;;; Commentary:
  15. ;; * Fontifys the ssh config keywords.
  16. ;; * keys for skipping from host section to host section.
  17. ;; * Add the following to your startup file.
  18. ;; (autoload 'ssh-config-mode "ssh-config-mode" t)
  19. ;; (add-to-list 'auto-mode-alist '("/\\.ssh/config\\'" . ssh-config-mode))
  20. ;; (add-to-list 'auto-mode-alist '("/sshd?_config\\'" . ssh-config-mode))
  21. ;; (add-to-list 'auto-mode-alist '("/knownhosts\\'" . ssh-known-hosts-mode))
  22. ;; (add-to-list 'auto-mode-alist '("/authorized_keys2?\\'" . ssh-authorized-keys-mode))
  23. ;; (add-hook 'ssh-config-mode-hook 'turn-on-font-lock)
  24. ;;; History:
  25. ;; * This keeps checkdoc happy.
  26. ;;; License
  27. ;;
  28. ;; This file is free software; you can redistribute it and/or modify
  29. ;; it under the terms of the GNU General Public License as published by
  30. ;; the Free Software Foundation; either version 3, or (at your option)
  31. ;; any later version.
  32. ;; This file is distributed in the hope that it will be useful,
  33. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  34. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  35. ;; GNU General Public License for more details.
  36. ;; You should have received a copy of the GNU General Public License
  37. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  38. ;;; Code:
  39. ;; (eval-buffer)
  40. ;; (progn (delete-trailing-whitespace) (indent-region 0 (point-max)))
  41. ;; (byte-compile-file "ssh-config-mode.el" t)
  42. ;; (load "ssh-config-mode")
  43. ;; We use eval-and-compile so we can use them at compile
  44. ;; time and call regexp-opt during compliation.
  45. (eval-and-compile
  46. (defun ssh-config-ssh-config-keywords-path ()
  47. "Find the path to 'ssh-config-keywords.txt'.
  48. It should be next to 'ssh-config-mode.el'.
  49. When testing add '.' to load-path so you find the local copy."
  50. (let ((path (locate-library "ssh-config-keywords.txt" nil)))
  51. ;;(message "ssh-config-keywords.txt is %s" path)
  52. path)))
  53. (eval-and-compile
  54. (defun ssh-config-read-keywords (&optional file-path)
  55. "Read the list of ssh keywords, returning them as a list."
  56. ;; (message "ssh-config-read-keywords")
  57. (let ((file-path
  58. (or file-path
  59. (ssh-config-ssh-config-keywords-path))))
  60. (with-temp-buffer
  61. (insert-file-contents file-path)
  62. (split-string (buffer-string) "\n" t)))))
  63. (eval-and-compile
  64. (defvar ssh-config-keywords
  65. (eval-when-compile
  66. (ssh-config-read-keywords)))
  67. "A list of keywords allowed in a user ssh config file.")
  68. (eval-and-compile
  69. (defvar ssh-config-font-lock-keywords
  70. (eval-when-compile
  71. `((
  72. ,(regexp-opt ssh-config-keywords 'words)
  73. (1 font-lock-keyword-face)
  74. )))
  75. "Expressions to hilight in `ssh-config-mode'."))
  76. ;; ssh-config-font-lock-keywords
  77. ;; Setup
  78. (defvar ssh-config-mode-load-hook nil
  79. "*Hook to run when `ssh-config-mode' is loaded.")
  80. ;;
  81. (defvar ssh-config-mode-hook nil
  82. "*Hook to setup `ssh-config-mode'.")
  83. (defvar ssh-config-host-regexp "^\\s-*Host\\b"
  84. "Regexp to match the start of a host entry.")
  85. (defvar ssh-config-match-regexp "^\\s-*Match\\b"
  86. "Regexp to match the start of a match entry.")
  87. ;;
  88. (defvar ssh-config-hostname-regexp
  89. "[-_.a-zA-Z0-9]+"
  90. "Regexp to match one hostname. (rfc1123 2.1).")
  91. (defcustom ssh-config-mode-indent 2
  92. "The width of indentation to use.
  93. By default it's set to 2 as that is what man page
  94. ssh_config(5) shows it as."
  95. :type 'integer
  96. :group 'ssh-config-mode)
  97. (defun ssh-config-host-next ()
  98. "Skip to the next host entry."
  99. (interactive "^")
  100. (search-forward-regexp ssh-config-host-regexp))
  101. (defun ssh-config-host-prev ()
  102. "Skip to the previous host entry."
  103. (interactive "^")
  104. (search-backward-regexp ssh-config-host-regexp))
  105. (defun ssh-config-in-host-block-p ()
  106. "Are we inside a Host block?"
  107. (save-excursion
  108. (search-backward-regexp ssh-config-host-regexp nil t)))
  109. (defun ssh-config-in-match-block-p ()
  110. "Are we inside a Match block?"
  111. (save-excursion
  112. (search-backward-regexp ssh-config-match-regexp nil t)))
  113. (defun ssh-config-compute-indent ()
  114. "Compute the target indent for the current line.
  115. Comments right above a 'Host' are considered to be about that Host."
  116. (save-excursion
  117. (beginning-of-line)
  118. (cond
  119. ;; Start of file and "Host" and "Match" should be at 0
  120. ((or (looking-at ssh-config-host-regexp)
  121. (looking-at ssh-config-match-regexp)
  122. (and (not (ssh-config-in-host-block-p))
  123. (not (ssh-config-in-match-block-p))))
  124. 0)
  125. ;; Comment line
  126. ((looking-at "\\s-*#")
  127. ;; comments right before a "Host" or "Match" should be at 0
  128. (while (looking-at "\\s-*#")
  129. (forward-line))
  130. (if (or (looking-at ssh-config-host-regexp)
  131. (looking-at ssh-config-match-regexp))
  132. 0
  133. ssh-config-mode-indent))
  134. ;; default.
  135. (t
  136. ssh-config-mode-indent))))
  137. (defun ssh-config-indent-line ()
  138. "Indent lines in the SSH config file."
  139. (interactive)
  140. (let ((target (ssh-config-compute-indent)))
  141. (save-excursion
  142. (indent-line-to target))))
  143. ;;
  144. (defvar ssh-config-mode-map
  145. (let ((map (make-sparse-keymap)))
  146. ;; Ctrl bindings
  147. (define-key map [C-down] 'ssh-config-host-next)
  148. (define-key map [C-up] 'ssh-config-host-prev)
  149. ;;
  150. (define-key map "\C-c}" 'ssh-config-host-next)
  151. (define-key map "\C-c{" 'ssh-config-host-prev)
  152. ;;
  153. (define-key map (kbd "TAB") 'indent-for-tab-command)
  154. map)
  155. "The local keymap for `ssh-config-mode'.")
  156. ;;
  157. (defvar ssh-config-mode-syntax-table nil)
  158. (unless ssh-config-mode-syntax-table
  159. (let ((table (make-syntax-table)))
  160. (modify-syntax-entry ?# "<" table)
  161. (modify-syntax-entry ?\n ">" table)
  162. (setq ssh-config-mode-syntax-table table)))
  163. (defvar ssh-config-imenu-generic-expression
  164. `(("Hosts"
  165. ,(concat ssh-config-host-regexp "\\s-+\\(" ssh-config-hostname-regexp "\\)") 1)
  166. ("Matches"
  167. ,(concat ssh-config-match-regexp "\\s-+\\(.*\\)") 1))
  168. "Value for `imenu-generic-expression' in `ssh-config-mode'.
  169. Only show the first hostname in the menu.")
  170. (defun ssh-config-completion-at-point ()
  171. "Function used for `completion-at-point-functions' in `ssh-config-mode'."
  172. (interactive)
  173. (let* (
  174. (bds (bounds-of-thing-at-point 'symbol))
  175. (start (car bds))
  176. (end (cdr bds)))
  177. (list start end ssh-config-keywords . nil )))
  178. ;;;###autoload
  179. (define-derived-mode ssh-config-mode
  180. fundamental-mode
  181. "ssh-config"
  182. "Major mode for fontifiying ssh config files.
  183. \\{ssh-config-mode-map}"
  184. (setq
  185. comment-start "#"
  186. comment-end "")
  187. (make-local-variable 'font-lock-defaults)
  188. (setq
  189. font-lock-defaults '(ssh-config-font-lock-keywords nil t))
  190. ;;
  191. (setq-local indent-line-function 'ssh-config-indent-line)
  192. (setq-local imenu-generic-expression ssh-config-imenu-generic-expression)
  193. (add-hook 'completion-at-point-functions 'ssh-config-completion-at-point nil 'local))
  194. ;;;###autoload
  195. (progn
  196. (add-to-list 'auto-mode-alist '("/\\.ssh/config\\'" . ssh-config-mode))
  197. (add-to-list 'auto-mode-alist '("/sshd?_config\\'" . ssh-config-mode))
  198. (add-to-list 'auto-mode-alist '("/known_hosts\\'" . ssh-known-hosts-mode))
  199. (add-to-list 'auto-mode-alist '("/authorized_keys\\'" . ssh-authorized-keys-mode)))
  200. ;;;;;
  201. (defvar ssh-known-hosts-mode-hook nil
  202. "*Hook to setup `ssh-config-mode'.")
  203. (defvar ssh-known-hosts-mode-map
  204. (let ((map (make-sparse-keymap)))
  205. ;; Ctrl bindings
  206. map)
  207. "The local keymap for `ssh-known-hosts-mode'.")
  208. (defvar ssh-known-hosts-mode-syntax-table
  209. (let ((table (make-syntax-table)))
  210. (modify-syntax-entry ?# "<" table)
  211. (modify-syntax-entry ?\n ">" table)
  212. table)
  213. "Syntax table for `ssh-known-hosts-mode'.
  214. Just sets the comment syntax.")
  215. ;;;;;
  216. ;; host.example.com,1.1.1.1
  217. ;; |1|hash=|hash=
  218. (defvar ssh-known-hosts-regex-hashed
  219. "\\(?:|[0-9]+|[-0-9A-Za-z=|/+]+\\)"
  220. "Regex for matching hashed addresses.")
  221. (defvar ssh-known-hosts-regex-ipv4
  222. "\\(?:[0-9]+.[0-9]+.[0-9]+.[0-9]+\\)"
  223. "Regex for matching ipv4 addresses.")
  224. (defvar ssh-known-hosts-regex-ipv6
  225. "\\(?:[0-9a-f:]+\\(?:%[a-z0-9]+\\)?\\)"
  226. "Regex for matching ipv6 addresses.")
  227. (defvar ssh-known-hosts-regex-ip
  228. (concat
  229. "\\(?:"
  230. ssh-known-hosts-regex-ipv4
  231. "\\|"
  232. ssh-known-hosts-regex-ipv6
  233. "\\)")
  234. "Regex for matching ip addresses.")
  235. (defvar ssh-known-hosts-regex-ipv6
  236. "\\(?:[0-9a-f:]+\\(?:%[a-z0-9]+\\)\\)"
  237. "Regex for matching ipv6 addresses.")
  238. ;; This is more specfic than "ssh-config-hostname-regexp"; merge them?
  239. (defvar ssh-known-hosts-regex-hostname
  240. "\\(?:\\(?:[a-zA-Z0-9_][-a-zA-Z0-9_]*[.]\\)*[a-zA-Z_][-a-zA-Z0-9_]*\\)"
  241. "Regex for matching hostnames.
  242. We permit underscores.")
  243. ;; :2222
  244. (defvar ssh-known-hosts-regex-port
  245. "\\(?:[0-9]+\\)"
  246. "Regex for matching an port.")
  247. (defvar ssh-known-hosts-regex-host
  248. (concat
  249. "\\(?:"
  250. ssh-known-hosts-regex-hashed
  251. "\\|"
  252. ssh-known-hosts-regex-ip
  253. "\\|"
  254. ssh-known-hosts-regex-hostname
  255. "\\)"))
  256. ;; NOTE: font-lock-studio might be of help when making changes.
  257. (defvar ssh-known-hosts-font-lock-keywords
  258. ;; We want to match lines like the following:
  259. ;; Short list in ./tests/known_hosts_short
  260. ;; Full list in ./tests/known_hosts
  261. ;; More test data in: openssh-portable/regress/unittests/hostkeys/testdata/known_hosts
  262. `(
  263. (,(concat
  264. "^"
  265. ;; @marker (optional):
  266. "\\(@[-a-z]+ +\\|\\)"
  267. ;; hostname:
  268. "\\("
  269. ;; |1|hash=|hash=
  270. ssh-known-hosts-regex-hashed
  271. "\\|"
  272. ;; hostname-only
  273. ssh-known-hosts-regex-hostname
  274. "\\|"
  275. ;; ip-only
  276. ssh-known-hosts-regex-ip
  277. "\\|"
  278. ;; hostname "," ip
  279. "\\(?:" ssh-known-hosts-regex-hostname "," ssh-known-hosts-regex-ip "\\)"
  280. "\\|"
  281. ;; [host-or-ip]:222
  282. "\\(?:\\[" ssh-known-hosts-regex-host "\\]:" ssh-known-hosts-regex-port "\\)"
  283. "\\|"
  284. ;; We arent matching ports, but they should be the same.
  285. ;; [ssh.github.com]:443,[192.1.2.3]:443
  286. "\\(?:"
  287. "\\[" ssh-known-hosts-regex-hostname "\\]:" ssh-known-hosts-regex-port ","
  288. "\\[" ssh-known-hosts-regex-ip "\\]:" ssh-known-hosts-regex-port "\\)"
  289. "\\)"
  290. "[ \t]+"
  291. ;; key type:
  292. ;; ssh-rsa, ecdsa-sha2-nistp384, ...
  293. "\\(\\(?:ecdsa\\|ssh\\)[-0-9A-Za-z]*\\)"
  294. "[ \t]+"
  295. ;; public key:
  296. ;; base64data==
  297. "\\(AA[0-9A-Za-z/+]+=*\\)"
  298. )
  299. (1 font-lock-warning-face)
  300. (2 font-lock-function-name-face)
  301. (3 font-lock-keyword-face)
  302. (4 font-lock-string-face)
  303. ))
  304. "Expressions to hilight in `ssh-known-hosts-mode'.
  305. We want to try and be a good match, so misformatted ones stand out.
  306. So we dont just match .* for the hostname.")
  307. ;;;###autoload
  308. (defun ssh-known-hosts-mode ()
  309. "Major mode for fontifiying ssh known_hosts files.
  310. \\{ssh-known-hosts-mode}"
  311. (interactive)
  312. (kill-all-local-variables)
  313. (set-syntax-table ssh-known-hosts-mode-syntax-table)
  314. (setq
  315. mode-name "ssh-known-hosts"
  316. major-mode 'ssh-known-hosts-mode
  317. comment-start "#"
  318. comment-end "")
  319. (use-local-map ssh-known-hosts-mode-map)
  320. ;;
  321. (make-local-variable 'font-lock-defaults)
  322. (setq font-lock-defaults '(ssh-known-hosts-font-lock-keywords))
  323. ;;
  324. (run-hooks 'ssh-known-hosts-mode-hook)
  325. nil)
  326. ;;;;;
  327. ;;;###autoload (autoload 'ssh-authorized-keys-mode "ssh-config-mode" nil t)
  328. (define-generic-mode ssh-authorized-keys-mode
  329. '(?\#)
  330. nil
  331. (eval-when-compile
  332. (list
  333. (list
  334. (concat
  335. ;; ignore options
  336. ;; double quoted string will be fontified by generic mode.
  337. ;; key type
  338. "\\(\\(?:ecdsa\\|ssh\\)-[^[:space:]]+\\)\\s-+"
  339. ;; base64
  340. "\\([0-9A-Za-z+/]+=*\\)"
  341. ;; comment in public key
  342. "\\(?: \\(.*\\)\\)?"
  343. "$")
  344. '(1 font-lock-keyword-face)
  345. '(2 font-lock-string-face)
  346. '(3 font-lock-comment-face nil t)
  347. )))
  348. ;; Not define `auto-mode-alist' obey the other mode in this elisp.
  349. nil
  350. nil)
  351. ;; done loading
  352. (run-hooks 'ssh-config-mode-load-hook)
  353. (provide 'ssh-config-mode)
  354. ;;; ssh-config-mode.el ends here