134 lines
4.8 KiB
EmacsLisp
134 lines
4.8 KiB
EmacsLisp
;;; sx-babel.el --- font-locking pre blocks according to language -*- lexical-binding: t; -*-
|
||
|
||
;; Copyright (C) 2014 Artur Malabarba
|
||
|
||
;; Author: Artur Malabarba <bruce.connor.am@gmail.com>
|
||
|
||
;; 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:
|