From 41b28dea8587c13b0bc59c1ec70b65afab3aeeca Mon Sep 17 00:00:00 2001 From: =?utf8?q?Vincent=20Bela=C3=AFche?= Date: Thu, 28 Jul 2016 17:41:21 +0200 Subject: [PATCH] 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. --- doc/misc/ses.texi | 189 +++++++++++++++++++++++++++++++++++++++------- lisp/ses.el | 125 +++++++++++++++++++++--------- 2 files changed, 250 insertions(+), 64 deletions(-) diff --git a/doc/misc/ses.texi b/doc/misc/ses.texi index 8b0bb82f174..1c5070b38a9 100644 --- a/doc/misc/ses.texi +++ b/doc/misc/ses.texi @@ -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 diff --git a/lisp/ses.el b/lisp/ses.el index 305027c73bb..9d278b6d222 100644 --- a/lisp/ses.el +++ b/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." -- 2.39.2