-
Notifications
You must be signed in to change notification settings - Fork 40
/
sx-babel.el
133 lines (115 loc) · 4.83 KB
/
sx-babel.el
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
;;; sx-babel.el --- font-locking pre blocks according to language -*- lexical-binding: t; -*-
;; Copyright (C) 2014-2018 Artur Malabarba
;; Author: Artur Malabarba <[email protected]>
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This file contains functions and a variable for font-locking the
;; content of markdown pre blocks according to their language. The
;; main configuration point, for both the user and the developer is
;; the variable `sx-babel-major-mode-alist', which see.
;;; Code:
(require 'sx-button)
(defvar sx-babel-major-mode-alist
`((,(rx (or "*" "#+")) org-mode)
(,(rx (or "[" "(" ";" "#(")) emacs-lisp-mode)
;; @TODO: Make shell-mode work here. Currently errors because it
;; needs a process. `sh-mode' isn't as nice.
(,(rx (or "$ " "# ")) sh-mode)
;; Not sure if leaving out "[{" might lead to false positives.
(,(rx "\\" (+ alnum) (any "[{")) latex-mode)
;; Right now, this will match a lot of stuff. Once we are capable
;; of determining major-mode from tags, site, and comments, this
;; will work as a last case fallback.
(,(rx (or (and "int" (+ space) "main" (* space) "("))) c-mode)
)
"List of cons cells determining which major-mode to use when.
Each car is a rule and each cdr is a major-mode. The first rule
which is satisfied activates the major-mode.
Point is moved to the first non-blank character before testing
the rule, which can either be a string or a function. If it is a
string, is tested as a regexp starting from point. If it is a
function, is called with no arguments and should return non-nil
on a match.")
(put 'sx-babel-major-mode-alist 'risky-local-variable-p t)
;;; Font-locking the text
(defun sx-babel--make-pre-button (beg end)
"Turn the region between BEG and END into a button."
(let ((text (buffer-substring-no-properties beg end))
indent mode copy)
(with-temp-buffer
(insert text)
(setq indent (sx-babel--unindent-buffer))
(goto-char (point-min))
(setq mode (sx-babel--determine-major-mode))
(setq copy (replace-regexp-in-string "[[:space:]]+\\'" "" (buffer-string)))
(when mode
(delay-mode-hooks (funcall mode)))
(font-lock-fontify-region (point-min) (point-max))
(goto-char (point-min))
(let ((space (make-string indent ?\s)))
(while (not (eobp))
(insert-and-inherit space)
(forward-line 1)))
(setq text (buffer-string)))
(goto-char beg)
(delete-region beg end)
(insert-text-button
text
'sx-button-copy copy
;; We store the mode here so it can be used if the user wants
;; to edit the code block.
'sx-mode mode
:type 'sx-question-mode-code-block)))
(defun sx-babel--determine-major-mode ()
"Return the major-mode most suitable for the current buffer."
(let ((alist sx-babel-major-mode-alist)
cell out)
(while (setq cell (pop alist))
(goto-char (point-min))
(skip-chars-forward "\r\n[:blank:]")
(let ((kar (car cell)))
(when (if (stringp kar) (looking-at kar) (funcall kar))
(setq alist nil)
(setq out (cadr cell)))))
out))
(defun sx-babel--unindent-buffer ()
"Remove absolute indentation in current buffer.
Finds the least indented line, and removes that amount of
indentation from all lines. Primarily designed to extract the
content of markdown code blocks.
Returns the amount of indentation removed."
(save-excursion
(goto-char (point-min))
(let (result)
;; Get indentation of each non-blank line
(while (null (eobp))
(skip-chars-forward "[:blank:]")
(unless (looking-at "$")
(push (current-column) result))
(forward-line 1))
(when result
(setq result (apply #'min result))
;; Build a regexp with the smallest indentation
(let ((rx (format "^ \\{0,%s\\}" result)))
(goto-char (point-min))
;; Use this regexp to remove that much indentation
;; throughout the buffer.
(while (and (null (eobp))
(search-forward-regexp rx nil 'noerror))
(replace-match "")
(forward-line 1))))
(or result 0))))
(provide 'sx-babel)
;;; sx-babel.el ends here
;; Local Variables:
;; indent-tabs-mode: nil
;; End: