From ab1823f00136f220f602ec8fae2b448342609501 Mon Sep 17 00:00:00 2001 From: Andreas Gerler Date: Thu, 31 Oct 2019 17:08:53 +0100 Subject: [PATCH] update csv-mode --- .../csv-mode-autoloads.el | 16 +- elpa/csv-mode-1.10/csv-mode-pkg.el | 2 + .../csv-mode.el | 799 ++++++++++++++---- elpa/csv-mode-1.7/csv-mode-pkg.el | 2 - 4 files changed, 658 insertions(+), 161 deletions(-) rename elpa/{csv-mode-1.7 => csv-mode-1.10}/csv-mode-autoloads.el (86%) create mode 100644 elpa/csv-mode-1.10/csv-mode-pkg.el rename elpa/{csv-mode-1.7 => csv-mode-1.10}/csv-mode.el (64%) delete mode 100644 elpa/csv-mode-1.7/csv-mode-pkg.el diff --git a/elpa/csv-mode-1.7/csv-mode-autoloads.el b/elpa/csv-mode-1.10/csv-mode-autoloads.el similarity index 86% rename from elpa/csv-mode-1.7/csv-mode-autoloads.el rename to elpa/csv-mode-1.10/csv-mode-autoloads.el index d883a808..9ccb8d5d 100644 --- a/elpa/csv-mode-1.7/csv-mode-autoloads.el +++ b/elpa/csv-mode-1.10/csv-mode-autoloads.el @@ -1,12 +1,10 @@ ;;; csv-mode-autoloads.el --- automatically extracted autoloads ;; ;;; Code: - -(add-to-list 'load-path (directory-file-name - (or (file-name-directory #$) (car load-path)))) - +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) -;;;### (autoloads nil "csv-mode" "csv-mode.el" (0 0 0 0)) +;;;### (autoloads nil "csv-mode" "csv-mode.el" (23994 42604 825270 +;;;;;; 991000)) ;;; Generated autoloads from csv-mode.el (autoload 'csv-mode "csv-mode" "\ @@ -49,7 +47,12 @@ CSV mode provides the following specific keyboard key bindings: (add-to-list 'auto-mode-alist '("\\.[Cc][Ss][Vv]\\'" . csv-mode)) -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "csv-mode" '("csv-"))) +(add-to-list 'auto-mode-alist '("\\.tsv\\'" . tsv-mode)) + +(autoload 'tsv-mode "csv-mode" "\ +Major mode for editing files of tab-separated value type. + +\(fn)" t nil) ;;;*** @@ -57,6 +60,5 @@ CSV mode provides the following specific keyboard key bindings: ;; version-control: never ;; no-byte-compile: t ;; no-update-autoloads: t -;; coding: utf-8 ;; End: ;;; csv-mode-autoloads.el ends here diff --git a/elpa/csv-mode-1.10/csv-mode-pkg.el b/elpa/csv-mode-1.10/csv-mode-pkg.el new file mode 100644 index 00000000..9164a3fd --- /dev/null +++ b/elpa/csv-mode-1.10/csv-mode-pkg.el @@ -0,0 +1,2 @@ +;;; -*- no-byte-compile: t -*- +(define-package "csv-mode" "1.10" "Major mode for editing comma/char separated values" '((emacs "24.1") (cl-lib "0.5")) :url "http://elpa.gnu.org/packages/csv-mode.html" :keywords '("convenience") :authors '(("\"Francis J. Wright\"" . "F.J.Wright@qmul.ac.uk")) :maintainer '(nil . "emacs-devel@gnu.org")) diff --git a/elpa/csv-mode-1.7/csv-mode.el b/elpa/csv-mode-1.10/csv-mode.el similarity index 64% rename from elpa/csv-mode-1.7/csv-mode.el rename to elpa/csv-mode-1.10/csv-mode.el index 4541d020..f837f6ad 100644 --- a/elpa/csv-mode-1.7/csv-mode.el +++ b/elpa/csv-mode-1.10/csv-mode.el @@ -1,10 +1,11 @@ ;;; csv-mode.el --- Major mode for editing comma/char separated values -*- lexical-binding: t -*- -;; Copyright (C) 2003, 2004, 2012-2017 Free Software Foundation, Inc +;; Copyright (C) 2003, 2004, 2012-2019 Free Software Foundation, Inc ;; Author: "Francis J. Wright" -;; Time-stamp: <23 August 2004> -;; Version: 1.7 +;; Maintainer: emacs-devel@gnu.org +;; Version: 1.10 +;; Package-Requires: ((emacs "24.1") (cl-lib "0.5")) ;; Keywords: convenience ;; This package is free software; you can redistribute it and/or modify @@ -24,7 +25,8 @@ ;; This package implements CSV mode, a major mode for editing records ;; in a generalized CSV (character-separated values) format. It binds -;; finds with prefix ".csv" to `csv-mode' in `auto-mode-alist'. +;; files with prefix ".csv" to `csv-mode' (and ".tsv" to `tsv-mode') in +;; `auto-mode-alist'. ;; In CSV mode, the following commands are available: @@ -41,10 +43,13 @@ ;; multiple killed fields can be yanked only as a fixed group ;; equivalent to a single field. -;; - C-c C-a (`csv-align-fields') aligns fields into columns - -;; - C-c C-u (`csv-unalign-fields') undoes such alignment; separators -;; can be hidden within aligned records. +;; - `csv-align-mode' keeps fields visually aligned, on-the-fly. +;; It truncates fields to a maximum width that can be changed per-column +;; with `csv-align-set-column-width'. +;; Alternatively, C-c C-a (`csv-align-fields') aligns fields into columns +;; and C-c C-u (`csv-unalign-fields') undoes such alignment; +;; separators can be hidden within aligned records (controlled by +;; `csv-invisibility-default' and `csv-toggle-invisibility'). ;; - C-c C-t (`csv-transpose') interchanges rows and columns. For ;; details, see the documentation for the individual commands. @@ -55,9 +60,10 @@ ;; characters (and must if they contain separator characters). This ;; implementation supports quoted fields, where the quote characters ;; allowed are specified by the value of the customizable user option -;; `csv-field-quotes'. By default, the only separator is a comma and -;; the only field quote is a double quote. These user options can be -;; changed ONLY by customizing them, e.g. via M-x customize-variable. +;; `csv-field-quotes'. By default, the both commas and tabs are considered +;; as separators and the only field quote is a double quote. +;; These user options can be changed ONLY by customizing them, e.g. via M-x +;; customize-variable. ;; CSV mode commands ignore blank lines and comment lines beginning ;; with the value of the buffer local variable `csv-comment-start', @@ -113,6 +119,8 @@ ;;; Code: +(eval-when-compile (require 'cl-lib)) + (defgroup CSV nil "Major mode for editing files of comma-separated value type." :group 'convenience) @@ -125,8 +133,8 @@ Set by customizing `csv-separators' -- do not set directly!") "Regexp to match a field separator. Set by customizing `csv-separators' -- do not set directly!") -(defvar csv--skip-regexp nil - "Regexp used by `skip-chars-forward' etc. to skip fields. +(defvar csv--skip-chars nil + "Char set used by `skip-chars-forward' etc. to skip fields. Set by customizing `csv-separators' -- do not set directly!") (defvar csv-font-lock-keywords nil @@ -152,9 +160,9 @@ All must be different from the field quote characters, `csv-field-quotes'." (error "%S is already a quote" x))) value) (custom-set-default variable value) - (setq csv-separator-chars (mapcar 'string-to-char value) - csv--skip-regexp (apply 'concat "^\n" csv-separators) - csv-separator-regexp (apply 'concat `("[" ,@value "]")) + (setq csv-separator-chars (mapcar #'string-to-char value) + csv--skip-chars (apply #'concat "^\n" csv-separators) + csv-separator-regexp (apply #'concat `("[" ,@value "]")) csv-font-lock-keywords ;; NB: csv-separator-face variable evaluates to itself. `((,csv-separator-regexp (0 'csv-separator-face)))))) @@ -216,18 +224,18 @@ Changing this variable does not affect any existing CSV mode buffer." :type '(choice (const :tag "None" nil) string) :set (lambda (variable value) (custom-set-default variable value) - (set-default 'csv-comment-start value))) + (setq-default csv-comment-start value))) (defcustom csv-align-style 'left "Aligned field style: one of `left', `centre', `right' or `auto'. -Alignment style used by `csv-align-fields'. +Alignment style used by `csv-align-mode' and `csv-align-fields'. Auto-alignment means left align text and right align numbers." :type '(choice (const left) (const centre) (const right) (const auto))) (defcustom csv-align-padding 1 "Aligned field spacing: must be a positive integer. -Number of spaces used by `csv-align-fields' after separators." +Number of spaces used by `csv-align-mode' and `csv-align-fields' after separators." :type 'integer) (defcustom csv-header-lines 0 @@ -303,16 +311,19 @@ Sort order is controlled by `csv-descending'. CSV mode provides the following specific keyboard key bindings: \\{csv-mode-map}" - (turn-off-auto-fill) + :group 'CSV + ;; We used to `turn-off-auto-fill' here instead, but that's not very + ;; effective since text-mode-hook is run afterwards anyway! + (setq-local normal-auto-fill-function nil) ;; Set syntax for field quotes: (csv-set-quote-syntax csv-field-quotes) ;; Make sexp functions apply to fields: - (set (make-local-variable 'forward-sexp-function) 'csv-forward-field) + (set (make-local-variable 'forward-sexp-function) #'csv-forward-field) (csv-set-comment-start csv-comment-start) - (setq - ;; Font locking -- separator plus syntactic: - font-lock-defaults '(csv-font-lock-keywords) - buffer-invisibility-spec csv-invisibility-default) + ;; Font locking -- separator plus syntactic: + (setq font-lock-defaults '(csv-font-lock-keywords)) + (setq-local jit-lock-contextually nil) ;Each line should be independent. + (if csv-invisibility-default (add-to-invisibility-spec 'csv)) ;; Mode line to support `csv-field-index-mode': (set (make-local-variable 'mode-line-position) (pcase mode-line-position @@ -365,12 +376,15 @@ Usually they sort in order of ascending sort key.") (message "Sort order is %sscending" (if csv-descending "de" "a"))) (defun csv-toggle-invisibility () + ;; FIXME: Make it into a proper minor mode? "Toggle `buffer-invisibility-spec'." (interactive) - (setq buffer-invisibility-spec (not buffer-invisibility-spec)) + (if (memq 'csv buffer-invisibility-spec) + (remove-from-invisibility-spec 'csv) + (add-to-invisibility-spec 'csv)) (message "Separators in aligned records will be %svisible \ \(after re-aligning if soft\)" - (if buffer-invisibility-spec "in" "")) + (if (memq 'csv buffer-invisibility-spec) "in" "")) (redraw-frame (selected-frame))) (easy-menu-define @@ -413,24 +427,27 @@ Usually they sort in order of ascending sort key.") ("Alignment Style" ["Left" (setq csv-align-style 'left) :active t :style radio :selected (eq csv-align-style 'left) - :help "If selected, `csv-align-fields' left aligns fields"] + :help "If selected, `csv-align' left aligns fields"] ["Centre" (setq csv-align-style 'centre) :active t :style radio :selected (eq csv-align-style 'centre) - :help "If selected, `csv-align-fields' centres fields"] + :help "If selected, `csv-align' centres fields"] ["Right" (setq csv-align-style 'right) :active t :style radio :selected (eq csv-align-style 'right) - :help "If selected, `csv-align-fields' right aligns fields"] + :help "If selected, `csv-align' right aligns fields"] ["Auto" (setq csv-align-style 'auto) :active t :style radio :selected (eq csv-align-style 'auto) :help "\ -If selected, `csv-align-fields' left aligns text and right aligns numbers"] +If selected, `csv-align' left aligns text and right aligns numbers"] ) ["Set header line" csv-header-line :active t] + ["Auto-(re)align fields" csv-align-mode + :style toggle :selected csv-align-mode] ["Show Current Field Index" csv-field-index-mode :active t :style toggle :selected csv-field-index-mode :help "If selected, display current field index in mode line"] ["Make Separators Invisible" csv-toggle-invisibility :active t - :style toggle :selected buffer-invisibility-spec + :style toggle :selected (memq 'csv buffer-invisibility-spec) + :visible (not (tsv--mode-p)) :help "If selected, separators in aligned records are invisible"] ["Set Buffer's Comment Start" csv-set-comment-start :active t :help "Set comment start string for this buffer"] @@ -588,7 +605,7 @@ BEG and END specify the region to sort." (barf-if-buffer-read-only) (csv-sort-fields-1 field beg end (lambda () (csv-sort-skip-fields field) nil) - (lambda () (skip-chars-forward csv--skip-regexp)))) + (lambda () (skip-chars-forward csv--skip-chars)))) (defun csv-sort-numeric-fields (field beg end) "Sort lines in region numerically by the ARGth field of each line. @@ -645,14 +662,14 @@ point or marker arguments, BEG and END, delimiting the region." (skip-chars-forward " ") (if (eq (char-syntax (following-char)) ?\") (goto-char (scan-sexps (point) 1))) - (skip-chars-forward csv--skip-regexp)) + (skip-chars-forward csv--skip-chars)) (defsubst csv-beginning-of-field () "Skip backward over one field." (skip-syntax-backward " ") (if (eq (char-syntax (preceding-char)) ?\") (goto-char (scan-sexps (point) -1))) - (skip-chars-backward csv--skip-regexp)) + (skip-chars-backward csv--skip-chars)) (defun csv-forward-field (arg) "Move forward across one field, cf. `forward-sexp'. @@ -770,7 +787,7 @@ the mode line after `csv-field-index-delay' seconds of Emacs idle time." (buffer-list))) (setq csv-field-index-idle-timer (run-with-idle-timer csv-field-index-delay t - 'csv-field-index))) + #'csv-field-index))) ;; but if the mode is off then remove the display from the mode ;; lines of all CSV buffers: (mapc (lambda (buffer) @@ -781,21 +798,24 @@ the mode line after `csv-field-index-delay' seconds of Emacs idle time." (force-mode-line-update)))) (buffer-list)))) +(defun csv--field-index () + (save-excursion + (let ((lbp (line-beginning-position)) (field 1)) + (while (re-search-backward csv-separator-regexp lbp 'move) + ;; Move as far as possible, i.e. to beginning of line. + (setq field (1+ field))) + (unless (csv-not-looking-at-record) field)))) + (defun csv-field-index () "Construct `csv-field-index-string' to display in mode line. Called by `csv-field-index-idle-timer'." (if (derived-mode-p 'csv-mode) - (save-excursion - (let ((lbp (line-beginning-position)) (field 1)) - (while (re-search-backward csv-separator-regexp lbp 1) - ;; Move as far as possible, i.e. to beginning of line. - (setq field (1+ field))) - (if (csv-not-looking-at-record) (setq field nil)) - (when (not (eq field csv-field-index-old)) - (setq csv-field-index-old field - csv-field-index-string - (and field (format "F%d" field))) - (force-mode-line-update)))))) + (let ((field (csv--field-index))) + (when (not (eq field csv-field-index-old)) + (setq csv-field-index-old field + csv-field-index-string + (and field (format "F%d" field))) + (force-mode-line-update))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Killing and yanking fields @@ -900,7 +920,7 @@ Ignore blank and comment lines." fields (cdr fields)) (beginning-of-line) (push (csv-kill-one-field field) killed-fields)) - (push (mapconcat 'identity killed-fields (car csv-separators)) + (push (mapconcat #'identity killed-fields (car csv-separators)) csv-killed-fields))) (forward-line))) @@ -968,30 +988,41 @@ The fields yanked are those last killed by `csv-kill-fields'." (defun csv--delete-overlay (o) (and (overlay-get o 'csv) (delete-overlay o))) -(defun csv--column-widths () - (let ((widths '())) +(defun csv--column-widths (beg end) + "Return a list of two lists (COLUMN-WIDTHS FIELD-WIDTHS). +COLUMN-WIDTHS is a list of elements (WIDTH START END) +indicating the widths of the columns after point (and the position of the +widest field that determined the overall width). +FIELD-WIDTHS contains the widths of each individual field after +point." + (let ((column-widths '()) + (field-widths '())) + (goto-char beg) ;; Construct list of column widths: - (while (not (eobp)) ; for each record... + (while (< (point) end) ; for each record... (or (csv-not-looking-at-record) - (let ((w widths) + (let ((w column-widths) (col (current-column)) - x) + (beg (point)) + field-width) (while (not (eolp)) (csv-end-of-field) - (setq x (- (current-column) col)) ; Field width. + (setq field-width (- (current-column) col)) + (push field-width field-widths) (if w - (if (> x (car w)) (setcar w x)) - (setq w (list x) - widths (nconc widths w))) - (or (eolp) (forward-char)) ; Skip separator. - (setq w (cdr w) col (current-column))))) + (if (> field-width (caar w)) + (setcar w (list field-width beg (point)))) + (setq w (list (list field-width beg (point))) + column-widths (nconc column-widths w))) + (or (eolp) (forward-char)) ; Skip separator. + (setq w (cdr w) col (current-column) beg (point))))) (forward-line)) - widths)) + (list column-widths (nreverse field-widths)))) (defun csv-align-fields (hard beg end) "Align all the fields in the region to form columns. The alignment style is specified by `csv-align-style'. The number of -spaces specified by `csv-align-fields' appears after each separator. +spaces specified by `csv-align-padding' appears after each separator. Use soft alignment done by displaying virtual white space after the separators unless invoked with an argument, in which case insert real space characters into the buffer after the separators. @@ -1010,30 +1041,29 @@ If there is no selected region, default to the whole buffer." (if (use-region-p) (list (region-beginning) (region-end)) (list (point-min) (point-max))))) + ;; FIXME: Use csv--jit-align when applicable! (setq end (copy-marker end)) (csv-unalign-fields hard beg end) ; If hard then barfs if buffer read only. (save-excursion - (save-restriction - (narrow-to-region beg end) - (set-marker end nil) - (goto-char (point-min)) - (let ((widths (csv--column-widths))) + (pcase-let ((`(,column-widths ,field-widths) (csv--column-widths beg end))) + (save-restriction + (narrow-to-region beg end) + (set-marker end nil) ;; Align fields: (goto-char (point-min)) (while (not (eobp)) ; for each record... (unless (csv-not-looking-at-record) - (let ((w widths) + (let ((w column-widths) (column 0)) ;Desired position of left-side of this column. (while (and w (not (eolp))) (let* ((beg (point)) (align-padding (if (bolp) 0 csv-align-padding)) (left-padding 0) (right-padding 0) - (field-width - (- (- (current-column) - (progn (csv-end-of-field) (current-column))))) - (column-width (pop w)) + (field-width (pop field-widths)) + (column-width (car (pop w))) (x (- column-width field-width))) ; Required padding. + (csv-end-of-field) (set-marker end (point)) ; End of current field. ;; beg = beginning of current field ;; end = (point) = end of current field @@ -1080,11 +1110,16 @@ If there is no selected region, default to the whole buffer." ;; conflict, so use the following only ;; with hard alignment: (csv--make-overlay (point) (1+ (point)) nil t nil - '(invisible t evaporate t)) + '(invisible csv evaporate t)) (forward-char))) ; skip separator ;; Soft alignment... - (buffer-invisibility-spec ; csv-invisibility-default + ((or (memq 'csv buffer-invisibility-spec) + ;; For TSV, hidden or not doesn't make much difference, + ;; but the behavior is slightly better when we "hide" + ;; the TABs with a `display' property than if we add + ;; before/after-strings. + (tsv--mode-p)) ;; Hide separators... ;; Merge right-padding from previous field @@ -1138,7 +1173,7 @@ If there is no selected region, default to the whole buffer." ;; Remove any soft alignment: (mapc #'csv--delete-overlay (overlays-in beg end)) (with-silent-modifications - (remove-list-of-text-properties beg end '(display))) + (remove-list-of-text-properties beg end '(display invisible))) (when hard (barf-if-buffer-read-only) ;; Remove any white-space padding around separators: @@ -1187,7 +1222,7 @@ When called non-interactively, BEG and END specify region to process." rows columns) ;; Remove soft alignment if necessary: (when align - (mapc 'csv--delete-overlay align) + (mapc #'csv--delete-overlay align) (setq align t)) (while (not (eobp)) (if (csv-not-looking-at-record) @@ -1195,9 +1230,9 @@ When called non-interactively, BEG and END specify region to process." (forward-line) (let ((lep (line-end-position))) (push - (csv-split-string + (split-string (buffer-substring-no-properties (point) lep) - csv-separator-regexp nil t) + csv-separator-regexp) rows) (delete-region (point) lep) (or (eobp) (delete-char 1))))) @@ -1231,53 +1266,11 @@ When called non-interactively, BEG and END specify region to process." ;; Insert columns into buffer as rows: (setq columns (nreverse columns)) (while columns - (insert (mapconcat 'identity (car columns) sep) ?\n) + (insert (mapconcat #'identity (car columns) sep) ?\n) (setq columns (cdr columns))) ;; Re-do soft alignment if necessary: (if align (csv-align-fields nil (point-min) (point-max))))))) -;; The following generalised version of `split-string' is taken from -;; the development version of WoMan and should probably replace the -;; standard version in subr.el. However, CSV mode (currently) needs -;; only the `allowbeg' option. - -(defun csv-split-string - (string &optional separators subexp allowbeg allowend) - "Splits STRING into substrings where there are matches for SEPARATORS. -Each match for SEPARATORS is a splitting point. -The substrings between the splitting points are made into a list -which is returned. -If SEPARATORS is absent, it defaults to \"[ \\f\\t\\n\\r\\v]+\". -SUBEXP specifies a subexpression of SEPARATORS to be the splitting -point\; it defaults to 0. - -If there is a match for SEPARATORS at the beginning of STRING, we do -not include a null substring for that, unless ALLOWBEG is non-nil. -Likewise, if there is a match at the end of STRING, we do not include -a null substring for that, unless ALLOWEND is non-nil. - -Modifies the match data; use `save-match-data' if necessary." - (or subexp (setq subexp 0)) - (let ((rexp (or separators "[ \f\t\n\r\v]+")) - (start 0) - notfirst - (list nil)) - (while (and (string-match rexp string - (if (and notfirst - (= start (match-beginning subexp)) - (< start (length string))) - (1+ start) start)) - (< (match-beginning subexp) (length string))) - (setq notfirst t) - (or (and (not allowbeg) (eq (match-beginning subexp) 0)) - (and (eq (match-beginning subexp) (match-end subexp)) - (eq (match-beginning subexp) start)) - (push (substring string start (match-beginning subexp)) list)) - (setq start (match-end subexp))) - (or (and (not allowend) (eq start (length string))) - (push (substring string start) list)) - (nreverse list))) - (defvar-local csv--header-line nil) (defvar-local csv--header-hscroll nil) (defvar-local csv--header-string nil) @@ -1290,47 +1283,548 @@ If there is already a header line, then unset the header line." (interactive "P") (if csv--header-line (progn + (delete-overlay csv--header-line) (setq csv--header-line nil) (kill-local-variable 'header-line-format)) - (setq csv--header-line (copy-marker - (if use-current-line - (line-beginning-position) - (point-min)))) - (setq csv--header-hscroll nil) + (save-excursion + (unless use-current-line (goto-char (point-min))) + (setq csv--header-line (make-overlay (line-beginning-position) + (line-end-position) + nil nil t)) + (overlay-put csv--header-line 'modification-hooks + '(csv--header-flush))) + (csv--header-flush) (setq header-line-format - '(:eval (progn - ;; FIXME: Won't work with multiple windows showing that - ;; same buffer. - (if (eq (window-hscroll) csv--header-hscroll) - csv--header-string - (setq csv--header-hscroll (window-hscroll)) - (setq csv--header-string - (csv--compute-header-string)))))))) + '(:eval (csv--header-string))))) + +(defun csv--header-flush (&rest _) + ;; Force re-computation of the header-line. + (setq csv--header-hscroll nil)) + +(defun csv--header-string () + ;; FIXME: Won't work with multiple windows showing that same buffer. + (if (eql (window-hscroll) csv--header-hscroll) + csv--header-string + (setq csv--header-hscroll (window-hscroll)) + (setq csv--header-string + (csv--compute-header-string)))) (defun csv--compute-header-string () + (with-demoted-errors "csv--compute-header-string %S" + (save-excursion + (goto-char (overlay-start csv--header-line)) + ;; Re-set the line-end-position, just in case. + (move-overlay csv--header-line (point) (line-end-position)) + (jit-lock-fontify-now (point) (line-end-position)) + ;; Not sure why it is sometimes nil! + (move-to-column (or csv--header-hscroll 0)) + (let ((str (buffer-substring (point) (line-end-position))) + (i 0)) + (while (and i (< i (length str))) + (let ((prop (get-text-property i 'display str))) + (and (eq (car-safe prop) 'space) + (eq (car-safe (cdr prop)) :align-to) + (let* ((x (nth 2 prop)) + (nexti (next-single-property-change i 'display str)) + (newprop + `(space :align-to + ,(if (numberp x) + (- x (or csv--header-hscroll 0)) + `(- ,x csv--header-hscroll))))) + (put-text-property i (or nexti (length str)) + 'display newprop str) + (setq i nexti)))) + (setq i (next-single-property-change i 'display str))) + (concat (propertize " " 'display '((space :align-to 0))) str))))) + +;;; Auto-alignment + +(defcustom csv-align-max-width 40 + "Maximum width of a column in `csv-align-mode'. +This does not apply to the last column (for which the usual `truncate-lines' +setting works better)." + :type 'integer) + +(defvar-local csv--config-column-widths nil + "Settings per column, stored as a list indexed by the column.") + +(defun csv-align--set-column (column value) + (let ((len (length csv--config-column-widths))) + (if (< len column) + (setq csv--config-column-widths + (nconc csv--config-column-widths (make-list (- column len) nil)))) + (setf (nth (1- column) csv--config-column-widths) value))) + +(defun csv-align-set-column-width (column width) + "Set the max WIDTH to use for COLUMN." + (interactive + (let* ((field (or (csv--field-index) 1)) + (curwidth (nth (1- field) csv--config-column-widths))) + (list field + (cond + ((numberp current-prefix-arg) + current-prefix-arg) + (current-prefix-arg + (read-number (format "Column width (for field %d): " field) + curwidth)) + (t (if curwidth nil (csv--ellipsis-width))))))) + (when (eql width csv-align-max-width) + (setq width nil)) + (csv-align--set-column column width) + (jit-lock-refontify)) + +(defvar-local csv--jit-columns nil) + +(defun csv--jit-merge-columns (column-widths) + ;; FIXME: The incremental update (delayed by jit-lock-context-time) of column + ;; width is a bit jarring at times. It's OK while scrolling or when + ;; extending a column, but not right when enabling the csv-align-mode or + ;; when shortening the longest field (or deleting the line containing it), + ;; because in that case we have *several* cascaded updates, e.g.: + ;; - Remove the line with the longest field of column N. + ;; - Edit some line: this line is updated as if its field was the widest, + ;; hence its subsequent fields are too much to the left. + ;; - The rest is updated starting from the first few lines (according + ;; to jit-lock-chunk-size). + ;; - After the first few lines, come the next set of few lines, + ;; which may cause the previous few lines to need refresh again. + ;; - etc.. until arriving again at the edited line which is re-aligned + ;; again. + ;; - etc.. until the end of the windows, potentially causing yet more + ;; refreshes as we discover yet-wider fields for this column. + (let ((old-columns csv--jit-columns) + (changed nil)) + (while (and old-columns column-widths) + (when (or (> (caar column-widths) (caar old-columns)) + ;; Apparently modification-hooks aren't run when the + ;; whole text containing the overlay is deleted (e.g. + ;; the whole line), so detect this case here. + ;; It's a bit too late, but better than never. + (null (overlay-buffer (cdar old-columns)))) + (setq changed t) ;; Return non-nil if some existing column changed. + (pcase-let ((`(,width ,beg ,end) (car column-widths))) + (setf (caar old-columns) width) + (move-overlay (cdar old-columns) beg end))) + (setq old-columns (cdr old-columns)) + (setq column-widths (cdr column-widths))) + (when column-widths + ;; New columns appeared. + (setq csv--jit-columns + (nconc csv--jit-columns + (mapcar (lambda (x) + (pcase-let* + ((`(,width ,beg ,end) x) + (ol (make-overlay beg end))) + (overlay-put ol 'csv-width t) + (overlay-put ol 'evaporate t) + (overlay-put ol 'modification-hooks + (list #'csv--jit-width-change)) + (cons width ol))) + column-widths)))) + changed)) + +(defun csv--jit-width-change (ol after _beg _end &optional len) + (when (and after (> len 0)) + ;; (let ((x (rassq ol csv--jit-columns))) + ;; (when x (setf (car x) -1))) + (delete-overlay ol))) + +(defun csv--jit-unalign (beg end) + (remove-text-properties beg end + '(display nil csv--jit nil invisible nil + cursor-sensor-functions nil csv--revealed nil)) + (remove-overlays beg end 'csv--jit t)) + +(defun csv--jit-flush (beg end) + "Cause all the buffer (except for the BEG...END region) to be re-aligned." + (cl-assert (>= end beg)) + ;; The buffer shouldn't have changed since beg/end were computed, + ;; but just in case, let's make sure they're still sane. + (when (< beg (point-min)) + (setq beg (point-min) end (max end beg))) + (when (< (point-max) end) + (setq end (point-max) beg (min end beg))) + (let ((pos (point-min))) + (while (and (< pos beg) + (setq pos (text-property-any pos beg 'csv--jit t))) + (jit-lock-refontify + pos (setq pos (or (text-property-any pos beg 'csv--jit nil) beg)))) + (setq pos end) + (while (and (< pos (point-max)) + (setq pos (text-property-any pos (point-max) 'csv--jit t))) + (jit-lock-refontify + pos (setq pos (or (text-property-any pos (point-max) 'csv--jit nil) + (point-max)))))) + (csv--header-flush)) + +(defun csv--ellipsis-width () + (let ((ellipsis + (when standard-display-table + (display-table-slot standard-display-table + 'selective-display)))) + (if ellipsis (length ellipsis) 3))) + +(defun csv-align--cursor-truncated (window oldpos dir) + ;; FIXME: Neither the `entered' nor the `left' event are guaranteed + ;; to be sent, and for the `left' case, even when we do get called, + ;; it may be unclear where the revealed text was (it's somewhere around + ;; `oldpos', but that position can be stale). + ;; Worse, if we have several windows displaying the buffer, when one + ;; cursor leaves we may need to keep the text revealed because of + ;; another window's cursor. + (let* ((prop (if (eq dir 'entered) 'invisible 'csv--revealed)) + (pos (cond + ((eq dir 'entered) (window-point window)) + (t (max (point-min) + (min (point-max) + (or oldpos (window-point window))))))) + (start (cond + ((and (> pos (point-min)) + (eq (get-text-property (1- pos) prop) 'csv-truncate)) + (or (previous-single-property-change pos prop) (point-min))) + (t pos))) + (end (if (eq (get-text-property pos prop) 'csv-truncate) + (or (next-single-property-change pos prop) (point-max)) + pos))) + (unless (eql start end) + (with-silent-modifications + (put-text-property start end + (if (eq dir 'entered) 'csv--revealed 'invisible) + 'csv-truncate) + (remove-text-properties start end (list prop)))))) + +(defun csv--jit-align (beg end) (save-excursion - (goto-char csv--header-line) - (move-to-column csv--header-hscroll) - (let ((str (buffer-substring (point) (line-end-position))) - (i 0)) - (while (and i (< i (length str))) - (let ((prop (get-text-property i 'display str))) - (and (eq (car-safe prop) 'space) - (eq (car-safe (cdr prop)) :align-to) - (let* ((x (nth 2 prop)) - (nexti (next-single-property-change i 'display str)) - (newprop - `(space :align-to - ,(if (numberp x) (- x csv--header-hscroll) - `(- ,x csv--header-hscroll))))) - (put-text-property i (or nexti (length str)) - 'display newprop str) - (setq i nexti)))) - (setq i (next-single-property-change i 'display str))) - (concat (propertize " " 'display '((space :align-to 0))) str)))) + ;; This is run with inhibit-modification-hooks set, so the overlays' + ;; modification-hook doesn't work :-( + (and csv--header-line + (<= beg (overlay-end csv--header-line)) + (>= end (overlay-start csv--header-line)) + (csv--header-flush)) + ;; First, round up to a whole number of lines. + (goto-char end) + (unless (bolp) (forward-line 1) (setq end (point))) + (goto-char beg) + (unless (bolp) (forward-line 1) (setq beg (point))) + (csv--jit-unalign beg end) + (put-text-property beg end 'csv--jit t) + + (pcase-let* ((`(,column-widths ,field-widths) (csv--column-widths beg end)) + (changed (csv--jit-merge-columns column-widths)) + (ellipsis-width (csv--ellipsis-width))) + (when changed + ;; Do it after the current redisplay is over. + (run-with-timer jit-lock-context-time nil #'csv--jit-flush beg end)) + + ;; Align fields: + (goto-char beg) + (while (< (point) end) + (unless (csv-not-looking-at-record) + (let ((w csv--jit-columns) + (widths-config csv--config-column-widths) + (column 0)) ;Desired position of left-side of this column. + (while (and w (not (eolp))) + (let* ((field-beg (point)) + (width-config (pop widths-config)) + (align-padding (if (bolp) 0 csv-align-padding)) + (left-padding 0) (right-padding 0) + (field-width (pop field-widths)) + (column-width + (min (car (pop w)) + (or width-config + ;; Don't apply csv-align-max-width + ;; to the last field! + (if w csv-align-max-width + most-positive-fixnum)))) + (x (- column-width field-width)) ; Required padding. + (truncate nil)) + (csv-end-of-field) + ;; beg = beginning of current field + ;; end = (point) = end of current field + (when (< x 0) + (setq truncate (max column + (+ column column-width + align-padding (- ellipsis-width)))) + (setq x 0)) + ;; Compute required padding: + (pcase csv-align-style + ('left + ;; Left align -- pad on the right: + (setq left-padding align-padding + right-padding x)) + ('right + ;; Right align -- pad on the left: + (setq left-padding (+ align-padding x))) + ('auto + ;; Auto align -- left align text, right align numbers: + (if (string-match "\\`[-+.[:digit:]]+\\'" + (buffer-substring field-beg (point))) + ;; Right align -- pad on the left: + (setq left-padding (+ align-padding x)) + ;; Left align -- pad on the right: + (setq left-padding align-padding + right-padding x))) + ('centre + ;; Centre -- pad on both left and right: + (let ((y (/ x 2))) ; truncated integer quotient + (setq left-padding (+ align-padding y) + right-padding (- x y))))) + + (cond + + ((or (memq 'csv buffer-invisibility-spec) + ;; For TSV, hidden or not doesn't make much difference, + ;; but the behavior is slightly better when we "hide" + ;; the TABs with a `display' property than if we add + ;; before/after-strings. + (tsv--mode-p)) + + ;; Hide separators... + ;; Merge right-padding from previous field + ;; with left-padding from this field: + (if (zerop column) + (when (> left-padding 0) + ;; Display spaces before first field + ;; by overlaying first character: + (csv--make-overlay + field-beg (1+ field-beg) nil nil nil + `(before-string ,(make-string left-padding ?\ ) + csv--jit t))) + ;; Display separator as spaces: + (with-silent-modifications + (put-text-property + (1- field-beg) field-beg + 'display `(space :align-to + ,(+ left-padding column)))))) + + (t ;; Do not hide separators... + (let ((overlay (csv--make-overlay field-beg (point) + nil nil t + '(csv--jit t)))) + (when (> left-padding 0) ; Pad on the left. + ;; Display spaces before field: + (overlay-put overlay 'before-string + (make-string left-padding ?\ ))) + (unless (eolp) + (if (> right-padding 0) ; Pad on the right. + ;; Display spaces after field: + (overlay-put + overlay + 'after-string (make-string right-padding ?\ ))))))) + (setq column (+ column column-width align-padding)) + ;; Do it after applying the property, so `move-to-column' can + ;; take it into account. + (when truncate + (let ((trunc-pos + (save-excursion + ;; ¡¡ BIG UGLY HACK !! + ;; `current-column' and `move-to-column' count + ;; text hidden with an ellipsis "as if" it were + ;; fully visible, which is completely wrong here, + ;; so circumvent this by temporarily pretending + ;; that `csv-truncate' is fully invisible (which + ;; isn't quite right either, but should work + ;; just well enough for us here). + (let ((buffer-invisibility-spec + buffer-invisibility-spec)) + (add-to-invisibility-spec 'csv-truncate) + (move-to-column truncate)) + (point)))) + (put-text-property trunc-pos (point) + 'invisible 'csv-truncate) + (when (> (- (point) trunc-pos) 1) + ;; Arrange to temporarily untruncate the string when + ;; cursor moves into it. + ;; FIXME: This only works if + ;; `global-disable-point-adjustment' is non-nil! + ;; Arguably this should be fixed by making + ;; point-adjustment code pay attention to + ;; cursor-sensor-functions! + (put-text-property + (1+ trunc-pos) (point) + 'cursor-sensor-functions + (list #'csv-align--cursor-truncated))))) + (unless (eolp) (forward-char)) ; Skip separator. + )))) + (forward-line))) + `(jit-lock-bounds ,beg . ,end))) + +(define-minor-mode csv-align-mode + "Align columns on the fly." + :global nil + (csv-unalign-fields nil (point-min) (point-max)) ;Just in case. + (cond + (csv-align-mode + (add-to-invisibility-spec '(csv-truncate . t)) + (kill-local-variable 'csv--jit-columns) + (cursor-sensor-mode 1) + (jit-lock-register #'csv--jit-align) + (jit-lock-refontify)) + (t + (remove-from-invisibility-spec '(csv-truncate . t)) + (jit-lock-unregister #'csv--jit-align) + (csv--jit-unalign (point-min) (point-max)))) + (csv--header-flush)) + +;;; TSV support + +;; Since "the" CSV format is really a bunch of different formats, it includes +;; TSV as a subcase, but this subcase is sufficiently interesting that it has +;; its own mime-type and mostly standard file extension, also it suffers +;; less from the usual quoting problems of CSV (because the only problematic +;; chars are LF and TAB, really, which are much less common inside fields than +;; commas, space, and semi-colons) so it's "better behaved". + +(defvar tsv-mode-syntax-table + ;; Inherit from `text-mode-syntax-table' rather than from + ;; `csv-mode-syntax-table' so as not to inherit the + ;; `csv-field-quotes' settings. + (let ((st (make-syntax-table text-mode-syntax-table))) + st)) + +(defvar tsv-mode-map + (let ((map (make-sparse-keymap))) + ;; In `tsv-mode', the `csv-invisibility-default/csv-toggle-invisibility' + ;; business doesn't make much sense. + (define-key map [remap csv-toggle-invisibility] #'undefined) + map)) + +;;;###autoload +(add-to-list 'auto-mode-alist '("\\.tsv\\'" . tsv-mode)) + +(defun tsv--mode-p () + (equal csv-separator-chars '(?\t))) + +;;;###autoload +(define-derived-mode tsv-mode csv-mode "TSV" + "Major mode for editing files of tab-separated value type." + :group 'CSV + ;; In TSV we know TAB is the only possible separator. + (setq-local csv-separators '("\t")) + ;; FIXME: Copy&pasted from the `:set'ter of csv-separators! + (setq-local csv-separator-chars '(?\t)) + (setq-local csv--skip-chars "^\n\t") + (setq-local csv-separator-regexp "\t") + (setq-local csv-font-lock-keywords + ;; NB: csv-separator-face variable evaluates to itself. + `((,csv-separator-regexp (0 'csv-separator-face)))) + + ;; According to wikipedia, TSV doesn't use quotes but uses backslash escapes + ;; of the form \n, \t, \r, and \\ instead. + (setq-local csv-field-quotes nil)) ;;;; ChangeLog: +;; 2019-10-22 Stefan Monnier +;; +;; * packages/csv-mode/csv-mode.el (csv-align--cursor-truncated): Fix C-e +;; case +;; +;; 2019-10-22 Stefan Monnier +;; +;; * packages/csv-mode/csv-mode.el: Auto-shorten columns as well +;; +;; (csv--column-widths): Also return the position of the widest field in +;; each column. +;; (csv-align-fields, csv--jit-align): Update accordingly. +;; (csv--jit-width-change): New function. +;; (csv--jit-merge-columns): Use it on overlays placed on the widest field +;; of each column, to detect when they're shortened. +;; +;; 2019-10-19 Stefan Monnier +;; +;; * packages/csv-mode/csv-mode.el: More cvs-align-mode improvements +;; +;; Rename csv-align-fields-* to cvs-align-*. +;; (csv-transpose): Use split-string. +;; (csv-split-string): Delete function. +;; (csv--config-column-widths): New var. +;; (csv-align--set-column): New function. +;; (csv-align-set-column-width): New command. +;; (csv--jit-align): Use them to obey the per-column width settings. Delay +;; context refresh by jit-lock-context-time. Set cursor-sensor-functions to +;; untruncate fields on-the-fly. +;; (csv-align--cursor-truncated): New function. +;; (csv-align-mode): Activate cursor-sensor-mode. +;; +;; 2019-10-19 Stefan Monnier +;; +;; * packages/csv-mode/csv-mode.el: Fix incorrect truncation +;; +;; (csv--field-index): New function, extracted from csv-field-index. +;; (csv--jit-align): Don't apply csv-align-fields-max-width to the last +;; column. Fix move-to-column call. +;; +;; 2019-10-10 Stefan Monnier +;; +;; * packages/csv-mode/csv-mode.el: Fix header-line's alignment +;; +;; (csv-header-line): Change csv--header-line into an overlay. Add a +;; modification-hooks to auto-refresh the header-line. +;; (csv--header-flush, csv--header-string): New functions. +;; (csv--compute-header-string): Make sure jit-lock was applied. +;; csv--header-hscroll can be nil sometimes somehow! +;; (csv--jit-flush, csv-align-fields-mode): Flush header-line as well. +;; (csv--jit-align): Flush header-line when applicable. Fix typo. +;; +;; 2019-10-09 Filipp Gunbin +;; +;; packages/csv-mode/csv-mode.el: Fix csv-align-fields doc +;; +;; (csv-align-fields): docstring mentioned csv-align-fields instead of +;; csv-align-padding +;; +;; 2019-09-29 Stefan Monnier +;; +;; * packages/csv-mode/csv-mode.el: Remove Francis as maintainer +;; +;; (csv-unalign-fields): Also remove the `invisible` property since we use +;; it to truncate fields in csv--jit-align. +;; (csv-align-fields-max-width): Rename from csv-align-field-max-width to +;; match the "csv-align-fields" prefix. +;; (csv--ellipsis-width): New function. +;; (csv--jit-align): Use it to truncate more correctly. +;; +;; 2019-09-27 Stefan Monnier +;; +;; * packages/csv-mode/csv-mode.el (csv-align-field-max-width): New var +;; +;; (csv--jit-unalign): Erase invisible property as well. +;; (csv--jit-align): Truncate field to fit within csv-align-field-max-width +;; when needed. +;; (csv-align-fields-mode): Add/remove `csv-truncate` to invisibility spec. +;; +;; 2019-09-27 Francis Wright +;; +;; * packages/csv-mode/csv-mode.el: Fix for customize-mode +;; +;; (csv-mode, tsv-mode): Specify :group explicitly for `customize-mode`s +;; benefit +;; +;; 2019-09-24 Stefan Monnier +;; +;; * packages/csv-mode/csv-mode.el: Add tsv-mode and csv-align-fields-mode +;; +;; Require cl-lib. Don't set buffer-invisibility-spec directly. +;; (csv--skip-chars): Rename from misleading csv--skip-regexp. +;; (csv-mode): Set normal-auto-fill-function to really disable +;; auto-fill-mode. +;; (csv--column-widths): Only operate over new args beg..end. +;; (csv-align-fields): No need to narrow before csv--column-widths any +;; more. +;; (csv-align-fields-mode): New minor mode. +;; (tsv-mode): New major mode. +;; +;; 2019-09-18 Simen Heggestøyl +;; +;; Speed up 'csv-align-fields' +;; +;; * packages/csv-mode/csv-mode.el: Bump version number and make the +;; dependency on Emacs 24.1 or higher explicit. +;; (csv--column-widths): Return the field widths as well. +;; (csv-align-fields): Speed up by using the field widths already computed +;; by 'csv--column-widths' (bug#37393). +;; ;; 2017-12-05 Stefan Monnier ;; ;; * csv-mode/csv-mode.el (csv-header-line): New command @@ -1449,6 +1943,7 @@ If there is already a header line, then unset the header line." ;; + (provide 'csv-mode) ;;; csv-mode.el ends here diff --git a/elpa/csv-mode-1.7/csv-mode-pkg.el b/elpa/csv-mode-1.7/csv-mode-pkg.el deleted file mode 100644 index 70e1360f..00000000 --- a/elpa/csv-mode-1.7/csv-mode-pkg.el +++ /dev/null @@ -1,2 +0,0 @@ -;;; -*- no-byte-compile: t -*- -(define-package "csv-mode" "1.7" "Major mode for editing comma/char separated values" 'nil :url "http://elpa.gnu.org/packages/csv-mode.html" :keywords '("convenience")) -- 2.47.3