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.

434 lines
16 KiB

  1. ;;; ein-multilang.el --- Notebook mode with multiple language fontification
  2. ;; Copyright (C) 2012 Takafumi Arakaki
  3. ;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
  4. ;; This file is NOT part of GNU Emacs.
  5. ;; ein-multilang.el is free software: you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;; ein-multilang.el is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with ein-multilang.el.
  15. ;; If not, see <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;;
  18. ;;; Code:
  19. (eval-when-compile (defvar markdown-mode-map))
  20. (require 'ein-worksheet)
  21. (require 'ein-multilang-fontify)
  22. (require 'python)
  23. (require 'ess-r-mode nil t)
  24. (require 'ess-custom nil t)
  25. (require 'clojure-mode nil t)
  26. (require 'julia-mode nil t)
  27. (require 'haskell-mode nil t)
  28. (require 'hy-mode nil t)
  29. (require 'cc-mode)
  30. (declare-function ess-indent-line "ess")
  31. (declare-function ess-r-eldoc-function "ess-r-completion")
  32. (declare-function ess-setq-vars-local "ess-utils")
  33. (declare-function haskell-indentation-indent-line "haskell-indentation")
  34. (defun ein:ml-fontify (limit)
  35. "Fontify next input area comes after the current point then
  36. return `t' or `nil' if not found.
  37. See info node `(elisp) Search-based Fontification'."
  38. (ein:log-ignore-errors
  39. (ein:ml-fontify-1 limit)))
  40. (defun ein:ml-current-or-next-input-cell (ewoc-node)
  41. "Almost identical to `ein:worksheet-next-input-cell' but return
  42. the current cell if EWOC-NODE is the input area node."
  43. (let* ((ewoc-data (ewoc-data ewoc-node))
  44. (cell (ein:$node-data ewoc-data))
  45. (path (ein:$node-path ewoc-data))
  46. (element (nth 1 path)))
  47. (if (memql element '(prompt input))
  48. cell
  49. (ein:cell-next cell))))
  50. (defun ein:ml-fontify-1 (limit)
  51. "Actual implementation of `ein:ml-fontify'.
  52. This function may raise an error."
  53. (ein:and-let* ((pos (point))
  54. (node (ein:worksheet-get-nearest-cell-ewoc-node pos limit))
  55. (cell (ein:ml-current-or-next-input-cell node))
  56. (start (ein:cell-input-pos-min cell))
  57. (end (ein:cell-input-pos-max cell))
  58. ((<= end limit))
  59. ((< start end))
  60. (lang (ein:cell-language cell)))
  61. (let ((inhibit-read-only t))
  62. (ein:mlf-font-lock-fontify-block lang start end)
  63. ;; Emacs fontification mechanism requires the function to move
  64. ;; the point. Do *not* use `(goto-char end)'. As END is in the
  65. ;; input area, fontification falls into an infinite loop.
  66. (ewoc-goto-node (slot-value cell 'ewoc) (ein:cell-element-get cell :footer)))
  67. t))
  68. (defun ein:ml-back-to-prev-node ()
  69. (ein:aand (ein:worksheet-get-ewoc) (ewoc-goto-prev it 1)))
  70. (defvar ein:ml-font-lock-keywords
  71. '((ein:ml-fontify))
  72. "Default `font-lock-keywords' for `ein:notebook-multilang-mode'.")
  73. (defun ein:ml-set-font-lock-defaults ()
  74. (setq-local font-lock-defaults
  75. '(ein:ml-font-lock-keywords
  76. ;; The following are adapted from org-mode but I am not sure
  77. ;; if I need them:
  78. t nil nil
  79. ein:ml-back-to-prev-node)))
  80. ;;;###autoload
  81. (define-derived-mode ein:notebook-multilang-mode prog-mode "EIN"
  82. "A mode for fontifying multiple languages.
  83. \\{ein:notebook-multilang-mode-map}
  84. "
  85. (setq-local beginning-of-defun-function
  86. 'ein:worksheet-beginning-of-cell-input)
  87. (setq-local end-of-defun-function
  88. 'ein:worksheet-end-of-cell-input)
  89. (ein:ml-set-font-lock-defaults))
  90. (eval-after-load "auto-complete"
  91. '(add-to-list 'ac-modes 'ein:notebook-multilang-mode))
  92. ;;; Language setup functions
  93. (defun ein:ml-narrow-to-cell ()
  94. "Narrow to the current cell."
  95. (ein:and-let* ((pos (point))
  96. (node (ein:worksheet-get-nearest-cell-ewoc-node pos))
  97. (cell (ein:ml-current-or-next-input-cell node))
  98. (start (ein:cell-input-pos-min cell))
  99. (end (ein:cell-input-pos-max cell))
  100. ((< start end)))
  101. (narrow-to-region start end)))
  102. (defun ein:ml-indent-line-function (lang-func)
  103. (save-restriction
  104. (ein:ml-narrow-to-cell)
  105. (funcall lang-func)))
  106. (defun ein:ml-indent-region (lang-func start end)
  107. (save-restriction
  108. (ein:ml-narrow-to-cell)
  109. (funcall lang-func start end)))
  110. (defun ein:ml-lang-setup-python ()
  111. "Presumably tkf had good reasons to choose only these forms from `python-mode'."
  112. (setq-local mode-name "EIN[Py]")
  113. (setq-local comment-start "# ")
  114. (setq-local comment-start-skip "#+\\s-*")
  115. (setq-local parse-sexp-lookup-properties t)
  116. (setq-local indent-line-function
  117. (apply-partially #'ein:ml-indent-line-function #'python-indent-line-function))
  118. (setq-local indent-region-function
  119. (apply-partially #'ein:ml-indent-region #'python-indent-region))
  120. (set-syntax-table python-mode-syntax-table)
  121. (set-keymap-parent ein:notebook-multilang-mode-map python-mode-map))
  122. (defun ein:ml-lang-setup-clojure ()
  123. "Minimally different than the the python setup"
  124. (when (featurep 'clojure-mode)
  125. (setq-local mode-name "EIN[Clj]")
  126. (setq-local comment-start "; ")
  127. (setq-local comment-start-skip ";+\\s-*")
  128. (setq-local parse-sexp-lookup-properties t)
  129. (setq-local indent-line-function
  130. (apply-partially #'ein:ml-indent-line-function #'clojure-indent-line))
  131. (setq-local indent-region-function
  132. (apply-partially #'ein:ml-indent-region #'clojure-indent-region))
  133. (set-syntax-table clojure-mode-syntax-table)
  134. (set-keymap-parent ein:notebook-multilang-mode-map clojure-mode-map)))
  135. (defun ein:ml-lang-setup-julia ()
  136. (when (featurep 'julia-mode)
  137. (setq-local mode-name "EIN[julia]")
  138. (setq-local comment-start "# ")
  139. (setq-local comment-start-skip "#+\\s-*")
  140. (setq-local indent-line-function
  141. (apply-partially #'ein:ml-indent-line-function #'julia-indent-line))
  142. (when (boundp 'julia-mode-syntax-table)
  143. (set-syntax-table julia-mode-syntax-table))
  144. (when (boundp 'julia-mode-map)
  145. (set-keymap-parent ein:notebook-multilang-mode-map julia-mode-map))))
  146. (defun ein:ml-lang-setup-R ()
  147. (when (and (featurep 'ess-r-mode) (featurep 'ess-custom))
  148. (setq-local mode-name "EIN[R]")
  149. (when (boundp 'ess-r-customize-alist)
  150. (ess-setq-vars-local ess-r-customize-alist))
  151. (setq-local paragraph-start (concat "\\s-*$\\|" page-delimiter))
  152. (setq-local paragraph-separate (concat "\\s-*$\\|" page-delimiter))
  153. (setq-local paragraph-ignore-fill-prefix t)
  154. (setq-local indent-line-function
  155. (apply-partially #'ein:ml-indent-line-function #'ess-indent-line))
  156. (when (and (boundp 'ess-style) (boundp 'ess-default-style))
  157. (setq-local ess-style ess-default-style))
  158. (when (and (boundp 'prettify-symbols-alist) (boundp 'ess-r-prettify-symbols))
  159. (setq-local prettify-symbols-alist ess-r-prettify-symbols))
  160. (when (boundp 'ess-r-mode-syntax-table)
  161. (set-syntax-table ess-r-mode-syntax-table))
  162. (when (boundp 'ess-r-mode-map)
  163. (set-keymap-parent ein:notebook-multilang-mode-map ess-r-mode-map))))
  164. (defun ein:ml-lang-setup-haskell ()
  165. (when (featurep 'haskell-mode)
  166. (setq-local mode-name "EIN[haskell]")
  167. (setq-local comment-start "-- ")
  168. ;; (setq-local comment-start-skip "--\\s-*")
  169. (when (boundp 'haskell-indentation-indent-line)
  170. (setq-local indent-line-function
  171. (apply-partially #'ein:ml-indent-line-function #'haskell-indentation-indent-line)))
  172. (when (boundp 'haskell-mode-syntax-table)
  173. (set-syntax-table haskell-mode-syntax-table))
  174. (when (boundp 'haskell-mode-map)
  175. (set-keymap-parent ein:notebook-multilang-mode-map haskell-mode-map))))
  176. (defun ein:ml-lang-setup-hy ()
  177. (when (featurep 'hy-mode)
  178. (setq-local mode-name "EIN[hy]")
  179. (hy-mode--setup-font-lock)
  180. (hy-mode--setup-syntax)
  181. (hy-mode--support-smartparens)
  182. (set-keymap-parent ein:notebook-multilang-mode-map hy-mode-map)))
  183. (defun ein:ml-lang-setup-c++ ()
  184. (when (featurep 'c++-mode)
  185. (setq-local mode-name "EIN[c++]")
  186. (setq-local comment-start "// ")
  187. (setq-local indent-line-function
  188. (apply-partially #'ein:ml-indent-line-function #'c-indent-line))
  189. (set-syntax-table c++-mode-syntax-table)
  190. (set-keymap-parent ein:notebook-multilang-mode-map c++-mode-map)))
  191. (defun ein:ml-lang-setup-c ()
  192. (when (featurep 'c-mode)
  193. (setq-local mode-name "EIN[c]")
  194. (setq-local comment-start "/* ")
  195. (setq-local comment-end " */")
  196. (setq-local indent-line-function
  197. (apply-partially #'ein:ml-indent-line-function #'c-indent-line))
  198. (set-syntax-table c-mode-syntax-table)
  199. (set-keymap-parent ein:notebook-multilang-mode-map c-mode-map)))
  200. (defun ein:ml-lang-setup-C++11 ()
  201. (ein:ml-lang-setup-c++))
  202. (defun ein:ml-lang-setup-C++14 ()
  203. (ein:ml-lang-setup-c++))
  204. (defun ein:ml-lang-setup-C++17 ()
  205. (ein:ml-lang-setup-c++))
  206. (defun ein:ml-lang-setup-generic ()
  207. (setq-local mode-name "EIN[unknown]")
  208. (setq-local indent-line-function
  209. (apply-partially #'ein:ml-indent-line-function #'indent-relative))
  210. (set-syntax-table prog-mode-syntax-table)
  211. (set-keymap-parent ein:notebook-multilang-mode-map prog-mode-map))
  212. (defun ein:ml-lang-setup (kernelspec)
  213. (let ((setup-func (intern (concat "ein:ml-lang-setup-" (ein:$kernelspec-language kernelspec)))))
  214. (if (fboundp setup-func)
  215. (funcall setup-func)
  216. (warn "ein:ml-lang-setup: unknown kernelspec language '%s', multilang support disabled."
  217. (ein:$kernelspec-language kernelspec))
  218. (ein:ml-lang-setup-generic))))
  219. ;; (defun ein:ml-lang-setup-markdown ()
  220. ;; "Use `markdown-mode-map'. NOTE: This function is not used now."
  221. ;; (when (featurep 'markdown-mode)
  222. ;; (set-keymap-parent ein:notebook-multilang-mode-map markdown-mode-map)))
  223. ;;; yasnippet
  224. (defvar ein:ml-yasnippet-parents '(python-mode markdown-mode)
  225. "Parent modes for `ein:notebook-multilang-mode' to register in yasnippet.")
  226. (defun ein:ml-setup-yasnippet ()
  227. (cl-loop for define-parents in '(yas/define-parents
  228. yas--define-parents)
  229. when (fboundp define-parents)
  230. do (ignore-errors
  231. ;; `let' is for workaround the bug in yasnippet
  232. (let ((mode-sym 'ein:notebook-multilang-mode))
  233. (funcall define-parents
  234. mode-sym
  235. ein:ml-yasnippet-parents)))))
  236. (eval-after-load "yasnippet" '(ein:ml-setup-yasnippet))
  237. ;;; Imenu Support
  238. ;; Most of this is borrowed from python.el
  239. ;; Just replace python with ein in most cases.
  240. (defvar ein:imenu-format-item-label-function
  241. 'ein:imenu-format-item-label
  242. "Imenu function used to format an item label.
  243. It must be a function with two arguments: TYPE and NAME.")
  244. (defvar ein:imenu-format-parent-item-label-function
  245. 'ein:imenu-format-parent-item-label
  246. "Imenu function used to format a parent item label.
  247. It must be a function with two arguments: TYPE and NAME.")
  248. (defvar ein:imenu-format-parent-item-jump-label-function
  249. 'ein:imenu-format-parent-item-jump-label
  250. "Imenu function used to format a parent jump item label.
  251. It must be a function with two arguments: TYPE and NAME.")
  252. (defun ein:imenu-format-item-label (type name)
  253. "Return Imenu label for single node using TYPE and NAME."
  254. (format "%s (%s)" name type))
  255. (defun ein:imenu-format-parent-item-label (type name)
  256. "Return Imenu label for parent node using TYPE and NAME."
  257. (format "%s..." (ein:imenu-format-item-label type name)))
  258. (defun python-imenu-format-parent-item-jump-label (type _name)
  259. "Return Imenu label for parent node jump using TYPE and NAME."
  260. (if (string= type "class")
  261. "*class definition*"
  262. "*function definition*"))
  263. (defun ein:imenu--put-parent (type name pos tree)
  264. "Add the parent with TYPE, NAME and POS to TREE."
  265. (let ((label
  266. (funcall ein:imenu-format-item-label-function type name))
  267. (jump-label
  268. (funcall ein:imenu-format-parent-item-jump-label-function type name)))
  269. (if (not tree)
  270. (cons label pos)
  271. (cons label (cons (cons jump-label pos) tree)))))
  272. (defun ein:imenu--build-tree (&optional min-indent prev-indent tree)
  273. "Recursively build the tree of nested definitions of a node.
  274. Arguments MIN-INDENT, PREV-INDENT and TREE are internal and should
  275. not be passed explicitly unless you know what you are doing."
  276. (setq min-indent (or min-indent 0)
  277. prev-indent (or prev-indent python-indent-offset))
  278. (let* ((pos (python-nav-backward-defun))
  279. (type)
  280. (name (when (and pos (looking-at python-nav-beginning-of-defun-regexp))
  281. (let ((split (split-string (match-string-no-properties 0))))
  282. (setq type (car split))
  283. (cadr split))))
  284. (label (when name
  285. (funcall ein:imenu-format-item-label-function type name)))
  286. (indent (current-indentation))
  287. (children-indent-limit (+ python-indent-offset min-indent)))
  288. (cond ((not pos)
  289. ;; Nothing found, probably near to bobp.
  290. nil)
  291. ((<= indent min-indent)
  292. ;; The current indentation points that this is a parent
  293. ;; node, add it to the tree and stop recursing.
  294. (ein:imenu--put-parent type name pos tree))
  295. (t
  296. (ein:imenu--build-tree
  297. min-indent
  298. indent
  299. (if (<= indent children-indent-limit)
  300. ;; This lies within the children indent offset range,
  301. ;; so it's a normal child of its parent (i.e., not
  302. ;; a child of a child).
  303. (cons (cons label pos) tree)
  304. ;; Oh no, a child of a child?! Fear not, we
  305. ;; know how to roll. We recursively parse these by
  306. ;; swapping prev-indent and min-indent plus adding this
  307. ;; newly found item to a fresh subtree. This works, I
  308. ;; promise.
  309. (cons
  310. (ein:imenu--build-tree
  311. prev-indent indent (list (cons label pos)))
  312. tree)))))))
  313. (defun ein:imenu-create-index ()
  314. "Return tree Imenu alist for the current Python buffer.
  315. Change `ein:imenu-format-item-label-function',
  316. `ein:imenu-format-parent-item-label-function',
  317. `ein:imenu-format-parent-item-jump-label-function' to
  318. customize how labels are formatted."
  319. (goto-char (point-max))
  320. (let ((index)
  321. (tree))
  322. (while (setq tree (ein:imenu--build-tree))
  323. (setq index (cons tree index)))
  324. index))
  325. (defun ein:imenu-create-flat-index (&optional alist prefix)
  326. "Return flat outline of the current Python buffer for Imenu.
  327. Optional argument ALIST is the tree to be flattened; when nil
  328. `ein:imenu-build-index' is used with
  329. `ein:imenu-format-parent-item-jump-label-function'
  330. `ein:imenu-format-parent-item-label-function'
  331. `ein:imenu-format-item-label-function' set to
  332. (lambda (type name) name)
  333. Optional argument PREFIX is used in recursive calls and should
  334. not be passed explicitly.
  335. Converts this:
  336. ((\"Foo\" . 103)
  337. (\"Bar\" . 138)
  338. (\"decorator\"
  339. (\"decorator\" . 173)
  340. (\"wrap\"
  341. (\"wrap\" . 353)
  342. (\"wrapped_f\" . 393))))
  343. To this:
  344. ((\"Foo\" . 103)
  345. (\"Bar\" . 138)
  346. (\"decorator\" . 173)
  347. (\"decorator.wrap\" . 353)
  348. (\"decorator.wrapped_f\" . 393))"
  349. ;; Inspired by imenu--flatten-index-alist removed in revno 21853.
  350. (apply
  351. 'nconc
  352. (mapcar
  353. (lambda (item)
  354. (let ((name (if prefix
  355. (concat prefix "." (car item))
  356. (car item)))
  357. (pos (cdr item)))
  358. (cond ((or (numberp pos) (markerp pos))
  359. (list (cons name pos)))
  360. ((listp pos)
  361. (cons
  362. (cons name (cdar pos))
  363. (python-imenu-create-flat-index (cddr item) name))))))
  364. (or alist
  365. (let* ((fn (lambda (_type name) name))
  366. (ein:imenu-format-item-label-function fn)
  367. (ein:imenu-format-parent-item-label-function fn)
  368. (ein:imenu-format-parent-item-jump-label-function fn))
  369. (python-imenu-create-index))))))
  370. (provide 'ein-multilang)
  371. ;;; ein-multilang.el ends here