;;; ess-noweb-mode.el --- edit noweb files with GNU Emacs ;; Copyright (C) 1995 by Thorsten.Ohl @ Physik.TH-Darmstadt.de ;; with a little help from Norman Ramsey ;; and Mark Lunt ;; and A.J. Rossini ;; Copyright (C) 1999--2010 A.J. Rossini, Richard M. Heiberger, Martin ;; Maechler, Kurt Hornik, Rodney Sparapani, and Stephen Eglen. ;; Copyright (C) 2011--2012 A.J. Rossini, Richard M. Heiberger, Martin Maechler, ;; Kurt Hornik, Rodney Sparapani, Stephen Eglen and Vitalie Spinu. ;; ESS-related Changes first added by Mark Lunt and A.J. Rossini, March, 1999. ;; Maintainer: ESS-core ;; 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 2, 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. ;; ;; A copy of the GNU General Public License is available at ;; https://www.r-project.org/Licenses/ ;; See bottom of this file for information on language-dependent ;; highlighting, and recent changes. ;; ;; BASED ON: (from Mark Lunt). ;; -- Id: ess-noweb-mode.el,v 1.11 1999/03/21 20:14:41 root Exp -- ;;; NEWS: ;; * [tho] M-n q, aka: M-x ess-noweb-fill-chunk ;; ;; * [tho] `M-n TAB', aka: `M-x ess-noweb-complete-chunk' ;; ;; * [tho] ess-noweb-occur ;; ;; * [nr] use `M-n' instead of `C-c n' as default command prefix ;; ;; * [nr] don't be fooled by ;; ;; @ ;; <>= ;; int foo; ;; @ %def foo ;; Here starts a new documentation chunk! ;; <>= ;; int bar; ;; ;; * [nr] switch mode changing commands off during isearch-mode ;; ;; * [tho] ess-noweb-goto-chunk proposes a default ;; ;; * commands for tangling, weaving,.. for Sweave: --> ./ess-swv.el ;; ;;; TODO: ;; * _maybe_ replace our `ess-noweb-chunk-vector' by text properties. We ;; could then use highlighting to jazz up the visual appearance. ;; (Highlighting is sorted: `ess-noweb-chunk-vector' can be ;; ditched. It is simple to determine if we are in a doc or code ;; chunk.) ;; ;; * wrapped `ess-noweb-goto-next' and `ess-noweb-goto-previous' ;; ;; * more range checks and error exits ;; ;; * `ess-noweb-hide-code-quotes' should be superfluous now, and could ;; be removed. For ESS 5.3.10, we disable these, using the new variable ;; ess-noweb-code-quote-handling. If nobody misses that code-protecting ;; behavior, all that should be removed entirely. ;;; Code: (require 'ess-custom) (require 'ess-utils) (defvar weave-process) ;;; Variables ;; (defconst ess-noweb-mode-RCS-Id ;; "Imported to ESS Subversion repository and RCS ids not maintained.") ;; (defconst ess-noweb-mode-RCS-Name ;; " ") (defvar ess-noweb-mode-prefix "\M-n" "*Prefix key to use for noweb mode commands. The value of this variable is checked as part of loading noweb mode. After that, changing the prefix key requires manipulating keymaps.") (defvar ess-noweb-mode-load-hook nil "Hook that is run after noweb mode is loaded.") (defvar ess-noweb-mode-hook nil "Hook that is run after entering noweb mode.") (defvar ess-noweb-select-code-mode-hook nil "Hook that is run after the code mode is selected. This is the place to overwrite keybindings of the ess-noweb-CODE-MODE.") (defvar ess-noweb-select-doc-mode-hook nil "Hook that is run after the documentation mode is selected. This is the place to overwrite keybindings of the ess-noweb-DOC-MODE.") (defvar ess-noweb-select-mode-hook nil "Hook that is run after the documentation or the code mode is selected. This is the place to overwrite keybindings of the other modes.") (defvar ess-noweb-changed-chunk-hook nil "Hook that is run every time point moves from one chunk to another. It will be run whether or not the major-mode changes.") (defvar ess-noweb-default-code-mode 'fundamental-mode "Default major mode for editing code chunks. This is set to FUNDAMENTAL-MODE by default, but you might want to change this in the Local Variables section of your file to something more appropriate, like C-MODE, FORTRAN-MODE, or even INDENTED-TEXT-MODE.") (defvar ess-noweb-code-mode 'c-mode "Major mode for editing this particular code chunk. It defaults to ess-noweb-default-code-mode, but can be reset by a comment on the first line of the chunk containing the string \"-*- NEWMODE -*-\" or \"-*- NEWMODE-mode -*-\" or \"-*- mode: NEWMODE -*- \" or \"-*- mode: NEWMODE-mode -*- \" Option three is recommended, as it is the closest to standard emacs usage.") (defvar ess-noweb-default-doc-mode 'latex-mode "Major mode for editing documentation chunks. Sensible choices would be tex-mode, latex-mode, sgml-mode, or html-mode. Maybe others will exist someday.") (defvar ess-noweb-doc-mode-syntax-table nil "A syntax-table syntax table that makes quoted code in doc chunks to behave.") (defvar ess-noweb-last-chunk-index 0 "This keeps track of the chunk we have just been in. If this is not the same as the current chunk, we have to check if we need to change major mode.") (defvar ess-noweb-chunk-vector nil "Vector of the chunks in this buffer.") (defvar ess-noweb-narrowing nil "If not NIL, the display will always be narrowed to the current chunk pair.") (defvar ess-noweb-electric-@-and-< t "If not nil, the keys `@' and `<' will be bound to ess-noweb-ELECTRIC-@ and ess-noweb-ELECTRIC-<, respectively.") (defvar ess-noweb-use-mouse-navigation t "If not nil, enables moving between chunks using mouse-1. Clicking on the '<<' at the beginning of a chunk name takes you to the previous occurence of that chunk name, clicking on the '>>' takes you to the next. Assumes mouse-1 is bound to mouse-set-point, so if you have rebound mouse-1, this will override your binding.") (defvar ess-noweb-code-quotes-handling nil "If not nil, the function pair \\[ess-noweb-hide-code-quotes] and \\[ess-noweb-restore-code-quotes] are used to \"protect\" code inside \"[[\" .. \"]]\" pairs. Note that rarely this has been found to be buggy with the \"catastrophic\" consequence of whole parts of your document being replaced by sequences of '*'.") (defvar ess-noweb-doc-mode ess-noweb-default-doc-mode "Default major mode for editing noweb documentation chunks. It is not possible to have more than one doc-mode in a file. However, this variable is used to determine whether the doc-mode needs to by added to the mode-line") ;; The following is apparently broken -- dangling code that was ;; commented out. Need to see if we can get it working? (defvar ess-noweb-weave-options "-delay") (defvar ess-noweb-latex-viewer "xdvi") (defvar ess-noweb-html-viewer "netscape") (defun ess-noweb-weave (&optional name) (interactive) (let ((buffer (get-buffer-create "Weave Buffer"))) (if (not name) (progn ;; Assume latex documentation, but set to html if appropriate (if (eq ess-noweb-doc-mode 'html-mode) (setq name (concat (substring (buffer-file-name) 0 (string-match ".nw" name)) ".html")) (setq name (concat (substring (buffer-file-name) 0 (string-match ".nw" name)) ".tex"))))) (setq name (concat "> " name)) (setq ess-noweb-weave-options (concat ess-noweb-weave-options name)) (start-process weave-process buffer "noweave" ess-noweb-weave-options))) ;;(defun ess-noweb-view ()) ;;; Setup (defvar ess-noweb-mode nil "Buffer local variable, T iff this buffer is edited in noweb mode.") ;; For some reason that I do not understand, `newline' does not do the ;; right thing in quoted code. If point is not preceded by whitespace, ;; it moves to the beginning of the current line, not the beginning of ;; the new line. `newline 1' works fine, hence the kludge. I'd love to ;; understand what's going on, though. Try running M-x newline in the ;; middle of a code quote in a doc chunk to see ;; what I mean: its odd. (defun ess-noweb-newline (&optional arg) "A kludge to get round very odd behaviour of newline in quoted code." (interactive "p") (if arg (newline arg) (newline 1)) (ess-noweb-indent-line)) (defvar ess-noweb-mode-prefix-map (let ((map (make-sparse-keymap))) (define-key map "\C-\M-x" 'ess-eval-chunk) (define-key map "\C-c" 'ess-eval-chunk-and-step) (define-key map "\C-n" 'ess-noweb-next-chunk) (define-key map "\C-p" 'ess-noweb-previous-chunk) (define-key map "\M-n" 'ess-noweb-goto-next) (define-key map "\M-m" 'ess-noweb-insert-default-mode-line) (define-key map "\M-p" 'ess-noweb-goto-previous) (define-key map "c" 'ess-noweb-next-code-chunk) (define-key map "C" 'ess-noweb-previous-code-chunk) (define-key map "d" 'ess-noweb-next-doc-chunk) (define-key map "D" 'ess-noweb-previous-doc-chunk) (define-key map "g" 'ess-noweb-goto-chunk) (define-key map "\C-l" 'ess-noweb-update-chunk-vector) (define-key map "\M-l" 'ess-noweb-update-chunk-vector) (define-key map "w" 'ess-noweb-copy-chunk-as-kill) (define-key map "W" 'ess-noweb-copy-chunk-pair-as-kill) (define-key map "k" 'ess-noweb-kill-chunk) (define-key map "K" 'ess-noweb-kill-chunk-pair) (define-key map "m" 'ess-noweb-mark-chunk) (define-key map "M" 'ess-noweb-mark-chunk-pair) (define-key map "n" 'ess-noweb-narrow-to-chunk) (define-key map "N" 'ess-noweb-narrow-to-chunk-pair) (define-key map "t" 'ess-noweb-toggle-narrowing) (define-key map "\t" 'ess-noweb-complete-chunk) (define-key map "q" 'ess-noweb-fill-chunk) (define-key map "i" 'ess-noweb-new-chunk) (define-key map "o" 'ess-noweb-occur) ;;(define-key map "v" 'ess-noweb-mode-version) (define-key map "h" 'ess-noweb-describe-mode) ;; do *NOT* override C-h (give all keybindings startings with M-n! map) "noweb minor-mode prefix keymap") (defvar ess-noweb-minor-mode-map (let ((map (make-sparse-keymap))) (if ess-noweb-electric-@-and-< (progn (define-key map "@" 'ess-noweb-electric-@) (define-key map "<" 'ess-noweb-electric-<))) (define-key map "\M-q" 'ess-noweb-fill-paragraph-chunk) (define-key map [(control meta ?\\)] 'ess-noweb-indent-region) ;;(define-key map "\C-c\C-n" 'ess-noweb-indent-line) ; Override TeX-normal! (define-key map "\t" 'ess-noweb-indent-line) ;; (define-key map [tab] 'ess-noweb-indent-line) ;; interferes with ac (define-key map "\r" 'ess-noweb-newline) ;; (define-key map [return] 'ess-noweb-newline) ;; interferes with ac (define-key map [mouse-1] 'ess-noweb-mouse-first-button) (define-key map ess-noweb-mode-prefix ess-noweb-mode-prefix-map) map) "ESS Noweb minor mode keymap") (easy-menu-define ess-noweb-minor-mode-menu ess-noweb-minor-mode-map "Menu keymap for noweb." '("Noweb" ("Movement" ["Previous chunk" ess-noweb-previous-chunk t] ["Next chunk" ess-noweb-next-chunk t] ["Previous chunk of same name" ess-noweb-goto-previous t] ["Next chunk of same name" ess-noweb-goto-next t] ["Goto chunk" ess-noweb-goto-chunk t] ["Previous code chunk" ess-noweb-previous-code-chunk t] ["Next code chunk" ess-noweb-next-code-chunk t] ["Previous documentation chunk" ess-noweb-previous-doc-chunk t] ["Next documentation chunk" ess-noweb-next-doc-chunk t]) ("Editing" ["Copy chunk" ess-noweb-copy-chunk-as-kill t] ["Copy chunk pair" ess-noweb-copy-chunk-pair-as-kill t] ["Kill chunk" ess-noweb-kill-chunk t] ["Kill chunk pair" ess-noweb-kill-chunk-pair t] ["Mark chunk" ess-noweb-mark-chunk t] ["Mark chunk pair" ess-noweb-mark-chunk-pair t]) ("Narrowing" ["Narrow to chunk" ess-noweb-narrow-to-chunk t] ["Narrow to chunk pair" ess-noweb-narrow-to-chunk-pair t] ["Toggle auto narrowing" ess-noweb-toggle-narrowing t] ["Widen" widen t]) ("Modes" ["Set documentation mode" ess-noweb-set-doc-mode t] ["Set default code mode" ess-noweb-set-code-mode t] ["Set code mode for this chunk" ess-noweb-set-this-code-mode t] ["Insert default mode line" ess-noweb-insert-default-mode-line t]) ("Tangling" ["Tangle current chunk" ess-noweb-tangle-chunk t] ["Tangle current thread" ess-noweb-tangle-current-thread t] ["Tangle named thread" ess-noweb-tangle-thread t]) ("Miscellaneous" ["Complete chunk name" ess-noweb-complete-chunk t] ["Fill current chunk" ess-noweb-fill-chunk t] ["Insert new chunk" ess-noweb-new-chunk t] ["Update the chunk vector" ess-noweb-update-chunk-vector t] ["Chunk occurrences" ess-noweb-occur t]) "--" ["Help" ess-noweb-describe-mode t] ;;["Version" ess-noweb-mode-version t] )) ;; Add ess-noweb-mode to the list of minor modes (if (not (assq 'ess-noweb-mode minor-mode-alist)) (setq minor-mode-alist (append minor-mode-alist (list '(ess-noweb-mode " Noweb"))))) ;; Add ess-noweb-minor-mode-map to the list of minor-mode keymaps ;; available. Then, whenever ess-noweb-mode is activated, the keymap is ;; automatically activated (if (not (assq 'ess-noweb-mode minor-mode-map-alist)) (setq minor-mode-map-alist (cons (cons 'ess-noweb-mode ess-noweb-minor-mode-map) minor-mode-map-alist))) (defun ess-noweb-minor-mode (&optional arg) "Minor meta mode for editing noweb files. See ess-noweb-mode." (interactive) (ess-noweb-mode arg)) ; this was ess-noweb-minor-mode??? (truly recursive) (declare-function ess-noweb-font-lock-mode "ess-noweb-font-lock-mode") ;;;###autoload (defun ess-noweb-mode ( &optional arg ) "Minor meta mode for editing noweb files. `Meta' refers to the fact that this minor mode is switching major modes depending on the location of point. The following special keystrokes are available in noweb mode: Movement: \\[ess-noweb-next-chunk] \tgoto the next chunk \\[ess-noweb-previous-chunk] \tgoto the previous chunk \\[ess-noweb-goto-previous] \tgoto the previous chunk of the same name \\[ess-noweb-goto-next] \tgoto the next chunk of the same name \\[ess-noweb-goto-chunk] \t\tgoto a chunk \\[ess-noweb-next-code-chunk] \t\tgoto the next code chunk \\[ess-noweb-previous-code-chunk] \t\tgoto the previous code chunk \\[ess-noweb-next-doc-chunk] \t\tgoto the next documentation chunk \\[ess-noweb-previous-doc-chunk] \t\tgoto the previous documentation chunk Copying/Killing/Marking/Narrowing: \\[ess-noweb-copy-chunk-as-kill] \t\tcopy the chunk the point is in into the kill ring \\[ess-noweb-copy-chunk-pair-as-kill] \t\tcopy the pair of doc/code chunks the point is in \\[ess-noweb-kill-chunk] \t\tkill the chunk the point is in \\[ess-noweb-kill-chunk-pair] \t\tkill the pair of doc/code chunks the point is in \\[ess-noweb-mark-chunk] \t\tmark the chunk the point is in \\[ess-noweb-mark-chunk-pair] \t\tmark the pair of doc/code chunks the point is in \\[ess-noweb-narrow-to-chunk] \t\tnarrow to the chunk the point is in \\[ess-noweb-narrow-to-chunk-pair] \t\tnarrow to the pair of doc/code chunks the point is in \\[widen] \twiden \\[ess-noweb-toggle-narrowing] \t\ttoggle auto narrowing Filling and Indenting: \\[ess-noweb-fill-chunk] \tfill (or indent) the chunk at point according to mode \\[ess-noweb-fill-paragraph-chunk] \tfill the paragraph at point, restricted to chunk \\[ess-noweb-indent-line] \tindent the line at point according to mode Insertion: \\[ess-noweb-insert-default-mode-line] \tinsert a line to set this file's code mode \\[ess-noweb-new-chunk] \t\tinsert a new chunk at point \\[ess-noweb-complete-chunk] \tcomplete the chunk name before point \\[ess-noweb-electric-@] \t\tinsert a `@' or start a new doc chunk \\[ess-noweb-electric-<] \t\tinsert a `<' or start a new code chunk Modes: \\[ess-noweb-set-doc-mode] \t\tset the major mode for editing doc chunks \\[ess-noweb-set-code-mode] \tset the major mode for editing code chunks \\[ess-noweb-set-this-code-mode] \tset the major mode for editing this code chunk Misc: \\[ess-noweb-occur] \t\tfind all occurrences of the current chunk \\[ess-noweb-update-chunk-vector] \tupdate the markers for chunks \\[ess-noweb-describe-mode] \tdescribe ess-noweb-mode " (interactive "P") ;; This bit is tricky: copied almost verbatim from bib-cite-mode.el ;; It seems to ensure that the variable ess-noweb-mode is made ;; local to this buffer. It then sets ess-noweb-mode to `t' if ;; 1) It was called with an argument greater than 0 ;; or 2) It was called with no argument, and ess-noweb-mode is ;; currently nil ;; ess-noweb-mode is nil if the argument was <= 0 or there ;; was no argument and ess-noweb-mode is currently `t' (kill-all-local-variables) (set (make-local-variable 'ess-noweb-mode) (if arg (> (prefix-numeric-value arg) 0) (not ess-noweb-mode))) ;; Now, if ess-noweb-mode is true, we want to turn ;; ess-noweb-mode on (cond (ess-noweb-mode ;Setup the minor-mode (mapc 'ess-noweb-make-variable-permanent-local '(ess-noweb-mode ess-local-process-name ;; also made permanent in ess-mode, but let it be ess-dialect ess-language ess-use-flymake after-change-functions before-change-functions ess-noweb-narrowing ess-noweb-chunk-vector post-command-hook isearch-mode-hook isearch-mode-end-hook ess-noweb-doc-mode ess-noweb-code-mode ess-noweb-default-code-mode ess-noweb-last-chunk-index)) (setq-local ess-use-flymake nil) (ess-noweb-update-chunk-vector) (setq ess-noweb-last-chunk-index (if (equal 0 (ess-noweb-find-chunk-index-buffer)) 1 0)) (if font-lock-mode (progn (font-lock-mode -1) (ess-noweb-font-lock-mode 1))) (add-hook 'post-command-hook 'ess-noweb-post-command-function) (add-hook 'after-change-functions 'ess-noweb-after-change-function nil t) (add-hook 'before-change-functions 'ess-noweb-before-change-function nil t) (add-hook 'ess-noweb-select-doc-mode-hook 'ess-noweb-auto-fill-doc-mode) (add-hook 'ess-noweb-select-code-mode-hook 'ess-noweb-auto-fill-code-mode) (add-hook 'isearch-mode-hook 'ess-noweb-note-isearch-mode) (add-hook 'isearch-mode-end-hook 'ess-noweb-note-isearch-mode-end) (setq ess-noweb-doc-mode-syntax-table nil) (run-hooks 'ess-noweb-mode-hook) (message "noweb mode: use `M-x ess-noweb-describe-mode' for further information")) ;; If we didn't do the above, then we want to turn ess-noweb-mode ;; off, no matter what (hence the condition `t') (t (remove-hook 'post-command-hook 'ess-noweb-post-command-function) (remove-hook 'after-change-functions 'ess-noweb-after-change-function t) (remove-hook 'before-change-functions 'ess-noweb-before-change-function t) (remove-hook 'ess-noweb-select-doc-mode-hook 'ess-noweb-auto-fill-doc-mode) (remove-hook 'ess-noweb-select-code-mode-hook 'ess-noweb-auto-fill-code-mode) (remove-hook 'isearch-mode-hook 'ess-noweb-note-isearch-mode) (remove-hook 'isearch-mode-end-hook 'ess-noweb-note-isearch-mode-end) (if (and (boundp 'ess-noweb-font-lock-mode) ess-noweb-font-lock-mode) (progn (ess-noweb-font-lock-mode -1) (message "ESS-Noweb and ESS-Noweb-Font-Lock Modes Removed")) (message "ESS-Noweb mode removed"))))) (defun ess-noweb-make-variable-permanent-local (var) "Declare VAR buffer local, but protect it from beeing killed by major mode changes." (make-variable-buffer-local var) (put var 'permanent-local 't)) (defun ess-noweb-note-isearch-mode () "Take note of an incremental search in progress" (remove-hook 'post-command-hook 'ess-noweb-post-command-function)) (defun ess-noweb-note-isearch-mode-end () "Take note of an incremental search having ended" (add-hook 'post-command-hook 'ess-noweb-post-command-function)) (defun ess-noweb-post-command-function () "The hook being run after each command in noweb mode." (ess-noweb-select-mode)) (defvar ess-noweb-chunk-boundary-changed nil "Whether the current change affects a chunk boundary.") (defvar ess-noweb-chunk-boundary-regexp "^\\(@[^@]\\)\\|\\(<<\\)") (defun ess-noweb-before-change-function (begin end) "Record changes to chunk boundaries." (save-excursion (goto-char begin) (setq ess-noweb-chunk-boundary-changed (re-search-forward ess-noweb-chunk-boundary-regexp end t)))) (defun ess-noweb-after-change-function (begin end length) "Function to run after every change in a noweb buffer. If the changed region contains a chunk boundary, it will update the chunk vector" (save-excursion (goto-char begin) (when (or ess-noweb-chunk-boundary-changed (re-search-forward ess-noweb-chunk-boundary-regexp end t)) (ess-noweb-update-chunk-vector) (setq ess-noweb-chunk-boundary-changed nil)))) ;;; Chunks (defun ess-noweb-update-chunk-vector () "Scan the whole buffer and place a marker at each \"^@\" and \"^<<\". Record them in ess-noweb-CHUNK-VECTOR." (interactive) (save-excursion (goto-char (point-min)) (let ((chunk-list (list (cons 'doc (point-marker))))) (while (re-search-forward "^\\(@\\( \\|$\\|\\( %def\\)\\)\\|<<\\(.*\\)>>=\\)" nil t) (goto-char (match-beginning 0)) ;; If the 3rd subexpression matched @ %def, we're still in a code ;; chunk (sort of), so don't place a marker here. (if (not (match-beginning 3)) (setq chunk-list ;; If the 4th subexpression matched inside <<...>>, ;; we're seeing a new code chunk. (cons (cons (if (match-beginning 4) ;;buffer-substring-no-properties better ;;than buffer-substring if highlighting ;;may be used (buffer-substring-no-properties (match-beginning 4) (match-end 4)) 'doc) (point-marker)) chunk-list)) ;; Scan forward either to !/^@ %def/, which will start a docs chunk, ;; or to /^<<.*>>=$/, which will start a code chunk. (progn (forward-line 1) (while (looking-at "@ %def") (forward-line 1)) (setq chunk-list ;; Now we can tell code vs docs (cons (cons (if (looking-at "<<\\(.*\\)>>=") (buffer-substring-no-properties (match-beginning 1) (match-end 1)) 'doc) (point-marker)) chunk-list)))) (forward-line 1)) (setq chunk-list (cons (cons 'doc (point-max-marker)) chunk-list)) (setq ess-noweb-chunk-vector (vconcat (reverse chunk-list)))))) (defun ess-noweb-find-chunk () "Return a pair consisting of the name (or 'DOC) and the marker of the current chunk." (if (not ess-noweb-chunk-vector) (ess-noweb-update-chunk-vector)) (aref ess-noweb-chunk-vector (ess-noweb-find-chunk-index-buffer))) (defun ess-noweb-chunk-is-code (index) "Return t if the chunk 'index' is a code chunk, nil otherwise" (interactive) (stringp (car (ess-noweb-chunk-vector-aref index)))) (defun ess-noweb-in-code-chunk () "Return t if we are in a code chunk, nil otherwise." (interactive) (ess-noweb-chunk-is-code (ess-noweb-find-chunk-index-buffer))) (defun ess-noweb-in-mode-line () "Return the name of the mode to use if we are in a mode line, nil otherwise." (interactive) (let (beg end mode) (save-excursion (beginning-of-line 1) (and (progn (ess-write-to-dribble-buffer (format "(n-i-m-l: 1)")) (search-forward "-*-" (save-excursion (end-of-line) (point)) t)) (progn (ess-write-to-dribble-buffer (format "(n-i-m-l: 2)")) (skip-chars-forward " \t") (setq beg (point)) (search-forward "-*-" (save-excursion (end-of-line) (point)) t)) (progn (ess-write-to-dribble-buffer (format "(n-i-m-l: 3)")) (forward-char -3) (skip-chars-backward " \t") (setq end (point)) (goto-char beg) (setq mode (concat (downcase (buffer-substring beg end)) "-mode")) (if (and (>= (length mode) 11)) (progn (if (equal (substring mode -10 -5) "-mode") (setq mode (substring mode 0 -5))) (if (equal (substring mode 0 5) "mode:") (setq mode (substring mode 6)))))) (progn (ess-write-to-dribble-buffer (format "(n-i-m-l: 3) mode=%s" mode)) (intern mode)))))) (defun ess-noweb-find-chunk-index-buffer () "Return the index of the current chunk in ess-noweb-CHUNK-VECTOR." (ess-noweb-find-chunk-index 0 (1- (length ess-noweb-chunk-vector)))) (defun ess-noweb-find-chunk-index (low hi) (if (= hi (1+ low)) low (let ((med (/ (+ low hi) 2))) (if (< (point) (cdr (aref ess-noweb-chunk-vector med))) (ess-noweb-find-chunk-index low med) (ess-noweb-find-chunk-index med hi))))) (defun ess-noweb-chunk-region () "Return a pair consisting of the beginning and end of the current chunk." (interactive) (let ((start (ess-noweb-find-chunk-index-buffer))) (cons (marker-position (cdr (aref ess-noweb-chunk-vector start))) (marker-position (cdr (aref ess-noweb-chunk-vector (1+ start))))))) (defun ess-noweb-copy-code-chunk () "Copy the current code chunk to the kill ring, excluding the chunk name. This will be particularly useful when interfacing with ESS." (interactive) (let ((r (ess-noweb-chunk-region))) (save-excursion (goto-char (car r)) (if (ess-noweb-in-code-chunk) (progn (beginning-of-line 2) (copy-region-as-kill (point) (cdr r))))))) (defun ess-noweb-extract-code-chunk () "Create a new buffer with the same name as the current code chunk, and copy all code from chunks of the same name to it." (interactive) (save-excursion (if (ess-noweb-in-code-chunk) (progn (let ((chunk-name (car (ess-noweb-find-chunk))) (chunk-counter 0) (copy-counter 0) (this-chunk) (oldbuf (current-buffer))) (if (get-buffer chunk-name) (progn (set-buffer-modified-p nil) (kill-buffer chunk-name))) (get-buffer-create chunk-name) (message "Created buffer %s" chunk-name) (while (< chunk-counter (- (length ess-noweb-chunk-vector) 2)) (setq this-chunk (ess-noweb-chunk-vector-aref chunk-counter)) (message "Current buffer is %s" (car this-chunk)) (if (equal chunk-name (car this-chunk)) (progn (setq copy-counter (+ copy-counter 1)) (goto-char (cdr this-chunk)) (ess-noweb-copy-code-chunk) (set-buffer chunk-name) (goto-char (point-max)) (yank) (set-buffer oldbuf))) (setq chunk-counter (+ chunk-counter 1))) (message "Copied %d bits" copy-counter) (set-buffer chunk-name) (copy-region-as-kill (point-min)(point-max))))))) (defun ess-noweb-chunk-pair-region () "Return a pair consisting of the beginning and end of the current pair of documentation and code chunks." (interactive) (let* ((start (ess-noweb-find-chunk-index-buffer)) (end (1+ start))) (if (ess-noweb-chunk-is-code start) (cons (marker-position (cdr (aref ess-noweb-chunk-vector (1- start)))) (marker-position (cdr (aref ess-noweb-chunk-vector end)))) (while (not (ess-noweb-chunk-is-code end)) (setq end (1+ end))) (cons (marker-position (cdr (aref ess-noweb-chunk-vector start))) (marker-position (cdr (aref ess-noweb-chunk-vector (1+ end)))))))) (defun ess-noweb-chunk-vector-aref (i) (if (< i 0) (error "Before first chunk.")) (if (not ess-noweb-chunk-vector) (ess-noweb-update-chunk-vector)) (if (>= i (length ess-noweb-chunk-vector)) (error "Beyond last chunk.")) (aref ess-noweb-chunk-vector i)) (defun ess-noweb-complete-chunk () "Complete the chunk name before point, if any." (interactive) (if (ess-noweb-in-code-chunk) (let ((end (point)) (beg (save-excursion (if (re-search-backward "<<" (save-excursion (beginning-of-line) (point)) t) (match-end 0) nil)))) (if beg (let* ((pattern (buffer-substring beg end)) (alist (ess-noweb-build-chunk-alist)) (completion (try-completion pattern alist))) (cond ((eq completion t)) ((null completion) (message "Can't find completion for \"%s\"" pattern) (ding)) ((not (string= pattern completion)) (delete-region beg end) (insert completion) (if (not (looking-at ">>")) (insert ">>"))) (t (message "Making completion list...") (with-output-to-temp-buffer "*Completions*" (display-completion-list (all-completions pattern alist))) (message "Making completion list...%s" "done")))) (message "Not at chunk name..."))) (message "Not in code chunk..."))) ;;; Filling, etc (defun ess-noweb-hide-code-quotes () "Replace all non blank characters in [[...]] code quotes in the current buffer (you might want to narrow to the interesting region first) by `*'. Return a list of pairs with the position and value of the original strings." (save-excursion (let ((quote-list nil)) (goto-char (point-min)) (while (re-search-forward "\\[\\[" nil 'move) (let ((beg (match-end 0)) (end (if (re-search-forward "\\]\\]" nil t) (match-beginning 0) (point-max)))) (goto-char beg) (while (< (point) end) ;; Move on to the next word: (let ((b (progn (skip-chars-forward " \t\n" end) (point))) (e (progn (skip-chars-forward "^ \t\n" end) (point)))) (if (> e b) ;; Save the string and a marker to the end of the ;; replacement text. A marker to the beginning is ;; useless. See ess-noweb-RESTORE-CODE-QUOTES. (save-excursion (setq quote-list (cons (cons (copy-marker e) (buffer-substring b e)) quote-list)) (goto-char b) (insert-char ?* (- e b) t) (delete-char (- e b)))))))) (reverse quote-list)))) (defun ess-noweb-restore-code-quotes (quote-list) "Reinsert the strings modified by `ess-noweb-hide-code-quotes'." (save-excursion (mapcar (lambda (q) (let* ((e (marker-position (car q))) ;; Slightly inefficient, but correct way to find ;; the beginning of the word to be replaced. ;; Using the marker at the beginning will loose ;; if whitespace has been rearranged (b (save-excursion (goto-char e) (skip-chars-backward "*") (point)))) (delete-region b e) (goto-char b) (insert (cdr q)))) quote-list))) (defun ess-noweb-fill-chunk () "Fill the current chunk according to mode. Run `fill-region' on documentation chunks and `indent-region' on code chunks." (interactive) (save-excursion (save-restriction (ess-noweb-narrow-to-chunk) (if (ess-noweb-in-code-chunk) (progn ;; Narrow to the code section proper; w/o the first and any ;; index declaration lines. (narrow-to-region (progn (goto-char (point-min)) (forward-line 1) (point)) (progn (goto-char (point-max)) (forward-line -1) (while (looking-at "@") (forward-line -1)) (forward-line 1) (point))) (if (or indent-region-function indent-line-function) (indent-region (point-min) (point-max) nil) (error "No indentation functions defined in %s!" major-mode))) (if ess-noweb-code-quotes-handling (let ((quote-list (ess-noweb-hide-code-quotes))) (fill-region (point-min) (point-max)) (ess-noweb-restore-code-quotes quote-list)) (fill-region (point-min) (point-max))))))) (defun ess-noweb-indent-region (beg end) "If region fits inside current chunk, narrow to chunk and then indent according to mode." (interactive "r") (let* ((inx (ess-noweb-find-chunk-index-buffer)) (ch-beg (marker-position (cdr (aref ess-noweb-chunk-vector inx)))) (ch-end (marker-position (cdr (aref ess-noweb-chunk-vector (1+ inx)))))) (if (and (< ch-beg beg) (> ch-end end)) (save-excursion (save-restriction (setq beg (max beg (progn (goto-char ch-beg) (forward-line 1) (point)))) (setq end (min end (progn (goto-char ch-end) (forward-line -1) (point)))) (narrow-to-region beg end) (indent-region beg end))) (indent-region beg end)))) (defun ess-noweb-indent-line () "Indent the current line according to mode, after narrowing to this chunk." (interactive) (ess-noweb-update-chunk-vector) (save-restriction (ess-noweb-narrow-to-chunk) (if (ess-noweb-in-code-chunk) (progn ;; Narrow to the code section proper; w/o the first and any ;; index declaration lines. (save-excursion (narrow-to-region (progn (goto-char (point-min)) (forward-line 1) (point)) (progn (goto-char (point-max)) (forward-line -1) (while (looking-at "@") (forward-line -1)) (forward-line 1) (point)))))) (indent-according-to-mode))) (defun ess-noweb-fill-paragraph-chunk (&optional justify) "Fill a paragraph in the current chunk." (interactive "P") (ess-noweb-update-chunk-vector) (save-excursion (save-restriction (ess-noweb-narrow-to-chunk) (if (ess-noweb-in-code-chunk) (progn ;; Narrow to the code section proper; w/o the first and any ;; index declaration lines. (narrow-to-region (progn (goto-char (point-min)) (forward-line 1) (point)) (progn (goto-char (point-max)) (forward-line -1) (while (looking-at "@") (forward-line -1)) (forward-line 1) (point))) (fill-paragraph justify)) (if ess-noweb-code-quotes-handling (let ((quote-list (ess-noweb-hide-code-quotes))) (fill-paragraph justify) (ess-noweb-restore-code-quotes quote-list)) (fill-paragraph justify)))))) (defun ess-noweb-auto-fill-doc-chunk () "Replacement for `do-auto-fill'." (save-restriction (narrow-to-region (car (ess-noweb-chunk-region)) (save-excursion (end-of-line) (point))) (if ess-noweb-code-quotes-handling (let ((quote-list (ess-noweb-hide-code-quotes))) (do-auto-fill) (ess-noweb-restore-code-quotes quote-list)) (do-auto-fill)))) (defun ess-noweb-auto-fill-doc-mode () "Install the improved auto fill function, iff necessary." (if auto-fill-function (setq auto-fill-function 'ess-noweb-auto-fill-doc-chunk))) (defun ess-noweb-auto-fill-code-chunk () "Replacement for do-auto-fill. Cancel filling in chunk headers" (unless (save-excursion (beginning-of-line) (looking-at "<<")) (do-auto-fill))) (defun ess-noweb-auto-fill-code-mode () "Install the default auto fill function, iff necessary." (if auto-fill-function (setq auto-fill-function 'ess-noweb-auto-fill-code-chunk))) ;;; Marking (defun ess-noweb-mark-chunk () "Mark the current chunk." (interactive) (let ((r (ess-noweb-chunk-region))) (goto-char (car r)) (push-mark (cdr r) nil t))) (defun ess-noweb-mark-chunk-pair () "Mark the current pair of documentation and code chunks." (interactive) (let ((r (ess-noweb-chunk-pair-region))) (goto-char (car r)) (push-mark (cdr r) nil t))) ;;; Narrowing (defun ess-noweb-toggle-narrowing (&optional arg) "Toggle if we should narrow the display to the current pair of documentation and code chunks after each movement. With argument: switch narrowing on." (interactive "P") (if (or arg (not ess-noweb-narrowing)) (progn (setq ess-noweb-narrowing t) (ess-noweb-narrow-to-chunk-pair)) (setq ess-noweb-narrowing nil) (widen))) (defun ess-noweb-narrow-to-chunk () "Narrow the display to the current chunk." (interactive) (let ((r (ess-noweb-chunk-region))) (narrow-to-region (car r) (cdr r)))) (defun ess-noweb-narrow-to-chunk-pair () "Narrow the display to the current pair of documentation and code chunks." (interactive) (let ((r (ess-noweb-chunk-pair-region))) (narrow-to-region (car r) (cdr r)))) ;;; Killing (defun ess-noweb-kill-chunk () "Kill the current chunk." (interactive) (let ((r (ess-noweb-chunk-region))) (kill-region (car r) (cdr r)))) (defun ess-noweb-kill-chunk-pair () "Kill the current pair of chunks." (interactive) (let ((r (ess-noweb-chunk-pair-region))) (kill-region (car r) (cdr r)))) (defun ess-noweb-copy-chunk-as-kill () "Place the current chunk on the kill ring." (interactive) (let ((r (ess-noweb-chunk-region))) (copy-region-as-kill (car r) (cdr r)))) (defun ess-noweb-copy-chunk-pair-as-kill () "Place the current pair of chunks on the kill ring." (interactive) (let ((r (ess-noweb-chunk-pair-region))) (copy-region-as-kill (car r) (cdr r)))) ;;; Movement (defun ess-noweb-sign (n) "Return the sign of N." (if (< n 0) -1 1)) (defun ess-noweb-next-doc-chunk (&optional cnt) "Goto to the Nth documentation chunk from point." (interactive "p") (widen) (let ((start (ess-noweb-find-chunk-index-buffer)) (i 1)) (while (<= i (abs cnt)) (setq start (+ (ess-noweb-sign cnt) start)) (while (ess-noweb-chunk-is-code start) (setq start (+ (ess-noweb-sign cnt) start))) (setq i (1+ i))) (goto-char (marker-position (cdr (ess-noweb-chunk-vector-aref start)))) (forward-char 1)) (if ess-noweb-narrowing (ess-noweb-narrow-to-chunk-pair))) (defun ess-noweb-previous-doc-chunk (&optional n) "Goto to the -Nth documentation chunk from point." (interactive "p") (ess-noweb-next-doc-chunk (- n))) (defun ess-noweb-next-code-chunk (&optional cnt) "Goto to the Nth code chunk from point." (interactive "p") (widen) (let ((start (ess-noweb-find-chunk-index-buffer)) (i 1)) (while (<= i (abs cnt)) (setq start (+ (ess-noweb-sign cnt) start)) (while (not (ess-noweb-chunk-is-code start)) (setq start (+ (ess-noweb-sign cnt) start))) (setq i (1+ i))) (goto-char (marker-position (cdr (ess-noweb-chunk-vector-aref start)))) (forward-line 1)) (if ess-noweb-narrowing (ess-noweb-narrow-to-chunk-pair))) (defun ess-noweb-previous-code-chunk (&optional n) "Goto to the -Nth code chunk from point." (interactive "p") (ess-noweb-next-code-chunk (- n))) (defun ess-noweb-next-chunk (&optional n) "If in a documentation chunk, goto to the Nth documentation chunk from point, else goto to the Nth code chunk from point." (interactive "p") (if (ess-noweb-in-code-chunk) (ess-noweb-next-code-chunk n) (ess-noweb-next-doc-chunk n))) (defun ess-noweb-previous-chunk (&optional n) "If in a documentation chunk, goto to the -Nth documentation chunk from point, else goto to the -Nth code chunk from point." (interactive "p") (ess-noweb-next-chunk (- n))) (defvar ess-noweb-chunk-history nil "") (defun ess-noweb-goto-chunk () "Goto the named chunk." (interactive) (widen) (let* ((completion-ignore-case t) (alist (ess-noweb-build-chunk-alist)) (chunk (ess-completing-read "Chunk" (delete "" (mapcar 'car alist)) nil t nil ess-noweb-chunk-history (ess-noweb-goto-chunk-default)))) (goto-char (cdr (assoc chunk alist)))) (if ess-noweb-narrowing (ess-noweb-narrow-to-chunk-pair))) (defun ess-noweb-goto-chunk-default () (save-excursion (if (re-search-backward "<<" (save-excursion (beginning-of-line) (point)) 'move) (goto-char (match-beginning 0))) (if (re-search-forward "<<\\(.*\\)>>" (save-excursion (end-of-line) (point)) t) (buffer-substring (match-beginning 1) (match-end 1)) nil))) (defun ess-noweb-build-chunk-alist () (if (not ess-noweb-chunk-vector) (ess-noweb-update-chunk-vector)) ;; The naive recursive solution will exceed MAX-LISP-EVAL-DEPTH in ;; buffers w/ many chunks. Maybe there is a tail recursivce solution, ;; but iterative solutions should be acceptable for dealing with vectors. (let ((alist nil) (i (1- (length ess-noweb-chunk-vector)))) (while (>= i 0) (let* ((chunk (aref ess-noweb-chunk-vector i)) (name (car chunk)) (marker (cdr chunk))) (if (and (stringp name) (not (assoc name alist))) (setq alist (cons (cons name marker) alist)))) (setq i (1- i))) alist)) (defun ess-noweb-goto-next (&optional cnt) "Goto the continuation of the current chunk." (interactive "p") (widen) (if (not ess-noweb-chunk-vector) (ess-noweb-update-chunk-vector)) (let ((start (ess-noweb-find-chunk-index-buffer))) (if (not (ess-noweb-chunk-is-code start)) (setq start (1+ start))) (if (ess-noweb-chunk-is-code start) (let ((name (car (ess-noweb-chunk-vector-aref start))) (i 1)) (while (<= i (abs cnt)) (setq start (+ (ess-noweb-sign cnt) start)) (while (not (equal (car (ess-noweb-chunk-vector-aref start)) name)) (setq start (+ (ess-noweb-sign cnt) start))) (setq i (1+ i))) (goto-char (marker-position (cdr (ess-noweb-chunk-vector-aref start)))) (forward-line 1)))) (if ess-noweb-narrowing (ess-noweb-narrow-to-chunk-pair))) (defun ess-noweb-goto-previous (&optional cnt) "Goto the previous chunk." (interactive "p") (ess-noweb-goto-next (- cnt))) (defun ess-noweb-occur (arg) "Find all occurences of the current chunk. This function simply runs OCCUR on \"<>\"." (interactive "P") (let ((n (if (and arg (numberp arg)) arg 0)) (idx (ess-noweb-find-chunk-index-buffer))) (if (ess-noweb-chunk-is-code idx) (occur (regexp-quote (concat "<<" (car (aref ess-noweb-chunk-vector idx)) ">>")) n) (setq idx (1+ idx)) (while (not (ess-noweb-chunk-is-code idx)) (setq idx (1+ idx))) (occur (regexp-quote (concat "<<" (car (aref ess-noweb-chunk-vector idx)) ">>")) n)))) ;;; Insertion (defun ess-noweb-new-chunk (name) "Insert a new chunk." (interactive "sChunk name: ") (insert "@ \n<<" name ">>=\n") (save-excursion (insert "@ %def \n")) (ess-noweb-update-chunk-vector)) (defun ess-noweb-at-beginning-of-line () (equal (save-excursion (beginning-of-line) (point)) (point))) (defun ess-noweb-electric-@ (arg) "Smart incarnation of `@', starting a new documentation chunk, maybe. If given an numerical argument, it will act just like the dumb `@'. Otherwise and if at the beginning of a line in a code chunk: insert \"@ \" and update the chunk vector." (interactive "P") (if arg (self-insert-command (if (numberp arg) arg 1)) (if (and (ess-noweb-at-beginning-of-line) (ess-noweb-in-code-chunk)) (progn (insert "@ ") (ess-noweb-update-chunk-vector)) (self-insert-command 1)))) (defun ess-noweb-electric-< (arg) "Smart incarnation of `<', starting a new code chunk, maybe. If given an numerical argument, it will act just like the dumb `<'. Otherwise and if at the beginning of a line in a documentation chunk: insert \"<<>>=\", a closing \"@\" and a newline if necessary. Leave point in the middle and and update the chunk vector." (interactive "P") (if arg (self-insert-command (if (numberp arg) arg 1)) (if (and (ess-noweb-at-beginning-of-line) (not (ess-noweb-in-code-chunk))) (progn (insert "<<") (save-excursion (insert ">>=\n@ ") (if (not (looking-at "\\s *$")) (newline))) (ess-noweb-update-chunk-vector)) (self-insert-command 1)))) ;;; Modes (defun ess-noweb-set-chunk-code-mode () "Set the ess-noweb-code-mode for the current chunk" (interactive) (if (ess-noweb-in-code-chunk) (progn ;; Reset code-mode to default and then check for a mode comment. (setq ess-noweb-code-mode ess-noweb-default-code-mode) (let (mode chunk-name) (save-excursion (save-restriction (end-of-line) (re-search-backward "^[ \t]*<<\\(.*\\)>>=" nil t) (setq chunk-name (match-string 1)) (widen) (goto-char (point-min)) (re-search-forward (concat "^<<" (regexp-quote chunk-name) ">>=") nil t) (beginning-of-line 2) (setq mode (ess-noweb-in-mode-line)) (if (functionp mode) (setq ess-noweb-code-mode mode)))))) (error "This only makes sense in a code chunk"))) (defun ess-noweb-set-doc-syntax-table () "Sets the doc-mode syntax-table to treat code quotes as comments." (interactive) (let ((square-bracket-string (char-to-string (char-syntax ?\[)))) (if (string= square-bracket-string "(") (progn (modify-syntax-entry ?\[ "(]12b" ess-noweb-doc-mode-syntax-table) (modify-syntax-entry ?\] ")[34b" ess-noweb-doc-mode-syntax-table)) (progn (modify-syntax-entry ?\[ (concat square-bracket-string " 12b") ess-noweb-doc-mode-syntax-table) (modify-syntax-entry ?\] (concat square-bracket-string " 34b") ess-noweb-doc-mode-syntax-table))))) (defun ess-noweb-select-mode () "Select ess-noweb-DOC-MODE or ess-noweb-CODE-MODE, as appropriate." (interactive) (let ((this-chunk-index (ess-noweb-find-chunk-index-buffer))) ;; Has the last change to the buffer taken us into a different ;; chunk ? (if (not (equal this-chunk-index ess-noweb-last-chunk-index)) (progn (setq ess-noweb-last-chunk-index this-chunk-index) (if (ess-noweb-in-code-chunk) ;; Inside a code chunk (progn ;; Find out which code mode to use (ess-noweb-set-chunk-code-mode) ;; If we aren't already using it, use it. (if (not (equal major-mode ess-noweb-code-mode)) (progn (funcall ess-noweb-code-mode) (run-hooks 'ess-noweb-select-mode-hook) (run-hooks 'ess-noweb-select-code-mode-hook)))) ;; Inside a documentation chunk (progn (if (not (equal major-mode ess-noweb-doc-mode)) (progn (funcall ess-noweb-doc-mode))) (if (not ess-noweb-doc-mode-syntax-table) (progn (message "Setting up syntax table") (setq ess-noweb-doc-mode-syntax-table (make-syntax-table (syntax-table))) (ess-noweb-set-doc-syntax-table))) (set-syntax-table ess-noweb-doc-mode-syntax-table) (run-hooks 'ess-noweb-select-mode-hook) (run-hooks 'ess-noweb-select-doc-mode-hook))) (run-hooks 'ess-noweb-changed-chunk-hook))))) (defun ess-noweb-set-doc-mode (mode) "Change the major mode for editing documentation chunks." (interactive "CNew major mode for documentation chunks: ") (setq ess-noweb-doc-mode mode) (setq ess-noweb-doc-mode-syntax-table nil) ;;Pretend we've changed chunk, so the mode will be reset if necessary (setq ess-noweb-last-chunk-index (1- ess-noweb-last-chunk-index)) (ess-noweb-select-mode)) (defun ess-noweb-set-code-mode (mode) "Change the major mode for editing all code chunks." (interactive "CNew major mode for all code chunks: ") (setq ess-noweb-default-code-mode mode) ;;Pretend we've changed chunk, so the mode will be reset if necessary (setq ess-noweb-last-chunk-index (1- ess-noweb-last-chunk-index)) (ess-noweb-select-mode)) (defun ess-noweb-set-this-code-mode (mode) "Change the major mode for editing this code chunk. The only sensible way to do this is to add a mode line to the chunk" (interactive "CNew major mode for this code chunk: ") (if (ess-noweb-in-code-chunk) (progn (setq ess-noweb-code-mode mode) (save-excursion (save-restriction (let (chunk-name) (widen) (end-of-line) (re-search-backward "^[ \t]*<<\\(.*\\)>>=" nil t) (setq chunk-name (match-string 1)) (goto-char (point-min)) (re-search-forward (concat "^<<" (regexp-quote chunk-name) ">>=") nil t) (beginning-of-line 2)) ;; remove mode-line, if there is one (if (ess-noweb-in-mode-line) (progn (kill-line) (kill-line))) (if (not (equal ess-noweb-code-mode ess-noweb-default-code-mode)) (progn (setq mode (substring (symbol-name mode) 0 -5)) ;; Need to set major mode so that we can comment out ;; the mode line (funcall ess-noweb-code-mode) (if (not (boundp 'comment-start)) (setq comment-start "#")) (insert comment-start " -*- " mode " -*- " comment-end "\n"))) (setq ess-noweb-last-chunk-index (1- ess-noweb-last-chunk-index))))) (message "This only makes sense in a code chunk."))) ;;; Misc (defun ess-noweb-mode-version () "Echo the RCS identification of noweb mode." (interactive) (message "Thorsten's ess-noweb-mode, now part of ESS version %s" ess-version)) (defun ess-noweb-describe-mode () "Describe noweb mode." (interactive) (describe-function 'ess-noweb-mode)) (defun ess-noweb-insert-default-mode-line () "Insert line that will set the noweb mode of this file in emacs. The file is set to use the current doc and default-code modes, so ensure they are set correctly (with ess-noweb-set-code-mode and ess-noweb-set-doc-mode) before calling this function" (interactive) (save-excursion (goto-char 1) (if (ess-noweb-in-mode-line) (progn (kill-line) (kill-line))) (if (not (eq major-mode ess-noweb-doc-mode)) (ess-noweb-select-mode)) (insert comment-start " -*- mode: noweb; ess-noweb-default-code-mode: " (symbol-name ess-noweb-default-code-mode) (if (not (eq ess-noweb-doc-mode ess-noweb-default-doc-mode)) (concat "; ess-noweb-doc-mode: " (symbol-name ess-noweb-doc-mode) ";") ";") " -*-" comment-end "\n")) (ess-noweb-select-mode)) (defun ess-noweb-mouse-first-button (event) (interactive "e") (mouse-set-point event) (if (and ess-noweb-use-mouse-navigation (eq (save-excursion (end-of-line) (re-search-backward "^[\t ]*\\(<<\\)\\(.*\\)\\(>>\\)" nil t)) (save-excursion (beginning-of-line) (point)))) (progn (if (< (point) (match-beginning 2)) (let ((chunk-name (buffer-substring-no-properties (match-beginning 2) (match-end 2)))) (re-search-backward (concat "<<" (regexp-quote chunk-name) ">>") nil t)) (if (and (<= (match-end 2) (point)) (> (+ 2 (match-end 2)) (point))) (let ((chunk-name (buffer-substring-no-properties (match-beginning 2) (match-end 2)))) (re-search-forward (concat "<<" (regexp-quote chunk-name) ">>") nil t))))))) ;;; Debugging (defun ess-noweb-log (s) (let ((b (current-buffer))) (switch-to-buffer (get-buffer-create "*noweb-log*")) (goto-char (point-max)) (setq buffer-read-only nil) (insert s) (setq buffer-read-only t) (switch-to-buffer b))) (defvar ess-noweb-thread-alist nil "A list of threads in the current buffer. Each entry in the list contains 5 elements: 1) The name of the threads 2) The name of the immdiate parent thread in which it is used (nil if it is a \"top-level\" thread which is not used anywhere). 3) The name of the top-level parent thread in which it is used (i.e. a thread in which it is used but which is not itself used anywhere: nil if this thread is not used anywhere. 4) The format string to use to define line numbers in the output file of this thread. Should only be set if this thread is not used anywhere: if a thread is used as part of another thread, the parent thread's format string should be used. 5) If this is nil, tabs are converted to spaces in the tangled file. If it is a number, tabs are copied to the tangled file unchanged, and tabs are also used for indentation, with the number of spaces per tab defined by this number. This MUST be set in order to tangle makefiles, which depend on tabs.Should only be set if this thread is not used anywhere. otherwise set to nil. " ) (defun ess-noweb-update-thread-alist () "Updates the list of threads in the current buffer. Each entry in the list contains 5 elements: 1) The name of the thread 2) The name of the immdiate parent thread in which it is used (nil if it is a \"top-level\" thread which is not used anywhere). 3) The name of the top-level parent thread in which it is used (i.e. a thread in which it is used but which is not itself used anywhere: nil if this thread is not used anywhere. 4) The format string to use to define line numbers in the output file of this thread. Should only be set if this thread is not used anywhere: if a thread is used as part of another thread, the parent thread's format string should be used. 5) If this is nil, tabs are converted to spaces in the tangled file. If it is a number, tabs are copied to the tangled file unchanged, and tabs are also used for indentation, with the number of spaces per tab defined by this number. This MUST be set in order to tangle makefiles, which depend on tabs.Should only be set if this thread is not used anywhere. otherwise set to nil. " (interactive) (save-excursion (goto-char (point-min)) (let ((thread-alist) (thread-list-entry) (chunk-use-name) (current-thread) (new-thread-alist)) (while (re-search-forward "^[ \t]*<<\\(.*\\)>>\\(=\\)?" nil t) (goto-char (match-beginning 0)) ;; Is this the definition of a chunk ? (if (match-beginning 2) ;;We have a chunk definition (progn ;; Get the thread name (setq current-thread (buffer-substring-no-properties (match-beginning 1) (match-end 1))) ;; Is this thread already in our list ? (if (assoc current-thread thread-alist) nil (progn ;; If not, create an entry with 4 nils at the end (setq thread-list-entry (list (cons current-thread (make-list 4 nil)))) ;; And add it to the list (setq thread-alist (append thread-alist thread-list-entry))))) ;; Not a definition but a use (progn ;; Get the thread name (setq chunk-use-name (buffer-substring-no-properties (match-beginning 1) (match-end 1))) ;; Has the thread already been defined before being used ? (if (setq thread-list-entry (assoc chunk-use-name thread-alist)) ;; If it has, set its parent to be the thread we are in at the moment (setcar (cdr thread-list-entry) current-thread) ;; If not, add it to the list, with its parent name and 3 nils (progn (setq thread-list-entry (list (cons chunk-use-name (cons current-thread (make-list 3 nil))))) (setq thread-alist (append thread-alist thread-list-entry))))) ) ;;Go to the next line (beginning-of-line 2)) ;; Now, the second element of each entry points to that thread's ;; immediate parent. Need to set it to the thread's ultimate ;; parent. (let ((thread-counter 0) (this-thread) (this-thread-parent)) (while (<= thread-counter (1- (length thread-alist))) (setq this-thread (nth thread-counter thread-alist)) (setq this-thread-parent (assoc (car (cdr this-thread)) thread-alist)) (while (not (equal nil (car (cdr this-thread-parent)))) (setq this-thread-parent (assoc (car (cdr this-thread-parent)) thread-alist))) (setq this-thread (cons (car this-thread) (cons (car (cdr this-thread)) (cons (car this-thread-parent) (nthcdr 2 this-thread))))) (setq new-thread-alist (append new-thread-alist (list this-thread))) (setq thread-counter (1+ thread-counter)))) (setq ess-noweb-thread-alist new-thread-alist)))) ; Option setting functions to go here (defun ess-noweb-set-thread-line-format ()) (defun ess-noweb-set-thread-tabs ()) (defvar ess-noweb-default-line-number-format nil "The format string to use to define line numbers in this thread. If nil, do not use line numbers.") (defvar ess-noweb-default-line-number-skip-lines 0 "The number of initial lines to output before the line number. This may be useful in shell scripts, where the first line (or two) must have a specific form.") (defvar ess-noweb-default-tab-width 8 "If a number, convert tabs to that number of spaces in the output. If nil, let tabs through to the output unaltered.") (defvar ess-noweb-line-number-format ess-noweb-default-line-number-format "The format string to use to define line numbers in this thread. If nil, do not use line numbers.") (defvar ess-noweb-line-number-skip-lines ess-noweb-default-line-number-skip-lines "The number of initial lines to output before the line number. This may be useful in shell scripts, where the first line (or two) must have a specific form.") (defvar ess-noweb-tab-width ess-noweb-default-tab-width "If a number, convert tabs to that number of spaces in the output. If nil, let tabs through to the output unaltered.") (defun ess-noweb-get-thread-local-variables () "Get the values of the variables that are local to a thread." (interactive) (save-excursion (save-restriction (end-of-line) (re-search-backward "^[ \t]*<<\\(.*\\)>>=" nil t) (let ((chunk-name (match-string 1))) (widen) (goto-char (point-min)) (re-search-forward (concat "^<<" (regexp-quote chunk-name) ">>=") nil t) (beginning-of-line 2) (while (looking-at ".*-\*-.*-\*-") (let ((this-line (buffer-substring-no-properties (point) (progn (end-of-line) (point))))) (if (string-match "mode:[ \t]*\\([^\t ]*\\)" this-line) (setq ess-noweb-code-mode (match-string-no-properties 1 this-line))) (if (string-match "ess-noweb-line-number-format:[ \t]*\"\\([^\"]*\\)\"" this-line) (setq ess-noweb-line-number-format (match-string-no-properties 1 this-line))) (if (string-match "ess-noweb-line-number-skip-lines:[ \t]*\\([^\t ]*\\)" this-line) (setq ess-noweb-line-number-skip-lines (string-to-number (match-string-no-properties 1 this-line)))) (if (string-match "ess-noweb-tab-width:[ \t]*\\([^\t ]*\\)" this-line) (setq ess-noweb-tab-width (string-to-number (match-string-no-properties 1 this-line)))) (beginning-of-line 2))))))) (defun ess-noweb-reset-thread-local-variables () "Resets the thread-local variables to their default values" (setq ess-noweb-tab-width ess-noweb-default-tab-width) (setq ess-noweb-line-number-format ess-noweb-default-line-number-format) (setq ess-noweb-line-number-skip-lines ess-noweb-default-line-number-skip-lines)) (defun ess-noweb-write-line-number (line-number-format buffer) (if line-number-format (progn (let ((this-line (count-lines (point-min)(point)))) (while (string-match ".*\\(%L\\).*" line-number-format) (setq line-number-format (replace-match (format "%d" this-line) t t line-number-format 1))) (while (string-match ".*\\(%F\\).*" line-number-format) (setq line-number-format (replace-match (format "%s" (buffer-file-name)) t t line-number-format 1))) (while (string-match ".*\\(%N\\).*" line-number-format) (setq line-number-format (replace-match "\n" t t line-number-format 1))) (with-current-buffer buffer (insert line-number-format)))))) (defun ess-noweb-tangle-chunk ( &optional buffer prefix-string) "Generate the code produced by this chunk, & any threads used in this chunk." (interactive) (save-excursion (ess-noweb-reset-thread-local-variables) (ess-noweb-get-thread-local-variables) (ess-noweb-update-chunk-vector) (let* ((chunk-end (progn (end-of-line) (re-search-forward "^@" nil t) (beginning-of-line) (point))) ;;get name and start point of this chunk (chunk-start (progn (re-search-backward "^<<\\([^>]*\\)>>=$" nil t) (beginning-of-line 2) (point))) (chunk-name (buffer-substring-no-properties (match-end 1) (match-beginning 1))) ;; get end of this chunk ;; Get information we need about this thread (thread-info (assoc chunk-name ess-noweb-thread-alist)) (thread-tabs (nth 4 thread-info)) (line-number-format (nth 3 thread-info)) (thread-name-re) (post-chunk) (pre-chunk) (first-line t) (tangle-buffer (generate-new-buffer "Tangle Buffer"))) (progn (goto-char chunk-start) ;; If this is a mode-line, ignore it (while (looking-at ".*-\\*-.*-\\*-") (beginning-of-line 2)) ;; If we want to include line numbers, write one (if line-number-format (while (> ess-noweb-line-number-skip-lines 0) (append-to-buffer tangle-buffer (point) (save-excursion (progn (end-of-line) (point)))) (beginning-of-line 2) (1- ess-noweb-line-number-skip-lines)) (ess-noweb-write-line-number line-number-format buffer)) (message "Now at %d" (point)) (while (< (point) chunk-end) (untabify (point) (save-excursion (beginning-of-line 2)(point))) ;; This RE gave me trouble. Without the `\"', it ;; recognised itself and so could not copy itself ;; correctly. (if (looking-at "\\([^\n\"@]*\\)<<\\(.*\\)\\(>>\\)\\([^\n\"]*\\)$") (progn (save-excursion (save-restriction (setq thread-name-re (concat "<<" (regexp-quote (match-string 2)) ">>=")) (setq pre-chunk (match-string 1)) (if prefix-string (setq pre-chunk (concat prefix-string pre-chunk))) (setq post-chunk (match-string 4)) (widen) (goto-char (point-min)) (while (re-search-forward thread-name-re nil t) (ess-noweb-tangle-chunk tangle-buffer pre-chunk) (forward-line 1))) (if post-chunk (with-current-buffer tangle-buffer (backward-char) (insert post-chunk) (beginning-of-line 2))))) ;; Otherwise, just copy this line (setq pre-chunk (buffer-substring (point) (save-excursion (beginning-of-line 2) (point)))) ;; Add a prefix if necessary (if (and prefix-string (> (length pre-chunk) 1)) (setq pre-chunk (concat prefix-string pre-chunk))) ;; And copy it to the buffer (with-current-buffer tangle-buffer (insert pre-chunk))) ;; If this is the first line of the chunk, we need to change ;; prefix-string to consist solely of spaces (if (and first-line prefix-string) (progn (setq prefix-string (make-string (length prefix-string) ?\ )) (setq first-line nil))) ;; Either way, go to the next line (beginning-of-line 2)) (with-current-buffer tangle-buffer (goto-char (point-min)) (while (re-search-forward "\@\<<" nil t) (replace-match "<<" nil nil) (forward-char 3)) (if thread-tabs (progn (setq tab-width thread-tabs) (tabify (point-min)(point-max))) (untabify (point-min)(point-max)))) (if buffer (with-current-buffer buffer (insert-buffer-substring tangle-buffer) (kill-buffer tangle-buffer))) )))) (defun ess-noweb-tangle-thread ( name &optional buffer) "Given the name of a thread, tangles the thread to buffer. If no buffer is given, create a new one with the same name as the thread." (interactive "sWhich thread ? ") (if (not buffer) (progn (setq buffer (get-buffer-create name)) (with-current-buffer buffer (erase-buffer)))) (save-excursion (goto-char (point-min)) (let ((chunk-counter 0)) (while (re-search-forward "^<<\\(.*\\)>>=[\t ]*" nil t) (if (string= (match-string 1) name) (progn (setq chunk-counter (1+ chunk-counter)) (message "Found %d chunks" chunk-counter) (ess-noweb-tangle-chunk buffer))))))) (defun ess-noweb-tangle-current-thread ( &optional buffer) (interactive) (save-excursion (let* ((chunk-start (progn (re-search-backward "^<<\\([^>]*\\)>>=[\t ]*$" nil t) (beginning-of-line 2) (point))) (chunk-name (buffer-substring-no-properties (match-end 1) (match-beginning 1)))) (ess-noweb-tangle-thread chunk-name buffer)))) ;menu functions ;;; Finale (run-hooks 'ess-noweb-mode-load-hook) (provide 'ess-noweb-mode) ;; Changes made by Mark Lunt (mark.lunt@mrc-bsu.cam.ac.uk) 22/03/1999 ;; The possibility of having code chunks using more than one language ;; was added. This was first developed by Adnan Yaqub ;; (AYaqub@orga.com) for syntax highlighting, but even people who hate ;; highlighting may like to maintain their Makefile with their code, ;; or test-scripts with their programs, or even user documentation as ;; latex-mode code chunks. ;; This required quite a few changes to ess-noweb-mode: ;; 1) A new variable `ess-noweb-default-code-mode' was create to do the job ;; `ess-noweb-code-mode' used to. ;; 2) ess-noweb-code-mode now contains the code-mode of the current chunk ;; 3) Each chunk can now have its own mode-line to tell emacs what ;; mode to use to edit it. The function `ess-noweb-in-mode-line' ;; recognises such mode-lines, and the function ;; `ess-noweb-set-this-code-mode' sets the code mode for the current ;; chunk and adds a mode-line if necessary. If several chunks have ;; the same name, the mode-line must appear in the first chunk with ;; that name. ;; 4) The mechanism for deciding whether to change mode was altered, ;; since the old method assumed a single code mode. Now, ;; `ess-noweb-last-chunk-index' keeps track of which chunk we were in ;; last. If we have moved to a different chunk, we have to check ;; which mode we should be in, and change if necessary. ;; The keymap and menu-map handling was changed. Easymenu was used to ;; define the menu, and it the keymap was attached to the 'official' ;; minor-modes-keymaps list. This means that ;; 1) It was automatically loaded when ess-noweb-mode was active and ;; unloaded when it was inactive. ;; 2) There was no need to worry about the major mode map clobbering ;; it , since it takes precedence over the major mode ;; map. `ess-noweb-setup-keymap' is therefore now superfluous ;; The menu was also reorganised to make it less cluttered, so there ;; would be room for adding tangling and weaving commands (one day). ;; Mouse navigation (at least under Emacs (AJR)) is supported, in so ;; far as clicking mouse-1 on the '<<' of a chunk name moves to the ;; previous instance of that chunk name, and clicking in the '>>' ;; moves to the next instance. They are not mouse-hightlighted, ;; though: too much hassle for zero added functionality. ;; ess-noweb-doc-mode has been given its own syntax-table. It is the same ;; as the current doc-mode syntax-table, except that [[ is a comment ;; start and ]] a comment end. Fixes some ugliness in LaTeX-mode if ;; `$' or `%' appear in quoted code (or even `<<', which happens often ;; in C++). ;; (This should make ess-noweb-hide-code-quotes and ;; ess-noweb-restore-code-quotes unnecessary, but I have not yet removed ;; them, nor the calls to them). ;; A new function `ess-noweb-indent-line' was defined and bound by default ;; to the tab key. This should indent the current line correctly in ;; whichever mode we are currently in. Previously, c-mode in ;; particular did not behave well with indentation (although ;; `ess-noweb-fill-chunk' worked fine). Indentation is only accurate ;; within the chunk: it does not know the syntax at the end of the ;; previous chunk, so it does not know where to start indenting in ;; this chunk. However, provided the indentation within each chunk is correct, ;; notangle will correctly indented code. ;; (I think it would be good to separate filling and indenting, ;; though, since `indent-region' and `fill-region' have completely ;; different meanings in LaTeX-mode (and both are useful)) ;; ess-noweb-mode and ess-noweb-minor-mode were given an optional argument, so ;; that (ess-noweb-mode -1) turns it off, (ess-noweb-mode 1) turns it on, and ;; (ess-noweb-mode) toggles it. This is considered normal for minor modes. ;; buffer-substring changed to buffer-substring-no-properties: ;; comparisons with buffer-substring can be unreliable if highlighting ;; is used. ;; New functions `ess-noweb-in-code-chunk' & `ess-noweb-chunk-is-code' created ;; to replace (if (stringp (car (ess-noweb-find-chunk)))) and ;; (if (stringp (car (ess-noweb-chunk-vector-aref index)))). ;; `ess-noweb-insert-mode-line' was renamed ;; `ess-noweb-insert-default-mode-line' and modified to put the mode-line ;; at the start of the file and remove any existing mode-line. ;; a '<=' in `ess-noweb-find-chunk-index' changed to '<', so we get the ;; right answer if point is on the first character in a chunk ;; The name of `ess-noweb-post-command-hook' changed to ;; `ess-noweb-post-command-function', since it is a function. ;; All the highlighting code moved to a separate file: ;; (ess-noweb-font-lock-mode.el) ;; Menu driven tangling is in the process of being added. It can ;; currently tangle a single chunk or a series of chunks with the ;; same name (which I refer to as a thread) into a separate ;; buffer. This buffer can then be saved to a file, sent to an ;; interpreter, whatever. I haven't tested using line-numbers as yet. ;;; ess-noweb-mode.el ends here