2005-09-25 Romain Francoise <romain@orebokech.com>
[bpt/emacs.git] / lisp / ses.el
CommitLineData
58cf70d3 1;;; ses.el -- Simple Emacs Spreadsheet -*- coding: utf-8 -*-
7ed9159a 2
0d30b337 3;; Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
7ed9159a 4
df961c06
JY
5;; Author: Jonathan Yavner <jyavner@member.fsf.org>
6;; Maintainer: Jonathan Yavner <jyavner@member.fsf.org>
7ed9159a
JY
7;; Keywords: spreadsheet
8
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software; you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation; either version 2, or (at your option)
14;; any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs; see the file COPYING. If not, write to the
086add15
LK
23;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24;; Boston, MA 02110-1301, USA.
7ed9159a 25
58cf70d3
SM
26;;; Commentary:
27
7ed9159a 28;;; To-do list:
58cf70d3 29
94be2532
JY
30;; * Use $ or … for truncated fields
31;; * Add command to make a range of columns be temporarily invisible.
32;; * Allow paste of one cell to a range of cells -- copy formula to each.
7ed9159a 33;; * Do something about control characters & octal codes in cell print
94be2532 34;; areas. Use string-width?
7ed9159a 35;; * Input validation functions. How specified?
7ed9159a
JY
36;; * Faces (colors & styles) in print cells.
37;; * Move a column by dragging its letter in the header line.
38;; * Left-margin column for row number.
39;; * Move a row by dragging its number in the left-margin.
40
58cf70d3
SM
41
42;;; Code:
43
7ed9159a
JY
44(require 'unsafep)
45
46
58cf70d3
SM
47;;----------------------------------------------------------------------------
48;; User-customizable variables
49;;----------------------------------------------------------------------------
7ed9159a
JY
50
51(defgroup ses nil
51616cd5 52 "Simple Emacs Spreadsheet."
7ed9159a
JY
53 :group 'applications
54 :prefix "ses-"
55 :version "21.1")
56
57(defcustom ses-initial-size '(1 . 1)
58 "Initial size of a new spreadsheet, as a cons (NUMROWS . NUMCOLS)."
59 :group 'ses
60 :type '(cons (integer :tag "numrows") (integer :tag "numcols")))
61
9e2d29b6 62(defcustom ses-initial-column-width 7
7ed9159a
JY
63 "Initial width of columns in a new spreadsheet."
64 :group 'ses
65 :type '(integer :match (lambda (widget value) (> value 0))))
66
67(defcustom ses-initial-default-printer "%.7g"
68 "Initial default printer for a new spreadsheet."
69 :group 'ses
70 :type '(choice string
71 (list :tag "Parenthesized string" string)
72 function))
73
74(defcustom ses-after-entry-functions '(forward-char)
58cf70d3
SM
75 "Things to do after entering a value into a cell.
76An abnormal hook that usually runs a cursor-movement function.
77Each function is called with ARG=1."
7ed9159a
JY
78 :group 'ses
79 :type 'hook
80 :options '(forward-char backward-char next-line previous-line))
81
82(defcustom ses-mode-hook nil
83 "Hook functions to be run upon entering SES mode."
84 :group 'ses
85 :type 'hook)
86
87
58cf70d3
SM
88;;----------------------------------------------------------------------------
89;; Global variables and constants
90;;----------------------------------------------------------------------------
7ed9159a
JY
91
92(defvar ses-read-cell-history nil
93 "List of formulas that have been typed in.")
94
95(defvar ses-read-printer-history nil
96 "List of printer functions that have been typed in.")
97
94be2532
JY
98(easy-menu-define ses-header-line-menu nil
99 "Context menu when mouse-3 is used on the header-line in an SES buffer."
100 '("SES header row"
101 ["Set current row" ses-set-header-row t]
58cf70d3 102 ["Unset row" ses-unset-header-row (> ses--header-row 0)]))
7ed9159a 103
94be2532
JY
104(defconst ses-mode-map
105 (let ((keys `("\C-c\M-\C-l" ses-reconstruct-all
106 "\C-c\C-l" ses-recalculate-all
107 "\C-c\C-n" ses-renarrow-buffer
108 "\C-c\C-c" ses-recalculate-cell
109 "\C-c\M-\C-s" ses-sort-column
110 "\C-c\M-\C-h" ses-set-header-row
111 "\C-c\C-t" ses-truncate-cell
112 "\C-c\C-j" ses-jump
113 "\C-c\C-p" ses-read-default-printer
114 "\M-\C-l" ses-reprint-all
115 [?\S-\C-l] ses-reprint-all
116 [header-line down-mouse-3] ,ses-header-line-menu
117 [header-line mouse-2] ses-sort-column-click))
118 (newmap (make-sparse-keymap)))
119 (while keys
120 (define-key (1value newmap) (car keys) (cadr keys))
121 (setq keys (cddr keys)))
122 newmap)
123 "Local keymap for Simple Emacs Spreadsheet.")
7ed9159a 124
94be2532
JY
125(easy-menu-define ses-menu ses-mode-map
126 "Menu bar menu for SES."
127 '("SES"
128 ["Insert row" ses-insert-row (ses-in-print-area)]
129 ["Delete row" ses-delete-row (ses-in-print-area)]
130 ["Insert column" ses-insert-column (ses-in-print-area)]
131 ["Delete column" ses-delete-column (ses-in-print-area)]
132 ["Set column printer" ses-read-column-printer t]
133 ["Set column width" ses-set-column-width t]
134 ["Set default printer" ses-read-default-printer t]
135 ["Jump to cell" ses-jump t]
136 ["Set cell printer" ses-read-cell-printer t]
137 ["Recalculate cell" ses-recalculate-cell t]
138 ["Truncate cell display" ses-truncate-cell t]
139 ["Export values" ses-export-tsv t]
140 ["Export formulas" ses-export-tsf t]))
141
142(defconst ses-mode-edit-map
143 (let ((keys '("\C-c\C-r" ses-insert-range
144 "\C-c\C-s" ses-insert-ses-range
145 [S-mouse-3] ses-insert-range-click
146 [C-S-mouse-3] ses-insert-ses-range-click
147 "\M-\C-i" lisp-complete-symbol))
148 (newmap (make-sparse-keymap)))
149 (set-keymap-parent newmap minibuffer-local-map)
150 (while keys
151 (define-key newmap (car keys) (cadr keys))
152 (setq keys (cddr keys)))
153 newmap)
7ed9159a
JY
154 "Local keymap for SES minibuffer cell-editing.")
155
94be2532
JY
156;Local keymap for SES print area
157(defalias 'ses-mode-print-map
158 (let ((keys '([backtab] backward-char
159 [tab] ses-forward-or-insert
160 "\C-i" ses-forward-or-insert ;Needed for ses-coverage.el?
161 "\M-o" ses-insert-column
162 "\C-o" ses-insert-row
163 "\C-m" ses-edit-cell
164 "\M-k" ses-delete-column
165 "\M-y" ses-yank-pop
166 "\C-k" ses-delete-row
167 "\C-j" ses-append-row-jump-first-column
168 "\M-h" ses-mark-row
169 "\M-H" ses-mark-column
170 "\C-d" ses-clear-cell-forward
171 "\C-?" ses-clear-cell-backward
172 "(" ses-read-cell
173 "\"" ses-read-cell
174 "'" ses-read-symbol
175 "=" ses-edit-cell
176 "j" ses-jump
177 "p" ses-read-cell-printer
178 "w" ses-set-column-width
179 "x" ses-export-keymap
180 "\M-p" ses-read-column-printer))
181 (repl '(;;We'll replace these wherever they appear in the keymap
182 clipboard-kill-region ses-kill-override
183 end-of-line ses-end-of-line
184 kill-line ses-delete-row
185 kill-region ses-kill-override
186 open-line ses-insert-row))
187 (numeric "0123456789.-")
188 (newmap (make-keymap)))
189 ;;Get rid of printables
190 (suppress-keymap newmap t)
191 ;;These keys insert themselves as the beginning of a numeric value
192 (dotimes (x (length numeric))
193 (define-key newmap (substring numeric x (1+ x)) 'ses-read-cell))
194 ;;Override these global functions wherever they're bound
195 (while repl
196 (substitute-key-definition (car repl) (cadr repl) newmap
197 (current-global-map))
198 (setq repl (cddr repl)))
199 ;;Apparently substitute-key-definition doesn't catch this?
200 (define-key newmap [(menu-bar) edit cut] 'ses-kill-override)
201 ;;Define our other local keys
202 (while keys
203 (define-key newmap (car keys) (cadr keys))
204 (setq keys (cddr keys)))
205 newmap))
206
207;;Helptext for ses-mode wants keymap as variable, not function
208(defconst ses-mode-print-map (symbol-function 'ses-mode-print-map))
209
210;;Key map used for 'x' key.
7ed9159a
JY
211(defalias 'ses-export-keymap
212 (let ((map (make-sparse-keymap "SES export")))
213 (define-key map "T" (cons " tab-formulas" 'ses-export-tsf))
214 (define-key map "t" (cons " tab-values" 'ses-export-tsv))
215 map))
216
217(defconst ses-print-data-boundary "\n\014\n"
58cf70d3 218 "Marker string denoting the boundary between print area and data area.")
7ed9159a
JY
219
220(defconst ses-initial-global-parameters
221 "\n( ;Global parameters (these are read first)\n 2 ;SES file-format\n 1 ;numrows\n 1 ;numcols\n)\n\n"
58cf70d3 222 "Initial contents for the three-element list at the bottom of the data area.")
7ed9159a
JY
223
224(defconst ses-initial-file-trailer
58cf70d3 225 ";; Local Variables:\n;; mode: ses\n;; End:\n"
7ed9159a
JY
226 "Initial contents for the file-trailer area at the bottom of the file.")
227
228(defconst ses-initial-file-contents
229 (concat " \n" ;One blank cell in print area
230 ses-print-data-boundary
231 "(ses-cell A1 nil nil nil nil)\n" ;One blank cell in data area
232 "\n" ;End-of-row terminator for the one row in data area
233 "(ses-column-widths [7])\n"
234 "(ses-column-printers [nil])\n"
235 "(ses-default-printer \"%.7g\")\n"
236 "(ses-header-row 0)\n"
237 ses-initial-global-parameters
238 ses-initial-file-trailer)
239 "The initial contents of an empty spreadsheet.")
240
7ed9159a 241(defconst ses-paramlines-plist
94be2532
JY
242 '(ses--col-widths 2 ses--col-printers 3 ses--default-printer 4
243 ses--header-row 5 ses--file-format 8 ses--numrows 9
244 ses--numcols 10)
7ed9159a
JY
245 "Offsets from last cell line to various parameter lines in the data area
246of a spreadsheet.")
247
248(defconst ses-box-prop '(:box (:line-width 2 :style released-button))
249 "Display properties to create a raised box for cells in the header line.")
250
251(defconst ses-standard-printer-functions
252 '(ses-center ses-center-span ses-dashfill ses-dashfill-span
253 ses-tildefill-span)
254 "List of print functions to be included in initial history of printer
255functions. None of these standard-printer functions is suitable for use as a
256column printer or a global-default printer because they invoke the column or
257default printer and then modify its output.")
258
259(eval-and-compile
260 (defconst ses-localvars
94be2532
JY
261 '(ses--blank-line ses--cells ses--col-printers ses--col-widths ses--curcell
262 ses--curcell-overlay ses--default-printer ses--deferred-narrow
263 ses--deferred-recalc ses--deferred-write ses--file-format
264 ses--header-hscroll ses--header-row ses--header-string ses--linewidth
265 ses--numcols ses--numrows ses--symbolic-formulas
266 ;;Global variables that we override
267 mode-line-process next-line-add-newlines transient-mark-mode)
7ed9159a
JY
268 "Buffer-local variables used by SES."))
269
270;;When compiling, create all the buffer locals and give them values
271(eval-when-compile
272 (dolist (x ses-localvars)
273 (make-local-variable x)
274 (set x nil)))
275
276
58cf70d3
SM
277;;
278;; "Side-effect variables". They are set in one function, altered in
279;; another as a side effect, then read back by the first, as a way of
280;; passing back more than one value. These declarations are just to make
281;; the compiler happy, and to conform to standard Emacs-Lisp practice (I
282;; think the make-local-variable trick above is cleaner).
283;;
7ed9159a
JY
284
285(defvar ses-relocate-return nil
286 "Set by `ses-relocate-formula' and `ses-relocate-range', read by
287`ses-relocate-all'. Set to 'delete if a cell-reference was deleted from a
288formula--so the formula needs recalculation. Set to 'range if the size of a
289`ses-range' was changed--so both the formula's value and list of dependents
290need to be recalculated.")
291
292(defvar ses-call-printer-return nil
293 "Set to t if last cell printer invoked by `ses-call-printer' requested
294left-justification of the result. Set to error-signal if ses-call-printer
295encountered an error during printing. Nil otherwise.")
296
297(defvar ses-start-time nil
298 "Time when current operation started. Used by `ses-time-check' to decide
299when to emit a progress message.")
300
301
58cf70d3
SM
302;;----------------------------------------------------------------------------
303;; Macros
304;;----------------------------------------------------------------------------
7ed9159a
JY
305
306(defmacro ses-get-cell (row col)
307 "Return the cell structure that stores information about cell (ROW,COL)."
94be2532 308 `(aref (aref ses--cells ,row) ,col))
7ed9159a 309
58cf70d3
SM
310;; We might want to use defstruct here, but cells are explicitly used as
311;; arrays in ses-set-cell, so we'd need to fix this first. --Stef
312(defsubst ses-make-cell (&optional symbol formula printer references)
313 (vector symbol formula printer references))
314
7ed9159a
JY
315(defmacro ses-cell-symbol (row &optional col)
316 "From a CELL or a pair (ROW,COL), get the symbol that names the local-variable holding its value. (0,0) => A1."
317 `(aref ,(if col `(ses-get-cell ,row ,col) row) 0))
318
319(defmacro ses-cell-formula (row &optional col)
320 "From a CELL or a pair (ROW,COL), get the function that computes its value."
321 `(aref ,(if col `(ses-get-cell ,row ,col) row) 1))
322
323(defmacro ses-cell-printer (row &optional col)
324 "From a CELL or a pair (ROW,COL), get the function that prints its value."
325 `(aref ,(if col `(ses-get-cell ,row ,col) row) 2))
326
327(defmacro ses-cell-references (row &optional col)
328 "From a CELL or a pair (ROW,COL), get the list of symbols for cells whose
329functions refer to its value."
330 `(aref ,(if col `(ses-get-cell ,row ,col) row) 3))
331
332(defmacro ses-cell-value (row &optional col)
333 "From a CELL or a pair (ROW,COL), get the current value for that cell."
334 `(symbol-value (ses-cell-symbol ,row ,col)))
335
336(defmacro ses-col-width (col)
337 "Return the width for column COL."
94be2532 338 `(aref ses--col-widths ,col))
7ed9159a
JY
339
340(defmacro ses-col-printer (col)
341 "Return the default printer for column COL."
94be2532 342 `(aref ses--col-printers ,col))
7ed9159a
JY
343
344(defmacro ses-sym-rowcol (sym)
345 "From a cell-symbol SYM, gets the cons (row . col). A1 => (0 . 0). Result
346is nil if SYM is not a symbol that names a cell."
347 `(and (symbolp ,sym) (get ,sym 'ses-cell)))
348
349(defmacro ses-cell (sym value formula printer references)
350 "Load a cell SYM from the spreadsheet file. Does not recompute VALUE from
351FORMULA, does not reprint using PRINTER, does not check REFERENCES. This is a
352macro to prevent propagate-on-load viruses. Safety-checking for FORMULA and
353PRINTER are deferred until first use."
354 (let ((rowcol (ses-sym-rowcol sym)))
355 (ses-formula-record formula)
356 (ses-printer-record printer)
357 (or (atom formula)
358 (eq safe-functions t)
359 (setq formula `(ses-safe-formula ,formula)))
360 (or (not printer)
361 (stringp printer)
362 (eq safe-functions t)
363 (setq printer `(ses-safe-printer ,printer)))
94be2532 364 (aset (aref ses--cells (car rowcol))
7ed9159a 365 (cdr rowcol)
58cf70d3 366 (ses-make-cell sym formula printer references)))
7ed9159a
JY
367 (set sym value)
368 sym)
369
370(defmacro ses-column-widths (widths)
371 "Load the vector of column widths from the spreadsheet file. This is a
372macro to prevent propagate-on-load viruses."
94be2532 373 (or (and (vectorp widths) (= (length widths) ses--numcols))
7ed9159a
JY
374 (error "Bad column-width vector"))
375 ;;To save time later, we also calculate the total width of each line in the
376 ;;print area (excluding the terminating newline)
94be2532
JY
377 (setq ses--col-widths widths
378 ses--linewidth (apply '+ -1 (mapcar '1+ widths))
51616cd5 379 ses--blank-line (concat (make-string ses--linewidth ?\s) "\n"))
7ed9159a
JY
380 t)
381
382(defmacro ses-column-printers (printers)
383 "Load the vector of column printers from the spreadsheet file and checks
384them for safety. This is a macro to prevent propagate-on-load viruses."
94be2532 385 (or (and (vectorp printers) (= (length printers) ses--numcols))
7ed9159a 386 (error "Bad column-printers vector"))
94be2532 387 (dotimes (x ses--numcols)
7ed9159a 388 (aset printers x (ses-safe-printer (aref printers x))))
94be2532 389 (setq ses--col-printers printers)
7ed9159a
JY
390 (mapc 'ses-printer-record printers)
391 t)
392
393(defmacro ses-default-printer (def)
394 "Load the global default printer from the spreadsheet file and checks it
395for safety. This is a macro to prevent propagate-on-load viruses."
94be2532 396 (setq ses--default-printer (ses-safe-printer def))
7ed9159a
JY
397 (ses-printer-record def)
398 t)
399
400(defmacro ses-header-row (row)
401 "Load the header row from the spreadsheet file and checks it
402for safety. This is a macro to prevent propagate-on-load viruses."
94be2532 403 (or (and (wholenump row) (< row ses--numrows))
7ed9159a 404 (error "Bad header-row"))
94be2532 405 (setq ses--header-row row)
7ed9159a
JY
406 t)
407
7ed9159a
JY
408(defmacro ses-dorange (curcell &rest body)
409 "Execute BODY repeatedly, with the variables `row' and `col' set to each
410cell in the range specified by CURCELL. The range is available in the
411variables `minrow', `maxrow', `mincol', and `maxcol'."
412 (let ((cur (make-symbol "cur"))
413 (min (make-symbol "min"))
414 (max (make-symbol "max"))
415 (r (make-symbol "r"))
416 (c (make-symbol "c")))
417 `(let* ((,cur ,curcell)
418 (,min (ses-sym-rowcol (if (consp ,cur) (car ,cur) ,cur)))
419 (,max (ses-sym-rowcol (if (consp ,cur) (cdr ,cur) ,cur))))
420 (let ((minrow (car ,min))
421 (maxrow (car ,max))
422 (mincol (cdr ,min))
423 (maxcol (cdr ,max))
424 row col)
425 (if (or (> minrow maxrow) (> mincol maxcol))
426 (error "Empty range"))
427 (dotimes (,r (- maxrow minrow -1))
428 (setq row (+ ,r minrow))
429 (dotimes (,c (- maxcol mincol -1))
430 (setq col (+ ,c mincol))
431 ,@body))))))
432
433(put 'ses-dorange 'lisp-indent-function 'defun)
434(def-edebug-spec ses-dorange (form body))
435
436;;Support for coverage testing.
437(defmacro 1value (form)
438 "For code-coverage testing, indicate that FORM is expected to always have
439the same value."
440 form)
441(defmacro noreturn (form)
442 "For code-coverage testing, indicate that FORM will always signal an error."
443 form)
444
445
58cf70d3
SM
446;;----------------------------------------------------------------------------
447;; Utility functions
448;;----------------------------------------------------------------------------
7ed9159a
JY
449
450(defun ses-vector-insert (array idx new)
451 "Create a new vector which is one larger than ARRAY and has NEW inserted
452before element IDX."
453 (let* ((len (length array))
454 (result (make-vector (1+ len) new)))
455 (dotimes (x len)
456 (aset result
457 (if (< x idx) x (1+ x))
458 (aref array x)))
459 result))
460
461;;Allow ARRAY to be a symbol for use in buffer-undo-list
462(defun ses-vector-delete (array idx count)
463 "Create a new vector which is a copy of ARRAY with COUNT objects removed
464starting at element IDX. ARRAY is either a vector or a symbol whose value
465is a vector--if a symbol, the new vector is assigned as the symbol's value."
466 (let* ((a (if (arrayp array) array (symbol-value array)))
467 (len (- (length a) count))
468 (result (make-vector len nil)))
469 (dotimes (x len)
470 (aset result x (aref a (if (< x idx) x (+ x count)))))
471 (if (symbolp array)
472 (set array result))
473 result))
474
475(defun ses-delete-line (count)
476 "Like `kill-line', but no kill ring."
477 (let ((pos (point)))
478 (forward-line count)
479 (delete-region pos (point))))
480
481(defun ses-printer-validate (printer)
482 "Signals an error if PRINTER is not a valid SES cell printer."
483 (or (not printer)
484 (stringp printer)
485 (functionp printer)
486 (and (stringp (car-safe printer)) (not (cdr printer)))
487 (error "Invalid printer function"))
488 printer)
489
490(defun ses-printer-record (printer)
491 "Add PRINTER to `ses-read-printer-history' if not already there, after first
492checking that it is a valid printer function."
493 (ses-printer-validate printer)
494 ;;To speed things up, we avoid calling prin1 for the very common "nil" case.
495 (if printer
496 (add-to-list 'ses-read-printer-history (prin1-to-string printer))))
497
498(defun ses-formula-record (formula)
499 "If FORMULA is of the form 'symbol, adds it to the list of symbolic formulas
500for this spreadsheet."
501 (when (and (eq (car-safe formula) 'quote)
502 (symbolp (cadr formula)))
94be2532 503 (add-to-list 'ses--symbolic-formulas
7ed9159a
JY
504 (list (symbol-name (cadr formula))))))
505
506(defun ses-column-letter (col)
507 "Converts a column number to A..Z or AA..ZZ"
508 (if (< col 26)
509 (char-to-string (+ ?A col))
510 (string (+ ?@ (/ col 26)) (+ ?A (% col 26)))))
511
512(defun ses-create-cell-symbol (row col)
513 "Produce a symbol that names the cell (ROW,COL). (0,0) => 'A1."
514 (intern (concat (ses-column-letter col) (number-to-string (1+ row)))))
515
516(defun ses-create-cell-variable-range (minrow maxrow mincol maxcol)
517 "Create buffer-local variables for cells. This is undoable."
63f3351c 518 (push `(apply ses-destroy-cell-variable-range ,minrow ,maxrow ,mincol ,maxcol)
7ed9159a
JY
519 buffer-undo-list)
520 (let (sym xrow xcol)
521 (dotimes (row (1+ (- maxrow minrow)))
522 (dotimes (col (1+ (- maxcol mincol)))
523 (setq xrow (+ row minrow)
524 xcol (+ col mincol)
525 sym (ses-create-cell-symbol xrow xcol))
526 (put sym 'ses-cell (cons xrow xcol))
527 (make-local-variable sym)))))
528
58cf70d3
SM
529;;We do not delete the ses-cell properties for the cell-variables, in case a
530;;formula that refers to this cell is in the kill-ring and is later pasted
531;;back in.
7ed9159a
JY
532(defun ses-destroy-cell-variable-range (minrow maxrow mincol maxcol)
533 "Destroy buffer-local variables for cells. This is undoable."
534 (let (sym)
535 (dotimes (row (1+ (- maxrow minrow)))
536 (dotimes (col (1+ (- maxcol mincol)))
537 (setq sym (ses-create-cell-symbol (+ row minrow) (+ col mincol)))
538 (if (boundp sym)
63f3351c 539 (push `(apply ses-set-with-undo ,sym ,(symbol-value sym))
7ed9159a
JY
540 buffer-undo-list))
541 (kill-local-variable sym))))
63f3351c 542 (push `(apply ses-create-cell-variable-range ,minrow ,maxrow ,mincol ,maxcol)
7ed9159a
JY
543 buffer-undo-list))
544
545(defun ses-reset-header-string ()
546 "Flags the header string for update. Upon undo, the header string will be
547updated again."
63f3351c 548 (push '(apply ses-reset-header-string) buffer-undo-list)
94be2532 549 (setq ses--header-hscroll -1))
7ed9159a
JY
550
551;;Split this code off into a function to avoid coverage-testing difficulties
552(defun ses-time-check (format arg)
553 "If `ses-start-time' is more than a second ago, call `message' with FORMAT
554and (eval ARG) and reset `ses-start-time' to the current time."
555 (when (> (- (float-time) ses-start-time) 1.0)
556 (message format (eval arg))
557 (setq ses-start-time (float-time)))
558 nil)
559
560
58cf70d3
SM
561;;----------------------------------------------------------------------------
562;; The cells
563;;----------------------------------------------------------------------------
7ed9159a
JY
564
565(defun ses-set-cell (row col field val)
566 "Install VAL as the contents for field FIELD (named by a quoted symbol) of
567cell (ROW,COL). This is undoable. The cell's data will be updated through
568`post-command-hook'."
569 (let ((cell (ses-get-cell row col))
570 (elt (plist-get '(value t symbol 0 formula 1 printer 2 references 3)
571 field))
572 change)
573 (or elt (signal 'args-out-of-range nil))
574 (setq change (if (eq elt t)
575 (ses-set-with-undo (ses-cell-symbol cell) val)
576 (ses-aset-with-undo cell elt val)))
577 (if change
94be2532 578 (add-to-list 'ses--deferred-write (cons row col))))
7ed9159a
JY
579 nil) ;Make coverage-tester happy
580
581(defun ses-cell-set-formula (row col formula)
582 "Store a new formula for (ROW . COL) and enqueues the cell for
583recalculation via `post-command-hook'. Updates the reference lists for the
584cells that this cell refers to. Does not update cell value or reprint the
585cell. To avoid inconsistencies, this function is not interruptible, which
586means Emacs will crash if FORMULA contains a circular list."
587 (let* ((cell (ses-get-cell row col))
588 (old (ses-cell-formula cell)))
589 (let ((sym (ses-cell-symbol cell))
590 (oldref (ses-formula-references old))
591 (newref (ses-formula-references formula))
592 (inhibit-quit t)
593 x xrow xcol)
94be2532 594 (add-to-list 'ses--deferred-recalc sym)
7ed9159a
JY
595 ;;Delete old references from this cell. Skip the ones that are also
596 ;;in the new list.
597 (dolist (ref oldref)
598 (unless (memq ref newref)
599 (setq x (ses-sym-rowcol ref)
600 xrow (car x)
601 xcol (cdr x))
602 (ses-set-cell xrow xcol 'references
603 (delq sym (ses-cell-references xrow xcol)))))
604 ;;Add new ones. Skip ones left over from old list
605 (dolist (ref newref)
606 (setq x (ses-sym-rowcol ref)
607 xrow (car x)
608 xcol (cdr x)
609 x (ses-cell-references xrow xcol))
610 (or (memq sym x)
611 (ses-set-cell xrow xcol 'references (cons sym x))))
612 (ses-formula-record formula)
613 (ses-set-cell row col 'formula formula))))
614
615(defun ses-calculate-cell (row col force)
616 "Calculate and print the value for cell (ROW,COL) using the cell's formula
617function and print functions, if any. Result is nil for normal operation, or
618the error signal if the formula or print function failed. The old value is
619left unchanged if it was *skip* and the new value is nil.
620 Any cells that depend on this cell are queued for update after the end of
621processing for the current keystroke, unless the new value is the same as
622the old and FORCE is nil."
623 (let ((cell (ses-get-cell row col))
624 formula-error printer-error)
58cf70d3 625 (let ((oldval (ses-cell-value cell))
7ed9159a
JY
626 (formula (ses-cell-formula cell))
627 newval)
628 (if (eq (car-safe formula) 'ses-safe-formula)
629 (ses-set-cell row col 'formula (ses-safe-formula (cadr formula))))
630 (condition-case sig
631 (setq newval (eval formula))
632 (error
633 (setq formula-error sig
634 newval '*error*)))
635 (if (and (not newval) (eq oldval '*skip*))
636 ;;Don't lose the *skip* - previous field spans this one
637 (setq newval '*skip*))
638 (when (or force (not (eq newval oldval)))
94be2532 639 (add-to-list 'ses--deferred-write (cons row col)) ;In case force=t
7ed9159a
JY
640 (ses-set-cell row col 'value newval)
641 (dolist (ref (ses-cell-references cell))
94be2532 642 (add-to-list 'ses--deferred-recalc ref))))
7ed9159a
JY
643 (setq printer-error (ses-print-cell row col))
644 (or formula-error printer-error)))
645
646(defun ses-clear-cell (row col)
647 "Delete formula and printer for cell (ROW,COL)."
648 (ses-set-cell row col 'printer nil)
649 (ses-cell-set-formula row col nil))
650
651(defun ses-update-cells (list &optional force)
652 "Recalculate cells in LIST, checking for dependency loops. Prints
653progress messages every second. Dependent cells are not recalculated
654if the cell's value is unchanged if FORCE is nil."
94be2532
JY
655 (let ((ses--deferred-recalc list)
656 (nextlist list)
657 (pos (point))
7ed9159a
JY
658 curlist prevlist rowcol formula)
659 (with-temp-message " "
94be2532 660 (while (and ses--deferred-recalc (not (equal nextlist prevlist)))
7ed9159a
JY
661 ;;In each loop, recalculate cells that refer only to other cells that
662 ;;have already been recalculated or aren't in the recalculation
663 ;;region. Repeat until all cells have been processed or until the
664 ;;set of cells being worked on stops changing.
665 (if prevlist
666 (message "Recalculating... (%d cells left)"
94be2532
JY
667 (length ses--deferred-recalc)))
668 (setq curlist ses--deferred-recalc
669 ses--deferred-recalc nil
670 prevlist nextlist)
7ed9159a
JY
671 (while curlist
672 (setq rowcol (ses-sym-rowcol (car curlist))
673 formula (ses-cell-formula (car rowcol) (cdr rowcol)))
674 (or (catch 'ref
675 (dolist (ref (ses-formula-references formula))
676 (when (or (memq ref curlist)
94be2532 677 (memq ref ses--deferred-recalc))
7ed9159a 678 ;;This cell refers to another that isn't done yet
94be2532 679 (add-to-list 'ses--deferred-recalc (car curlist))
7ed9159a
JY
680 (throw 'ref t))))
681 ;;ses-update-cells is called from post-command-hook, so
682 ;;inhibit-quit is implicitly bound to t.
683 (when quit-flag
684 ;;Abort the recalculation. User will probably undo now.
685 (error "Quit"))
686 (ses-calculate-cell (car rowcol) (cdr rowcol) force))
687 (setq curlist (cdr curlist)))
94be2532 688 (dolist (ref ses--deferred-recalc)
7ed9159a
JY
689 (add-to-list 'nextlist ref))
690 (setq nextlist (sort (copy-sequence nextlist) 'string<))
691 (if (equal nextlist prevlist)
692 ;;We'll go around the loop one more time.
693 (add-to-list 'nextlist t)))
94be2532 694 (when ses--deferred-recalc
7ed9159a 695 ;;Just couldn't finish these
94be2532 696 (dolist (x ses--deferred-recalc)
7ed9159a
JY
697 (let ((rowcol (ses-sym-rowcol x)))
698 (ses-set-cell (car rowcol) (cdr rowcol) 'value '*error*)
699 (1value (ses-print-cell (car rowcol) (cdr rowcol)))))
94be2532 700 (error "Circular references: %s" ses--deferred-recalc))
7ed9159a
JY
701 (message " "))
702 ;;Can't use save-excursion here: if the cell under point is
703 ;;updated, save-excusion's marker will move past the cell.
704 (goto-char pos)))
705
706
58cf70d3
SM
707;;----------------------------------------------------------------------------
708;; The print area
709;;----------------------------------------------------------------------------
7ed9159a 710
94be2532
JY
711(defun ses-in-print-area ()
712 "Returns t if point is in print area of spreadsheet."
713 (eq (get-text-property (point) 'keymap) 'ses-mode-print-map))
714
58cf70d3
SM
715;;We turn off point-motion-hooks and explicitly position the cursor, in case
716;;the intangible properties have gotten screwed up (e.g., when
717;;ses-goto-print is called during a recursive ses-print-cell).
7ed9159a
JY
718(defun ses-goto-print (row col)
719 "Move point to print area for cell (ROW,COL)."
720 (let ((inhibit-point-motion-hooks t))
94be2532 721 (goto-char (point-min))
7ed9159a
JY
722 (forward-line row)
723 (dotimes (c col)
724 (forward-char (1+ (ses-col-width c))))))
725
726(defun ses-set-curcell ()
94be2532 727 "Sets `ses--curcell' to the current cell symbol, or a cons (BEG,END) for a
7ed9159a
JY
728region, or nil if cursor is not at a cell."
729 (if (or (not mark-active)
730 deactivate-mark
731 (= (region-beginning) (region-end)))
732 ;;Single cell
94be2532 733 (setq ses--curcell (get-text-property (point) 'intangible))
7ed9159a
JY
734 ;;Range
735 (let ((bcell (get-text-property (region-beginning) 'intangible))
736 (ecell (get-text-property (1- (region-end)) 'intangible)))
94be2532
JY
737 (setq ses--curcell (if (and bcell ecell)
738 (cons bcell ecell)
739 nil))))
7ed9159a
JY
740 nil)
741
742(defun ses-check-curcell (&rest args)
94be2532 743 "Signal an error if ses--curcell is inappropriate. The end marker is
7ed9159a
JY
744appropriate if some argument is 'end. A range is appropriate if some
745argument is 'range. A single cell is appropriate unless some argument is
746'needrange."
94be2532 747 (if (eq ses--curcell t)
7ed9159a
JY
748 ;;curcell recalculation was postponed, but user typed ahead
749 (ses-set-curcell))
750 (cond
94be2532 751 ((not ses--curcell)
7ed9159a
JY
752 (or (memq 'end args)
753 (error "Not at cell")))
94be2532 754 ((consp ses--curcell)
7ed9159a
JY
755 (or (memq 'range args)
756 (memq 'needrange args)
757 (error "Can't use a range")))
758 ((memq 'needrange args)
759 (error "Need a range"))))
760
761(defun ses-print-cell (row col)
58cf70d3
SM
762 "Format and print the value of cell (ROW,COL) to the print area.
763Use the cell's printer function. If the cell's new print form is too wide,
764it will spill over into the following cell, but will not run off the end of the
765row or overwrite the next non-nil field. Result is nil for normal operation,
766or the error signal if the printer function failed and the cell was formatted
7ed9159a
JY
767with \"%s\". If the cell's value is *skip*, nothing is printed because the
768preceding cell has spilled over."
769 (catch 'ses-print-cell
770 (let* ((cell (ses-get-cell row col))
771 (value (ses-cell-value cell))
772 (printer (ses-cell-printer cell))
773 (maxcol (1+ col))
774 text sig startpos x)
775 ;;Create the string to print
776 (cond
777 ((eq value '*skip*)
778 ;;Don't print anything
779 (throw 'ses-print-cell nil))
780 ((eq value '*error*)
781 (setq text (make-string (ses-col-width col) ?#)))
782 (t
783 ;;Deferred safety-check on printer
784 (if (eq (car-safe printer) 'ses-safe-printer)
785 (ses-set-cell row col 'printer
786 (setq printer (ses-safe-printer (cadr printer)))))
787 ;;Print the value
788 (setq text (ses-call-printer (or printer
789 (ses-col-printer col)
94be2532 790 ses--default-printer)
7ed9159a
JY
791 value))
792 (if (consp ses-call-printer-return)
793 ;;Printer returned an error
794 (setq sig ses-call-printer-return))))
795 ;;Adjust print width to match column width
796 (let ((width (ses-col-width col))
797 (len (length text)))
798 (cond
799 ((< len width)
800 ;;Fill field to length with spaces
51616cd5 801 (setq len (make-string (- width len) ?\s)
7ed9159a
JY
802 text (if (eq ses-call-printer-return t)
803 (concat text len)
804 (concat len text))))
805 ((> len width)
806 ;;Spill over into following cells, if possible
807 (let ((maxwidth width))
808 (while (and (> len maxwidth)
94be2532 809 (< maxcol ses--numcols)
7ed9159a
JY
810 (or (not (setq x (ses-cell-value row maxcol)))
811 (eq x '*skip*)))
812 (unless x
813 ;;Set this cell to '*skip* so it won't overwrite our spillover
814 (ses-set-cell row maxcol 'value '*skip*))
815 (setq maxwidth (+ maxwidth (ses-col-width maxcol) 1)
816 maxcol (1+ maxcol)))
817 (if (<= len maxwidth)
818 ;;Fill to complete width of all the fields spanned
51616cd5 819 (setq text (concat text (make-string (- maxwidth len) ?\s)))
7ed9159a 820 ;;Not enough room to end of line or next non-nil field. Truncate
9e2d29b6 821 ;;if string or decimal; otherwise fill with error indicator
7ed9159a 822 (setq sig `(error "Too wide" ,text))
9e2d29b6
JY
823 (cond
824 ((stringp value)
825 (setq text (substring text 0 maxwidth)))
826 ((and (numberp value)
827 (string-match "\\.[0-9]+" text)
828 (>= 0 (setq width
829 (- len maxwidth
830 (- (match-end 0) (match-beginning 0))))))
831 ;; Turn 6.6666666666e+49 into 6.66e+49. Rounding is too hard!
832 (setq text (concat (substring text
833 0
834 (- (match-beginning 0) width))
835 (substring text (match-end 0)))))
836 (t
837 (setq text (make-string maxwidth ?#)))))))))
7ed9159a
JY
838 ;;Substitute question marks for tabs and newlines. Newlines are
839 ;;used as row-separators; tabs could confuse the reimport logic.
840 (setq text (replace-regexp-in-string "[\t\n]" "?" text))
841 (ses-goto-print row col)
842 (setq startpos (point))
843 ;;Install the printed result. This is not interruptible.
844 (let ((inhibit-read-only t)
845 (inhibit-quit t))
846 (delete-char (1+ (length text)))
847 ;;We use concat instead of inserting separate strings in order to
848 ;;reduce the number of cells in the undo list.
94be2532 849 (setq x (concat text (if (< maxcol ses--numcols) " " "\n")))
7ed9159a
JY
850 ;;We use set-text-properties to prevent a wacky print function
851 ;;from inserting rogue properties, and to ensure that the keymap
852 ;;property is inherited (is it a bug that only unpropertied strings
853 ;;actually inherit from surrounding text?)
854 (set-text-properties 0 (length x) nil x)
855 (insert-and-inherit x)
856 (put-text-property startpos (point) 'intangible
857 (ses-cell-symbol cell))
858 (when (and (zerop row) (zerop col))
859 ;;Reconstruct special beginning-of-buffer attributes
94be2532
JY
860 (put-text-property (point-min) (point) 'keymap 'ses-mode-print-map)
861 (put-text-property (point-min) (point) 'read-only 'ses)
862 (put-text-property (point-min) (1+ (point-min)) 'front-sticky t)))
863 (if (= row (1- ses--header-row))
7ed9159a
JY
864 ;;This line is part of the header - force recalc
865 (ses-reset-header-string))
866 ;;If this cell (or a preceding one on the line) previously spilled over
867 ;;and has gotten shorter, redraw following cells on line recursively.
94be2532
JY
868 (when (and (< maxcol ses--numcols)
869 (eq (ses-cell-value row maxcol) '*skip*))
7ed9159a
JY
870 (ses-set-cell row maxcol 'value nil)
871 (ses-print-cell row maxcol))
872 ;;Return to start of cell
873 (goto-char startpos)
874 sig)))
875
876(defun ses-call-printer (printer &optional value)
877 "Invokes PRINTER (a string or parenthesized string or function-symbol or
878lambda of one argument) on VALUE. Result is the the printed cell as a
879string. The variable `ses-call-printer-return' is set to t if the printer
880used parenthesis to request left-justification, or the error-signal if the
d0f6a32f 881printer signaled one (and \"%s\" is used as the default printer), else nil."
7ed9159a
JY
882 (setq ses-call-printer-return nil)
883 (unless value
884 (setq value ""))
885 (condition-case signal
886 (cond
887 ((stringp printer)
888 (format printer value))
889 ((stringp (car-safe printer))
890 (setq ses-call-printer-return t)
891 (format (car printer) value))
892 (t
893 (setq value (funcall printer value))
894 (if (stringp value)
895 value
896 (or (stringp (car-safe value))
897 (error "Printer should return \"string\" or (\"string\")"))
898 (setq ses-call-printer-return t)
899 (car value))))
900 (error
901 (setq ses-call-printer-return signal)
902 (prin1-to-string value t))))
903
904(defun ses-adjust-print-width (col change)
905 "Insert CHANGE spaces in front of column COL, or at end of line if
906COL=NUMCOLS. Deletes characters if CHANGE < 0. Caller should bind
907inhibit-quit to t."
908 (let ((inhibit-read-only t)
51616cd5 909 (blank (if (> change 0) (make-string change ?\s)))
94be2532
JY
910 (at-end (= col ses--numcols)))
911 (ses-set-with-undo 'ses--linewidth (+ ses--linewidth change))
7ed9159a 912 ;;ses-set-with-undo always returns t for strings.
94be2532 913 (1value (ses-set-with-undo 'ses--blank-line
51616cd5 914 (concat (make-string ses--linewidth ?\s) "\n")))
94be2532 915 (dotimes (row ses--numrows)
7ed9159a
JY
916 (ses-goto-print row col)
917 (when at-end
918 ;;Insert new columns before newline
919 (let ((inhibit-point-motion-hooks t))
920 (backward-char 1)))
921 (if blank
922 (insert blank)
923 (delete-char (- change))))))
924
925(defun ses-print-cell-new-width (row col)
926 "Same as ses-print-cell, except if the cell's value is *skip*, the preceding
927nonskipped cell is reprinted. This function is used when the width of
928cell (ROW,COL) has changed."
929 (if (not (eq (ses-cell-value row col) '*skip*))
930 (ses-print-cell row col)
931 ;;Cell was skipped over - reprint previous
932 (ses-goto-print row col)
933 (backward-char 1)
934 (let ((rowcol (ses-sym-rowcol (get-text-property (point) 'intangible))))
935 (ses-print-cell (car rowcol) (cdr rowcol)))))
936
937
58cf70d3
SM
938;;----------------------------------------------------------------------------
939;; The data area
940;;----------------------------------------------------------------------------
941
942(defun ses-narrowed-p () (/= (- (point-max) (point-min)) (buffer-size)))
7ed9159a
JY
943
944(defun ses-goto-data (def &optional col)
94be2532
JY
945 "Move point to data area for (DEF,COL). If DEF is a row
946number, COL is the column number for a data cell -- otherwise DEF
947is one of the symbols ses--col-widths, ses--col-printers,
948ses--default-printer, ses--numrows, or ses--numcols."
58cf70d3 949 (if (ses-narrowed-p)
94be2532 950 (setq ses--deferred-narrow t))
7ed9159a
JY
951 (widen)
952 (let ((inhibit-point-motion-hooks t)) ;In case intangible attrs are wrong
94be2532 953 (goto-char (point-min))
7ed9159a
JY
954 (if col
955 ;;It's a cell
94be2532 956 (forward-line (+ ses--numrows 2 (* def (1+ ses--numcols)) col))
7ed9159a
JY
957 ;;Convert def-symbol to offset
958 (setq def (plist-get ses-paramlines-plist def))
959 (or def (signal 'args-out-of-range nil))
94be2532 960 (forward-line (+ (* ses--numrows (+ ses--numcols 2)) def)))))
7ed9159a
JY
961
962(defun ses-set-parameter (def value &optional elem)
58cf70d3
SM
963 "Set parameter DEF to VALUE (with undo) and write the value to the data area.
964See `ses-goto-data' for meaning of DEF. Newlines in the data are escaped.
965If ELEM is specified, it is the array subscript within DEF to be set to VALUE."
7ed9159a
JY
966 (save-excursion
967 ;;We call ses-goto-data early, using the old values of numrows and
968 ;;numcols in case one of them is being changed.
969 (ses-goto-data def)
970 (if elem
971 (ses-aset-with-undo (symbol-value def) elem value)
972 (ses-set-with-undo def value))
973 (let ((inhibit-read-only t)
c21c3d89 974 (fmt (plist-get '(ses--col-widths "(ses-column-widths %S)"
94be2532
JY
975 ses--col-printers "(ses-column-printers %S)"
976 ses--default-printer "(ses-default-printer %S)"
977 ses--header-row "(ses-header-row %S)"
978 ses--file-format " %S ;SES file-format"
979 ses--numrows " %S ;numrows"
980 ses--numcols " %S ;numcols")
7ed9159a
JY
981 def)))
982 (delete-region (point) (line-end-position))
983 (insert (format fmt (symbol-value def))))))
984
985(defun ses-write-cells ()
58cf70d3
SM
986 "Write cells in `ses--deferred-write' from local variables to data area.
987Newlines in the data are escaped."
7ed9159a
JY
988 (let* ((inhibit-read-only t)
989 (print-escape-newlines t)
990 rowcol row col cell sym formula printer text)
991 (setq ses-start-time (float-time))
992 (with-temp-message " "
993 (save-excursion
94be2532 994 (while ses--deferred-write
7ed9159a 995 (ses-time-check "Writing... (%d cells left)"
94be2532
JY
996 '(length ses--deferred-write))
997 (setq rowcol (pop ses--deferred-write)
7ed9159a
JY
998 row (car rowcol)
999 col (cdr rowcol)
1000 cell (ses-get-cell row col)
1001 sym (ses-cell-symbol cell)
1002 formula (ses-cell-formula cell)
1003 printer (ses-cell-printer cell))
1004 (if (eq (car-safe formula) 'ses-safe-formula)
1005 (setq formula (cadr formula)))
1006 (if (eq (car-safe printer) 'ses-safe-printer)
1007 (setq printer (cadr printer)))
1008 ;;This is noticably faster than (format "%S %S %S %S %S")
1009 (setq text (concat "(ses-cell "
1010 (symbol-name sym)
1011 " "
1012 (prin1-to-string (symbol-value sym))
1013 " "
1014 (prin1-to-string formula)
1015 " "
1016 (prin1-to-string printer)
1017 " "
1018 (if (atom (ses-cell-references cell))
1019 "nil"
1020 (concat "("
1021 (mapconcat 'symbol-name
1022 (ses-cell-references cell)
1023 " ")
1024 ")"))
1025 ")"))
1026 (ses-goto-data row col)
1027 (delete-region (point) (line-end-position))
1028 (insert text)))
1029 (message " "))))
1030
1031
58cf70d3
SM
1032;;----------------------------------------------------------------------------
1033;; Formula relocation
1034;;----------------------------------------------------------------------------
7ed9159a
JY
1035
1036(defun ses-formula-references (formula &optional result-so-far)
1037 "Produce a list of symbols for cells that this formula's value
1038refers to. For recursive calls, RESULT-SO-FAR is the list being constructed,
1039or t to get a wrong-type-argument error when the first reference is found."
1040 (if (atom formula)
1041 (if (ses-sym-rowcol formula)
1042 ;;Entire formula is one symbol
1043 (add-to-list 'result-so-far formula)
1044 ) ;;Ignore other atoms
1045 (dolist (cur formula)
1046 (cond
1047 ((ses-sym-rowcol cur)
1048 ;;Save this reference
1049 (add-to-list 'result-so-far cur))
1050 ((eq (car-safe cur) 'ses-range)
1051 ;;All symbols in range are referenced
1052 (dolist (x (cdr (macroexpand cur)))
1053 (add-to-list 'result-so-far x)))
1054 ((and (consp cur) (not (eq (car cur) 'quote)))
1055 ;;Recursive call for subformulas
1056 (setq result-so-far (ses-formula-references cur result-so-far)))
1057 (t
1058 ;;Ignore other stuff
1059 ))))
1060 result-so-far)
1061
1062(defun ses-relocate-formula (formula startrow startcol rowincr colincr)
1063 "Produce a copy of FORMULA where all symbols that refer to cells in row
1064STARTROW or above and col STARTCOL or above are altered by adding ROWINCR
1065and COLINCR. STARTROW and STARTCOL are 0-based. Example:
1066 (ses-relocate-formula '(+ A1 B2 D3) 1 2 1 -1)
1067 => (+ A1 B2 C4)
1068If ROWINCR or COLINCR is negative, references to cells being deleted are
1069removed. Example:
1070 (ses-relocate-formula '(+ A1 B2 D3) 0 1 0 -1)
1071 => (+ A1 C3)
1072Sets `ses-relocate-return' to 'delete if cell-references were removed."
1073 (let (rowcol result)
1074 (if (or (atom formula) (eq (car formula) 'quote))
1075 (if (setq rowcol (ses-sym-rowcol formula))
1076 (ses-relocate-symbol formula rowcol
1077 startrow startcol rowincr colincr)
1078 formula) ;Pass through as-is
1079 (dolist (cur formula)
1080 (setq rowcol (ses-sym-rowcol cur))
1081 (cond
1082 (rowcol
1083 (setq cur (ses-relocate-symbol cur rowcol
1084 startrow startcol rowincr colincr))
1085 (if cur
1086 (push cur result)
1087 ;;Reference to a deleted cell. Set a flag in ses-relocate-return.
1088 ;;don't change the flag if it's already 'range, since range
1089 ;;implies 'delete.
1090 (unless ses-relocate-return
1091 (setq ses-relocate-return 'delete))))
1092 ((eq (car-safe cur) 'ses-range)
1093 (setq cur (ses-relocate-range cur startrow startcol rowincr colincr))
1094 (if cur
1095 (push cur result)))
1096 ((or (atom cur) (eq (car cur) 'quote))
1097 ;;Constants pass through unchanged
1098 (push cur result))
1099 (t
1100 ;;Recursively copy and alter subformulas
1101 (push (ses-relocate-formula cur startrow startcol
1102 rowincr colincr)
1103 result))))
1104 (nreverse result))))
1105
1106(defun ses-relocate-symbol (sym rowcol startrow startcol rowincr colincr)
1107 "Relocate one symbol SYM, whichs corresponds to ROWCOL (a cons of ROW and
1108COL). Cells starting at (STARTROW,STARTCOL) are being shifted
1109by (ROWINCR,COLINCR)."
1110 (let ((row (car rowcol))
1111 (col (cdr rowcol)))
1112 (if (or (< row startrow) (< col startcol))
1113 sym
1114 (setq row (+ row rowincr)
1115 col (+ col colincr))
1116 (if (and (>= row startrow) (>= col startcol)
94be2532 1117 (< row ses--numrows) (< col ses--numcols))
7ed9159a
JY
1118 ;;Relocate this variable
1119 (ses-create-cell-symbol row col)
1120 ;;Delete reference to a deleted cell
1121 nil))))
1122
1123(defun ses-relocate-range (range startrow startcol rowincr colincr)
1124 "Relocate one RANGE, of the form '(ses-range min max). Cells starting
1125at (STARTROW,STARTCOL) are being shifted by (ROWINCR,COLINCR). Result is the
1126new range, or nil if the entire range is deleted. If new rows are being added
1127just beyond the end of a row range, or new columns just beyond a column range,
1128the new rows/columns will be added to the range. Sets `ses-relocate-return'
1129if the range was altered."
1130 (let* ((minorig (cadr range))
1131 (minrowcol (ses-sym-rowcol minorig))
1132 (min (ses-relocate-symbol minorig minrowcol
1133 startrow startcol
1134 rowincr colincr))
1135 (maxorig (nth 2 range))
1136 (maxrowcol (ses-sym-rowcol maxorig))
1137 (max (ses-relocate-symbol maxorig maxrowcol
1138 startrow startcol
1139 rowincr colincr))
1140 field)
1141 (cond
1142 ((and (not min) (not max))
1143 (setq range nil)) ;;The entire range is deleted
1144 ((zerop colincr)
1145 ;;Inserting or deleting rows
1146 (setq field 'car)
1147 (if (not min)
1148 ;;Chopped off beginning of range
1149 (setq min (ses-create-cell-symbol startrow (cdr minrowcol))
1150 ses-relocate-return 'range))
1151 (if (not max)
1152 (if (> rowincr 0)
1153 ;;Trying to insert a nonexistent row
94be2532
JY
1154 (setq max (ses-create-cell-symbol (1- ses--numrows)
1155 (cdr minrowcol)))
7ed9159a
JY
1156 ;;End of range is being deleted
1157 (setq max (ses-create-cell-symbol (1- startrow) (cdr minrowcol))
1158 ses-relocate-return 'range))
1159 (and (> rowincr 0)
1160 (= (car maxrowcol) (1- startrow))
1161 (= (cdr minrowcol) (cdr maxrowcol))
1162 ;;Insert after ending row of vertical range - include it
1163 (setq max (ses-create-cell-symbol (+ startrow rowincr -1)
1164 (cdr maxrowcol))))))
1165 (t
1166 ;;Inserting or deleting columns
1167 (setq field 'cdr)
1168 (if (not min)
1169 ;;Chopped off beginning of range
1170 (setq min (ses-create-cell-symbol (car minrowcol) startcol)
1171 ses-relocate-return 'range))
1172 (if (not max)
1173 (if (> colincr 0)
1174 ;;Trying to insert a nonexistent column
94be2532
JY
1175 (setq max (ses-create-cell-symbol (car maxrowcol)
1176 (1- ses--numcols)))
7ed9159a
JY
1177 ;;End of range is being deleted
1178 (setq max (ses-create-cell-symbol (car maxrowcol) (1- startcol))
1179 ses-relocate-return 'range))
1180 (and (> colincr 0)
1181 (= (cdr maxrowcol) (1- startcol))
1182 (= (car minrowcol) (car maxrowcol))
1183 ;;Insert after ending column of horizontal range - include it
1184 (setq max (ses-create-cell-symbol (car maxrowcol)
1185 (+ startcol colincr -1)))))))
1186 (when range
1187 (if (/= (- (funcall field maxrowcol)
1188 (funcall field minrowcol))
1189 (- (funcall field (ses-sym-rowcol max))
1190 (funcall field (ses-sym-rowcol min))))
1191 ;;This range has changed size
1192 (setq ses-relocate-return 'range))
1193 (list 'ses-range min max))))
1194
1195(defun ses-relocate-all (minrow mincol rowincr colincr)
1196 "Alter all cell values, symbols, formulas, and reference-lists to relocate
1197the rectangle (MINROW,MINCOL)..(NUMROWS,NUMCOLS) by adding ROWINCR and COLINCR
1198to each symbol."
1199 (let (reform)
1200 (let (mycell newval)
7c018923
SM
1201 (dotimes-with-progress-reporter
1202 (row ses--numrows) "Relocating formulas..."
94be2532 1203 (dotimes (col ses--numcols)
7ed9159a
JY
1204 (setq ses-relocate-return nil
1205 mycell (ses-get-cell row col)
1206 newval (ses-relocate-formula (ses-cell-formula mycell)
1207 minrow mincol rowincr colincr))
1208 (ses-set-cell row col 'formula newval)
1209 (if (eq ses-relocate-return 'range)
1210 ;;This cell contains a (ses-range X Y) where a cell has been
1211 ;;inserted or deleted in the middle of the range.
1212 (push (cons row col) reform))
1213 (if ses-relocate-return
1214 ;;This cell referred to a cell that's been deleted or is no
1215 ;;longer part of the range. We can't fix that now because
1216 ;;reference lists cells have been partially updated.
94be2532 1217 (add-to-list 'ses--deferred-recalc
7ed9159a
JY
1218 (ses-create-cell-symbol row col)))
1219 (setq newval (ses-relocate-formula (ses-cell-references mycell)
1220 minrow mincol rowincr colincr))
1221 (ses-set-cell row col 'references newval)
1222 (and (>= row minrow) (>= col mincol)
1223 (ses-set-cell row col 'symbol
1224 (ses-create-cell-symbol row col))))))
1225 ;;Relocate the cell values
1226 (let (oldval myrow mycol xrow xcol)
1227 (cond
1228 ((and (<= rowincr 0) (<= colincr 0))
1229 ;;Deletion of rows and/or columns
7c018923
SM
1230 (dotimes-with-progress-reporter
1231 (row (- ses--numrows minrow)) "Relocating variables..."
7ed9159a 1232 (setq myrow (+ row minrow))
94be2532 1233 (dotimes (col (- ses--numcols mincol))
7ed9159a
JY
1234 (setq mycol (+ col mincol)
1235 xrow (- myrow rowincr)
1236 xcol (- mycol colincr))
94be2532 1237 (if (and (< xrow ses--numrows) (< xcol ses--numcols))
7ed9159a
JY
1238 (setq oldval (ses-cell-value xrow xcol))
1239 ;;Cell is off the end of the array
1240 (setq oldval (symbol-value (ses-create-cell-symbol xrow xcol))))
1241 (ses-set-cell myrow mycol 'value oldval))))
1242 ((and (wholenump rowincr) (wholenump colincr))
1243 ;;Insertion of rows and/or columns. Run the loop backwards.
94be2532
JY
1244 (let ((disty (1- ses--numrows))
1245 (distx (1- ses--numcols))
7ed9159a 1246 myrow mycol)
7c018923
SM
1247 (dotimes-with-progress-reporter
1248 (row (- ses--numrows minrow)) "Relocating variables..."
7ed9159a 1249 (setq myrow (- disty row))
94be2532 1250 (dotimes (col (- ses--numcols mincol))
7ed9159a
JY
1251 (setq mycol (- distx col)
1252 xrow (- myrow rowincr)
1253 xcol (- mycol colincr))
1254 (if (or (< xrow minrow) (< xcol mincol))
1255 ;;Newly-inserted value
1256 (setq oldval nil)
1257 ;;Transfer old value
1258 (setq oldval (ses-cell-value xrow xcol)))
1259 (ses-set-cell myrow mycol 'value oldval)))
1260 t)) ;Make testcover happy by returning non-nil here
1261 (t
1262 (error "ROWINCR and COLINCR must have the same sign"))))
1263 ;;Reconstruct reference lists for cells that contain ses-ranges that
1264 ;;have changed size.
1265 (when reform
1266 (message "Fixing ses-ranges...")
1267 (let (row col)
1268 (setq ses-start-time (float-time))
1269 (while reform
1270 (ses-time-check "Fixing ses-ranges... (%d left)" '(length reform))
1271 (setq row (caar reform)
1272 col (cdar reform)
1273 reform (cdr reform))
1274 (ses-cell-set-formula row col (ses-cell-formula row col))))
1275 (message nil))))
1276
1277
58cf70d3
SM
1278;;----------------------------------------------------------------------------
1279;; Undo control
1280;;----------------------------------------------------------------------------
7ed9159a 1281
137e4002
RS
1282;; This should be unnecessary, because the feature is now built in.
1283
eedeacb3
JY
1284(defadvice undo-more (around ses-undo-more activate preactivate)
1285 "For SES mode, allow undo outside of narrowed buffer range."
1286 (if (not (eq major-mode 'ses-mode))
1287 ad-do-it
1288 ;;Here is some extra code for SES mode.
1289 (setq ses--deferred-narrow
1290 (or ses--deferred-narrow (ses-narrowed-p)))
1291 (widen)
1292 (condition-case x
1293 ad-do-it
1294 (error
1295 ;;Restore narrow if appropriate
1296 (ses-command-hook)
1297 (signal (car x) (cdr x))))))
7ed9159a
JY
1298
1299(defun ses-begin-change ()
137e4002 1300 "For undo, remember point before we start changing hidden stuff."
7ed9159a
JY
1301 (let ((inhibit-read-only t))
1302 (insert-and-inherit "X")
1303 (delete-region (1- (point)) (point))))
1304
1305(defun ses-set-with-undo (sym newval)
1306 "Like set, but undoable. Result is t if value has changed."
1307 ;;We avoid adding redundant entries to the undo list, but this is
1308 ;;unavoidable for strings because equal ignores text properties and there's
1309 ;;no easy way to get the whole property list to see if it's different!
1310 (unless (and (boundp sym)
1311 (equal (symbol-value sym) newval)
1312 (not (stringp newval)))
1313 (push (if (boundp sym)
63f3351c
KS
1314 `(apply ses-set-with-undo ,sym ,(symbol-value sym))
1315 `(apply ses-unset-with-undo ,sym))
7ed9159a
JY
1316 buffer-undo-list)
1317 (set sym newval)
1318 t))
1319
1320(defun ses-unset-with-undo (sym)
1321 "Set SYM to be unbound. This is undoable."
1322 (when (1value (boundp sym)) ;;Always bound, except after a programming error
63f3351c 1323 (push `(apply ses-set-with-undo ,sym ,(symbol-value sym)) buffer-undo-list)
7ed9159a
JY
1324 (makunbound sym)))
1325
1326(defun ses-aset-with-undo (array idx newval)
1327 "Like aset, but undoable. Result is t if element has changed"
1328 (unless (equal (aref array idx) newval)
63f3351c 1329 (push `(apply ses-aset-with-undo ,array ,idx ,(aref array idx)) buffer-undo-list)
7ed9159a
JY
1330 (aset array idx newval)
1331 t))
1332
1333
58cf70d3
SM
1334;;----------------------------------------------------------------------------
1335;; Startup for major mode
1336;;----------------------------------------------------------------------------
7ed9159a 1337
7ed9159a
JY
1338(defun ses-load ()
1339 "Parse the current buffer and sets up buffer-local variables. Does not
1340execute cell formulas or print functions."
1341 (widen)
1342 ;;Read our global parameters, which should be a 3-element list
1343 (goto-char (point-max))
58cf70d3 1344 (search-backward ";; Local Variables:\n" nil t)
7ed9159a 1345 (backward-list 1)
58cf70d3 1346 (let ((params (condition-case nil (read (current-buffer)) (error nil))))
7ed9159a
JY
1347 (or (and (= (safe-length params) 3)
1348 (numberp (car params))
1349 (numberp (cadr params))
1350 (> (cadr params) 0)
1351 (numberp (nth 2 params))
1352 (> (nth 2 params) 0))
1353 (error "Invalid SES file"))
94be2532
JY
1354 (setq ses--file-format (car params)
1355 ses--numrows (cadr params)
1356 ses--numcols (nth 2 params))
1357 (when (= ses--file-format 1)
7ed9159a 1358 (let (buffer-undo-list) ;This is not undoable
94be2532 1359 (ses-goto-data 'ses--header-row)
7ed9159a 1360 (insert "(ses-header-row 0)\n")
94be2532 1361 (ses-set-parameter 'ses--file-format 2)
7ed9159a 1362 (message "Upgrading from SES-1 file format")))
94be2532 1363 (or (= ses--file-format 2)
e20bb629 1364 (error "This file needs a newer version of the SES library code"))
94be2532 1365 (ses-create-cell-variable-range 0 (1- ses--numrows) 0 (1- ses--numcols))
7ed9159a 1366 ;;Initialize cell array
94be2532
JY
1367 (setq ses--cells (make-vector ses--numrows nil))
1368 (dotimes (row ses--numrows)
1369 (aset ses--cells row (make-vector ses--numcols nil))))
7ed9159a 1370 ;;Skip over print area, which we assume is correct
94be2532
JY
1371 (goto-char (point-min))
1372 (forward-line ses--numrows)
7ed9159a
JY
1373 (or (looking-at ses-print-data-boundary)
1374 (error "Missing marker between print and data areas"))
1375 (forward-char (length ses-print-data-boundary))
1376 ;;Initialize printer and symbol lists
1377 (mapc 'ses-printer-record ses-standard-printer-functions)
94be2532 1378 (setq ses--symbolic-formulas nil)
7ed9159a 1379 ;;Load cell definitions
94be2532
JY
1380 (dotimes (row ses--numrows)
1381 (dotimes (col ses--numcols)
7ed9159a
JY
1382 (let* ((x (read (current-buffer)))
1383 (rowcol (ses-sym-rowcol (car-safe (cdr-safe x)))))
1384 (or (and (looking-at "\n")
1385 (eq (car-safe x) 'ses-cell)
1386 (eq row (car rowcol))
1387 (eq col (cdr rowcol)))
1388 (error "Cell-def error"))
1389 (eval x)))
1390 (or (looking-at "\n\n")
1391 (error "Missing blank line between rows")))
1392 ;;Load global parameters
1393 (let ((widths (read (current-buffer)))
1394 (n1 (char-after (point)))
1395 (printers (read (current-buffer)))
1396 (n2 (char-after (point)))
1397 (def-printer (read (current-buffer)))
1398 (n3 (char-after (point)))
1399 (head-row (read (current-buffer)))
1400 (n4 (char-after (point))))
1401 (or (and (eq (car-safe widths) 'ses-column-widths)
1402 (= n1 ?\n)
1403 (eq (car-safe printers) 'ses-column-printers)
1404 (= n2 ?\n)
1405 (eq (car-safe def-printer) 'ses-default-printer)
1406 (= n3 ?\n)
1407 (eq (car-safe head-row) 'ses-header-row)
1408 (= n4 ?\n))
1409 (error "Invalid SES global parameters"))
1410 (1value (eval widths))
1411 (1value (eval def-printer))
1412 (1value (eval printers))
1413 (1value (eval head-row)))
1414 ;;Should be back at global-params
1415 (forward-char 1)
1416 (or (looking-at (replace-regexp-in-string "1" "[0-9]+"
1417 ses-initial-global-parameters))
1418 (error "Problem with column-defs or global-params"))
1419 ;;Check for overall newline count in definitions area
1420 (forward-line 3)
1421 (let ((start (point)))
94be2532 1422 (ses-goto-data 'ses--numrows)
7ed9159a
JY
1423 (or (= (point) start)
1424 (error "Extraneous newlines someplace?"))))
1425
1426(defun ses-setup ()
1427 "Set up for display of only the printed cell values.
1428
1429Narrows the buffer to show only the print area. Gives it `read-only' and
1430`intangible' properties. Sets up highlighting for current cell."
1431 (interactive)
94be2532 1432 (let ((end (point-min))
7ed9159a
JY
1433 (inhibit-read-only t)
1434 (was-modified (buffer-modified-p))
1435 pos sym)
1436 (ses-goto-data 0 0) ;;Include marker between print-area and data-area
94be2532
JY
1437 (set-text-properties (point) (point-max) nil) ;Delete garbage props
1438 (mapc 'delete-overlay (overlays-in (point-min) (point-max)))
7ed9159a
JY
1439 ;;The print area is read-only (except for our special commands) and uses a
1440 ;;special keymap.
94be2532
JY
1441 (put-text-property (point-min) (1- (point)) 'read-only 'ses)
1442 (put-text-property (point-min) (1- (point)) 'keymap 'ses-mode-print-map)
7ed9159a
JY
1443 ;;For the beginning of the buffer, we want the read-only and keymap
1444 ;;attributes to be inherited from the first character
94be2532 1445 (put-text-property (point-min) (1+ (point-min)) 'front-sticky t)
7ed9159a
JY
1446 ;;Create intangible properties, which also indicate which cell the text
1447 ;;came from.
7c018923 1448 (dotimes-with-progress-reporter (row ses--numrows) "Finding cells..."
94be2532 1449 (dotimes (col ses--numcols)
7ed9159a
JY
1450 (setq pos end
1451 sym (ses-cell-symbol row col))
1452 ;;Include skipped cells following this one
94be2532 1453 (while (and (< col (1- ses--numcols))
7ed9159a
JY
1454 (eq (ses-cell-value row (1+ col)) '*skip*))
1455 (setq end (+ end (ses-col-width col) 1)
1456 col (1+ col)))
1457 (setq end (+ end (ses-col-width col) 1))
1458 (put-text-property pos end 'intangible sym)))
1459 ;;Adding these properties did not actually alter the text
1460 (unless was-modified
58cf70d3 1461 (restore-buffer-modified-p nil)
7ed9159a
JY
1462 (buffer-disable-undo)
1463 (buffer-enable-undo)))
1464 ;;Create the underlining overlay. It's impossible for (point) to be 2,
1465 ;;because column A must be at least 1 column wide.
94be2532
JY
1466 (setq ses--curcell-overlay (make-overlay (1+ (point-min)) (1+ (point-min))))
1467 (overlay-put ses--curcell-overlay 'face 'underline))
7ed9159a
JY
1468
1469(defun ses-cleanup ()
1470 "Cleanup when changing a buffer from SES mode to something else. Delete
1471overlay, remove special text properties."
1472 (widen)
1473 (let ((inhibit-read-only t)
58cf70d3 1474 (was-modified (buffer-modified-p)))
7ed9159a 1475 ;;Delete read-only, keymap, and intangible properties
94be2532 1476 (set-text-properties (point-min) (point-max) nil)
7ed9159a 1477 ;;Delete overlay
94be2532 1478 (mapc 'delete-overlay (overlays-in (point-min) (point-max)))
7ed9159a
JY
1479 (unless was-modified
1480 (set-buffer-modified-p nil))))
1481
1482;;;###autoload
1483(defun ses-mode ()
94be2532
JY
1484 "Major mode for Simple Emacs Spreadsheet.
1485See \"ses-example.ses\" (in the etc data directory) for more info.
7ed9159a
JY
1486
1487Key definitions:
1488\\{ses-mode-map}
1489These key definitions are active only in the print area (the visible part):
1490\\{ses-mode-print-map}
1491These are active only in the minibuffer, when entering or editing a formula:
1492\\{ses-mode-edit-map}"
1493 (interactive)
94be2532
JY
1494 (unless (and (boundp 'ses--deferred-narrow)
1495 (eq ses--deferred-narrow 'ses-mode))
7ed9159a
JY
1496 (kill-all-local-variables)
1497 (mapc 'make-local-variable ses-localvars)
1498 (setq major-mode 'ses-mode
1499 mode-name "SES"
1500 next-line-add-newlines nil
1501 truncate-lines t
1502 ;;SES deliberately puts lots of trailing whitespace in its buffer
1503 show-trailing-whitespace nil
1504 ;;Cell ranges do not work reasonably without this
1505 transient-mark-mode t)
7ed9159a
JY
1506 (1value (add-hook 'change-major-mode-hook 'ses-cleanup nil t))
1507 (1value (add-hook 'before-revert-hook 'ses-cleanup nil t))
94be2532
JY
1508 (setq ses--curcell nil
1509 ses--deferred-recalc nil
1510 ses--deferred-write nil
1511 ses--header-hscroll -1 ;Flag for "initial recalc needed"
1512 header-line-format '(:eval (progn
1513 (when (/= (window-hscroll)
1514 ses--header-hscroll)
1515 ;;Reset ses--header-hscroll first, to
1516 ;;avoid recursion problems when
1517 ;;debugging ses-create-header-string
1518 (setq ses--header-hscroll
1519 (window-hscroll))
1520 (ses-create-header-string))
1521 ses--header-string)))
7ed9159a
JY
1522 (let ((was-empty (zerop (buffer-size)))
1523 (was-modified (buffer-modified-p)))
1524 (save-excursion
1525 (if was-empty
1526 ;;Initialize buffer to contain one cell, for now
1527 (insert ses-initial-file-contents))
1528 (ses-load)
1529 (ses-setup))
1530 (when was-empty
94be2532 1531 (unless (equal ses-initial-default-printer (1value ses--default-printer))
7ed9159a
JY
1532 (1value (ses-read-default-printer ses-initial-default-printer)))
1533 (unless (= ses-initial-column-width (1value (ses-col-width 0)))
1534 (1value (ses-set-column-width 0 ses-initial-column-width)))
1535 (ses-set-curcell)
94be2532 1536 (if (> (car ses-initial-size) (1value ses--numrows))
7ed9159a 1537 (1value (ses-insert-row (1- (car ses-initial-size)))))
94be2532 1538 (if (> (cdr ses-initial-size) (1value ses--numcols))
7ed9159a
JY
1539 (1value (ses-insert-column (1- (cdr ses-initial-size)))))
1540 (ses-write-cells)
94be2532 1541 (restore-buffer-modified-p was-modified)
7ed9159a
JY
1542 (buffer-disable-undo)
1543 (buffer-enable-undo)
94be2532 1544 (goto-char (point-min))))
7ed9159a
JY
1545 (use-local-map ses-mode-map)
1546 ;;Set the deferred narrowing flag (we can't narrow until after
1547 ;;after-find-file completes). If .ses is on the auto-load alist and the
1548 ;;file has "mode: ses", our ses-mode function will be called twice! Use
1549 ;;a special flag to detect this (will be reset by ses-command-hook).
1550 ;;For find-alternate-file, post-command-hook doesn't get run for some
1551 ;;reason, so use an idle timer to make sure.
94be2532 1552 (setq ses--deferred-narrow 'ses-mode)
7ed9159a
JY
1553 (1value (add-hook 'post-command-hook 'ses-command-hook nil t))
1554 (run-with-idle-timer 0.01 nil 'ses-command-hook)
859540e3 1555 (run-mode-hooks 'ses-mode-hook)))
7ed9159a
JY
1556
1557(put 'ses-mode 'mode-class 'special)
1558
1559(defun ses-command-hook ()
1560 "Invoked from `post-command-hook'. If point has moved to a different cell,
1561moves the underlining overlay. Performs any recalculations or cell-data
1562writes that have been deferred. If buffer-narrowing has been deferred,
1563narrows the buffer now."
1564 (condition-case err
1565 (when (eq major-mode 'ses-mode) ;Otherwise, not our buffer anymore
94be2532 1566 (when ses--deferred-recalc
7ed9159a
JY
1567 ;;We reset the deferred list before starting on the recalc -- in case
1568 ;;of error, we don't want to retry the recalc after every keystroke!
94be2532
JY
1569 (let ((old ses--deferred-recalc))
1570 (setq ses--deferred-recalc nil)
7ed9159a 1571 (ses-update-cells old)))
94be2532 1572 (if ses--deferred-write
7ed9159a
JY
1573 ;;We don't reset the deferred list before starting -- the most
1574 ;;likely error is keyboard-quit, and we do want to keep trying
1575 ;;these writes after a quit.
1576 (ses-write-cells))
94be2532 1577 (when ses--deferred-narrow
7ed9159a
JY
1578 ;;We're not allowed to narrow the buffer until after-find-file has
1579 ;;read the local variables at the end of the file. Now it's safe to
1580 ;;do the narrowing.
1581 (save-excursion
94be2532
JY
1582 (goto-char (point-min))
1583 (forward-line ses--numrows)
1584 (narrow-to-region (point-min) (point)))
1585 (setq ses--deferred-narrow nil))
7ed9159a 1586 ;;Update the modeline
94be2532 1587 (let ((oldcell ses--curcell))
7ed9159a 1588 (ses-set-curcell)
94be2532 1589 (unless (eq ses--curcell oldcell)
7ed9159a 1590 (cond
94be2532 1591 ((not ses--curcell)
7ed9159a 1592 (setq mode-line-process nil))
94be2532
JY
1593 ((atom ses--curcell)
1594 (setq mode-line-process (list " cell "
1595 (symbol-name ses--curcell))))
7ed9159a
JY
1596 (t
1597 (setq mode-line-process (list " range "
94be2532 1598 (symbol-name (car ses--curcell))
7ed9159a 1599 "-"
94be2532 1600 (symbol-name (cdr ses--curcell))))))
7ed9159a
JY
1601 (force-mode-line-update)))
1602 ;;Use underline overlay for single-cells only, turn off otherwise
94be2532
JY
1603 (if (listp ses--curcell)
1604 (move-overlay ses--curcell-overlay 2 2)
7ed9159a 1605 (let ((next (next-single-property-change (point) 'intangible)))
94be2532 1606 (move-overlay ses--curcell-overlay (point) (1- next))))
7ed9159a
JY
1607 (when (not (pos-visible-in-window-p))
1608 ;;Scrolling will happen later
1609 (run-with-idle-timer 0.01 nil 'ses-command-hook)
94be2532 1610 (setq ses--curcell t)))
7ed9159a
JY
1611 ;;Prevent errors in this post-command-hook from silently erasing the hook!
1612 (error
1613 (unless executing-kbd-macro
1614 (ding))
8a26c165 1615 (message "%s" (error-message-string err))))
7ed9159a
JY
1616 nil) ;Make coverage-tester happy
1617
1618(defun ses-create-header-string ()
58cf70d3
SM
1619 "Set up `ses--header-string' as the buffer's header line.
1620Based on the current set of columns and `window-hscroll' position."
1621 (let ((totwidth (- (window-hscroll)))
1622 result width x)
125366f8 1623 ;;Leave room for the left-side fringe and scrollbar
58cf70d3 1624 (push (propertize " " 'display '((space :align-to 0))) result)
94be2532 1625 (dotimes (col ses--numcols)
7ed9159a
JY
1626 (setq width (ses-col-width col)
1627 totwidth (+ totwidth width 1))
58cf70d3 1628 (if (= totwidth 1)
125366f8 1629 ;;Scrolled so intercolumn space is leftmost
7ed9159a 1630 (push " " result))
58cf70d3 1631 (when (> totwidth 1)
94be2532 1632 (if (> ses--header-row 0)
7ed9159a 1633 (save-excursion
94be2532 1634 (ses-goto-print (1- ses--header-row) col)
7ed9159a
JY
1635 (setq x (buffer-substring-no-properties (point)
1636 (+ (point) width)))
58cf70d3
SM
1637 ;; Strip trailing space.
1638 (if (string-match "[ \t]+\\'" x)
1639 (setq x (substring x 0 (match-beginning 0))))
1640 ;; Cut off excess text.
1641 (if (>= (length x) totwidth)
1642 (setq x (substring x 0 (- totwidth -1)))))
1643 (setq x (ses-column-letter col)))
7ed9159a 1644 (push (propertize x 'face ses-box-prop) result)
58cf70d3 1645 (push (propertize "."
7ed9159a
JY
1646 'display `((space :align-to ,(1- totwidth)))
1647 'face ses-box-prop)
58cf70d3 1648 result)
7ed9159a
JY
1649 ;;Allow the following space to be squished to make room for the 3-D box
1650 ;;Coverage test ignores properties, thinks this is always a space!
1651 (push (1value (propertize " " 'display `((space :align-to ,totwidth))))
1652 result)))
94be2532
JY
1653 (if (> ses--header-row 0)
1654 (push (propertize (format " [row %d]" ses--header-row)
7ed9159a
JY
1655 'display '((height (- 1))))
1656 result))
94be2532 1657 (setq ses--header-string (apply 'concat (nreverse result)))))
7ed9159a
JY
1658
1659
58cf70d3
SM
1660;;----------------------------------------------------------------------------
1661;; Redisplay and recalculation
1662;;----------------------------------------------------------------------------
7ed9159a
JY
1663
1664(defun ses-jump (sym)
1665 "Move point to cell SYM."
1666 (interactive "SJump to cell: ")
1667 (let ((rowcol (ses-sym-rowcol sym)))
1668 (or rowcol (error "Invalid cell name"))
1669 (if (eq (symbol-value sym) '*skip*)
1670 (error "Cell is covered by preceding cell"))
1671 (ses-goto-print (car rowcol) (cdr rowcol))))
1672
1673(defun ses-jump-safe (cell)
1674 "Like `ses-jump', but no error if invalid cell."
1675 (condition-case nil
1676 (ses-jump cell)
1677 (error)))
1678
1679(defun ses-reprint-all (&optional nonarrow)
1680 "Recreate the display area. Calls all printer functions. Narrows to
1681print area if NONARROW is nil."
1682 (interactive "*P")
1683 (widen)
1684 (unless nonarrow
94be2532 1685 (setq ses--deferred-narrow t))
7ed9159a
JY
1686 (let ((startcell (get-text-property (point) 'intangible))
1687 (inhibit-read-only t))
1688 (ses-begin-change)
94be2532 1689 (goto-char (point-min))
7ed9159a
JY
1690 (search-forward ses-print-data-boundary)
1691 (backward-char (length ses-print-data-boundary))
94be2532 1692 (delete-region (point-min) (point))
7ed9159a
JY
1693 ;;Insert all blank lines before printing anything, so ses-print-cell can
1694 ;;find the data area when inserting or deleting *skip* values for cells
94be2532
JY
1695 (dotimes (row ses--numrows)
1696 (insert-and-inherit ses--blank-line))
7c018923 1697 (dotimes-with-progress-reporter (row ses--numrows) "Reprinting..."
7ed9159a
JY
1698 (if (eq (ses-cell-value row 0) '*skip*)
1699 ;;Column deletion left a dangling skip
1700 (ses-set-cell row 0 'value nil))
94be2532 1701 (dotimes (col ses--numcols)
7ed9159a
JY
1702 (ses-print-cell row col))
1703 (beginning-of-line 2))
1704 (ses-jump-safe startcell)))
1705
1706(defun ses-recalculate-cell ()
1707 "Recalculate and reprint the current cell or range.
1708
1709For an individual cell, shows the error if the formula or printer
1710signals one, or otherwise shows the cell's complete value. For a range, the
1711cells are recalculated in \"natural\" order, so cells that other cells refer
1712to are recalculated first."
1713 (interactive "*")
1714 (ses-check-curcell 'range)
1715 (ses-begin-change)
1716 (let (sig)
1717 (setq ses-start-time (float-time))
94be2532
JY
1718 (if (atom ses--curcell)
1719 (setq sig (ses-sym-rowcol ses--curcell)
7ed9159a
JY
1720 sig (ses-calculate-cell (car sig) (cdr sig) t))
1721 ;;First, recalculate all cells that don't refer to other cells and
1722 ;;produce a list of cells with references.
94be2532 1723 (ses-dorange ses--curcell
7ed9159a
JY
1724 (ses-time-check "Recalculating... %s" '(ses-cell-symbol row col))
1725 (condition-case nil
1726 (progn
1727 ;;The t causes an error if the cell has references.
1728 ;;If no references, the t will be the result value.
1729 (1value (ses-formula-references (ses-cell-formula row col) t))
1730 (setq sig (ses-calculate-cell row col t)))
1731 (wrong-type-argument
1732 ;;The formula contains a reference
94be2532 1733 (add-to-list 'ses--deferred-recalc (ses-cell-symbol row col))))))
7ed9159a 1734 ;;Do the update now, so we can force recalculation
94be2532
JY
1735 (let ((x ses--deferred-recalc))
1736 (setq ses--deferred-recalc nil)
7ed9159a
JY
1737 (condition-case hold
1738 (ses-update-cells x t)
1739 (error (setq sig hold))))
1740 (cond
1741 (sig
8a26c165 1742 (message "%s" (error-message-string sig)))
94be2532 1743 ((consp ses--curcell)
7ed9159a
JY
1744 (message " "))
1745 (t
94be2532 1746 (princ (symbol-value ses--curcell))))))
7ed9159a
JY
1747
1748(defun ses-recalculate-all ()
1749 "Recalculate and reprint all cells."
1750 (interactive "*")
94be2532
JY
1751 (let ((startcell (get-text-property (point) 'intangible))
1752 (ses--curcell (cons 'A1 (ses-cell-symbol (1- ses--numrows)
1753 (1- ses--numcols)))))
7ed9159a
JY
1754 (ses-recalculate-cell)
1755 (ses-jump-safe startcell)))
1756
1757(defun ses-truncate-cell ()
1758 "Reprint current cell, but without spillover into any following blank
1759cells."
1760 (interactive "*")
1761 (ses-check-curcell)
94be2532 1762 (let* ((rowcol (ses-sym-rowcol ses--curcell))
7ed9159a
JY
1763 (row (car rowcol))
1764 (col (cdr rowcol)))
94be2532 1765 (when (and (< col (1- ses--numcols)) ;;Last column can't spill over, anyway
7ed9159a
JY
1766 (eq (ses-cell-value row (1+ col)) '*skip*))
1767 ;;This cell has spill-over. We'll momentarily pretend the following
1768 ;;cell has a `t' in it.
1769 (eval `(let ((,(ses-cell-symbol row (1+ col)) t))
1770 (ses-print-cell row col)))
1771 ;;Now remove the *skip*. ses-print-cell is always nil here
1772 (ses-set-cell row (1+ col) 'value nil)
1773 (1value (ses-print-cell row (1+ col))))))
1774
1775(defun ses-reconstruct-all ()
1776 "Reconstruct buffer based on cell data stored in Emacs variables."
1777 (interactive "*")
1778 (ses-begin-change)
1779 ;;Reconstruct reference lists.
58cf70d3 1780 (let (x yrow ycol)
7ed9159a 1781 ;;Delete old reference lists
7c018923
SM
1782 (dotimes-with-progress-reporter
1783 (row ses--numrows) "Deleting references..."
94be2532 1784 (dotimes (col ses--numcols)
7ed9159a
JY
1785 (ses-set-cell row col 'references nil)))
1786 ;;Create new reference lists
7c018923
SM
1787 (dotimes-with-progress-reporter
1788 (row ses--numrows) "Computing references..."
94be2532 1789 (dotimes (col ses--numcols)
7ed9159a
JY
1790 (dolist (ref (ses-formula-references (ses-cell-formula row col)))
1791 (setq x (ses-sym-rowcol ref)
1792 yrow (car x)
1793 ycol (cdr x))
1794 (ses-set-cell yrow ycol 'references
1795 (cons (ses-cell-symbol row col)
1796 (ses-cell-references yrow ycol)))))))
1797 ;;Delete everything and reconstruct basic data area
58cf70d3 1798 (if (ses-narrowed-p)
94be2532 1799 (setq ses--deferred-narrow t))
7ed9159a
JY
1800 (widen)
1801 (let ((inhibit-read-only t))
1802 (goto-char (point-max))
58cf70d3 1803 (if (search-backward ";; Local Variables:\n" nil t)
94be2532 1804 (delete-region (point-min) (point))
7ed9159a 1805 ;;Buffer is quite screwed up - can't even save the user-specified locals
94be2532 1806 (delete-region (point-min) (point-max))
7ed9159a 1807 (insert ses-initial-file-trailer)
94be2532 1808 (goto-char (point-min)))
7ed9159a 1809 ;;Create a blank display area
94be2532
JY
1810 (dotimes (row ses--numrows)
1811 (insert ses--blank-line))
7ed9159a
JY
1812 (insert ses-print-data-boundary)
1813 ;;Placeholders for cell data
94be2532 1814 (insert (make-string (* ses--numrows (1+ ses--numcols)) ?\n))
7ed9159a
JY
1815 ;;Placeholders for col-widths, col-printers, default-printer, header-row
1816 (insert "\n\n\n\n")
1817 (insert ses-initial-global-parameters))
94be2532
JY
1818 (ses-set-parameter 'ses--col-widths ses--col-widths)
1819 (ses-set-parameter 'ses--col-printers ses--col-printers)
1820 (ses-set-parameter 'ses--default-printer ses--default-printer)
1821 (ses-set-parameter 'ses--header-row ses--header-row)
1822 (ses-set-parameter 'ses--numrows ses--numrows)
1823 (ses-set-parameter 'ses--numcols ses--numcols)
7ed9159a
JY
1824 ;;Keep our old narrowing
1825 (ses-setup)
1826 (ses-recalculate-all)
94be2532 1827 (goto-char (point-min)))
7ed9159a
JY
1828
1829
58cf70d3
SM
1830;;----------------------------------------------------------------------------
1831;; Input of cell formulas
1832;;----------------------------------------------------------------------------
7ed9159a
JY
1833
1834(defun ses-edit-cell (row col newval)
1835 "Display current cell contents in minibuffer, for editing. Returns nil if
1836cell formula was unsafe and user declined confirmation."
1837 (interactive
1838 (progn
1839 (barf-if-buffer-read-only)
1840 (ses-check-curcell)
94be2532 1841 (let* ((rowcol (ses-sym-rowcol ses--curcell))
7ed9159a
JY
1842 (row (car rowcol))
1843 (col (cdr rowcol))
1844 (formula (ses-cell-formula row col))
1845 initial)
1846 (if (eq (car-safe formula) 'ses-safe-formula)
1847 (setq formula (cadr formula)))
1848 (if (eq (car-safe formula) 'quote)
1849 (setq initial (format "'%S" (cadr formula)))
1850 (setq initial (prin1-to-string formula)))
1851 (if (stringp formula)
1852 ;;Position cursor inside close-quote
1853 (setq initial (cons initial (length initial))))
1854 (list row col
94be2532 1855 (read-from-minibuffer (format "Cell %s: " ses--curcell)
7ed9159a
JY
1856 initial
1857 ses-mode-edit-map
1858 t ;Convert to Lisp object
1859 'ses-read-cell-history)))))
1860 (when (ses-warn-unsafe newval 'unsafep)
1861 (ses-begin-change)
1862 (ses-cell-set-formula row col newval)
1863 t))
1864
1865(defun ses-read-cell (row col newval)
1866 "Self-insert for initial character of cell function."
1867 (interactive
1868 (let ((initial (this-command-keys))
94be2532 1869 (rowcol (progn (ses-check-curcell) (ses-sym-rowcol ses--curcell))))
7ed9159a
JY
1870 (barf-if-buffer-read-only)
1871 (if (string= initial "\"")
1872 (setq initial "\"\"") ;Enter a string
1873 (if (string= initial "(")
1874 (setq initial "()"))) ;Enter a formula list
1875 (list (car rowcol)
1876 (cdr rowcol)
94be2532 1877 (read-from-minibuffer (format "Cell %s: " ses--curcell)
7ed9159a
JY
1878 (cons initial 2)
1879 ses-mode-edit-map
1880 t ;Convert to Lisp object
1881 'ses-read-cell-history))))
1882 (when (ses-edit-cell row col newval)
1883 (ses-command-hook) ;Update cell widths before movement
1884 (dolist (x ses-after-entry-functions)
1885 (funcall x 1))))
1886
1887(defun ses-read-symbol (row col symb)
1888 "Self-insert for a symbol as a cell formula. The set of all symbols that
1889have been used as formulas in this spreadsheet is available for completions."
1890 (interactive
94be2532 1891 (let ((rowcol (progn (ses-check-curcell) (ses-sym-rowcol ses--curcell)))
7ed9159a
JY
1892 newval)
1893 (barf-if-buffer-read-only)
94be2532
JY
1894 (setq newval (completing-read (format "Cell %s ': " ses--curcell)
1895 ses--symbolic-formulas))
7ed9159a
JY
1896 (list (car rowcol)
1897 (cdr rowcol)
1898 (if (string= newval "")
1899 nil ;Don't create zero-length symbols!
1900 (list 'quote (intern newval))))))
1901 (when (ses-edit-cell row col symb)
1902 (ses-command-hook) ;Update cell widths before movement
1903 (dolist (x ses-after-entry-functions)
1904 (funcall x 1))))
1905
1906(defun ses-clear-cell-forward (count)
1907 "Delete formula and printer for current cell and then move to next cell.
1908With prefix, deletes several cells."
1909 (interactive "*p")
1910 (if (< count 0)
1911 (1value (ses-clear-cell-backward (- count)))
1912 (ses-check-curcell)
1913 (ses-begin-change)
1914 (dotimes (x count)
1915 (ses-set-curcell)
94be2532 1916 (let ((rowcol (ses-sym-rowcol ses--curcell)))
7ed9159a
JY
1917 (or rowcol (signal 'end-of-buffer nil))
1918 (ses-clear-cell (car rowcol) (cdr rowcol)))
1919 (forward-char 1))))
1920
1921(defun ses-clear-cell-backward (count)
1922 "Move to previous cell and then delete it. With prefix, deletes several
1923cells."
1924 (interactive "*p")
1925 (if (< count 0)
1926 (1value (ses-clear-cell-forward (- count)))
1927 (ses-check-curcell 'end)
1928 (ses-begin-change)
1929 (dotimes (x count)
1930 (backward-char 1) ;Will signal 'beginning-of-buffer if appropriate
1931 (ses-set-curcell)
94be2532 1932 (let ((rowcol (ses-sym-rowcol ses--curcell)))
7ed9159a
JY
1933 (ses-clear-cell (car rowcol) (cdr rowcol))))))
1934
1935
58cf70d3
SM
1936;;----------------------------------------------------------------------------
1937;; Input of cell-printer functions
1938;;----------------------------------------------------------------------------
7ed9159a
JY
1939
1940(defun ses-read-printer (prompt default)
1941 "Common code for `ses-read-cell-printer', `ses-read-column-printer', and `ses-read-default-printer'.
1942PROMPT should end with \": \". Result is t if operation was cancelled."
1943 (barf-if-buffer-read-only)
1944 (if (eq default t)
1945 (setq default "")
1946 (setq prompt (format "%s [currently %S]: "
1947 (substring prompt 0 -2)
1948 default)))
1949 (let ((new (read-from-minibuffer prompt
1950 nil ;Initial contents
1951 ses-mode-edit-map
1952 t ;Evaluate the result
1953 'ses-read-printer-history
1954 (prin1-to-string default))))
1955 (if (equal new default)
1956 ;;User changed mind, decided not to change printer
1957 (setq new t)
1958 (ses-printer-validate new)
1959 (or (not new)
1960 (stringp new)
1961 (stringp (car-safe new))
1962 (ses-warn-unsafe new 'unsafep-function)
1963 (setq new t)))
1964 new))
1965
1966(defun ses-read-cell-printer (newval)
1967 "Set the printer function for the current cell or range.
1968
1969A printer function is either a string (a format control-string with one
1970%-sequence -- result from format will be right-justified), or a list of one
1971string (result from format will be left-justified), or a lambda-expression of
1972one argument, or a symbol that names a function of one argument. In the
1973latter two cases, the function's result should be either a string (will be
1974right-justified) or a list of one string (will be left-justified)."
1975 (interactive
1976 (let ((default t)
58cf70d3 1977 x)
7ed9159a
JY
1978 (ses-check-curcell 'range)
1979 ;;Default is none if not all cells in range have same printer
1980 (catch 'ses-read-cell-printer
94be2532 1981 (ses-dorange ses--curcell
7ed9159a
JY
1982 (setq x (ses-cell-printer row col))
1983 (if (eq (car-safe x) 'ses-safe-printer)
1984 (setq x (cadr x)))
1985 (if (eq default t)
1986 (setq default x)
1987 (unless (equal default x)
1988 ;;Range contains differing printer functions
1989 (setq default t)
1990 (throw 'ses-read-cell-printer t)))))
94be2532
JY
1991 (list (ses-read-printer (format "Cell %S printer: " ses--curcell)
1992 default))))
7ed9159a
JY
1993 (unless (eq newval t)
1994 (ses-begin-change)
94be2532 1995 (ses-dorange ses--curcell
7ed9159a
JY
1996 (ses-set-cell row col 'printer newval)
1997 (ses-print-cell row col))))
1998
1999(defun ses-read-column-printer (col newval)
2000 "Set the printer function for the current column. See
2001`ses-read-cell-printer' for input forms."
2002 (interactive
94be2532 2003 (let ((col (cdr (ses-sym-rowcol ses--curcell))))
7ed9159a
JY
2004 (ses-check-curcell)
2005 (list col (ses-read-printer (format "Column %s printer: "
2006 (ses-column-letter col))
2007 (ses-col-printer col)))))
2008
2009 (unless (eq newval t)
2010 (ses-begin-change)
94be2532 2011 (ses-set-parameter 'ses--col-printers newval col)
7ed9159a 2012 (save-excursion
94be2532 2013 (dotimes (row ses--numrows)
7ed9159a
JY
2014 (ses-print-cell row col)))))
2015
2016(defun ses-read-default-printer (newval)
2017 "Set the default printer function for cells that have no other. See
2018`ses-read-cell-printer' for input forms."
2019 (interactive
94be2532 2020 (list (ses-read-printer "Default printer: " ses--default-printer)))
7ed9159a
JY
2021 (unless (eq newval t)
2022 (ses-begin-change)
94be2532 2023 (ses-set-parameter 'ses--default-printer newval)
7ed9159a
JY
2024 (ses-reprint-all t)))
2025
2026
58cf70d3
SM
2027;;----------------------------------------------------------------------------
2028;; Spreadsheet size adjustments
2029;;----------------------------------------------------------------------------
7ed9159a
JY
2030
2031(defun ses-insert-row (count)
2032 "Insert a new row before the current one. With prefix, insert COUNT rows
2033before current one."
2034 (interactive "*p")
2035 (ses-check-curcell 'end)
2036 (or (> count 0) (signal 'args-out-of-range nil))
2037 (ses-begin-change)
2038 (let ((inhibit-quit t)
2039 (inhibit-read-only t)
94be2532 2040 (row (or (car (ses-sym-rowcol ses--curcell)) ses--numrows))
7ed9159a
JY
2041 newrow)
2042 ;;Create a new set of cell-variables
94be2532
JY
2043 (ses-create-cell-variable-range ses--numrows (+ ses--numrows count -1)
2044 0 (1- ses--numcols))
2045 (ses-set-parameter 'ses--numrows (+ ses--numrows count))
7ed9159a
JY
2046 ;;Insert each row
2047 (ses-goto-print row 0)
7c018923 2048 (dotimes-with-progress-reporter (x count) "Inserting row..."
7ed9159a
JY
2049 ;;Create a row of empty cells. The `symbol' fields will be set by
2050 ;;the call to ses-relocate-all.
94be2532
JY
2051 (setq newrow (make-vector ses--numcols nil))
2052 (dotimes (col ses--numcols)
58cf70d3 2053 (aset newrow col (ses-make-cell)))
94be2532 2054 (setq ses--cells (ses-vector-insert ses--cells row newrow))
63f3351c 2055 (push `(apply ses-vector-delete ses--cells ,row 1) buffer-undo-list)
94be2532 2056 (insert ses--blank-line))
7ed9159a
JY
2057 ;;Insert empty lines in cell data area (will be replaced by
2058 ;;ses-relocate-all)
2059 (ses-goto-data row 0)
94be2532 2060 (insert (make-string (* (1+ ses--numcols) count) ?\n))
7ed9159a
JY
2061 (ses-relocate-all row 0 count 0)
2062 ;;If any cell printers insert constant text, insert that text
2063 ;;into the line.
94be2532
JY
2064 (let ((cols (mapconcat #'ses-call-printer ses--col-printers nil))
2065 (global (ses-call-printer ses--default-printer)))
7ed9159a
JY
2066 (if (or (> (length cols) 0) (> (length global) 0))
2067 (dotimes (x count)
94be2532 2068 (dotimes (col ses--numcols)
7ed9159a
JY
2069 ;;These cells are always nil, only constant formatting printed
2070 (1value (ses-print-cell (+ x row) col))))))
94be2532 2071 (when (> ses--header-row row)
7ed9159a 2072 ;;Inserting before header
94be2532 2073 (ses-set-parameter 'ses--header-row (+ ses--header-row count))
7ed9159a
JY
2074 (ses-reset-header-string)))
2075 ;;Reconstruct text attributes
2076 (ses-setup)
2077 ;;Return to current cell
94be2532
JY
2078 (if ses--curcell
2079 (ses-jump-safe ses--curcell)
2080 (ses-goto-print (1- ses--numrows) 0)))
7ed9159a
JY
2081
2082(defun ses-delete-row (count)
2083 "Delete the current row. With prefix, Deletes COUNT rows starting from the
2084current one."
2085 (interactive "*p")
2086 (ses-check-curcell)
2087 (or (> count 0) (signal 'args-out-of-range nil))
2088 (let ((inhibit-quit t)
2089 (inhibit-read-only t)
58cf70d3 2090 (row (car (ses-sym-rowcol ses--curcell))))
94be2532 2091 (setq count (min count (- ses--numrows row)))
7ed9159a 2092 (ses-begin-change)
94be2532 2093 (ses-set-parameter 'ses--numrows (- ses--numrows count))
7ed9159a
JY
2094 ;;Delete lines from print area
2095 (ses-goto-print row 0)
2096 (ses-delete-line count)
2097 ;;Delete lines from cell data area
2098 (ses-goto-data row 0)
94be2532 2099 (ses-delete-line (* count (1+ ses--numcols)))
7ed9159a 2100 ;;Relocate variables and formulas
94be2532 2101 (ses-set-with-undo 'ses--cells (ses-vector-delete ses--cells row count))
7ed9159a 2102 (ses-relocate-all row 0 (- count) 0)
94be2532
JY
2103 (ses-destroy-cell-variable-range ses--numrows (+ ses--numrows count -1)
2104 0 (1- ses--numcols))
2105 (when (> ses--header-row row)
2106 (if (<= ses--header-row (+ row count))
7ed9159a 2107 ;;Deleting the header row
94be2532
JY
2108 (ses-set-parameter 'ses--header-row 0)
2109 (ses-set-parameter 'ses--header-row (- ses--header-row count)))
7ed9159a
JY
2110 (ses-reset-header-string)))
2111 ;;Reconstruct attributes
2112 (ses-setup)
94be2532 2113 (ses-jump-safe ses--curcell))
7ed9159a
JY
2114
2115(defun ses-insert-column (count &optional col width printer)
58cf70d3
SM
2116 "Insert a new column before COL (default is the current one).
2117With prefix, insert COUNT columns before current one.
2118If COL is specified, the new column(s) get the specified WIDTH and PRINTER
2119\(otherwise they're taken from the current column)."
7ed9159a
JY
2120 (interactive "*p")
2121 (ses-check-curcell)
2122 (or (> count 0) (signal 'args-out-of-range nil))
2123 (or col
94be2532 2124 (setq col (cdr (ses-sym-rowcol ses--curcell))
7ed9159a
JY
2125 width (ses-col-width col)
2126 printer (ses-col-printer col)))
2127 (ses-begin-change)
2128 (let ((inhibit-quit t)
2129 (inhibit-read-only t)
94be2532
JY
2130 (widths ses--col-widths)
2131 (printers ses--col-printers)
7ed9159a
JY
2132 has-skip)
2133 ;;Create a new set of cell-variables
94be2532
JY
2134 (ses-create-cell-variable-range 0 (1- ses--numrows)
2135 ses--numcols (+ ses--numcols count -1))
7ed9159a 2136 ;;Insert each column.
7c018923 2137 (dotimes-with-progress-reporter (x count) "Inserting column..."
7ed9159a
JY
2138 ;;Create a column of empty cells. The `symbol' fields will be set by
2139 ;;the call to ses-relocate-all.
2140 (ses-adjust-print-width col (1+ width))
94be2532
JY
2141 (ses-set-parameter 'ses--numcols (1+ ses--numcols))
2142 (dotimes (row ses--numrows)
2143 (and (< (1+ col) ses--numcols) (eq (ses-cell-value row col) '*skip*)
7ed9159a
JY
2144 ;;Inserting in the middle of a spill-over
2145 (setq has-skip t))
94be2532
JY
2146 (ses-aset-with-undo ses--cells row
2147 (ses-vector-insert (aref ses--cells row)
58cf70d3 2148 col (ses-make-cell)))
7ed9159a
JY
2149 ;;Insert empty lines in cell data area (will be replaced by
2150 ;;ses-relocate-all)
2151 (ses-goto-data row col)
2152 (insert ?\n))
2153 ;;Insert column width and printer
2154 (setq widths (ses-vector-insert widths col width)
2155 printers (ses-vector-insert printers col printer)))
94be2532
JY
2156 (ses-set-parameter 'ses--col-widths widths)
2157 (ses-set-parameter 'ses--col-printers printers)
7ed9159a
JY
2158 (ses-reset-header-string)
2159 (ses-relocate-all 0 col 0 count)
2160 (if has-skip
2161 (ses-reprint-all t)
2162 (when (or (> (length (ses-call-printer printer)) 0)
94be2532 2163 (> (length (ses-call-printer ses--default-printer)) 0))
7ed9159a
JY
2164 ;;Either column printer or global printer inserts some constant text
2165 ;;Reprint the new columns to insert that text.
94be2532 2166 (dotimes (x ses--numrows)
7ed9159a
JY
2167 (dotimes (y count)
2168 ;Always nil here - this is a blank column
2169 (1value (ses-print-cell-new-width x (+ y col))))))
2170 (ses-setup)))
94be2532 2171 (ses-jump-safe ses--curcell))
7ed9159a
JY
2172
2173(defun ses-delete-column (count)
2174 "Delete the current column. With prefix, Deletes COUNT columns starting
2175from the current one."
2176 (interactive "*p")
2177 (ses-check-curcell)
2178 (or (> count 0) (signal 'args-out-of-range nil))
2179 (let ((inhibit-quit t)
2180 (inhibit-read-only t)
94be2532 2181 (rowcol (ses-sym-rowcol ses--curcell))
7ed9159a 2182 (width 0)
58cf70d3 2183 col origrow has-skip)
7ed9159a
JY
2184 (setq origrow (car rowcol)
2185 col (cdr rowcol)
94be2532
JY
2186 count (min count (- ses--numcols col)))
2187 (if (= count ses--numcols)
7ed9159a
JY
2188 (error "Can't delete all columns!"))
2189 ;;Determine width of column(s) being deleted
2190 (dotimes (x count)
2191 (setq width (+ width (ses-col-width (+ col x)) 1)))
2192 (ses-begin-change)
94be2532 2193 (ses-set-parameter 'ses--numcols (- ses--numcols count))
7ed9159a 2194 (ses-adjust-print-width col (- width))
7c018923 2195 (dotimes-with-progress-reporter (row ses--numrows) "Deleting column..."
7ed9159a
JY
2196 ;;Delete lines from cell data area
2197 (ses-goto-data row col)
2198 (ses-delete-line count)
2199 ;;Delete cells. Check if deletion area begins or ends with a skip.
2200 (if (or (eq (ses-cell-value row col) '*skip*)
94be2532 2201 (and (< col ses--numcols)
7ed9159a
JY
2202 (eq (ses-cell-value row (+ col count)) '*skip*)))
2203 (setq has-skip t))
94be2532
JY
2204 (ses-aset-with-undo ses--cells row
2205 (ses-vector-delete (aref ses--cells row) col count)))
7ed9159a 2206 ;;Update globals
94be2532
JY
2207 (ses-set-parameter 'ses--col-widths
2208 (ses-vector-delete ses--col-widths col count))
2209 (ses-set-parameter 'ses--col-printers
2210 (ses-vector-delete ses--col-printers col count))
7ed9159a
JY
2211 (ses-reset-header-string)
2212 ;;Relocate variables and formulas
2213 (ses-relocate-all 0 col 0 (- count))
94be2532
JY
2214 (ses-destroy-cell-variable-range 0 (1- ses--numrows)
2215 ses--numcols (+ ses--numcols count -1))
7ed9159a
JY
2216 (if has-skip
2217 (ses-reprint-all t)
2218 (ses-setup))
94be2532 2219 (if (>= col ses--numcols)
7ed9159a
JY
2220 (setq col (1- col)))
2221 (ses-goto-print origrow col)))
2222
2223(defun ses-forward-or-insert (&optional count)
2224 "Move to next cell in row, or inserts a new cell if already in last one, or
2225inserts a new row if at bottom of print area. Repeat COUNT times."
2226 (interactive "p")
2227 (ses-check-curcell 'end)
2228 (setq deactivate-mark t) ;Doesn't combine well with ranges
2229 (dotimes (x count)
2230 (ses-set-curcell)
94be2532 2231 (if (not ses--curcell)
7ed9159a
JY
2232 (progn ;At bottom of print area
2233 (barf-if-buffer-read-only)
2234 (ses-insert-row 1))
94be2532 2235 (let ((col (cdr (ses-sym-rowcol ses--curcell))))
7ed9159a
JY
2236 (when (/= 32
2237 (char-before (next-single-property-change (point)
2238 'intangible)))
2239 ;;We're already in last nonskipped cell on line. Need to create a
2240 ;;new column.
2241 (barf-if-buffer-read-only)
2242 (ses-insert-column (- count x)
94be2532 2243 ses--numcols
7ed9159a
JY
2244 (ses-col-width col)
2245 (ses-col-printer col)))))
2246 (forward-char)))
2247
2248(defun ses-append-row-jump-first-column ()
2249 "Insert a new row after current one and jumps to its first column."
2250 (interactive "*")
2251 (ses-check-curcell)
2252 (ses-begin-change)
2253 (beginning-of-line 2)
2254 (ses-set-curcell)
2255 (ses-insert-row 1))
2256
2257(defun ses-set-column-width (col newwidth)
2258 "Set the width of the current column."
2259 (interactive
94be2532 2260 (let ((col (cdr (progn (ses-check-curcell) (ses-sym-rowcol ses--curcell)))))
7ed9159a
JY
2261 (barf-if-buffer-read-only)
2262 (list col
2263 (if current-prefix-arg
2264 (prefix-numeric-value current-prefix-arg)
2265 (read-from-minibuffer (format "Column %s width [currently %d]: "
2266 (ses-column-letter col)
2267 (ses-col-width col))
2268 nil ;No initial contents
2269 nil ;No override keymap
2270 t ;Convert to Lisp object
2271 nil ;No history
2272 (number-to-string
2273 (ses-col-width col))))))) ;Default value
2274 (if (< newwidth 1)
2275 (error "Invalid column width"))
2276 (ses-begin-change)
2277 (ses-reset-header-string)
2278 (save-excursion
2279 (let ((inhibit-quit t))
2280 (ses-adjust-print-width col (- newwidth (ses-col-width col)))
94be2532
JY
2281 (ses-set-parameter 'ses--col-widths newwidth col))
2282 (dotimes (row ses--numrows)
7ed9159a
JY
2283 (ses-print-cell-new-width row col))))
2284
2285
58cf70d3
SM
2286;;----------------------------------------------------------------------------
2287;; Cut and paste, import and export
2288;;----------------------------------------------------------------------------
7ed9159a
JY
2289
2290(defadvice copy-region-as-kill (around ses-copy-region-as-kill
2291 activate preactivate)
2292 "It doesn't make sense to copy read-only or intangible attributes into the
2293kill ring. It probably doesn't make sense to copy keymap properties.
2294We'll assume copying front-sticky properties doesn't make sense, either.
2295
2296This advice also includes some SES-specific code because otherwise it's too
2297hard to override how mouse-1 works."
2298 (when (> beg end)
2299 (let ((temp beg))
2300 (setq beg end
2301 end temp)))
2302 (if (not (and (eq major-mode 'ses-mode)
2303 (eq (get-text-property beg 'read-only) 'ses)
2304 (eq (get-text-property (1- end) 'read-only) 'ses)))
2305 ad-do-it ;Normal copy-region-as-kill
4c759a32
SM
2306 (kill-new (ses-copy-region beg end))
2307 (if transient-mark-mode
2308 (setq deactivate-mark t))
2309 nil))
7ed9159a
JY
2310
2311(defun ses-copy-region (beg end)
2312 "Treat the region as rectangular. Convert the intangible attributes to
2313SES attributes recording the contents of the cell as of the time of copying."
2314 (let* ((inhibit-point-motion-hooks t)
2315 (x (mapconcat 'ses-copy-region-helper
2316 (extract-rectangle beg (1- end)) "\n")))
2317 (remove-text-properties 0 (length x)
2318 '(read-only t
2319 intangible t
2320 keymap t
2321 front-sticky t)
2322 x)
2323 x))
2324
2325(defun ses-copy-region-helper (line)
2326 "Converts one line (of a rectangle being extracted from a spreadsheet) to
2327external form by attaching to each print cell a 'ses attribute that records
2328the corresponding data cell."
2329 (or (> (length line) 1)
2330 (error "Empty range"))
2331 (let ((inhibit-read-only t)
2332 (pos 0)
2333 mycell next sym rowcol)
2334 (while pos
2335 (setq sym (get-text-property pos 'intangible line)
2336 next (next-single-property-change pos 'intangible line)
2337 rowcol (ses-sym-rowcol sym)
2338 mycell (ses-get-cell (car rowcol) (cdr rowcol)))
2339 (put-text-property pos (or next (length line))
2340 'ses
2341 (list (ses-cell-symbol mycell)
2342 (ses-cell-formula mycell)
2343 (ses-cell-printer mycell))
2344 line)
2345 (setq pos next)))
2346 line)
2347
2348(defun ses-kill-override (beg end)
2349 "Generic override for any commands that kill text. We clear the killed
2350cells instead of deleting them."
2351 (interactive "r")
2352 (ses-check-curcell 'needrange)
2353 ;;For some reason, the text-read-only error is not caught by
2354 ;;`delete-region', so we have to use subterfuge.
2355 (let ((buffer-read-only t))
2356 (1value (condition-case x
2357 (noreturn (funcall (lookup-key (current-global-map)
2358 (this-command-keys))
2359 beg end))
2360 (buffer-read-only nil)))) ;The expected error
2361 ;;Because the buffer was marked read-only, the kill command turned itself
2362 ;;into a copy. Now we clear the cells or signal the error. First we
2363 ;;check whether the buffer really is read-only.
2364 (barf-if-buffer-read-only)
2365 (ses-begin-change)
94be2532 2366 (ses-dorange ses--curcell
7ed9159a 2367 (ses-clear-cell row col))
94be2532 2368 (ses-jump (car ses--curcell)))
7ed9159a
JY
2369
2370(defadvice yank (around ses-yank activate preactivate)
2371 "In SES mode, the yanked text is inserted as cells.
2372
2373If the text contains 'ses attributes (meaning it went to the kill-ring from a
2374SES buffer), the formulas and print functions are restored for the cells. If
2375the text contains tabs, this is an insertion of tab-separated formulas.
2376Otherwise the text is inserted as the formula for the current cell.
2377
2378When inserting cells, the formulas are usually relocated to keep the same
2379relative references to neighboring cells. This is best if the formulas
2380generally refer to other cells within the yanked text. You can use the C-u
2381prefix to specify insertion without relocation, which is best when the
2382formulas refer to cells outsite the yanked text.
2383
2384When inserting formulas, the text is treated as a string constant if it doesn't
2385make sense as a sexp or would otherwise be considered a symbol. Use 'sym to
2386explicitly insert a symbol, or use the C-u prefix to treat all unmarked words
2387as symbols."
2388 (if (not (and (eq major-mode 'ses-mode)
2389 (eq (get-text-property (point) 'keymap) 'ses-mode-print-map)))
2390 ad-do-it ;Normal non-SES yank
2391 (ses-check-curcell 'end)
2392 (push-mark (point))
2393 (let ((text (current-kill (cond
2394 ((listp arg) 0)
2395 ((eq arg '-) -1)
2396 (t (1- arg))))))
2397 (or (ses-yank-cells text arg)
2398 (ses-yank-tsf text arg)
2399 (ses-yank-one (ses-yank-resize 1 1)
2400 text
2401 0
2402 (if (memq (aref text (1- (length text))) '(?\t ?\n))
2403 ;;Just one cell - delete final tab or newline
2404 (1- (length text)))
2405 arg)))
2406 (if (consp arg)
2407 (exchange-point-and-mark))))
2408
2409(defun ses-yank-pop (arg)
2410 "Replace just-yanked stretch of killed text with a different stretch.
2411This command is allowed only immediately after a `yank' or a `yank-pop', when
2412the region contains a stretch of reinserted previously-killed text. We
2413replace it with a different stretch of killed text.
2414 Unlike standard `yank-pop', this function uses `undo' to delete the
2415previous insertion."
2416 (interactive "*p")
2417 (or (eq last-command 'yank)
2418 ;;Use noreturn here just to avoid a "poor-coverage" warning in its
2419 ;;macro definition.
2420 (noreturn (error "Previous command was not a yank")))
2421 (undo)
2422 (ses-set-curcell)
2423 (yank (1+ (or arg 1)))
2424 (setq this-command 'yank))
2425
2426(defun ses-yank-cells (text arg)
2427 "If the TEXT has a proper set of 'ses attributes, inserts the text as
2428cells, else return nil. The cells are reprinted--the supplied text is
2429ignored because the column widths, default printer, etc. at yank time might
2430be different from those at kill-time. ARG is a list to indicate that
2431formulas are to be inserted without relocation."
2432 (let ((first (get-text-property 0 'ses text))
2433 (last (get-text-property (1- (length text)) 'ses text)))
2434 (when (and first last) ;;Otherwise not proper set of attributes
2435 (setq first (ses-sym-rowcol (car first))
2436 last (ses-sym-rowcol (car last)))
2437 (let* ((needrows (- (car last) (car first) -1))
2438 (needcols (- (cdr last) (cdr first) -1))
2439 (rowcol (ses-yank-resize needrows needcols))
2440 (rowincr (- (car rowcol) (car first)))
2441 (colincr (- (cdr rowcol) (cdr first)))
2442 (pos 0)
2443 myrow mycol x)
7c018923 2444 (dotimes-with-progress-reporter (row needrows) "Yanking..."
7ed9159a
JY
2445 (setq myrow (+ row (car rowcol)))
2446 (dotimes (col needcols)
2447 (setq mycol (+ col (cdr rowcol))
2448 last (get-text-property pos 'ses text)
2449 pos (next-single-property-change pos 'ses text)
2450 x (ses-sym-rowcol (car last)))
2451 (if (not last)
2452 ;;Newline - all remaining cells on row are skipped
2453 (setq x (cons (- myrow rowincr) (+ needcols colincr -1))
2454 last (list nil nil nil)
2455 pos (1- pos)))
2456 (if (/= (car x) (- myrow rowincr))
2457 (error "Cell row error"))
2458 (if (< (- mycol colincr) (cdr x))
2459 ;;Some columns were skipped
2460 (let ((oldcol mycol))
2461 (while (< (- mycol colincr) (cdr x))
2462 (ses-clear-cell myrow mycol)
2463 (setq col (1+ col)
2464 mycol (1+ mycol)))
2465 (ses-print-cell myrow (1- oldcol)))) ;;This inserts *skip*
2466 (when (car last) ;Skip this for *skip* cells
2467 (setq x (nth 2 last))
2468 (unless (equal x (ses-cell-printer myrow mycol))
2469 (or (not x)
2470 (stringp x)
2471 (eq (car-safe x) 'ses-safe-printer)
2472 (setq x `(ses-safe-printer ,x)))
2473 (ses-set-cell myrow mycol 'printer x))
2474 (setq x (cadr last))
2475 (if (atom arg)
2476 (setq x (ses-relocate-formula x 0 0 rowincr colincr)))
2477 (or (atom x)
2478 (eq (car-safe x) 'ses-safe-formula)
2479 (setq x `(ses-safe-formula ,x)))
2480 (ses-cell-set-formula myrow mycol x)))
2481 (when pos
2482 (if (get-text-property pos 'ses text)
2483 (error "Missing newline between rows"))
2484 (setq pos (next-single-property-change pos 'ses text))))
2485 t))))
2486
2487(defun ses-yank-one (rowcol text from to arg)
2488 "Insert the substring [FROM,TO] of TEXT as the formula for cell ROWCOL (a
2489cons of ROW and COL). Treat plain symbols as strings unless ARG is a list."
2490 (let ((val (condition-case nil
2491 (read-from-string text from to)
2492 (error (cons nil from)))))
2493 (cond
2494 ((< (cdr val) (or to (length text)))
2495 ;;Invalid sexp - leave it as a string
2496 (setq val (substring text from to)))
2497 ((and (car val) (symbolp (car val)))
2498 (if (consp arg)
2499 (setq val (list 'quote (car val))) ;Keep symbol
2500 (setq val (substring text from to)))) ;Treat symbol as text
2501 (t
2502 (setq val (car val))))
2503 (let ((row (car rowcol))
2504 (col (cdr rowcol)))
2505 (or (atom val)
2506 (setq val `(ses-safe-formula ,val)))
2507 (ses-cell-set-formula row col val))))
2508
2509(defun ses-yank-tsf (text arg)
2510 "If TEXT contains tabs and/or newlines, treats the tabs as
2511column-separators and the newlines as row-separators and inserts the text as
2512cell formulas--else return nil. Treat plain symbols as strings unless ARG
2513is a list. Ignore a final newline."
2514 (if (or (not (string-match "[\t\n]" text))
2515 (= (match-end 0) (length text)))
2516 ;;Not TSF format
2517 nil
2518 (if (/= (aref text (1- (length text))) ?\n)
2519 (setq text (concat text "\n")))
2520 (let ((pos -1)
2521 (spots (list -1))
2522 (cols 0)
2523 (needrows 0)
2524 needcols rowcol)
2525 ;;Find all the tabs and newlines
2526 (while (setq pos (string-match "[\t\n]" text (1+ pos)))
2527 (push pos spots)
2528 (setq cols (1+ cols))
2529 (when (eq (aref text pos) ?\n)
2530 (if (not needcols)
2531 (setq needcols cols)
2532 (or (= needcols cols)
2533 (error "Inconsistent row lengths")))
2534 (setq cols 0
2535 needrows (1+ needrows))))
2536 ;;Insert the formulas
2537 (setq rowcol (ses-yank-resize needrows needcols))
2538 (dotimes (row needrows)
2539 (dotimes (col needcols)
2540 (ses-yank-one (cons (+ (car rowcol) needrows (- row) -1)
2541 (+ (cdr rowcol) needcols (- col) -1))
2542 text (1+ (cadr spots)) (car spots) arg)
2543 (setq spots (cdr spots))))
2544 (ses-goto-print (+ (car rowcol) needrows -1)
2545 (+ (cdr rowcol) needcols -1))
2546 t)))
2547
2548(defun ses-yank-resize (needrows needcols)
2549 "If this yank will require inserting rows and/or columns, asks for
2550confirmation and then inserts them. Result is (row,col) for top left of yank
2551spot, or error signal if user requests cancel."
2552 (ses-begin-change)
94be2532
JY
2553 (let ((rowcol (if ses--curcell
2554 (ses-sym-rowcol ses--curcell)
2555 (cons ses--numrows 0)))
7ed9159a 2556 rowbool colbool)
94be2532
JY
2557 (setq needrows (- (+ (car rowcol) needrows) ses--numrows)
2558 needcols (- (+ (cdr rowcol) needcols) ses--numcols)
7ed9159a
JY
2559 rowbool (> needrows 0)
2560 colbool (> needcols 0))
2561 (when (or rowbool colbool)
2562 ;;Need to insert. Get confirm
2563 (or (y-or-n-p (format "Yank will insert %s%s%s. Continue "
2564 (if rowbool (format "%d rows" needrows) "")
2565 (if (and rowbool colbool) " and " "")
2566 (if colbool (format "%d columns" needcols) "")))
2567 (error "Cancelled"))
2568 (when rowbool
94be2532 2569 (let (ses--curcell)
7ed9159a 2570 (save-excursion
94be2532 2571 (ses-goto-print ses--numrows 0)
7ed9159a
JY
2572 (ses-insert-row needrows))))
2573 (when colbool
2574 (ses-insert-column needcols
94be2532
JY
2575 ses--numcols
2576 (ses-col-width (1- ses--numcols))
2577 (ses-col-printer (1- ses--numcols)))))
7ed9159a
JY
2578 rowcol))
2579
2580(defun ses-export-tsv (beg end)
2581 "Export values from the current range, with tabs between columns and
2582newlines between rows. Result is placed in kill ring."
2583 (interactive "r")
2584 (ses-export-tab nil))
2585
2586(defun ses-export-tsf (beg end)
2587 "Export formulas from the current range, with tabs between columns and
2588newlines between rows. Result is placed in kill ring."
2589 (interactive "r")
2590 (ses-export-tab t))
2591
2592(defun ses-export-tab (want-formulas)
2593 "Export the current range with tabs between columns and newlines between
2594rows. Result is placed in kill ring. The export is values unless
2595WANT-FORMULAS is non-nil. Newlines and tabs in the export text are escaped."
2596 (ses-check-curcell 'needrange)
2597 (let ((print-escape-newlines t)
2598 result item)
94be2532 2599 (ses-dorange ses--curcell
7ed9159a
JY
2600 (setq item (if want-formulas
2601 (ses-cell-formula row col)
2602 (ses-cell-value row col)))
2603 (if (eq (car-safe item) 'ses-safe-formula)
2604 ;;Hide our deferred safety-check marker
2605 (setq item (cadr item)))
2606 (if (or (not item) (eq item '*skip*))
2607 (setq item ""))
2608 (when (eq (car-safe item) 'quote)
2609 (push "'" result)
2610 (setq item (cadr item)))
2611 (setq item (prin1-to-string item t))
2612 (setq item (replace-regexp-in-string "\t" "\\\\t" item))
2613 (push item result)
2614 (cond
2615 ((< col maxcol)
2616 (push "\t" result))
2617 ((< row maxrow)
2618 (push "\n" result))))
2619 (setq result (apply 'concat (nreverse result)))
2620 (kill-new result)))
2621
2622
58cf70d3
SM
2623;;----------------------------------------------------------------------------
2624;; Other user commands
2625;;----------------------------------------------------------------------------
7ed9159a 2626
94be2532
JY
2627(defun ses-unset-header-row ()
2628 "Select the default header row."
2629 (interactive)
2630 (ses-set-header-row 0))
2631
2632(defun ses-set-header-row (row)
2633 "Set the ROW to display in the header-line.
2634With a numerical prefix arg, use that row.
2635With no prefix arg, use the current row.
2636With a \\[universal-argument] prefix arg, prompt the user.
2637The top row is row 1. Selecting row 0 displays the default header row."
2638 (interactive
2639 (list (if (numberp current-prefix-arg) current-prefix-arg
2640 (let ((currow (1+ (car (ses-sym-rowcol ses--curcell)))))
2641 (if current-prefix-arg
e9c8c8e7 2642 (read-number "Header row: " currow)
94be2532
JY
2643 currow)))))
2644 (if (or (< row 0) (> row ses--numrows))
7ed9159a
JY
2645 (error "Invalid header-row"))
2646 (ses-begin-change)
94be2532 2647 (ses-set-parameter 'ses--header-row row)
7ed9159a
JY
2648 (ses-reset-header-string))
2649
2650(defun ses-mark-row ()
2651 "Marks the entirety of current row as a range."
2652 (interactive)
2653 (ses-check-curcell 'range)
94be2532 2654 (let ((row (car (ses-sym-rowcol (or (car-safe ses--curcell) ses--curcell)))))
7ed9159a
JY
2655 (push-mark (point))
2656 (ses-goto-print (1+ row) 0)
2657 (push-mark (point) nil t)
2658 (ses-goto-print row 0)))
2659
2660(defun ses-mark-column ()
2661 "Marks the entirety of current column as a range."
2662 (interactive)
2663 (ses-check-curcell 'range)
94be2532 2664 (let ((col (cdr (ses-sym-rowcol (or (car-safe ses--curcell) ses--curcell))))
7ed9159a
JY
2665 (row 0))
2666 (push-mark (point))
94be2532 2667 (ses-goto-print (1- ses--numrows) col)
7ed9159a
JY
2668 (forward-char 1)
2669 (push-mark (point) nil t)
2670 (while (eq '*skip* (ses-cell-value row col))
2671 ;;Skip over initial cells in column that can't be selected
2672 (setq row (1+ row)))
2673 (ses-goto-print row col)))
2674
2675(defun ses-end-of-line ()
2676 "Move point to last cell on line."
2677 (interactive)
2678 (ses-check-curcell 'end 'range)
94be2532
JY
2679 (when ses--curcell ;Otherwise we're at the bottom row, which is empty anyway
2680 (let ((col (1- ses--numcols))
7ed9159a 2681 row rowcol)
94be2532 2682 (if (symbolp ses--curcell)
7ed9159a 2683 ;;Single cell
94be2532 2684 (setq row (car (ses-sym-rowcol ses--curcell)))
7ed9159a
JY
2685 ;;Range - use whichever end of the range the point is at
2686 (setq rowcol (ses-sym-rowcol (if (< (point) (mark))
94be2532
JY
2687 (car ses--curcell)
2688 (cdr ses--curcell))))
7ed9159a
JY
2689 ;;If range already includes the last cell in a row, point is actually
2690 ;;in the following row
2691 (if (<= (cdr rowcol) (1- col))
2692 (setq row (car rowcol))
2693 (setq row (1+ (car rowcol)))
94be2532 2694 (if (= row ses--numrows)
7ed9159a
JY
2695 ;;Already at end - can't go anywhere
2696 (setq col 0))))
94be2532 2697 (when (< row ses--numrows) ;Otherwise it's a range that includes last cell
7ed9159a
JY
2698 (while (eq (ses-cell-value row col) '*skip*)
2699 ;;Back to beginning of multi-column cell
2700 (setq col (1- col)))
2701 (ses-goto-print row col)))))
2702
2703(defun ses-renarrow-buffer ()
2704 "Narrow the buffer so only the print area is visible. Use after \\[widen]."
2705 (interactive)
94be2532 2706 (setq ses--deferred-narrow t))
7ed9159a
JY
2707
2708(defun ses-sort-column (sorter &optional reverse)
2709 "Sorts the range by a specified column. With prefix, sorts in
2710REVERSE order."
2711 (interactive "*sSort column: \nP")
2712 (ses-check-curcell 'needrange)
94be2532
JY
2713 (let ((min (ses-sym-rowcol (car ses--curcell)))
2714 (max (ses-sym-rowcol (cdr ses--curcell))))
7ed9159a
JY
2715 (let ((minrow (car min))
2716 (mincol (cdr min))
2717 (maxrow (car max))
2718 (maxcol (cdr max))
2719 keys extracts end)
2720 (setq sorter (cdr (ses-sym-rowcol (intern (concat sorter "1")))))
2721 (or (and sorter (>= sorter mincol) (<= sorter maxcol))
2722 (error "Invalid sort column"))
2723 ;;Get key columns and sort them
2724 (dotimes (x (- maxrow minrow -1))
2725 (ses-goto-print (+ minrow x) sorter)
2726 (setq end (next-single-property-change (point) 'intangible))
2727 (push (cons (buffer-substring-no-properties (point) end)
2728 (+ minrow x))
2729 keys))
2730 (setq keys (sort keys #'(lambda (x y) (string< (car x) (car y)))))
2731 ;;Extract the lines in reverse sorted order
2732 (or reverse
2733 (setq keys (nreverse keys)))
2734 (dolist (x keys)
2735 (ses-goto-print (cdr x) (1+ maxcol))
2736 (setq end (point))
2737 (ses-goto-print (cdr x) mincol)
2738 (push (ses-copy-region (point) end) extracts))
2739 (deactivate-mark)
2740 ;;Paste the lines sequentially
2741 (dotimes (x (- maxrow minrow -1))
2742 (ses-goto-print (+ minrow x) mincol)
2743 (ses-set-curcell)
2744 (ses-yank-cells (pop extracts) nil)))))
2745
2746(defun ses-sort-column-click (event reverse)
94be2532 2747 "Mouse version of `ses-sort-column'."
7ed9159a
JY
2748 (interactive "*e\nP")
2749 (setq event (event-end event))
2750 (select-window (posn-window event))
2751 (setq event (car (posn-col-row event))) ;Click column
2752 (let ((col 0))
94be2532 2753 (while (and (< col ses--numcols) (> event (ses-col-width col)))
7ed9159a
JY
2754 (setq event (- event (ses-col-width col) 1)
2755 col (1+ col)))
94be2532 2756 (if (>= col ses--numcols)
7ed9159a
JY
2757 (ding)
2758 (ses-sort-column (ses-column-letter col) reverse))))
2759
2760(defun ses-insert-range ()
2761 "Inserts into minibuffer the list of cells currently highlighted in the
2762spreadsheet."
2763 (interactive "*")
2764 (let (x)
2765 (with-current-buffer (window-buffer minibuffer-scroll-window)
2766 (ses-command-hook) ;For ses-coverage
2767 (ses-check-curcell 'needrange)
94be2532
JY
2768 (setq x (cdr (macroexpand `(ses-range ,(car ses--curcell)
2769 ,(cdr ses--curcell))))))
7ed9159a
JY
2770 (insert (substring (prin1-to-string (nreverse x)) 1 -1))))
2771
2772(defun ses-insert-ses-range ()
2773 "Inserts \"(ses-range x y)\" in the minibuffer to represent the currently
2774highlighted range in the spreadsheet."
2775 (interactive "*")
2776 (let (x)
2777 (with-current-buffer (window-buffer minibuffer-scroll-window)
2778 (ses-command-hook) ;For ses-coverage
2779 (ses-check-curcell 'needrange)
94be2532
JY
2780 (setq x (format "(ses-range %S %S)"
2781 (car ses--curcell)
2782 (cdr ses--curcell))))
7ed9159a
JY
2783 (insert x)))
2784
2785(defun ses-insert-range-click (event)
2786 "Mouse version of `ses-insert-range'."
2787 (interactive "*e")
2788 (mouse-set-point event)
2789 (ses-insert-range))
2790
2791(defun ses-insert-ses-range-click (event)
2792 "Mouse version of `ses-insert-ses-range'."
2793 (interactive "*e")
2794 (mouse-set-point event)
2795 (ses-insert-ses-range))
2796
2797
58cf70d3
SM
2798;;----------------------------------------------------------------------------
2799;; Checking formulas for safety
2800;;----------------------------------------------------------------------------
7ed9159a
JY
2801
2802(defun ses-safe-printer (printer)
2803 "Returns PRINTER if safe, or the substitute printer `ses-unsafe' otherwise."
2804 (if (or (stringp printer)
2805 (stringp (car-safe printer))
2806 (not printer)
2807 (ses-warn-unsafe printer 'unsafep-function))
2808 printer
2809 'ses-unsafe))
2810
2811(defun ses-safe-formula (formula)
2812 "Returns FORMULA if safe, or the substitute formula *unsafe* otherwise."
2813 (if (ses-warn-unsafe formula 'unsafep)
2814 formula
2815 `(ses-unsafe ',formula)))
2816
2817(defun ses-warn-unsafe (formula checker)
2818 "Applies CHECKER to FORMULA. If result is non-nil, asks user for
2819confirmation about FORMULA, which might be unsafe. Returns t if formula
2820is safe or user allows execution anyway. Always returns t if
2821`safe-functions' is t."
2822 (if (eq safe-functions t)
2823 t
2824 (setq checker (funcall checker formula))
2825 (if (not checker)
2826 t
2827 (y-or-n-p (format "Formula %S\nmight be unsafe %S. Process it? "
2828 formula checker)))))
2829
2830
58cf70d3
SM
2831;;----------------------------------------------------------------------------
2832;; Standard formulas
2833;;----------------------------------------------------------------------------
7ed9159a
JY
2834
2835(defmacro ses-range (from to)
2836 "Expands to a list of cell-symbols for the range. The range automatically
2837expands to include any new row or column inserted into its middle. The SES
2838library code specifically looks for the symbol `ses-range', so don't create an
2839alias for this macro!"
2840 (let (result)
2841 (ses-dorange (cons from to)
2842 (push (ses-cell-symbol row col) result))
2843 (cons 'list result)))
2844
2845(defun ses-delete-blanks (&rest args)
2846 "Return ARGS reversed, with the blank elements (nil and *skip*) removed."
2847 (let (result)
2848 (dolist (cur args)
58cf70d3
SM
2849 (unless (memq cur '(nil *skip*))
2850 (push cur result)))
7ed9159a
JY
2851 result))
2852
2853(defun ses+ (&rest args)
2854 "Compute the sum of the arguments, ignoring blanks."
2855 (apply '+ (apply 'ses-delete-blanks args)))
2856
2857(defun ses-average (list)
2858 "Computes the sum of the numbers in LIST, divided by their length. Blanks
2859are ignored. Result is always floating-point, even if all args are integers."
2860 (setq list (apply 'ses-delete-blanks list))
2861 (/ (float (apply '+ list)) (length list)))
2862
2863(defmacro ses-select (fromrange test torange)
2864 "Select cells in FROMRANGE that are `equal' to TEST. For each match, return
2865the corresponding cell from TORANGE. The ranges are macroexpanded but not
2866evaluated so they should be either (ses-range BEG END) or (list ...). The
2867TEST is evaluated."
2868 (setq fromrange (cdr (macroexpand fromrange))
2869 torange (cdr (macroexpand torange))
2870 test (eval test))
2871 (or (= (length fromrange) (length torange))
2872 (error "ses-select: Ranges not same length"))
2873 (let (result)
2874 (dolist (x fromrange)
2875 (if (equal test (symbol-value x))
2876 (push (car torange) result))
2877 (setq torange (cdr torange)))
2878 (cons 'list result)))
2879
2880;;All standard formulas are safe
2881(dolist (x '(ses-range ses-delete-blanks ses+ ses-average ses-select))
2882 (put x 'side-effect-free t))
2883
2884
58cf70d3
SM
2885;;----------------------------------------------------------------------------
2886;; Standard print functions
2887;;----------------------------------------------------------------------------
7ed9159a
JY
2888
2889;;These functions use the variables 'row' and 'col' that are
2890;;dynamically bound by ses-print-cell. We define these varables at
2891;;compile-time to make the compiler happy.
2892(eval-when-compile
94be2532
JY
2893 (dolist (x '(row col))
2894 (make-local-variable x)
2895 (set x nil)))
7ed9159a
JY
2896
2897(defun ses-center (value &optional span fill)
2898 "Print VALUE, centered within column. FILL is the fill character for
2899centering (default = space). SPAN indicates how many additional rightward
2900columns to include in width (default = 0)."
94be2532 2901 (let ((printer (or (ses-col-printer col) ses--default-printer))
7ed9159a
JY
2902 (width (ses-col-width col))
2903 half)
51616cd5 2904 (or fill (setq fill ?\s))
7ed9159a
JY
2905 (or span (setq span 0))
2906 (setq value (ses-call-printer printer value))
2907 (dotimes (x span)
2908 (setq width (+ width 1 (ses-col-width (+ col span (- x))))))
2909 (setq width (- width (length value)))
2910 (if (<= width 0)
2911 value ;Too large for field, anyway
2912 (setq half (make-string (/ width 2) fill))
2913 (concat half value half
2914 (if (> (% width 2) 0) (char-to-string fill))))))
2915
2916(defun ses-center-span (value &optional fill)
2917 "Print VALUE, centered within the span that starts in the current column
2918and continues until the next nonblank column. FILL specifies the fill
2919character (default = space)."
2920 (let ((end (1+ col)))
94be2532 2921 (while (and (< end ses--numcols)
7ed9159a
JY
2922 (memq (ses-cell-value row end) '(nil *skip*)))
2923 (setq end (1+ end)))
2924 (ses-center value (- end col 1) fill)))
2925
2926(defun ses-dashfill (value &optional span)
2927 "Print VALUE centered using dashes. SPAN indicates how many rightward
2928columns to include in width (default = 0)."
2929 (ses-center value span ?-))
2930
2931(defun ses-dashfill-span (value)
2932 "Print VALUE, centered using dashes within the span that starts in the
2933current column and continues until the next nonblank column."
2934 (ses-center-span value ?-))
2935
2936(defun ses-tildefill-span (value)
2937 "Print VALUE, centered using tildes within the span that starts in the
2938current column and continues until the next nonblank column."
2939 (ses-center-span value ?~))
2940
2941(defun ses-unsafe (value)
2942 "Substitute for an unsafe formula or printer"
2943 (error "Unsafe formula or printer"))
2944
2945;;All standard printers are safe, including ses-unsafe!
2946(dolist (x (cons 'ses-unsafe ses-standard-printer-functions))
2947 (put x 'side-effect-free t))
2948
2949(provide 'ses)
2950
58cf70d3
SM
2951;; arch-tag: 88c1ccf0-4293-4824-8c5d-0757b52217f3
2952;;; ses.el ends here