;;; 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 <norman@bellcore.com>
|
|
;; and Mark Lunt <mark.lunt@mrc-bsu.cam.ac.uk>
|
|
;; and A.J. Rossini <rossini@biostat.washington.edu>
|
|
;; 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 <ESS-core@r-project.org>
|
|
|
|
;; 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
|
|
;;
|
|
;; @
|
|
;; <<foo>>=
|
|
;; int foo;
|
|
;; @ %def foo
|
|
;; Here starts a new documentation chunk!
|
|
;; <<bar>>=
|
|
;; 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)
|
|
|
|
(defcustom Rnw-mode-hook nil
|
|
"Hook run when entering Rnw mode."
|
|
:type 'hook
|
|
:group 'ess-R)
|
|
|
|
(defvar-local ess--make-local-vars-permanent nil
|
|
"If this variable is non-nil in a buffer make all variable permannet.
|
|
Used in noweb modes.")
|
|
(put 'ess--make-local-vars-permanent 'permanent-local t)
|
|
|
|
(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-local ess--make-local-vars-permanent nil
|
|
"If this variable is non-nil in a buffer make all variable permannet.
|
|
Used in noweb modes.")
|
|
(put 'ess--make-local-vars-permanent 'permanent-local t)
|
|
|
|
(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 \"<<NAME>>\"."
|
|
(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
|
|
|
|
(defvar ess-version)
|
|
(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)))
|
|
(pop-to-buffer-same-window (get-buffer-create "*noweb-log*"))
|
|
(goto-char (point-max))
|
|
(setq buffer-read-only nil)
|
|
(insert s)
|
|
(setq buffer-read-only t)
|
|
(pop-to-buffer-same-window 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
|
|
|
|
;;;###autoload
|
|
(defun Rnw-mode ()
|
|
"Major mode for editing Sweave(R) source.
|
|
See `ess-noweb-mode' and `R-mode' for more help."
|
|
(interactive)
|
|
(require 'ess-noweb);; << probably someplace else
|
|
(setq ess--make-local-vars-permanent t)
|
|
(ess-noweb-mode 1); turn it on
|
|
(ess-noweb-set-doc-mode 'latex-mode)
|
|
(ess-noweb-set-code-mode 'R-mode)
|
|
(setq ess--local-handy-commands
|
|
(append '(("weave" . ess-swv-weave)
|
|
("tangle" . ess-swv-tangle))
|
|
ess-handy-commands)
|
|
ess-dialect "R"
|
|
ess-language "S")
|
|
(put 'ess--local-handy-commands 'permanent-local t)
|
|
(run-mode-hooks 'Rnw-mode-hook))
|
|
|
|
(fset 'Snw-mode 'Rnw-mode); just a synonym (for now or ever)
|
|
|
|
;;;###autoload
|
|
(add-to-list 'auto-mode-alist '("\\.[rR]nw\\'" . Rnw-mode))
|
|
;;;###autoload
|
|
(add-to-list 'auto-mode-alist '("\\.[sS]nw\\'" . Snw-mode))
|
|
|
|
|
|
;;; 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
|