Note: ses.el is included in latest Emacs.

Simple Emacs Spreadsheet (SES, ses-mode or SesMode) is major mode in Emacs that mimics traditional SpreadSheet software, and uses EmacsLisp formulas. It was written by Jonathan Vayner:

Not sure whether this comment still applies:

per author, not functional for xemacs because of text property differences

The latest release on news://gnu.emacs.sources said:

NEW FOR VERSION 21: Viral immunity, header line, sort by column, customizable variables. Also, great speed improvements and some bugfixes.

Example formulas:

    (ses-average (ses-range D2 D37))
    (apply (quote +) (ses-range B36 N36))

SES will come standard with version 22 of GnuEmacs.

Addons for SES,

I used SES for my classes: A list of students, points for all the exercises, totals over all exercises, averages for each exercise, calculate a grade based on the totals and some systematic hand-waving. It worked very nicely. – AlexSchroeder

I’m trying to write a simple eLisp function to automaticaly open an SES file and export its whole content to value. I’m facing problems of ses-export-tsv and region. Does anyone know how to write a lisp function to export the whole content ? Thanks.

This is what I use to write out a tsv file each time I save a ses buffer (PeterJones):

  (defun pmade:ses-export-buffer-to-tsv nil
    "Export the current SES buffer to a TSV file.  The file name
    will be derived from the current buffer name."
    (let* ((fname (or (buffer-file-name) "export"))
           (bcell (get-text-property (point-min) 'intangible))
           (ecell (get-text-property (- (point-max) 2) 'intangible))
           (ses--curcell (cons bcell ecell)))
      (setq fname
            (if (string-match "\\..*$" fname)
                (replace-match ".tsv" nil nil fname)
              (concat fname ".tsv")))
      (ses-export-tab nil)
      (with-temp-file fname (insert (car kill-ring)))))
  (add-hook 'ses-mode-hook
    (lambda () 
      (add-hook 'after-save-hook 'pmade:ses-export-buffer-to-tsv t t)))

So I have numerous files that were manually indented into a tree structure, an outline, by hand. Attempting to paste these into SES failed, with an “inconsistent row lengths” error. I’m not surprised that the rows have inconsistent lengths. I’d just like to get the data imported, and have enough columns to hold the line with the most tabs. Only one cell should be filled in each row. Any solutions?

Well, I worked around the problem by appending 22 tabs to the end of each line, and then writing a keyboard macro that searched for the 20th tab on a line, deleting the rest of the line, and repeated that for each row in the file. That gave me a consistent number of tabs on each line, and then the yank into the spreadsheet worked… well, at least it didn’t complain about inconsistent row lengths. It still hasn’t shown me the spreadsheet yet… pegging out the CPU… but the file does have 5000+ lines… still going… guess this spreadsheet is for puny data sets… After a two hours, I killed the emacs because it never finished, and never showed me a spreadsheet, other than the one cell one that I tried to paste into..

Functions to read/write a CSV file.

;; ses-csv.el -- Read/Write CSV file for SES
;; Author: Takashi Hattori (
;; Requires: Ruby

(defun ses-read-from-csv-file (file)
  "Insert the contents of a CSV file named FILE into the current position."
  (interactive "fCSV file: ")
  (let ((buf (get-buffer-create "*ses-csv*"))
      (set-buffer buf)
      (process-file "ruby" file buf nil "-e" "require 'csv'; CSV::Reader.parse(STDIN) { |x| puts x.join(\"\\t\") }")
      (setq text (buffer-substring (point-min) (point-max))))
    (ses-yank-tsf text nil)))

(defun ses-write-to-csv-file (file)
  "Write the values of the current buffer into a CSV file named FILE."
  (interactive "FCSV file: ")
  (push-mark (point-min) t t)
  (goto-char (- (point-max) 1))
  (ses-write-to-csv-file-region file))

(defun ses-write-to-csv-file-region (file)
  "Write the values of the region into a CSV file named FILE."
  (interactive "FCSV file: ")
  (ses-export-tab nil)
  (let ((buf (get-buffer-create "*ses-csv*")))
      (set-buffer buf)
      (call-process-region (point-min) (point-max) "ruby" t buf nil "-e" "require 'csv'; w = CSV::Writer.create(STDOUT); STDIN.each { |x| w << x.chomp.split(/\\t/) }")
      (write-region (point-min) (point-max) file))))

CategoryCalculators CategoryModes