;;; ses.el -- Simple Emacs Spreadsheet -*- coding: utf-8 -*-
-;; Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
+;; Copyright (C) 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
;; Author: Jonathan Yavner <jyavner@member.fsf.org>
;; Maintainer: Jonathan Yavner <jyavner@member.fsf.org>
(newmap (make-sparse-keymap)))
(set-keymap-parent newmap minibuffer-local-map)
(while keys
- (define-key newmap (car keys) (cadr keys))
- (setq keys (cddr keys)))
+ (define-key newmap (pop keys) (pop keys)))
newmap)
"Local keymap for SES minibuffer cell-editing.")
ses-initial-file-trailer)
"The initial contents of an empty spreadsheet.")
-(defconst ses-paramlines-plist
- '(ses--col-widths 2 ses--col-printers 3 ses--default-printer 4
- ses--header-row 5 ses--file-format 8 ses--numrows 9
- ses--numcols 10)
- "Offsets from last cell line to various parameter lines in the data area
-of a spreadsheet.")
-
(defconst ses-box-prop '(:box (:line-width 2 :style released-button))
"Display properties to create a raised box for cells in the header line.")
column printer or a global-default printer because they invoke the column or
default printer and then modify its output.")
+
+;;----------------------------------------------------------------------------
+;; Local variables and constants
+;;----------------------------------------------------------------------------
+
(eval-and-compile
(defconst ses-localvars
'(ses--blank-line ses--cells ses--col-printers ses--col-widths ses--curcell
ses--curcell-overlay ses--default-printer ses--deferred-narrow
ses--deferred-recalc ses--deferred-write ses--file-format
ses--header-hscroll ses--header-row ses--header-string ses--linewidth
- ses--numcols ses--numrows ses--symbolic-formulas
+ ses--numcols ses--numrows ses--symbolic-formulas ses--data-marker
+ ses--params-marker
;;Global variables that we override
mode-line-process next-line-add-newlines transient-mark-mode)
"Buffer-local variables used by SES."))
(make-local-variable x)
(set x nil)))
+(defconst ses-paramlines-plist
+ '(ses--col-widths -5 ses--col-printers -4 ses--default-printer -3
+ ses--header-row -2 ses--file-format 1 ses--numrows 2
+ ses--numcols 3)
+ "Offsets from 'Global parameters' line to various parameter lines in the
+data area of a spreadsheet.")
+
;;
;; "Side-effect variables". They are set in one function, altered in
(defvar ses-call-printer-return nil
"Set to t if last cell printer invoked by `ses-call-printer' requested
left-justification of the result. Set to error-signal if ses-call-printer
-encountered an error during printing. Nil otherwise.")
+encountered an error during printing. nil otherwise.")
(defvar ses-start-time nil
"Time when current operation started. Used by `ses-time-check' to decide
(defmacro ses-header-row (row)
"Load the header row from the spreadsheet file and checks it
for safety. This is a macro to prevent propagate-on-load viruses."
- (or (and (wholenump row) (< row ses--numrows))
+ (or (and (wholenump row) (or (zerop ses--numrows) (< row ses--numrows)))
(error "Bad header-row"))
(setq ses--header-row row)
t)
"Execute BODY repeatedly, with the variables `row' and `col' set to each
cell in the range specified by CURCELL. The range is available in the
variables `minrow', `maxrow', `mincol', and `maxcol'."
+ (declare (indent defun) (debug (form body)))
(let ((cur (make-symbol "cur"))
(min (make-symbol "min"))
(max (make-symbol "max"))
(setq col (+ ,c mincol))
,@body))))))
-(put 'ses-dorange 'lisp-indent-function 'defun)
-(def-edebug-spec ses-dorange (form body))
-
;;Support for coverage testing.
(defmacro 1value (form)
"For code-coverage testing, indicate that FORM is expected to always have
(defun ses-update-cells (list &optional force)
"Recalculate cells in LIST, checking for dependency loops. Prints
progress messages every second. Dependent cells are not recalculated
-if the cell's value is unchanged if FORCE is nil."
+if the cell's value is unchanged and FORCE is nil."
(let ((ses--deferred-recalc list)
(nextlist list)
(pos (point))
(defun ses-in-print-area ()
"Returns t if point is in print area of spreadsheet."
- (eq (get-text-property (point) 'keymap) 'ses-mode-print-map))
+ (<= (point) ses--data-marker))
;;We turn off point-motion-hooks and explicitly position the cursor, in case
;;the intangible properties have gotten screwed up (e.g., when
(defun ses-narrowed-p () (/= (- (point-max) (point-min)) (buffer-size)))
+(defun ses-widen ()
+ "Turn off narrowing, to be reenabled at end of command loop."
+ (if (ses-narrowed-p)
+ (setq ses--deferred-narrow t))
+ (widen))
+
(defun ses-goto-data (def &optional col)
"Move point to data area for (DEF,COL). If DEF is a row
number, COL is the column number for a data cell -- otherwise DEF
is one of the symbols ses--col-widths, ses--col-printers,
ses--default-printer, ses--numrows, or ses--numcols."
- (if (ses-narrowed-p)
- (setq ses--deferred-narrow t))
- (widen)
+ (ses-widen)
(let ((inhibit-point-motion-hooks t)) ;In case intangible attrs are wrong
- (goto-char (point-min))
(if col
- ;;It's a cell
- (forward-line (+ ses--numrows 2 (* def (1+ ses--numcols)) col))
- ;;Convert def-symbol to offset
- (setq def (plist-get ses-paramlines-plist def))
- (or def (signal 'args-out-of-range nil))
- (forward-line (+ (* ses--numrows (+ ses--numcols 2)) def)))))
+ ;;It's a cell
+ (progn
+ (goto-char ses--data-marker)
+ (forward-line (+ 1 (* def (1+ ses--numcols)) col)))
+ ;;Convert def-symbol to offset
+ (setq def (plist-get ses-paramlines-plist def))
+ (or def (signal 'args-out-of-range nil))
+ (goto-char ses--params-marker)
+ (forward-line def))))
(defun ses-set-parameter (def value &optional elem)
"Set parameter DEF to VALUE (with undo) and write the value to the data area.
;;We call ses-goto-data early, using the old values of numrows and
;;numcols in case one of them is being changed.
(ses-goto-data def)
- (if elem
- (ses-aset-with-undo (symbol-value def) elem value)
- (ses-set-with-undo def value))
(let ((inhibit-read-only t)
(fmt (plist-get '(ses--col-widths "(ses-column-widths %S)"
ses--col-printers "(ses-column-printers %S)"
ses--file-format " %S ;SES file-format"
ses--numrows " %S ;numrows"
ses--numcols " %S ;numcols")
- def)))
- (delete-region (point) (line-end-position))
- (insert (format fmt (symbol-value def))))))
+ def))
+ oldval)
+ (if elem
+ (progn
+ (setq oldval (aref (symbol-value def) elem))
+ (aset (symbol-value def) elem value))
+ (setq oldval (symbol-value def))
+ (set def value))
+ ;;Special undo since it's outside the narrowed buffer
+ (let (buffer-undo-list)
+ (delete-region (point) (line-end-position))
+ (insert (format fmt (symbol-value def))))
+ (push `(apply ses-set-parameter ,def ,oldval ,elem) buffer-undo-list))))
+
(defun ses-write-cells ()
"Write cells in `ses--deferred-write' from local variables to data area.
))))
result-so-far)
+(defsubst ses-relocate-symbol (sym rowcol startrow startcol rowincr colincr)
+ "Relocate one symbol SYM, whichs corresponds to ROWCOL (a cons of ROW and
+COL). Cells starting at (STARTROW,STARTCOL) are being shifted
+by (ROWINCR,COLINCR)."
+ (let ((row (car rowcol))
+ (col (cdr rowcol)))
+ (if (or (< row startrow) (< col startcol))
+ sym
+ (setq row (+ row rowincr)
+ col (+ col colincr))
+ (if (and (>= row startrow) (>= col startcol)
+ (< row ses--numrows) (< col ses--numcols))
+ ;;Relocate this variable
+ (ses-create-cell-symbol row col)
+ ;;Delete reference to a deleted cell
+ nil))))
+
(defun ses-relocate-formula (formula startrow startcol rowincr colincr)
"Produce a copy of FORMULA where all symbols that refer to cells in row
STARTROW or above and col STARTCOL or above are altered by adding ROWINCR
result))))
(nreverse result))))
-(defun ses-relocate-symbol (sym rowcol startrow startcol rowincr colincr)
- "Relocate one symbol SYM, whichs corresponds to ROWCOL (a cons of ROW and
-COL). Cells starting at (STARTROW,STARTCOL) are being shifted
-by (ROWINCR,COLINCR)."
- (let ((row (car rowcol))
- (col (cdr rowcol)))
- (if (or (< row startrow) (< col startcol))
- sym
- (setq row (+ row rowincr)
- col (+ col colincr))
- (if (and (>= row startrow) (>= col startcol)
- (< row ses--numrows) (< col ses--numcols))
- ;;Relocate this variable
- (ses-create-cell-symbol row col)
- ;;Delete reference to a deleted cell
- nil))))
-
(defun ses-relocate-range (range startrow startcol rowincr colincr)
"Relocate one RANGE, of the form '(ses-range min max). Cells starting
at (STARTROW,STARTCOL) are being shifted by (ROWINCR,COLINCR). Result is the
;; Undo control
;;----------------------------------------------------------------------------
-;; This should be unnecessary, because the feature is now built in.
-
-(defadvice undo-more (around ses-undo-more activate preactivate)
- "For SES mode, allow undo outside of narrowed buffer range."
- (if (not (eq major-mode 'ses-mode))
- ad-do-it
- ;;Here is some extra code for SES mode.
- (setq ses--deferred-narrow
- (or ses--deferred-narrow (ses-narrowed-p)))
- (widen)
- (condition-case x
- ad-do-it
- (error
- ;;Restore narrow if appropriate
- (ses-command-hook)
- (signal (car x) (cdr x))))))
-
(defun ses-begin-change ()
"For undo, remember point before we start changing hidden stuff."
(let ((inhibit-read-only t))
(defun ses-set-with-undo (sym newval)
"Like set, but undoable. Result is t if value has changed."
- ;;We avoid adding redundant entries to the undo list, but this is
+ ;;We try to avoid adding redundant entries to the undo list, but this is
;;unavoidable for strings because equal ignores text properties and there's
;;no easy way to get the whole property list to see if it's different!
(unless (and (boundp sym)
(goto-char (point-max))
(search-backward ";; Local Variables:\n" nil t)
(backward-list 1)
+ (setq ses--params-marker (point-marker))
(let ((params (condition-case nil (read (current-buffer)) (error nil))))
(or (and (= (safe-length params) 3)
(numberp (car params))
(numberp (cadr params))
- (> (cadr params) 0)
+ (>= (cadr params) 0)
(numberp (nth 2 params))
(> (nth 2 params) 0))
(error "Invalid SES file"))
(forward-line ses--numrows)
(or (looking-at ses-print-data-boundary)
(error "Missing marker between print and data areas"))
- (forward-char (length ses-print-data-boundary))
+ (forward-char 1)
+ (setq ses--data-marker (point-marker))
+ (forward-char (1- (length ses-print-data-boundary)))
;;Initialize printer and symbol lists
(mapc 'ses-printer-record ses-standard-printer-functions)
(setq ses--symbolic-formulas nil)
(let ((old ses--deferred-recalc))
(setq ses--deferred-recalc nil)
(ses-update-cells old)))
- (if ses--deferred-write
- ;;We don't reset the deferred list before starting -- the most
- ;;likely error is keyboard-quit, and we do want to keep trying
- ;;these writes after a quit.
- (ses-write-cells))
+ (when ses--deferred-write
+ ;;We don't reset the deferred list before starting -- the most
+ ;;likely error is keyboard-quit, and we do want to keep trying
+ ;;these writes after a quit.
+ (ses-write-cells)
+ (push '(apply ses-widen) buffer-undo-list))
(when ses--deferred-narrow
;;We're not allowed to narrow the buffer until after-find-file has
;;read the local variables at the end of the file. Now it's safe to
;;do the narrowing.
- (save-excursion
- (goto-char (point-min))
- (forward-line ses--numrows)
- (narrow-to-region (point-min) (point)))
+ (narrow-to-region (point-min) ses--data-marker)
(setq ses--deferred-narrow nil))
;;Update the modeline
(let ((oldcell ses--curcell))
(error
(unless executing-kbd-macro
(ding))
- (message (error-message-string err))))
+ (message "%s" (error-message-string err))))
nil) ;Make coverage-tester happy
(defun ses-create-header-string ()
(error (setq sig hold))))
(cond
(sig
- (message (error-message-string sig)))
+ (message "%s" (error-message-string sig)))
((consp ses--curcell)
(message " "))
(t
(cons (ses-cell-symbol row col)
(ses-cell-references yrow ycol)))))))
;;Delete everything and reconstruct basic data area
- (if (ses-narrowed-p)
- (setq ses--deferred-narrow t))
- (widen)
+ (ses-widen)
(let ((inhibit-read-only t))
(goto-char (point-max))
(if (search-backward ";; Local Variables:\n" nil t)
(dotimes (row ses--numrows)
(insert ses--blank-line))
(insert ses-print-data-boundary)
+ (backward-char (1- (length ses-print-data-boundary)))
+ (setq ses--data-marker (point-marker))
+ (forward-char (1- (length ses-print-data-boundary)))
;;Placeholders for cell data
(insert (make-string (* ses--numrows (1+ ses--numcols)) ?\n))
;;Placeholders for col-widths, col-printers, default-printer, header-row
(insert "\n\n\n\n")
- (insert ses-initial-global-parameters))
+ (insert ses-initial-global-parameters)
+ (backward-char (1- (length ses-initial-global-parameters)))
+ (setq ses--params-marker (point-marker))
+ (forward-char (1- (length ses-initial-global-parameters))))
(ses-set-parameter 'ses--col-widths ses--col-widths)
(ses-set-parameter 'ses--col-printers ses--col-printers)
(ses-set-parameter 'ses--default-printer ses--default-printer)
(defun ses-read-cell (row col newval)
"Self-insert for initial character of cell function."
(interactive
- (let ((initial (this-command-keys))
- (rowcol (progn (ses-check-curcell) (ses-sym-rowcol ses--curcell))))
+ (let* ((initial (this-command-keys))
+ (rowcol (progn (ses-check-curcell) (ses-sym-rowcol ses--curcell)))
+ (curval (ses-cell-formula (car rowcol) (cdr rowcol))))
(barf-if-buffer-read-only)
- (if (string= initial "\"")
- (setq initial "\"\"") ;Enter a string
- (if (string= initial "(")
- (setq initial "()"))) ;Enter a formula list
(list (car rowcol)
(cdr rowcol)
- (read-from-minibuffer (format "Cell %s: " ses--curcell)
- (cons initial 2)
- ses-mode-edit-map
- t ;Convert to Lisp object
- 'ses-read-cell-history))))
+ (read-from-minibuffer
+ (format "Cell %s: " ses--curcell)
+ (cons (if (equal initial "\"") "\"\""
+ (if (equal initial "(") "()" initial)) 2)
+ ses-mode-edit-map
+ t ;Convert to Lisp object
+ 'ses-read-cell-history
+ (prin1-to-string (if (eq (car-safe curval) 'ses-safe-formula)
+ (cadr curval)
+ curval))))))
(when (ses-edit-cell row col newval)
(ses-command-hook) ;Update cell widths before movement
(dolist (x ses-after-entry-functions)
(ses-reset-header-string)))
;;Reconstruct text attributes
(ses-setup)
+ ;;Prepare for undo
+ (push '(apply ses-widen) buffer-undo-list)
;;Return to current cell
(if ses--curcell
(ses-jump-safe ses--curcell)
(ses-reset-header-string)))
;;Reconstruct attributes
(ses-setup)
+ ;;Prepare for undo
+ (push '(apply ses-widen) buffer-undo-list)
(ses-jump-safe ses--curcell))
(defun ses-insert-column (count &optional col width printer)
colbool (> needcols 0))
(when (or rowbool colbool)
;;Need to insert. Get confirm
- (or (y-or-n-p (format "Yank will insert %s%s%s. Continue "
+ (or (y-or-n-p (format "Yank will insert %s%s%s. Continue? "
(if rowbool (format "%d rows" needrows) "")
(if (and rowbool colbool) " and " "")
(if colbool (format "%d columns" needcols) "")))
(if (or (< row 0) (> row ses--numrows))
(error "Invalid header-row"))
(ses-begin-change)
- (ses-set-parameter 'ses--header-row row)
+ (let ((oldval ses--header-row))
+ (let (buffer-undo-list)
+ (ses-set-parameter 'ses--header-row row))
+ (push `(apply ses-set-header-row ,oldval) buffer-undo-list))
(ses-reset-header-string))
(defun ses-mark-row ()
(cons 'list result)))
;;All standard formulas are safe
-(dolist (x '(ses-range ses-delete-blanks ses+ ses-average ses-select))
+(dolist (x '(ses-cell-value ses-range ses-delete-blanks ses+ ses-average
+ ses-select))
(put x 'side-effect-free t))