spe@cad.cs.cmu.edu (Sean Engelson) (01/20/87)
Keywords:
Following is a Scribe mode that I wrote, it does all kinds of nice
things, just do a describe-function on scribe-mode to find out what
they are. All comments are welcome, further versions (if any) will be
posted.
;; Copyright (c) 1986 Sean Philip Engelson
;; All GNU Emacs copyright restrictions and permissions apply
;; Some parts of this file are modified from tex-mode.el in the
;; GNU Emacs v18+ distribution, those portions are
;; Copyright (C) 1985 Richard M. Stallman
;; Rewritten following contributions by William F. Schelter
;; and Dick King (king@kestrel).
;; This file is part of GNU Emacs.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY. No author or distributor
;; accepts responsibility to anyone for the consequences of using it
;; or for whether it serves any particular purpose or works at all,
;; unless he says so in writing. Refer to the GNU Emacs General Public
;; License for full details.
;; Everyone is granted permission to copy, modify and redistribute
;; GNU Emacs, but only under the conditions described in the
;; GNU Emacs General Public License. A copy of this license is
;; supposed to have been given to you along with GNU Emacs so you
;; can know your rights and responsibilities. It should be in a
;; file named COPYING. Among other things, the copyright notice
;; and this notice must be preserved on all copies.
(defvar scribe-mode-syntax-table nil
"Syntax table used while in scribe mode.")
(defvar scribe-mode-hook nil
"Function (if any) to be called on invocation of scribe mode.")
(defvar scribe-zap-file nil
"Temporary file name used for text being sent as input to scribe.")
(defvar scribe-command "cd /tmp; scribe"
"The command to run scribe on a file in /tmp, to make output in /tmp.")
(defvar scribe-press-print-command "cz"
"Command string used to print a .press file.")
(defvar scribe-trailer "\n"
"Scribe input supplied after the end of a region sent to scribe by M-x scribe-region.")
(defvar scribe-big-env-region-size 150
"Size to use @begin and @end for region environments")
(defvar scribe-open-paren "[\\[({<\"'`]"
"Regular expression that matches a valid scribe open paren")
(defvar scribe-close-paren "[])}>\"']"
"Regular expression that matches a valid scribe close paren")
(defvar scribe-default-open "{"
"Default open paren for scribe commands")
(defvar scribe-default-close "}"
"Default close paren for scribe commands")
(defvar scribe-mode-map nil)
(if scribe-mode-map
nil
(setq scribe-mode-map (make-sparse-keymap))
(define-key scribe-mode-map "\C-j" 'scribe-terminate-paragraph)
(define-key scribe-mode-map "\C-ce" 'scribe-environment-block)
(define-key scribe-mode-map "\C-cr" 'scribe-region)
(define-key scribe-mode-map "\C-cs" 'scribe-buffer)
(define-key scribe-mode-map "\C-cp" 'scribe-print)
(define-key scribe-mode-map "\C-c\C-f" 'forward-scribe-command)
(define-key scribe-mode-map "\C-c\C-b" 'backward-scribe-command)
(define-key scribe-mode-map "\C-c\C-a" 'beginning-of-scribe-env)
(define-key scribe-mode-map "\C-c\C-e" 'end-of-scribe-env)
(define-key scribe-mode-map "\e(" 'parenthesize-word)
(define-key scribe-mode-map "\C-cw" 'scribe-env-wrap-line)
(define-key scribe-mode-map "\C-c\C-w" 'scribe-env-wrap-region)
(define-key scribe-mode-map "\C-cb" 'scribe-boldface-word)
(define-key scribe-mode-map "\C-ci" 'scribe-italicize-word)
(define-key scribe-mode-map "\C-cu" 'scribe-underline-word)
(define-key scribe-mode-map "\C-c\C-c" (global-key-binding "\C-c")))
(defun validate-scribe-buffer ()
"Check current buffer for paragraphs containing mismatched parens.
As each such paragraph is found, a mark is pushed at its beginning,
and the location is displayed for a few seconds. Mismatched @begin/@end
pairs are also detected and displayed."
(interactive)
(let ((opoint (point)))
(if (eq (forward-scribe-begin-end (point)) 'error)
(sit-for 4))
(goto-char (point-max))
;; Does not use save-excursion
;; because we do not want to save the mark.
(unwind-protect
(while (and (not (input-pending-p)) (not (bobp)))
(let ((end (point)))
(search-backward "\n\n" nil 'move)
(or (scribe-validate-paragraph (point) end)
(progn
(push-mark (point))
(message "Mismatch found in pararaph starting here")
(sit-for 4)))))
(goto-char opoint))))
(defun scribe-validate-paragraph (start end)
(condition-case ()
(save-excursion
(save-restriction
(narrow-to-region start end)
(goto-char start)
(forward-sexp (- end start))
t))
(error nil)))
(defun scribe-terminate-paragraph (inhibit-validation)
"Insert two newlines, breaking a paragraph for scribe.
Check for mismatched parens in paragraph being terminated.
A prefix arg inhibits the checking."
(interactive "P")
(or inhibit-validation
(scribe-validate-paragraph
(save-excursion
(search-backward "\n\n" nil 'move)
(point))
(point))
(message "Paragraph being closed appears to contain a mismatch"))
(insert "\n\n"))
;; Invoking scribe in an inferior shell.
(defun scribe-region (beg end)
"Run scribe on current region. Optionally process buffer's header
first. The buffer's header is everything up to a line saying
\"@comment(end of header)\". It is processed as input by scribe
before the region itself. The file has a header if one of the first
ten lines says \"@comment(start of header)\". The value of
scribe-trailer is supplied as input to scribe after the region. It
defaults to \"\\n\"."
(interactive "r")
(or (get-buffer "*scribe-shell*")
(progn
(require 'shell)
(make-shell "scribe-shell" "csh")))
(or scribe-zap-file (setq scribe-zap-file (make-temp-name "/tmp/tz")))
(let ((scribe-out-file (concat scribe-zap-file ".mss")))
(save-excursion
(goto-char (point-min))
(forward-line 10)
(let ((search-end (point))
hbeg)
(goto-char (point-min))
;; Initialize the temp file with either the header or nothing
(if (and (search-forward "@comment(start of header)" search-end t)
(< (point) beg))
(progn
(forward-line 1)
(setq hbeg (point))
(search-forward "@comment(end of header)")
(beginning-of-line)
(write-region hbeg (point) scribe-out-file))
(write-region (point) (point) scribe-out-file))
;; Append the region to be printed.
(write-region beg end scribe-out-file t)))
(send-string "scribe-shell" (concat scribe-command " " scribe-out-file "\n")))
(if scribe-trailer
(send-string "scribe-shell" scribe-trailer))
(pop-to-buffer "*scribe-shell*"))
(defun scribe-buffer ()
"Run scribe on current buffer."
(interactive)
(let (scribe-trailer)
(scribe-region (point-min) (point-max))))
(defun scribe-print ()
"Print the .press file made by \\[scribe-region] or \\[scribe-buffer]."
(interactive)
(send-string "scribe-shell"
(concat scribe-press-print-command " " scribe-zap-file ".press\n")))
(defun scribe-environment-block (env)
"Create a @begin()/@end() environment block"
(interactive "sEnvironment: ")
(insert "@begin(" env ")\n")
(save-excursion
(insert "\n@end(" (substring env 0 (string-match "," env)) ")\n")))
(defun forward-scribe-command (count)
"Go forward to the next Scribe command in the buffer. With prefix arg,
go forward that many commands."
(interactive "p")
(forward-char)
(if (> count 0)
(progn
(re-search-forward "@[^@]" nil nil count)
(backward-char 2))
(re-search-backward "@[^@]" nil nil (- count))))
(defun backward-scribe-command (count)
"Go backward to the next Scribe command in the buffer. With prefix arg,
go backward that many commands."
(interactive "p")
(forward-scribe-command (- count)))
(defun scribe-insert-parens ()
"Make a pair of default parens and be poised to type inside of them."
(interactive)
(insert scribe-default-open)
(save-excursion
(insert scribe-default-close)))
(defun parenthesize-region (beg end)
"Put regular parens () around the current region."
(interactive "r")
(save-excursion
(goto-char beg)
(insert "(")
(goto-char end)
(insert ")")))
(defun parenthesize-line (point)
"Put regular parens () around the current line."
(interactive "d")
(save-excursion
(goto-char point)
(beginning-of-line)
(let ((b (point)))
(end-of-line)
(forward-char)
(parenthesize-region b (point)))))
(defun parenthesize-word (point)
"Put regular parens () around the current word. With a prefix arg,
parenthesize that many words."
(interactive "d")
(save-excursion
(goto-char point)
(forward-char)
(forward-word -1)
(let ((b (point)))
(forward-word 1)
(forward-char)
(parenthesize-region b (point)))))
(defun scribe-env-wrap-region (beg end env)
"Wrap an environment around a region. If the region is bigger than
SCRIBE-BIG-ENV-REGION-SIZE, use @begin and @end to delimit the environment."
(interactive "r\nsEnvironment: ")
(if (> (- end beg) scribe-big-env-region-size)
(save-excursion
(goto-char end)
(insert "@end(" (substring env 0 (string-match "," env)) ")")
(goto-char beg)
(insert "@begin(" env ")\n"))
(save-excursion
(goto-char end)
(insert scribe-default-close)
(goto-char beg)
(insert "@" env scribe-default-open))))
(defun scribe-env-wrap-line (env)
"Wrap a scribe environment around the current line."
(interactive "sEnvironment: ")
(save-excursion
(beginning-of-line)
(insert "@" env scribe-default-open)
(end-of-line)
(insert scribe-default-close)))
(defun scribe-env-wrap-word (env count)
"Wrap a scribe environment around the current word. With prefix arg, wrap
that many words forward or backward."
(interactive "sEnvironment: \np")
(save-excursion
(forward-char 1)
(if (< count 0)
(forward-word count)
(forward-word -1))
(insert "@" env scribe-default-open)
(if (> count 0)
(forward-word count)
(forward-word (- count)))
(insert scribe-default-close)))
(defun scribe-italicize-word (count)
"Italicize a word. With prefix arg, italicize that many words."
(interactive "p")
(scribe-env-wrap-word "i" count))
(defun scribe-boldface-word (count)
"Boldface a word. With prefix arg, boldface that many words."
(interactive "p")
(scribe-env-wrap-word "b" count))
(defun scribe-underline-word (count)
"Underline a word. With prefix arg, underline that many words."
(interactive "p")
(scribe-env-wrap-word "ux" count))
(defun beginning-of-scribe-env (count)
"Go to the beginning of the current scribe environment, if it exists."
(interactive "p")
(if (not (re-search-backward "@begin" nil t count))
(message "No environment block found")))
(defun end-of-scribe-env (count)
"Go to the end of the current scribe environment, if it exists."
(interactive "p")
(if (re-search-forward "@end" nil t count)
(forward-sexp)
(message "No environment block found")))
(defun forward-scribe-begin-end (point)
(interactive "d")
(goto-char point)
(let (env-name)
(if (re-search-forward "\\(@begin\\)\\|\\(@end\\)" nil t 1)
(if (string= (last-word) "begin")
(progn
(forward-char 1)
(let ((p (point)))
(forward-word 1)
(setq env-name (buffer-substring p (point))))
(if (eq (forward-scribe-begin-end (point)) 'error)
'error
(if (and (re-search-forward (concat "@end" scribe-open-paren)
nil t 1)
(string= (next-word) env-name))
(progn
(forward-char 1)
t)
(progn
(beep)
(message "@begin/@end matching error with env '%s'"
env-name)
'error))))
(progn
(goto-char point) ; original starting point
nil))
nil)))
(defun last-word ()
(save-excursion
(let ((p (point)))
(forward-word -1)
(buffer-substring (point) p))))
(defun next-word ()
(save-excursion
(let ((p (point)))
(forward-word 1)
(buffer-substring p (point)))))
(defun scribe-mode ()
"Major mode for editing files of input for scribe.
), ], >, }, and \" display the characters they match. Use
\\[validate-scribe-buffer] to check buffer for paragraphs containing
mismatched ()'s, []'s, {}'s, or <>'s.
Use \\[parenthesize-region] and \\[parenthesize-line] to wrap parentheses
around the region and line, respectively. Use \\[parenthesize-word]
to parenthesize a word.
Use \\[scribe-environment-block] to create a @begin() @end()
environment block. Use \\[environment-wrap-line] and
\\[environment-wrap-region] to wrap environments around the line and
the region, respectively.
Use \\[scribe-region] to run scribe on the current region, plus a
\"header\" copied from the top of the file (containing macro
definitions, etc.), running scribe under a special subshell.
\\[scribe-buffer] does the whole buffer. \\[scribe-print] prints the
.press file made by either of those.
Full list of special commands:
\\{scribe-mode-map}
Entering scribe mode calls the value of text-mode-hook,
and then the value of scribe-mode-hook."
(interactive)
(kill-all-local-variables)
(use-local-map scribe-mode-map)
(setq mode-name "Scribe")
(setq major-mode 'scribe-mode)
(setq local-abbrev-table text-mode-abbrev-table)
(if (null scribe-mode-syntax-table)
(progn
(setq scribe-mode-syntax-table (make-syntax-table))
(set-syntax-table scribe-mode-syntax-table)
(modify-syntax-entry ?\\ "\\ ")
(modify-syntax-entry ?\[ "(] ")
(modify-syntax-entry ?\] ")[ ")
(modify-syntax-entry ?\< "(> ")
(modify-syntax-entry ?\> ")< ")
(modify-syntax-entry ?\{ "(} ")
(modify-syntax-entry ?\} "){ ")
(modify-syntax-entry ?\" "$$ ")
(modify-syntax-entry ?' "w "))
(set-syntax-table scribe-mode-syntax-table))
(make-local-variable 'paragraph-start)
(setq paragraph-start "^\n")
(make-local-variable 'paragraph-separate)
(setq paragraph-separate paragraph-start)
(if text-mode-hook (funcall text-mode-hook))
(if scribe-mode-hook (funcall scribe-mode-hook)))
(setq auto-mode-alist ;tell the editor about scribe mode
(cons '("\\.mss$" . scribe-mode)
auto-mode-alist))
--
----------------------------------------------------------------------
I have no opinions. Therefore my employer is mine.
----------------------------------------------------------------------
Sean Philip Engelson +---------------+
Carnegie-Mellon University | POST NO BILLS |
Computer Science Department +---------------+
----------------------------------------------------------------------
ARPA: spe@cad.cs.cmu.edu
UUCP: {harvard | seismo | ucbvax}!cad.cs.cmu.edu!spe
----------------------------------------------------------------------