在Emacs中实现类似星球大战中字幕滚动的效果
正如不久前我提到过的, 我并不是严格意义上的星球大战的粉丝. 我从小的梦想其实是从政. 当然,即使我对星球大战第7季并不抱太大希望,我还是会去看它的.
另外,作为Emacs的超级大粉丝,我无法拒绝下面这一小端代码. 你可以试试,选中buffer中的一些文本,然后运行 star-wars-scroll
看看效果.
(一开始我是想录一段视频的,但是最终还是没有这样做: 毕竟你只需要拷贝这段代码到你的Emacs中就能自己看到笑过了. 当然如果你没有安装Emacs,那我也没有办法了)
这段代码还能在文本终端环境执行,这样效果更酷.
(defvar sw/substitution-list '(("." . 0) (" " . 0) ("[.,;:!?] " . 1) ("\\B[aeiou]\\B" . 0) ("\\B[bcdfghjklmnpqrstvwxyz]\\B" . 0) ("\\w\\b" . 0) ("[.,;:!?]" . 0) ("[^.,;:!?] " . 1) ("\\b\\w" . 0) (".$" . 0) ("^." . 0)) "A list of dotted pairs with car equal to the regex matching the character we want to delete and cdr equal to how many characters we want to move the point forward before actually deleting a character (useful in the case of space after a punctuation). We begin with the substitutions we want to perform first. If more than one regex matches, the last one is valid, so it is probably a good idea to begin with \".\".") (defun center-line-no-tabs () "A simplified version of center-line, using no tabs (and not taking into account leading/trailing whitespace." (save-excursion (let ((length (progn (end-of-line) (current-column)))) (beginning-of-line) (insert (make-string (max 0 (/ (- fill-column length) 2)) ?\s))))) (defun sw/scroll-prepare-marker-list () "Prepare (and return) a list of markers pointing at characters to delete from the current line, in the \"right\" order." (save-excursion (let ((limit (progn (center-line-no-tabs) ; this may use tabs! (back-to-indentation) (point))) (subst-list sw/substitution-list) (marker-list nil)) (while subst-list (end-of-line) (while (re-search-backward (caar subst-list) limit t) (forward-char (cdar subst-list)) (push (point-marker) marker-list)) (setq subst-list (cdr subst-list))) (delete-dups marker-list) (setq marker-list (nreverse marker-list))))) (defvar sw/untouched-lines 3 "Number of lines at the bottom of the window which should not be touched by character deletion.") (defvar sw/delay .5 "Delay (in seconds) between frames of animation.") (defun sw/scroll-current-buffer () "Actually do SW-like scroll in the current buffer." (let (marker-list-list) (beginning-of-buffer) (open-line (window-height)) (goto-char (point-max)) (move-beginning-of-line 0) (while (progn (push (sw/scroll-prepare-marker-list) marker-list-list) (> (point) (+ (point-min) (window-height)))) (previous-line)) (while (< (point-min) (point-max)) ; here the actual scroll begins (goto-char (point-min)) (kill-line 1) (redisplay t) (sleep-for sw/delay) (let ((walker marker-list-list)) (while (progn ; (goto-char (or (caar walker) (point-min))) (and walker (< (line-number-at-pos) (- (window-height) sw/untouched-lines)))) (when (car walker) (goto-char (caar walker)) (delete-char 1) (setf (car walker) (cdar walker))) (when (car walker) (goto-char (caar walker)) (delete-char 1) (setf (car walker) (cdar walker)) (beginning-of-line) (insert " ")) (setq walker (cdr walker))))))) (defun star-wars-scroll () "Do Star-Wars-like scroll of the region, or the whole buffer if no region is active, in a temporary buffer, and delete it afterwards. Special care is taken to make the lines more or less legible as long as possible, for example spaces after punctuation are deleted before vowels, vowels are deleted before consonants etc." (interactive) (save-excursion (let ((begin (point-min)) (end (point-max))) (when (region-active-p) (setq begin (region-beginning)) (setq end (region-end))) (copy-region-as-kill begin end) (with-temp-buffer (switch-to-buffer (current-buffer)) (rename-buffer "*Star Wars Scroll*") (untabify (point-min) (point-max)) (save-window-excursion (delete-other-windows) (yank) (sw/scroll-current-buffer))))))
这段代码还远远谈不上完美,代码凌乱而不好懂. 我是在几年前写得这段代码,当时我的Elisp知识远不及现在. (那时候我的博客elegant-code-fu也远没有现在这么多内容-至少我是这么认为的. 这些年来我学到很多东西; 毕竟,通过学习Lisp使我掌握了许多他人觉得不自然的能力.) 但是我还是保留了代码的原样,以此证明我每年都有进步: 我现在不会再写这么混乱的代码了. 你知道其他人是怎么评论这段代码的吗? - 这段代码的质量简直不敢恭维. 而且这段代码可能还有很多的BUG.(比如,不知道为什么,我总觉得这段代码在处理太长的行时会有问题. 我简直不能相信我三年前写得代码能这么烂…) 但是写这段代码的过程还是给我带来了许多乐趣. (我以后可能会从头重写这份代码,毕竟我觉得从这份代码目前的状态来看,重写比修改还来得更容易些…)
这段代码还有一个有趣的特点: 它会尽可能的保持文本的可读性. 例如它会先删除元音字母再删除辅音字母,先删除单词中间的字母再删除单词两端的单词.
玩的开心点,另外不要太把星球大战当回事了.