Enable addition of local printers from a mode hook.
* doc/misc/ses.texi (Printer functions): Split the node into 5 sub-nodes + add some extra documentation. (Various kinds of printer functions): Make an itemisation to disintguish better the 3 types of printers, give an example of lambda printer definition. (Standard printer functions): Add documentation for ses-prin1 printer function. (Local printer functions): Add documentation for creating local printers programmatically from a hook. (Writing a lambda printer function): Add documentation about anti-stackoverflow precautions to take when you call the standard printer functions from inside a local printer. * lisp/ses.el (ses-standard-printer-functions): Add ses-prin1 among standard printer function, and update docstring accordingly. (ses-call-printer, ses-export-tab): Call `ses-prin1' instead of prin1-to-string. (ses-define-local-printer): Add definition to arguments so that a local printer can be defined programmatically from a mode hook. Make docstring more substantial. Use completing read for local printer name input. Plus some minor optimization. (ses-define-if-new-local-printer): New defsubst. (ses-center, ses-center-span, ses-dashfill) (ses-dashfill-span, ses-tildefill-span): Allow to pass printer as an optional argument to superseed column printer/default spreadsheet printer. (ses-prin1): New defun.
This commit is contained in:
parent
3f4f21b406
commit
41b28dea85
2 changed files with 250 additions and 64 deletions
|
@ -374,26 +374,62 @@ Undo previous action (@code{(undo)}).
|
|||
@cindex printer functions
|
||||
@cindex cell formatting
|
||||
@cindex formatting cells
|
||||
@findex ses-read-cell-printer
|
||||
@findex ses-read-column-printer
|
||||
@findex ses-read-default-printer
|
||||
@findex ses-define-local-printer
|
||||
@findex ses-center
|
||||
@findex ses-center-span
|
||||
@findex ses-dashfill
|
||||
@findex ses-dashfill-span
|
||||
@findex ses-tildefill-span
|
||||
|
||||
|
||||
Printer functions convert binary cell values into the print forms that
|
||||
Emacs will display on the screen.
|
||||
|
||||
A printer can be a format string, like @samp{"$%.2f"}. The result
|
||||
@menu
|
||||
* Various kinds of printer functions::
|
||||
* Configuring what printer function applies::
|
||||
* Standard printer functions::
|
||||
* Local printer functions::
|
||||
* Writing a lambda printer function::
|
||||
@end menu
|
||||
|
||||
@node Various kinds of printer functions
|
||||
@subsection Various kinds of printer functions
|
||||
|
||||
When configuring what printer function applies (@pxref{Configuring
|
||||
what printer function applies}), you can enter a printer function as
|
||||
one of the following:
|
||||
|
||||
@itemize
|
||||
@item
|
||||
A format string, like @samp{"$%.2f"}. The result
|
||||
string is right-aligned within the print cell. To get left-alignment,
|
||||
use parentheses: @samp{("$%.2f")}. A printer can also be a
|
||||
one-argument function (a symbol or a lambda), whose result is a string
|
||||
(right-aligned) or list of one string (left-aligned). While typing in
|
||||
a lambda, you can use @kbd{M-@key{TAB}} to complete the names of symbols.
|
||||
use parentheses: @samp{("$%.2f")}.
|
||||
@item
|
||||
A printer can also be a one-argument function, the result of which is
|
||||
a string (right-aligned) or list of one string (left-aligned). Such a
|
||||
function can be in turn configured as:
|
||||
@itemize
|
||||
@item
|
||||
A lambda expression, for instance:
|
||||
|
||||
@lisp
|
||||
(lambda (x)
|
||||
(cond
|
||||
((null x) "")
|
||||
((numberp x) (format "%.2f" x))
|
||||
(t (ses-center-span x ?# 'ses-prin1))))
|
||||
@end lisp
|
||||
|
||||
While typing in a lambda, you can use @kbd{M-@key{TAB}} to complete
|
||||
the names of symbols.
|
||||
@item
|
||||
A symbol referring to a standard printer function (@pxref{Standard
|
||||
printer functions}).
|
||||
@item
|
||||
A symbol referring to a local printer function (@pxref{Local printer
|
||||
functions}).
|
||||
@end itemize
|
||||
|
||||
|
||||
@end itemize
|
||||
|
||||
|
||||
@node Configuring what printer function applies
|
||||
@subsection Configuring what printer function applies
|
||||
|
||||
Each cell has a printer. If @code{nil}, the column-printer for the cell's
|
||||
column is used. If that is also @code{nil}, the default-printer for the
|
||||
|
@ -401,25 +437,35 @@ spreadsheet is used.
|
|||
|
||||
@table @kbd
|
||||
@item p
|
||||
@findex ses-read-cell-printer
|
||||
Enter a printer for current cell or range (@code{ses-read-cell-printer}).
|
||||
|
||||
@item M-p
|
||||
@findex ses-read-column-printer
|
||||
Enter a printer for the current column (@code{ses-read-column-printer}).
|
||||
|
||||
@item C-c C-p
|
||||
@findex ses-read-default-printer
|
||||
Enter the default printer for the spreadsheet
|
||||
(@code{ses-read-default-printer}).
|
||||
@end table
|
||||
|
||||
The @code{ses-read-@r{XXX}-printer} commands have their own minibuffer
|
||||
history, which is preloaded with the set of all printers used in this
|
||||
spreadsheet, plus the standard printers.
|
||||
The @code{ses-read-@var{xxx}-printer} commands have their own
|
||||
minibuffer history, which is preloaded with the set of all printers
|
||||
used in this spreadsheet, plus the standard printers (@pxref{Standard
|
||||
printer functions}) and the local printers (@pxref{Local printer
|
||||
functions}).
|
||||
|
||||
The standard printers are suitable only for cells, not columns or
|
||||
default, because they format the value using the column-printer (or
|
||||
default-printer if @code{nil}) and then center the result:
|
||||
@node Standard printer functions
|
||||
@subsection Standard printer functions
|
||||
|
||||
@table @code
|
||||
|
||||
Except for @code{ses-prin1}, the other standard printers are suitable
|
||||
only for cells, not columns or default, because they format the value
|
||||
using the column-printer (or default-printer if @code{nil}) and then
|
||||
center the result:
|
||||
|
||||
@ftable @code
|
||||
@item ses-center
|
||||
Just centering.
|
||||
|
||||
|
@ -434,8 +480,16 @@ Centering with dashes and spill-over.
|
|||
|
||||
@item ses-tildefill-span
|
||||
Centering with tildes (~) and spill-over.
|
||||
@end table
|
||||
|
||||
@item ses-prin1
|
||||
This is the fallback printer, used when calling the configured printer
|
||||
throws some error.
|
||||
@end ftable
|
||||
|
||||
@node Local printer functions
|
||||
@subsection Local printer functions
|
||||
|
||||
@findex ses-define-local-printer
|
||||
You can define printer function local to a sheet with the command
|
||||
@code{ses-define-local-printer}. For instance, define a printer
|
||||
@samp{foo} to @code{"%.2f"}, and then use symbol @samp{foo} as a
|
||||
|
@ -444,9 +498,50 @@ printer function. Then, if you call again
|
|||
@code{"%.3f"}, all the cells using printer @samp{foo} will be
|
||||
reprinted accordingly.
|
||||
|
||||
When you define a printer function with a lambda expression taking one
|
||||
argument, please take care that the returned value is a string, or a
|
||||
list containing a string, even when the input argument has an
|
||||
Sometimes there are local printers that you want to define or
|
||||
re-define automatically every time you open a sheet. For instance
|
||||
imagine that you want to define/re-define automatically a local
|
||||
printer @code{euro} to display a number like an amount of euros, that
|
||||
is to say number @code{3.1} would be displayed as
|
||||
@code{3.10@dmn{}@euro{}}. To do so in any non read-only SES buffer,
|
||||
you can add some code like this to your @file{.emacs} init file:
|
||||
|
||||
@lisp
|
||||
(defun my-ses-mode-hook ()
|
||||
(unless buffer-read-only
|
||||
(ses-define-local-printer
|
||||
'euro
|
||||
(lambda (x)
|
||||
(cond
|
||||
((null x) "")
|
||||
((numberp x) (format "%.2f€" x))
|
||||
(t (ses-center-span x ?# 'ses-prin1)))))))
|
||||
(add-hook 'ses-mode-hook 'my-ses-mode-hook)
|
||||
@end lisp
|
||||
|
||||
If you replace command @code{ses-define-local-printer} by function
|
||||
@code{ses-define-if-new-local-printer}
|
||||
@findex ses-define-if-new-local-printer
|
||||
the definition will occur only if a local printer with the same name
|
||||
in not already defined.
|
||||
|
||||
|
||||
@node Writing a lambda printer function
|
||||
@subsection Writing a lambda printer function
|
||||
|
||||
You can write a printer function with a lambda expression taking one
|
||||
argument in two cases:
|
||||
|
||||
@itemize
|
||||
@item
|
||||
when you configure the printer function applying to a cell or column, or
|
||||
@item
|
||||
when you define a local printer function with command
|
||||
@code{ses-define-local-printer}.
|
||||
@end itemize
|
||||
|
||||
When doing so, please take care that the returned value is a string,
|
||||
or a list containing a string, even when the input argument has an
|
||||
unexpected value. Here is an example:
|
||||
|
||||
@example
|
||||
|
@ -454,10 +549,11 @@ unexpected value. Here is an example:
|
|||
(cond
|
||||
((null val) "")
|
||||
((and (numberp val) (>= val 0)) (format "%.1f" val))
|
||||
(t (ses-center-span (format "%S" val) ?#))))
|
||||
(t (ses-center-span val ?# 'ses-prin1))))
|
||||
@end example
|
||||
|
||||
This example will:
|
||||
|
||||
@itemize
|
||||
@item
|
||||
When the cell is empty (ie.@: when @code{val} is @code{nil}), print an
|
||||
|
@ -467,12 +563,47 @@ When the cell value is a non negative number, format the the value in
|
|||
fixed-point notation with one decimal after point
|
||||
@item
|
||||
Otherwise, handle the value as erroneous by printing it as an
|
||||
s-expression (using @code{prin1}), centered and surrounded by @code{#}
|
||||
filling.
|
||||
s-expression (using @code{ses-prin1}), centered and surrounded by
|
||||
@code{#} filling.
|
||||
@end itemize
|
||||
|
||||
Another precaution to take is to avoid stack-overflow (due to a
|
||||
printer function indefintely recursively re-calling itself). This can
|
||||
happen mistakenly when you use a local printer as a column printer,
|
||||
and this local printer implicitely call the current column printer, so
|
||||
will call itself recursively. Imagine for instance that you want to
|
||||
create some local printer @code{=fill} that would center the content
|
||||
of a cell and surround it by equal signs @code{=}, and you do it this
|
||||
way:
|
||||
|
||||
@lisp
|
||||
(lambda (x)
|
||||
(cond
|
||||
((null x) "")
|
||||
(t (ses-center x 0 ?=))))
|
||||
@end lisp
|
||||
|
||||
Because @code{=fill} uses standard printer @code{ses-center} without
|
||||
passing explicitely any printer to it, @code{ses-center} will call the
|
||||
current column printer if any or the spreadsheet default printer
|
||||
otherwise. So using @code{=fill} as a column printer will result in a
|
||||
stack overflow in this column. SES does not make any check for that,
|
||||
you just have to be careful. For instance re-write @code{=fill} like
|
||||
this:
|
||||
|
||||
@lisp
|
||||
(lambda (x)
|
||||
(cond
|
||||
((null x) "")
|
||||
((stringp x) (ses-center x 0 ?= " %s "))
|
||||
(t (ses-center-span x ?# 'ses-prin1))))
|
||||
@end lisp
|
||||
|
||||
The code above applies the @code{=} filling only to strings, it also
|
||||
surrounds the string by one space on each side before filling with
|
||||
@code{=} signs. So string @samp{Foo} will be displayed like @samp{@w{===
|
||||
Foo ===}} in an 11 character wide column. Anything else than empty cell
|
||||
or non string is displayed like errouneous by using @code{#} filling.
|
||||
|
||||
@node Clearing cells
|
||||
@section Clearing cells
|
||||
|
|
125
lisp/ses.el
125
lisp/ses.el
|
@ -275,12 +275,15 @@ Each function is called with ARG=1."
|
|||
"Display properties to create a raised box for cells in the header line.")
|
||||
|
||||
(defconst ses-standard-printer-functions
|
||||
'(ses-center ses-center-span ses-dashfill ses-dashfill-span
|
||||
ses-tildefill-span)
|
||||
"List of print functions to be included in initial history of printer
|
||||
functions. None of these standard-printer functions is suitable for use as a
|
||||
column printer or a global-default printer because they invoke the column or
|
||||
default printer and then modify its output.")
|
||||
'(ses-center
|
||||
ses-center-span ses-dashfill ses-dashfill-span
|
||||
ses-tildefill-span
|
||||
ses-prin1)
|
||||
"List of print functions to be included in initial history of
|
||||
printer functions. None of these standard-printer functions,
|
||||
except function `ses-prin1', is suitable for use as a column
|
||||
printer or a global-default printer because they invoke the
|
||||
column or default printer and then modify its output.")
|
||||
|
||||
|
||||
;;----------------------------------------------------------------------------
|
||||
|
@ -1328,7 +1331,7 @@ printer signaled one (and \"%s\" is used as the default printer), else nil."
|
|||
(car value))))
|
||||
(error
|
||||
(setq ses-call-printer-return signal)
|
||||
(prin1-to-string value t))))
|
||||
(ses-prin1 value))))
|
||||
|
||||
(defun ses-adjust-print-width (col change)
|
||||
"Insert CHANGE spaces in front of column COL, or at end of line if
|
||||
|
@ -3232,7 +3235,7 @@ is non-nil. Newlines and tabs in the export text are escaped."
|
|||
(when (eq (car-safe item) 'quote)
|
||||
(push "'" result)
|
||||
(setq item (cadr item)))
|
||||
(setq item (prin1-to-string item t))
|
||||
(setq item (ses-prin1 item))
|
||||
(setq item (replace-regexp-in-string "\t" "\\\\t" item))
|
||||
(push item result)
|
||||
(cond
|
||||
|
@ -3531,34 +3534,67 @@ Uses the value COMPILED-VALUE for this printer."
|
|||
(ses-begin-change))
|
||||
(ses-print-cell row col)))))))
|
||||
|
||||
(defun ses-define-local-printer (name)
|
||||
"Define a local printer with name NAME."
|
||||
(interactive "*SEnter printer name: ")
|
||||
|
||||
(defun ses-define-local-printer (name definition)
|
||||
"Define a local printer with name NAME and definition DEFINITION.
|
||||
|
||||
NAME shall be a symbol. Use TAB to complete over existing local
|
||||
printer names.
|
||||
|
||||
DEFINITION shall be either a string formatter, e.g.:
|
||||
|
||||
\"%.2f\" or (\"%.2f\") for left alignment.
|
||||
|
||||
or a lambda expression, e.g. for formatting in ISO format dates
|
||||
created with a '(calcFunc-date YEAR MONTH DAY)' formula:
|
||||
|
||||
(lambda (x)
|
||||
(cond
|
||||
((null val) \"\")
|
||||
((eq (car-safe x) 'date)
|
||||
(let ((calc-format-date '(X YYYY \"-\" MM \"-\" DD)))
|
||||
(math-format-date x)))
|
||||
(t (ses-center-span val ?# 'ses-prin1))))
|
||||
|
||||
If NAME is already used to name a local printer function, then
|
||||
the current definition is proposed as default value, and the
|
||||
function is redefined."
|
||||
(interactive
|
||||
(let (name def already-defined-names)
|
||||
(maphash (lambda (key val) (push (symbol-name key) already-defined-names))
|
||||
ses--local-printer-hashmap)
|
||||
(setq name (completing-read "Enter printer name: " already-defined-names))
|
||||
(when (string= name "")
|
||||
(error "Invalid printer name"))
|
||||
(setq name (intern name))
|
||||
(let* ((cur-printer (gethash name ses--local-printer-hashmap))
|
||||
(default (and cur-printer (ses--locprn-def cur-printer))))
|
||||
(setq def (ses-read-printer (format "Enter definition of printer %S: " name)
|
||||
default)))
|
||||
(list name def)))
|
||||
|
||||
(let* ((cur-printer (gethash name ses--local-printer-hashmap))
|
||||
(default (and (vectorp cur-printer) (ses--locprn-def cur-printer)))
|
||||
create-printer
|
||||
(new-def
|
||||
(ses-read-printer (format "Enter definition of printer %S: " name)
|
||||
default)))
|
||||
(default (and cur-printer (ses--locprn-def cur-printer)))
|
||||
create-printer)
|
||||
(cond
|
||||
;; cancelled operation => do nothing
|
||||
((eq new-def t))
|
||||
((eq definition t))
|
||||
;; no change => do nothing
|
||||
((and (vectorp cur-printer) (equal new-def default)))
|
||||
((and cur-printer (equal definition default)))
|
||||
;; re-defined printer
|
||||
((vectorp cur-printer)
|
||||
(cur-printer
|
||||
(setq create-printer 0)
|
||||
(setf (ses--locprn-def cur-printer) new-def)
|
||||
(setf (ses--locprn-def cur-printer) definition)
|
||||
(ses-refresh-local-printer
|
||||
name
|
||||
(setf (ses--locprn-compiled cur-printer)
|
||||
(ses-local-printer-compile new-def))))
|
||||
(ses-local-printer-compile definition))))
|
||||
;; new definition
|
||||
(t
|
||||
(setq create-printer 1)
|
||||
(puthash name
|
||||
(setq cur-printer
|
||||
(ses-make-local-printer-info new-def))
|
||||
(ses-make-local-printer-info definition))
|
||||
ses--local-printer-hashmap)))
|
||||
(when create-printer
|
||||
(let ((printer-def-text
|
||||
|
@ -3582,8 +3618,18 @@ Uses the value COMPILED-VALUE for this printer."
|
|||
(when (= create-printer 1)
|
||||
(ses-file-format-extend-parameter-list 3)
|
||||
(ses-set-parameter 'ses--numlocprn
|
||||
(+ ses--numlocprn create-printer))))))))))
|
||||
(1+ ses--numlocprn))))))))))
|
||||
|
||||
(defsubst ses-define-if-new-local-printer (name def)
|
||||
"Same as function `ses-define-if-new-local-printer', except
|
||||
that the definition occurs only when the local printer does not
|
||||
already exists.
|
||||
|
||||
Function `ses-define-if-new-local-printer' is not interactive, it
|
||||
is intended for mode hooks to programatically automatically add
|
||||
local printers."
|
||||
(unless (gethash name ses--local-printer-hashmap)
|
||||
(ses-define-local-printer name def)))
|
||||
|
||||
;;----------------------------------------------------------------------------
|
||||
;; Checking formulas for safety
|
||||
|
@ -3794,13 +3840,16 @@ either (ses-range BEG END) or (list ...). The TEST is evaluated."
|
|||
;; Standard print functions
|
||||
;;----------------------------------------------------------------------------
|
||||
|
||||
(defun ses-center (value &optional span fill)
|
||||
(defun ses-center (value &optional span fill printer)
|
||||
"Print VALUE, centered within column.
|
||||
FILL is the fill character for centering (default = space).
|
||||
SPAN indicates how many additional rightward columns to include
|
||||
in width (default = 0)."
|
||||
(let ((printer (or (ses-col-printer ses--col) ses--default-printer))
|
||||
(width (ses-col-width ses--col))
|
||||
in width (default = 0).
|
||||
PRINTER is the printer to use for printing the value, default is the
|
||||
column printer if any, or the spreadsheet the spreadsheet default
|
||||
printer otherwise."
|
||||
(setq printer (or printer (ses-col-printer ses--col) ses--default-printer))
|
||||
(let ((width (ses-col-width ses--col))
|
||||
half)
|
||||
(or fill (setq fill ?\s))
|
||||
(or span (setq span 0))
|
||||
|
@ -3815,7 +3864,7 @@ in width (default = 0)."
|
|||
(concat half value half
|
||||
(if (> (% width 2) 0) (char-to-string fill))))))
|
||||
|
||||
(defun ses-center-span (value &optional fill)
|
||||
(defun ses-center-span (value &optional fill printer)
|
||||
"Print VALUE, centered within the span that starts in the current column
|
||||
and continues until the next nonblank column.
|
||||
FILL specifies the fill character (default = space)."
|
||||
|
@ -3823,22 +3872,28 @@ FILL specifies the fill character (default = space)."
|
|||
(while (and (< end ses--numcols)
|
||||
(memq (ses-cell-value ses--row end) '(nil *skip*)))
|
||||
(setq end (1+ end)))
|
||||
(ses-center value (- end ses--col 1) fill)))
|
||||
(ses-center value (- end ses--col 1) fill printer)))
|
||||
|
||||
(defun ses-dashfill (value &optional span)
|
||||
(defun ses-dashfill (value &optional span printer)
|
||||
"Print VALUE centered using dashes.
|
||||
SPAN indicates how many rightward columns to include in width (default = 0)."
|
||||
(ses-center value span ?-))
|
||||
(ses-center value span ?- printer))
|
||||
|
||||
(defun ses-dashfill-span (value)
|
||||
(defun ses-dashfill-span (value &optional printer)
|
||||
"Print VALUE, centered using dashes within the span that starts in the
|
||||
current column and continues until the next nonblank column."
|
||||
(ses-center-span value ?-))
|
||||
(ses-center-span value ?- printer))
|
||||
|
||||
(defun ses-tildefill-span (value)
|
||||
(defun ses-tildefill-span (value &optional printer)
|
||||
"Print VALUE, centered using tildes within the span that starts in the
|
||||
current column and continues until the next nonblank column."
|
||||
(ses-center-span value ?~))
|
||||
(ses-center-span value ?~ printer))
|
||||
|
||||
(defun ses-prin1 (value)
|
||||
"Shorthand for '(prin1-to-string VALUE t)'.
|
||||
Usefull to handle the default behaviour in custom lambda based
|
||||
printer functions."
|
||||
(prin1-to-string value t))
|
||||
|
||||
(defun ses-unsafe (_value)
|
||||
"Substitute for an unsafe formula or printer."
|
||||
|
|
Loading…
Add table
Reference in a new issue