diff options
| -rw-r--r-- | lisp/emacs-lisp/vtable.el | 321 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/vtable-tests.el | 91 |
2 files changed, 263 insertions, 149 deletions
diff --git a/lisp/emacs-lisp/vtable.el b/lisp/emacs-lisp/vtable.el index bcdd280fb92..29791e2aaa0 100644 --- a/lisp/emacs-lisp/vtable.el +++ b/lisp/emacs-lisp/vtable.el | |||
| @@ -67,8 +67,10 @@ | |||
| 67 | (ellipsis :initarg :ellipsis :accessor vtable-ellipsis) | 67 | (ellipsis :initarg :ellipsis :accessor vtable-ellipsis) |
| 68 | (column-colors :initarg :column-colors :accessor vtable-column-colors) | 68 | (column-colors :initarg :column-colors :accessor vtable-column-colors) |
| 69 | (row-colors :initarg :row-colors :accessor vtable-row-colors) | 69 | (row-colors :initarg :row-colors :accessor vtable-row-colors) |
| 70 | (buffer :initform nil :accessor vtable-buffer) | ||
| 70 | (-cached-colors :initform nil) | 71 | (-cached-colors :initform nil) |
| 71 | (-cache :initform (make-hash-table :test #'equal)) | 72 | (-cache :initform (make-hash-table :test #'equal)) |
| 73 | (-current-cache :initform nil :accessor vtable--current-cache) | ||
| 72 | (-cached-keymap :initform nil) | 74 | (-cached-keymap :initform nil) |
| 73 | (-has-column-spec :initform nil)) | 75 | (-has-column-spec :initform nil)) |
| 74 | "An object to hold the data for a table.") | 76 | "An object to hold the data for a table.") |
| @@ -287,8 +289,7 @@ compared with `equal'), signal an error. | |||
| 287 | TABLE must be at point in the current buffer." | 289 | TABLE must be at point in the current buffer." |
| 288 | (unless old-object | 290 | (unless old-object |
| 289 | (setq old-object object)) | 291 | (setq old-object object)) |
| 290 | (let* ((objects (vtable-objects table)) | 292 | (let ((objects (vtable-objects table))) |
| 291 | (inhibit-read-only t)) | ||
| 292 | ;; First replace the object in the object storage. | 293 | ;; First replace the object in the object storage. |
| 293 | (if (eq old-object (car objects)) | 294 | (if (eq old-object (car objects)) |
| 294 | ;; It's at the head, so replace it there. | 295 | ;; It's at the head, so replace it there. |
| @@ -301,31 +302,32 @@ TABLE must be at point in the current buffer." | |||
| 301 | (unless (cdr objects) | 302 | (unless (cdr objects) |
| 302 | (error "Can't find the old object")) | 303 | (error "Can't find the old object")) |
| 303 | (setcar (cdr objects) object)) | 304 | (setcar (cdr objects) object)) |
| 304 | ;; Then update the rendered vtable in the current buffer. | 305 | ;; Then update the rendered vtable in its buffer. |
| 305 | (if-let* ((cache (vtable--current-cache)) | 306 | (if-let* ((cache (vtable--current-cache table)) |
| 306 | (line-number (seq-position (vtable--cache-lines cache) | 307 | (line-number (seq-position (vtable--cache-lines cache) |
| 307 | old-object | 308 | old-object |
| 308 | (lambda (a b) | 309 | (lambda (a b) |
| 309 | (equal (car a) b)))) | 310 | (equal (car a) b)))) |
| 310 | (line (elt (vtable--cache-lines cache) line-number))) | 311 | (line (elt (vtable--cache-lines cache) line-number))) |
| 311 | (progn | 312 | (with-current-buffer (vtable-buffer table) |
| 312 | (setcar line object) | 313 | (let ((inhibit-read-only t) |
| 313 | (setcdr line (vtable--compute-cached-line table object)) | 314 | (inhibit-modification-hooks t)) |
| 314 | ;; ... and redisplay the line in question. | 315 | (setcar line object) |
| 315 | (save-excursion | 316 | (setcdr line (vtable--compute-cached-line table object)) |
| 316 | (vtable-goto-object old-object) | 317 | ;; ... and redisplay the line in question. |
| 317 | (let ((keymap (get-text-property (point) 'keymap)) | 318 | (save-excursion |
| 318 | (start (point))) | 319 | (vtable-goto-object old-object) |
| 319 | (delete-line) | 320 | (let ((keymap (get-text-property (point) 'keymap)) |
| 320 | (vtable--insert-line table line line-number | 321 | (start (point))) |
| 321 | (vtable--cache-widths cache) | 322 | (delete-line) |
| 322 | (vtable--spacer table)) | 323 | (vtable--insert-line table line line-number |
| 323 | (add-text-properties start (point) (list 'keymap keymap | 324 | (vtable--cache-widths cache) |
| 324 | 'vtable table | 325 | (vtable--spacer table)) |
| 325 | 'vtable-cache cache)))) | 326 | (add-text-properties start (point) (list 'keymap keymap |
| 326 | ;; We may have inserted a non-numerical value into a previously | 327 | 'vtable table)))) |
| 327 | ;; all-numerical table, so recompute. | 328 | ;; We may have inserted a non-numerical value into a previously |
| 328 | (vtable--recompute-numerical table (cdr line))) | 329 | ;; all-numerical table, so recompute. |
| 330 | (vtable--recompute-numerical table (cdr line)))) | ||
| 329 | (error "Can't find cached object in vtable")))) | 331 | (error "Can't find cached object in vtable")))) |
| 330 | 332 | ||
| 331 | (defun vtable-remove-object (table object) | 333 | (defun vtable-remove-object (table object) |
| @@ -334,14 +336,16 @@ This will also remove the displayed line." | |||
| 334 | ;; First remove from the objects. | 336 | ;; First remove from the objects. |
| 335 | (setf (vtable-objects table) (delq object (vtable-objects table))) | 337 | (setf (vtable-objects table) (delq object (vtable-objects table))) |
| 336 | ;; Then adjust the cache and display. | 338 | ;; Then adjust the cache and display. |
| 337 | (save-excursion | 339 | (with-current-buffer (vtable-buffer table) |
| 338 | (vtable-goto-table table) | 340 | (save-excursion |
| 339 | (let ((cache (vtable--current-cache)) | 341 | (vtable-goto-table table) |
| 340 | (inhibit-read-only t)) | 342 | (let ((cache (vtable--current-cache table)) |
| 341 | (setcar cache (delq (assq object (vtable--cache-lines cache)) | 343 | (inhibit-read-only t) |
| 342 | (vtable--cache-lines cache))) | 344 | (inhibit-modification-hooks t)) |
| 343 | (when (vtable-goto-object object) | 345 | (setcar cache (delq (assq object (vtable--cache-lines cache)) |
| 344 | (delete-line))))) | 346 | (vtable--cache-lines cache))) |
| 347 | (when (vtable-goto-object object) | ||
| 348 | (delete-line)))))) | ||
| 345 | 349 | ||
| 346 | ;; FIXME: The fact that the `location' argument of | 350 | ;; FIXME: The fact that the `location' argument of |
| 347 | ;; `vtable-insert-object' can be an integer and is then interpreted as | 351 | ;; `vtable-insert-object' can be an integer and is then interpreted as |
| @@ -369,8 +373,9 @@ This also updates the displayed table." | |||
| 369 | (progn | 373 | (progn |
| 370 | (setf (vtable-objects table) (list object)) | 374 | (setf (vtable-objects table) (list object)) |
| 371 | (vtable--recompute-numerical table (vtable--compute-cached-line table object)) | 375 | (vtable--recompute-numerical table (vtable--compute-cached-line table object)) |
| 372 | (vtable-goto-table table) | 376 | (with-current-buffer (vtable-buffer table) |
| 373 | (vtable-revert-command)) | 377 | (vtable-goto-table table) |
| 378 | (vtable-revert-command))) | ||
| 374 | ;; First insert into the objects. | 379 | ;; First insert into the objects. |
| 375 | (let ((pos (if location | 380 | (let ((pos (if location |
| 376 | (if (integerp location) | 381 | (if (integerp location) |
| @@ -398,56 +403,58 @@ This also updates the displayed table." | |||
| 398 | ;; Otherwise, append the object. | 403 | ;; Otherwise, append the object. |
| 399 | (nconc (vtable-objects table) (list object))))) | 404 | (nconc (vtable-objects table) (list object))))) |
| 400 | ;; Then adjust the cache and display. | 405 | ;; Then adjust the cache and display. |
| 401 | (save-excursion | 406 | (let* ((cache (vtable--current-cache table)) |
| 402 | (vtable-goto-table table) | 407 | (lines (vtable--cache-lines cache)) |
| 403 | (let* ((cache (vtable--current-cache)) | 408 | (elem (if location ; This binding mirrors the binding of `pos' above. |
| 404 | (inhibit-read-only t) | 409 | (if (integerp location) |
| 405 | (keymap (get-text-property (point) 'keymap)) | 410 | (nth location lines) |
| 406 | (ellipsis (if (vtable-ellipsis table) | 411 | (or (assq location lines) |
| 407 | (propertize (truncate-string-ellipsis) | 412 | (and before (car lines)))) |
| 408 | 'face (vtable-face table)) | 413 | (if before (car lines)))) |
| 409 | "")) | 414 | (pos (memq elem lines)) |
| 410 | (ellipsis-width (string-pixel-width ellipsis)) | 415 | (line (cons object (vtable--compute-cached-line table object)))) |
| 411 | (lines (vtable--cache-lines cache)) | 416 | (with-current-buffer (vtable-buffer table) |
| 412 | (elem (if location ; This binding mirrors the binding of `pos' above. | 417 | (let ((inhibit-read-only t) |
| 413 | (if (integerp location) | 418 | (inhibit-modification-hooks t)) |
| 414 | (nth location lines) | 419 | (save-excursion |
| 415 | (or (assq location lines) | 420 | (vtable-goto-table table) |
| 416 | (and before (car lines)))) | 421 | (if (or before |
| 417 | (if before (car lines)))) | 422 | (and pos (integerp location))) |
| 418 | (pos (memq elem lines)) | 423 | ;; Add the new object before:. |
| 419 | (line (cons object (vtable--compute-cached-line table object)))) | 424 | (let ((old-line (car pos))) |
| 420 | (if (or before | 425 | (setcar pos line) |
| 421 | (and pos (integerp location))) | 426 | (setcdr pos (cons old-line (cdr pos))) |
| 422 | ;; Add the new object before:. | 427 | (unless (vtable-goto-object (car elem)) |
| 423 | (let ((old-line (car pos))) | 428 | (vtable-beginning-of-table))) |
| 424 | (setcar pos line) | 429 | ;; Otherwise, add the object after. |
| 425 | (setcdr pos (cons old-line (cdr pos))) | 430 | (if pos |
| 426 | (unless (vtable-goto-object (car elem)) | 431 | ;; Splice the object into the list. |
| 427 | (vtable-beginning-of-table))) | 432 | (progn |
| 428 | ;; Otherwise, add the object after. | 433 | (setcdr pos (cons line (cdr pos))) |
| 429 | (if pos | 434 | (if (vtable-goto-object location) |
| 430 | ;; Splice the object into the list. | 435 | (forward-line 1) ; Insert *after*. |
| 431 | (progn | 436 | (vtable-end-of-table))) |
| 432 | (setcdr pos (cons line (cdr pos))) | 437 | ;; Otherwise, append the object. |
| 433 | (if (vtable-goto-object location) | 438 | (setcar cache (nconc lines (list line))) |
| 434 | (forward-line 1) ; Insert *after*. | 439 | (vtable-end-of-table))) |
| 435 | (vtable-end-of-table))) | 440 | (let* ((start (point)) |
| 436 | ;; Otherwise, append the object. | 441 | (ellipsis (if (vtable-ellipsis table) |
| 437 | (setcar cache (nconc lines (list line))) | 442 | (propertize (truncate-string-ellipsis) |
| 438 | (vtable-end-of-table))) | 443 | 'face (vtable-face table)) |
| 439 | (let ((start (point))) | 444 | "")) |
| 440 | ;; FIXME: We have to adjust colors in lines below this if we | 445 | (ellipsis-width (string-pixel-width ellipsis (current-buffer))) |
| 441 | ;; have :row-colors. | 446 | (keymap (get-text-property (point) 'keymap))) |
| 442 | (vtable--insert-line table line 0 | 447 | ;; FIXME: We have to adjust colors in lines below this if we |
| 443 | (vtable--cache-widths cache) (vtable--spacer table) | 448 | ;; have :row-colors. |
| 444 | ellipsis ellipsis-width) | 449 | (vtable--insert-line table line 0 |
| 445 | (add-text-properties start (point) (list 'keymap keymap | 450 | (vtable--cache-widths cache) |
| 446 | 'vtable table | 451 | (vtable--spacer table) |
| 447 | 'vtable-cache cache))) | 452 | ellipsis ellipsis-width) |
| 448 | ;; We may have inserted a non-numerical value into a previously | 453 | (add-text-properties start (point) (list 'keymap keymap |
| 449 | ;; all-numerical table, so recompute. | 454 | 'vtable table))) |
| 450 | (vtable--recompute-numerical table (cdr line)))))) | 455 | ;; We may have inserted a non-numerical value into a previously |
| 456 | ;; all-numerical table, so recompute. | ||
| 457 | (vtable--recompute-numerical table (cdr line)))))))) | ||
| 451 | 458 | ||
| 452 | (defun vtable-column (table index) | 459 | (defun vtable-column (table index) |
| 453 | "Return the name of the INDEXth column in TABLE." | 460 | "Return the name of the INDEXth column in TABLE." |
| @@ -520,14 +527,14 @@ recompute the column specs when the table data has changed." | |||
| 520 | (defun vtable--cache-lines (cache) | 527 | (defun vtable--cache-lines (cache) |
| 521 | (car cache)) | 528 | (car cache)) |
| 522 | 529 | ||
| 523 | (defun vtable-insert (table) | 530 | (defun vtable--insert (table) |
| 524 | (let* ((spacer (vtable--spacer table)) | 531 | (let* ((spacer (vtable--spacer table)) |
| 525 | (start (point)) | 532 | (start (point)) |
| 526 | (ellipsis (if (vtable-ellipsis table) | 533 | (ellipsis (if (vtable-ellipsis table) |
| 527 | (propertize (truncate-string-ellipsis) | 534 | (propertize (truncate-string-ellipsis) |
| 528 | 'face (vtable-face table)) | 535 | 'face (vtable-face table)) |
| 529 | "")) | 536 | "")) |
| 530 | (ellipsis-width (string-pixel-width ellipsis)) | 537 | (ellipsis-width (string-pixel-width ellipsis (vtable-buffer table))) |
| 531 | ;; We maintain a cache per screen/window width, so that we render | 538 | ;; We maintain a cache per screen/window width, so that we render |
| 532 | ;; correctly if Emacs is open on two different screens (or the | 539 | ;; correctly if Emacs is open on two different screens (or the |
| 533 | ;; user resizes the frame). | 540 | ;; user resizes the frame). |
| @@ -549,8 +556,7 @@ recompute the column specs when the table data has changed." | |||
| 549 | (add-text-properties start (point) | 556 | (add-text-properties start (point) |
| 550 | (list 'keymap vtable-header-line-map | 557 | (list 'keymap vtable-header-line-map |
| 551 | 'rear-nonsticky t | 558 | 'rear-nonsticky t |
| 552 | 'vtable table | 559 | 'vtable table)) |
| 553 | 'vtable-cache cache)) | ||
| 554 | (setq start (point)))) | 560 | (setq start (point)))) |
| 555 | (vtable--sort table cache) | 561 | (vtable--sort table cache) |
| 556 | ;; Insert the data. | 562 | ;; Insert the data. |
| @@ -561,13 +567,35 @@ recompute the column specs when the table data has changed." | |||
| 561 | (setq line-number (1+ line-number)))) | 567 | (setq line-number (1+ line-number)))) |
| 562 | (add-text-properties start (point) | 568 | (add-text-properties start (point) |
| 563 | (list 'rear-nonsticky t | 569 | (list 'rear-nonsticky t |
| 564 | 'vtable table | 570 | 'vtable table)) |
| 565 | 'vtable-cache cache)) | 571 | (setf (vtable--current-cache table) cache) |
| 566 | (goto-char start))) | 572 | (goto-char start))) |
| 567 | 573 | ||
| 574 | (defun vtable-insert (table) | ||
| 575 | "Insert TABLE into the current buffer. | ||
| 576 | The current buffer will be recorded as TABLE's buffer. If the table is | ||
| 577 | inserted into a buffer other than its originating buffer, signal an | ||
| 578 | error. A table may be reinserted into its own buffer, but insert only | ||
| 579 | one instance per buffer. This restriction needs to be enforced by the | ||
| 580 | caller." | ||
| 581 | (if-let* ((table-buffer (vtable-buffer table))) | ||
| 582 | (when (not (eq table-buffer (current-buffer))) | ||
| 583 | (error "A vtable cannot be inserted into more than one buffer"))) | ||
| 584 | (setf (vtable-buffer table) (current-buffer)) | ||
| 585 | (let ((inhibit-read-only t) | ||
| 586 | (inhibit-modification-hooks t)) | ||
| 587 | (vtable--insert table))) | ||
| 588 | |||
| 589 | (defun vtable-set-buffer (table buffer) | ||
| 590 | "Associate BUFFER with TABLE. | ||
| 591 | Use this function with care, and ensure your vtable instance renders | ||
| 592 | itself in the new buffer." | ||
| 593 | (setf (vtable-buffer table) buffer)) | ||
| 594 | |||
| 568 | (defun vtable--insert-line (table line line-number widths spacer | 595 | (defun vtable--insert-line (table line line-number widths spacer |
| 569 | &optional ellipsis ellipsis-width) | 596 | &optional ellipsis ellipsis-width) |
| 570 | (let ((start (point)) | 597 | (let ((start (point)) |
| 598 | (buffer (vtable-buffer table)) | ||
| 571 | (columns (vtable-columns table)) | 599 | (columns (vtable-columns table)) |
| 572 | (column-colors | 600 | (column-colors |
| 573 | (and (vtable-column-colors table) | 601 | (and (vtable-column-colors table) |
| @@ -607,16 +635,18 @@ recompute the column specs when the table data has changed." | |||
| 607 | (concat | 635 | (concat |
| 608 | (vtable--limit-string | 636 | (vtable--limit-string |
| 609 | pre-computed (- (elt widths index) | 637 | pre-computed (- (elt widths index) |
| 610 | (or ellipsis-width 0))) | 638 | (or ellipsis-width 0)) |
| 639 | buffer) | ||
| 611 | ellipsis) | 640 | ellipsis) |
| 612 | pre-computed)) | 641 | pre-computed)) |
| 613 | ;; Recompute widths. | 642 | ;; Recompute widths. |
| 614 | (t | 643 | (t |
| 615 | (if (> (string-pixel-width value) (elt widths index)) | 644 | (if (> (string-pixel-width value buffer) (elt widths index)) |
| 616 | (concat | 645 | (concat |
| 617 | (vtable--limit-string | 646 | (vtable--limit-string |
| 618 | value (- (elt widths index) | 647 | value (- (elt widths index) |
| 619 | (or ellipsis-width 0))) | 648 | (or ellipsis-width 0)) |
| 649 | buffer) | ||
| 620 | ellipsis) | 650 | ellipsis) |
| 621 | value)))) | 651 | value)))) |
| 622 | (start (point)) | 652 | (start (point)) |
| @@ -630,14 +660,15 @@ recompute the column specs when the table data has changed." | |||
| 630 | (list 'space | 660 | (list 'space |
| 631 | :width (list | 661 | :width (list |
| 632 | (+ (- (elt widths index) | 662 | (+ (- (elt widths index) |
| 633 | (string-pixel-width displayed)) | 663 | (string-pixel-width |
| 664 | displayed buffer)) | ||
| 634 | (if last 0 spacer))))))) | 665 | (if last 0 spacer))))))) |
| 635 | ;; Align to the right. | 666 | ;; Align to the right. |
| 636 | (insert (propertize " " 'display | 667 | (insert (propertize " " 'display |
| 637 | (list 'space | 668 | (list 'space |
| 638 | :width (list (- (elt widths index) | 669 | :width (list (- (elt widths index) |
| 639 | (string-pixel-width | 670 | (string-pixel-width |
| 640 | displayed))))) | 671 | displayed buffer))))) |
| 641 | displayed) | 672 | displayed) |
| 642 | (unless last | 673 | (unless last |
| 643 | (insert (propertize " " 'display | 674 | (insert (propertize " " 'display |
| @@ -664,16 +695,6 @@ recompute the column specs when the table data has changed." | |||
| 664 | (defun vtable--cache-key () | 695 | (defun vtable--cache-key () |
| 665 | (cons (frame-terminal) (window-width))) | 696 | (cons (frame-terminal) (window-width))) |
| 666 | 697 | ||
| 667 | (defun vtable--current-cache () | ||
| 668 | "Return the current cache for the table at point. | ||
| 669 | |||
| 670 | In `vtable-insert', the lines and widths of the vtable text are computed | ||
| 671 | based on the current selected frame and window and stored in a cache. | ||
| 672 | Subsequent interaction with the text of the vtable should use that cache | ||
| 673 | via this function rather than by calling `vtable--cache-key' to look up | ||
| 674 | the cache." | ||
| 675 | (get-text-property (point) 'vtable-cache)) | ||
| 676 | |||
| 677 | (defun vtable--clear-cache (table) | 698 | (defun vtable--clear-cache (table) |
| 678 | (setf (gethash (vtable--cache-key) (slot-value table '-cache)) nil)) | 699 | (setf (gethash (vtable--cache-key) (slot-value table '-cache)) nil)) |
| 679 | 700 | ||
| @@ -718,6 +739,7 @@ the cache." | |||
| 718 | (defun vtable--insert-header-line (table widths spacer) | 739 | (defun vtable--insert-header-line (table widths spacer) |
| 719 | ;; Insert the header directly into the buffer. | 740 | ;; Insert the header directly into the buffer. |
| 720 | (let ((start (point)) | 741 | (let ((start (point)) |
| 742 | (buffer (vtable-buffer table)) | ||
| 721 | (divider (vtable-divider table)) | 743 | (divider (vtable-divider table)) |
| 722 | (cmap (define-keymap | 744 | (cmap (define-keymap |
| 723 | "<header-line> <drag-mouse-1>" #'vtable--drag-resize-column | 745 | "<header-line> <drag-mouse-1>" #'vtable--drag-resize-column |
| @@ -737,14 +759,15 @@ the cache." | |||
| 737 | 'keymap cmap)) | 759 | 'keymap cmap)) |
| 738 | (start (point)) | 760 | (start (point)) |
| 739 | (indicator (vtable--indicator table index)) | 761 | (indicator (vtable--indicator table index)) |
| 740 | (indicator-width (string-pixel-width indicator)) | 762 | (indicator-width (string-pixel-width indicator buffer)) |
| 741 | (last (= index (1- (length (vtable-columns table))))) | 763 | (last (= index (1- (length (vtable-columns table))))) |
| 742 | displayed) | 764 | displayed) |
| 743 | (setq displayed | 765 | (setq displayed |
| 744 | (if (> (string-pixel-width name) | 766 | (if (> (string-pixel-width name buffer) |
| 745 | (- (elt widths index) indicator-width)) | 767 | (- (elt widths index) indicator-width)) |
| 746 | (vtable--limit-string | 768 | (vtable--limit-string |
| 747 | name (- (elt widths index) indicator-width)) | 769 | name (- (elt widths index) indicator-width) |
| 770 | buffer) | ||
| 748 | name)) | 771 | name)) |
| 749 | (let* ((indicator-lead-width | 772 | (let* ((indicator-lead-width |
| 750 | ;; We want the indicator to not be quite flush right. | 773 | ;; We want the indicator to not be quite flush right. |
| @@ -753,7 +776,7 @@ the cache." | |||
| 753 | indicator-lead-width)) | 776 | indicator-lead-width)) |
| 754 | (fill-width | 777 | (fill-width |
| 755 | (+ (- (elt widths index) | 778 | (+ (- (elt widths index) |
| 756 | (string-pixel-width displayed) | 779 | (string-pixel-width displayed buffer) |
| 757 | indicator-width | 780 | indicator-width |
| 758 | indicator-lead-width) | 781 | indicator-lead-width) |
| 759 | (if last 0 spacer)))) | 782 | (if last 0 spacer)))) |
| @@ -771,7 +794,8 @@ the cache." | |||
| 771 | ;; This is the final column, and we have a sorting | 794 | ;; This is the final column, and we have a sorting |
| 772 | ;; indicator, and the table is too wide for the window. | 795 | ;; indicator, and the table is too wide for the window. |
| 773 | (let* ((pre-indicator (string-pixel-width | 796 | (let* ((pre-indicator (string-pixel-width |
| 774 | (buffer-substring (point-min) (point)))) | 797 | (buffer-substring (point-min) (point)) |
| 798 | buffer)) | ||
| 775 | (pre-fill | 799 | (pre-fill |
| 776 | (- (window-width nil t) | 800 | (- (window-width nil t) |
| 777 | pre-indicator | 801 | pre-indicator |
| @@ -850,14 +874,16 @@ If NEXT, do the next column." | |||
| 850 | (buffer-substring (point-min) (1- (point-max)))))) | 874 | (buffer-substring (point-min) (1- (point-max)))))) |
| 851 | (vtable-header-mode 1)) | 875 | (vtable-header-mode 1)) |
| 852 | 876 | ||
| 853 | (defun vtable--limit-string (string pixels) | 877 | |
| 878 | (defun vtable--limit-string (string pixels buffer) | ||
| 854 | (while (and (length> string 0) | 879 | (while (and (length> string 0) |
| 855 | (> (string-pixel-width string) pixels)) | 880 | (> (string-pixel-width string buffer) pixels)) |
| 856 | (setq string (substring string 0 (1- (length string))))) | 881 | (setq string (substring string 0 (1- (length string))))) |
| 857 | string) | 882 | string) |
| 858 | 883 | ||
| 859 | (defun vtable--char-width (table) | 884 | (defun vtable--char-width (table) |
| 860 | (string-pixel-width (propertize "x" 'face (vtable-face table)))) | 885 | (string-pixel-width (propertize "x" 'face (vtable-face table)) |
| 886 | (vtable-buffer table))) | ||
| 861 | 887 | ||
| 862 | (defun vtable--compute-width (table spec) | 888 | (defun vtable--compute-width (table spec) |
| 863 | (cond | 889 | (cond |
| @@ -936,7 +962,7 @@ CACHE is TABLE's cache data as returned by `vtable--compute-cache'." | |||
| 936 | ;; We stash the computed width and string here -- if there are | 962 | ;; We stash the computed width and string here -- if there are |
| 937 | ;; no formatters/displayers, we'll be using the string, and | 963 | ;; no formatters/displayers, we'll be using the string, and |
| 938 | ;; then won't have to recreate it. | 964 | ;; then won't have to recreate it. |
| 939 | (list value (string-pixel-width string) string))) | 965 | (list value (string-pixel-width string (vtable-buffer table)) string))) |
| 940 | (vtable-columns table))) | 966 | (vtable-columns table))) |
| 941 | 967 | ||
| 942 | (defun vtable--make-keymap (table) | 968 | (defun vtable--make-keymap (table) |
| @@ -967,20 +993,22 @@ CACHE is TABLE's cache data as returned by `vtable--compute-cache'." | |||
| 967 | (vtable-keymap table)) | 993 | (vtable-keymap table)) |
| 968 | map))) | 994 | map))) |
| 969 | 995 | ||
| 970 | (defun vtable-revert () | 996 | (defun vtable-revert (&optional table) |
| 971 | "Regenerate the table under point." | 997 | "Regenerate TABLE, defaulting to the table under point." |
| 972 | (let ((table (vtable-current-table)) | 998 | (setq table (or table (vtable-current-table))) |
| 973 | (object (vtable-current-object)) | 999 | (unless table |
| 974 | (column (vtable-current-column)) | 1000 | (user-error "No table under point")) |
| 975 | (inhibit-read-only t)) | 1001 | (with-current-buffer (vtable-buffer table) |
| 976 | (unless table | 1002 | (let ((object (vtable-current-object)) |
| 977 | (user-error "No table under point")) | 1003 | (column (vtable-current-column)) |
| 978 | (delete-region (vtable-beginning-of-table) (vtable-end-of-table)) | 1004 | (inhibit-read-only t) |
| 979 | (vtable-insert table) | 1005 | (inhibit-modification-hooks t)) |
| 980 | (when object | 1006 | (delete-region (vtable-beginning-of-table) (vtable-end-of-table)) |
| 981 | (vtable-goto-object object)) | 1007 | (vtable--insert table) |
| 982 | (when column | 1008 | (when object |
| 983 | (vtable-goto-column column)))) | 1009 | (vtable-goto-object object)) |
| 1010 | (when column | ||
| 1011 | (vtable-goto-column column))))) | ||
| 984 | 1012 | ||
| 985 | ;;; Commands. | 1013 | ;;; Commands. |
| 986 | 1014 | ||
| @@ -1006,14 +1034,14 @@ Interactively, N is the prefix argument." | |||
| 1006 | (- (* (vtable--char-width table) (or n 1)))))) | 1034 | (- (* (vtable--char-width table) (or n 1)))))) |
| 1007 | 1035 | ||
| 1008 | (defun vtable--alter-column-width (table column delta) | 1036 | (defun vtable--alter-column-width (table column delta) |
| 1009 | (let ((widths (vtable--cache-widths (vtable--current-cache)))) | 1037 | (let ((widths (vtable--cache-widths (vtable--current-cache table)))) |
| 1010 | (setf (aref widths column) | 1038 | (setf (aref widths column) |
| 1011 | (max (* (vtable--char-width table) 2) | 1039 | (max (* (vtable--char-width table) 2) |
| 1012 | (+ (aref widths column) delta))) | 1040 | (+ (aref widths column) delta))) |
| 1013 | ;; Store the width so it'll be respected on a revert. | 1041 | ;; Store the width so it'll be respected on a revert. |
| 1014 | (setf (vtable-column-width (elt (vtable-columns table) column)) | 1042 | (setf (vtable-column-width (elt (vtable-columns table) column)) |
| 1015 | (format "%dpx" (aref widths column))) | 1043 | (format "%dpx" (aref widths column))) |
| 1016 | (vtable-revert))) | 1044 | (vtable-revert table))) |
| 1017 | 1045 | ||
| 1018 | (defun vtable-widen-current-column (&optional n) | 1046 | (defun vtable-widen-current-column (&optional n) |
| 1019 | "Widen the current column by N characters. | 1047 | "Widen the current column by N characters. |
| @@ -1028,24 +1056,29 @@ Interactively, N is the prefix argument." | |||
| 1028 | (interactive) | 1056 | (interactive) |
| 1029 | (vtable-goto-column | 1057 | (vtable-goto-column |
| 1030 | (max 0 (1- (or (vtable-current-column) | 1058 | (max 0 (1- (or (vtable-current-column) |
| 1031 | (length (vtable--cache-widths (vtable--current-cache)))))))) | 1059 | (length (vtable--cache-widths |
| 1060 | (vtable--current-cache (vtable-current-table))))))))) | ||
| 1032 | 1061 | ||
| 1033 | (defun vtable-next-column () | 1062 | (defun vtable-next-column () |
| 1034 | "Go to the next column." | 1063 | "Go to the next column." |
| 1035 | (interactive) | 1064 | (interactive) |
| 1036 | (when (vtable-current-column) | 1065 | (when (vtable-current-column) |
| 1037 | (vtable-goto-column | 1066 | (vtable-goto-column |
| 1038 | (min (1- (length (vtable--cache-widths (vtable--current-cache)))) | 1067 | (min (1- (length (vtable--cache-widths |
| 1068 | (vtable--current-cache (vtable-current-table))))) | ||
| 1039 | (1+ (vtable-current-column)))))) | 1069 | (1+ (vtable-current-column)))))) |
| 1040 | 1070 | ||
| 1041 | (defun vtable-revert-command () | 1071 | (defun vtable-revert-command (&optional table) |
| 1042 | "Re-query data and regenerate the table under point." | 1072 | "Re-query data and regenerate TABLE. |
| 1073 | If TABLE is nil, use the table under point." | ||
| 1043 | (interactive) | 1074 | (interactive) |
| 1044 | (let ((table (vtable-current-table))) | 1075 | (setq table (or table (vtable-current-table))) |
| 1045 | (when (vtable-objects-function table) | 1076 | (unless table |
| 1046 | (setf (vtable-objects table) (funcall (vtable-objects-function table)))) | 1077 | (user-error "No table found")) |
| 1047 | (vtable--clear-cache table)) | 1078 | (when (vtable-objects-function table) |
| 1048 | (vtable-revert)) | 1079 | (setf (vtable-objects table) (funcall (vtable-objects-function table)))) |
| 1080 | (vtable--clear-cache table) | ||
| 1081 | (vtable-revert table)) | ||
| 1049 | 1082 | ||
| 1050 | (defun vtable-sort-by-current-column () | 1083 | (defun vtable-sort-by-current-column () |
| 1051 | "Sort the table under point by the column under point." | 1084 | "Sort the table under point by the column under point." |
| @@ -1067,8 +1100,8 @@ Interactively, N is the prefix argument." | |||
| 1067 | (if (eq (cdr last) 'ascend) | 1100 | (if (eq (cdr last) 'ascend) |
| 1068 | 'descend | 1101 | 'descend |
| 1069 | 'ascend) | 1102 | 'ascend) |
| 1070 | 'ascend)))))) | 1103 | 'ascend))))) |
| 1071 | (vtable-revert)) | 1104 | (vtable-revert table))) |
| 1072 | 1105 | ||
| 1073 | (defun vtable-header-line-sort (e) | 1106 | (defun vtable-header-line-sort (e) |
| 1074 | "Sort a vtable from the header line." | 1107 | "Sort a vtable from the header line." |
diff --git a/test/lisp/emacs-lisp/vtable-tests.el b/test/lisp/emacs-lisp/vtable-tests.el index 74fb8cc8139..f865206dc19 100644 --- a/test/lisp/emacs-lisp/vtable-tests.el +++ b/test/lisp/emacs-lisp/vtable-tests.el | |||
| @@ -27,16 +27,19 @@ | |||
| 27 | (require 'ert) | 27 | (require 'ert) |
| 28 | (require 'ert-x) | 28 | (require 'ert-x) |
| 29 | 29 | ||
| 30 | (ert-deftest test-vstable-compute-columns () | 30 | (defun vtable-tests--make-no-header-2-object-table () |
| 31 | (make-vtable :columns '("a" "b" "c") | ||
| 32 | :objects '(("foo" 1 2) | ||
| 33 | ("bar" 3 :zot)) | ||
| 34 | :insert nil)) | ||
| 35 | |||
| 36 | (ert-deftest test-vtable-compute-columns () | ||
| 31 | (should | 37 | (should |
| 32 | (equal (mapcar | 38 | (equal (mapcar |
| 33 | (lambda (column) | 39 | (lambda (column) |
| 34 | (vtable-column-align column)) | 40 | (vtable-column-align column)) |
| 35 | (vtable--compute-columns | 41 | (vtable--compute-columns |
| 36 | (make-vtable :columns '("a" "b" "c") | 42 | (vtable-tests--make-no-header-2-object-table))) |
| 37 | :objects '(("foo" 1 2) | ||
| 38 | ("bar" 3 :zot)) | ||
| 39 | :insert nil))) | ||
| 40 | '(left right left)))) | 43 | '(left right left)))) |
| 41 | 44 | ||
| 42 | (ert-deftest test-vtable-insert-object () | 45 | (ert-deftest test-vtable-insert-object () |
| @@ -69,4 +72,82 @@ | |||
| 69 | (mapcar #'cadr (vtable-objects table)))) | 72 | (mapcar #'cadr (vtable-objects table)))) |
| 70 | (number-sequence 0 11)))) | 73 | (number-sequence 0 11)))) |
| 71 | 74 | ||
| 75 | (ert-deftest test-vtable-unique-buffer () | ||
| 76 | (let ((table (vtable-tests--make-no-header-2-object-table))) | ||
| 77 | (with-temp-buffer | ||
| 78 | (vtable-insert table) | ||
| 79 | (with-temp-buffer | ||
| 80 | (should-error (vtable-insert table))) | ||
| 81 | (with-temp-buffer | ||
| 82 | (vtable-set-buffer table (current-buffer)) | ||
| 83 | (vtable-insert table))))) | ||
| 84 | |||
| 85 | (ert-deftest test-vtable-read-only-buffer () | ||
| 86 | (let ((table (vtable-tests--make-no-header-2-object-table))) | ||
| 87 | (with-temp-buffer | ||
| 88 | (setq buffer-read-only t) | ||
| 89 | (vtable-insert table)))) | ||
| 90 | |||
| 91 | (ert-deftest test-vtable-non-current-buffer-insert-object () | ||
| 92 | (let ((table (vtable-tests--make-no-header-2-object-table)) | ||
| 93 | (obj '("baz" 4 5))) | ||
| 94 | (with-temp-buffer | ||
| 95 | (vtable-insert table) | ||
| 96 | (should (= (count-lines (point-min) (point-max)) 2)) | ||
| 97 | (with-temp-buffer | ||
| 98 | (vtable-insert-object table obj)) | ||
| 99 | (should (= (count-lines (point-min) (point-max)) 3))))) | ||
| 100 | |||
| 101 | (ert-deftest test-vtable-non-current-buffer-remove-object () | ||
| 102 | (let ((table (vtable-tests--make-no-header-2-object-table)) | ||
| 103 | (obj '("baz" 4 5))) | ||
| 104 | (with-temp-buffer | ||
| 105 | (vtable-insert table) | ||
| 106 | (vtable-insert-object table obj) | ||
| 107 | (should (= (count-lines (point-min) (point-max)) 3)) | ||
| 108 | (with-temp-buffer | ||
| 109 | (vtable-remove-object table obj)) | ||
| 110 | (should (= (count-lines (point-min) (point-max)) 2))))) | ||
| 111 | |||
| 112 | (ert-deftest test-vtable-non-current-buffer-update-object () | ||
| 113 | (let ((table (vtable-tests--make-no-header-2-object-table)) | ||
| 114 | (obj '("baz" 4 5)) | ||
| 115 | (obj-2 '("qux" 6 7))) | ||
| 116 | (with-temp-buffer | ||
| 117 | (vtable-insert table) | ||
| 118 | (vtable-insert-object table obj) | ||
| 119 | (should (= (count-lines (point-min) (point-max)) 3)) | ||
| 120 | (let ((line-2 (progn | ||
| 121 | (goto-char (point-min)) | ||
| 122 | (forward-line 2) | ||
| 123 | (buffer-substring (point) (point-max))))) | ||
| 124 | (with-temp-buffer | ||
| 125 | (vtable-update-object table obj-2 obj)) | ||
| 126 | (let ((line-2-new (progn | ||
| 127 | (goto-char (point-min)) | ||
| 128 | (forward-line 2) | ||
| 129 | (buffer-substring (point) (point-max))))) | ||
| 130 | (should (= (count-lines (point-min) (point-max)) 3)) | ||
| 131 | (should (not (string= line-2 line-2-new)))))))) | ||
| 132 | |||
| 133 | (ert-deftest test-vtable--limit-string-with-face-remapped-buffer () | ||
| 134 | (with-temp-buffer | ||
| 135 | (let ((text (propertize "XXXXX" | ||
| 136 | 'face 'variable-pitch))) | ||
| 137 | (face-remap-add-relative 'default :height 1.5) | ||
| 138 | (cond ((< emacs-major-version 31) ; TODO: Remove the pre-31 test, eventually. | ||
| 139 | (let* ((x-width (string-pixel-width (substring text 0 1))) | ||
| 140 | (char-limit 2) | ||
| 141 | (pixel-limit (* char-limit x-width))) | ||
| 142 | (should (eq | ||
| 143 | char-limit | ||
| 144 | (length (vtable--limit-string text pixel-limit)))))) | ||
| 145 | (t | ||
| 146 | (let* ((x-width (string-pixel-width (substring text 0 1) (current-buffer))) | ||
| 147 | (char-limit 2) | ||
| 148 | (pixel-limit (* char-limit x-width))) | ||
| 149 | (should (eq | ||
| 150 | char-limit | ||
| 151 | (length (vtable--limit-string text pixel-limit (current-buffer))))))))))) | ||
| 152 | |||
| 72 | ;;; vtable-tests.el ends here | 153 | ;;; vtable-tests.el ends here |