From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Felix Dietrich Newsgroups: gmane.emacs.help Subject: Re: buffer substring of only visible text Date: Wed, 28 Sep 2022 19:59:46 +0200 Message-ID: <8735cb2xd9.fsf@sperrhaken.name> References: <8735clm05b.fsf@sperrhaken.name> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="20902"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.1 (gnu/linux) Cc: help-gnu-emacs@gnu.org To: Samuel Wales Original-X-From: help-gnu-emacs-bounces+geh-help-gnu-emacs=m.gmane-mx.org@gnu.org Wed Sep 28 20:12:04 2022 Return-path: Envelope-to: geh-help-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1odbXP-0005BK-EG for geh-help-gnu-emacs@m.gmane-mx.org; Wed, 28 Sep 2022 20:12:03 +0200 Original-Received: from localhost ([::1]:50884 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1odbXO-0007c6-DD for geh-help-gnu-emacs@m.gmane-mx.org; Wed, 28 Sep 2022 14:12:02 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:40194) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1odbLf-0000yZ-W4 for help-gnu-emacs@gnu.org; Wed, 28 Sep 2022 13:59:56 -0400 Original-Received: from mout.kundenserver.de ([217.72.192.75]:53997) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1odbLe-0006Ks-3q for help-gnu-emacs@gnu.org; Wed, 28 Sep 2022 13:59:55 -0400 Original-Received: from localhost ([77.13.244.240]) by mrelayeu.kundenserver.de (mreue106 [212.227.15.183]) with ESMTPSA (Nemesis) id 1MXpM2-1ojDXX12tg-00YDCv; Wed, 28 Sep 2022 19:59:51 +0200 In-Reply-To: (Samuel Wales's message of "Wed, 21 Sep 2022 15:57:57 -0700") X-Provags-ID: V03:K1:Eu704b60T4F+GmoedLSmUKA/jSxFvdJIibW5LLANxN07/lkX477 yWVtTDZwSXKpiZbtigYS7kcHmu/ChRqhHEmJm6F4Mot9/cCHqRuAIjjpOi0/2UbyjIbEk3b Rfl8/1bC0hd6k1lGbO2NR4n8g9EEONlPPIUOA6eFsuUMDzba8oEWj/EGHJOnin5NECqcfzs LjsgC72WojeEe5O8OqJ4g== X-UI-Out-Filterresults: notjunk:1;V03:K0:+2y1rWeDsjk=:+gAJYfH6gP+OsxRw4bMFuC igoIiR3dCQ04x8jlguIv38UrXINfvBizrtWw5JqzfiECo9L3D2Xt6h16acWkljc0YkH3PqJsi 1NG5rsbFyn46NBg3ip+CIma76PStlBF/QQVEd31K0yBOUjNylEJmp1xdUQqxgl8cuboafchZD DiD9zicyVhXSsO1/A4YyxaMH/lor/DLLAX54nxGRqpgzM3zRdy/2udKUZo7DaW9H+FCXsTgkG jagJh/bd3AC4V8WhshA8zTdU9+u+yXZ9EQrjbBvd6RKyBW/gAuxxqnYwhtimGAa/NsgUO0LP8 MwE2kjGUqiQOubDH6+3KmQWcPx+/Onocyjz4ADWAz1i1wOqeXyFAvSOPnwziBa13vUw8TJxaO +5AOwNjZY3YdbdgDvGUcI5Q1UGBch0FCUz8IqgbHFxKiQiFAdqPUlveojQPpC30ZaAcV0GjdV PLtKIP5zSTxFPxyfWzHfKY4NtmrsGS/XQkNbLeW48N5bMzptDOazErhTHwiyuzIBNSisODOpS 3CwdvijhyU3/j0qaZFNtt+0Zf2DEgSobjtJqNTCofla4Of2vs0jm3O7hpHb27Jo5dG73mPtJ2 Yzft7Maki+gKfjM+JknqHNULrdxOys2teuHmJaIM74WaPmvEsib02ZHMhW7ia3fm890DLd6cH ic0hG0PSlDk5pmn8SBw8g0qkxxqXoQEtyt+CEr/fQvM3LWfbPiQMUmInSFo/FMMj8FwMq29KK tEuLLwbhR87ELehH0i2UF0ImsJL+Azh52cBlamFEZwChji4SJdvoDPEKykf6FcqWBSqtRDD8 Received-SPF: none client-ip=217.72.192.75; envelope-from=felix.dietrich@sperrhaken.name; helo=mout.kundenserver.de X-Spam_score_int: -18 X-Spam_score: -1.9 X-Spam_bar: - X-Spam_report: (-1.9 / 5.0 requ) BAYES_00=-1.9, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_NONE=0.001, SPF_NONE=0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: help-gnu-emacs@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Users list for the GNU Emacs text editor List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: help-gnu-emacs-bounces+geh-help-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: "help-gnu-emacs" Xref: news.gmane.io gmane.emacs.help:139667 Archived-At: --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Hello Samuel, Samuel Wales writes: > thank you for your response. very interesting and thorough. > > this problem with or without manuals has made my head swim ever since > text properties arrived. i'm sure the manuals are good but my broken > brain is not really getting text properties. results are sometimes > off, i don't know the special ones, etc. If you have a concrete problem, some small snippet you were experimenting with and that does not do what you were expecting then, maybe, someone on the list could help you with understanding it. Just play around with some code, compare the actual results with your expectations, and try to figure out your misapprehensions either yourself or by asking others. This way, after some time and frustration, things will hopefully become clearer and you develop an intuition for Emacs=CA=BC idiosyncrasies. > i forgot to mention more explicitly that i was hoping to not depend on > htmlize as it is not in core, but that's a side point, as i will use > htmlize if necessary, and also i think now, maybe, it /might/ be > better to go line by line? but i am not sure. The little snippet only uses a few utility functions that you could easily copy and paste from htmlize and give your own prefix. > for me it would be better if thre were a tool more powerful than c-u > c-x =3D to inspect them. for example, if there were a tool to show all > text properties and their intervals in a region or buffer or string. > and also links to where any special text properties are documented. > but that's a wish list i guess. it would help me understand emacs > anyway. I dabbled around a little with your idea and came up with two commands. They do not do exactly what you want, have issues (only some of them I am vaguely aware of) and might be at least a little broken (=E2=80=98hl-line-mode=E2=80=99 for example leaves partially highlighted li= nes around), but you might still find them interesting as toys to play around with. In any case, I had fun and learned a little about Emacs and its Lisp. One shows in another buffer the active properties at point; the buffer is updated when point is moved, the other shows active properties within a region. Properties in overlapping overlays with lower priority are not shown. --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=property-browser.el Content-Transfer-Encoding: quoted-printable Content-Description: Commands to display text properties in a buffer ;; -*- lexical-binding: t; -*- ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;;;; Helper Functions (defun property-browser-text-and-overlay-property-symbols-at (pos) "Return a list of properties at POS in the current buffer. The list contains just the the property symbols not their assigned values." (let* ((text-props (text-properties-at pos)) (overlay-props (mapcan #'overlay-properties (overlays-at pos))) (all-props (nconc text-props overlay-props)) (prop-symbols (mapcar #'car (seq-partition all-props 2)))) (delete-dups prop-symbols))) (defun property-browser-get-char-properties-and-overlays (pos) "Return the char properties in the current buffer at POS. The return value is an alist with elements (property . (value . source)), where source is either nil, if the value was taken from a text property, or an overlay." (let* ((get-prop (apply-partially #'get-char-property-and-overlay pos= )) (prop-symbols (property-browser-text-and-overlay-property-symbols-at pos)) (prop-values (mapcar get-prop prop-symbols))) (seq-mapn #'cons prop-symbols prop-values))) (defun property-browser-generate-new-at-point-buffer () "Generate a new buffer for =E2=80=98list-properties-at-point-minor-mode= =E2=80=99. Return the newly generated buffer." (let* ((at-point-buffer-name (concat "*properties-at-point-" (buffer-name) "*")) (at-point-buffer (generate-new-buffer at-point-buffer-name)) (watched-buffer (current-buffer))) (with-current-buffer watched-buffer (setq-local property-browser-at-point-buffer at-point-buffer)) (with-current-buffer at-point-buffer (setq-local property-browser-watched-buffer watched-buffer) (tabulated-list-mode) (setq tabulated-list-format [("O" 3 t) ; overlay-marker ("Name" 25 t) ("Value" 50 t)]) (tabulated-list-init-header)) at-point-buffer)) (defun property-browser-get-at-point-buffer-create () "Return for the current buffer the buffer to show properties in. See the =E2=80=98list-properties-at-point-minor-mode=E2=80=99." (let ((at-point-buffer property-browser-at-point-buffer)) (unless (buffer-live-p at-point-buffer) (setq at-point-buffer (property-browser-generate-new-at-point-buffer)= )) at-point-buffer)) (defun property-browser-update-at-point-buffer (at-point-buffer rows) "Replace existing data in AT-POINT-BUFFER with ROWS. ROWS is a list of elements as expected by =E2=80=98tabulated-list-mode=E2= =80=99 with the format used by =E2=80=98list-properties-at-point-minor-mode=E2=80= =99: ((id-or-nil [overlay-marker, name, value])=E2=80=A6)" (with-current-buffer at-point-buffer (setq tabulated-list-entries rows) (tabulated-list-print))) (defun property-browser-make-rows-from-properties-at (pos) "Return rows for use by =E2=80=98list-properties-at-point-minor-mode=E2= =80=99. POS referes to a buffer position in the current buffer. Each row has the format expected by =E2=80=98tabulated-list-mode=E2=80=99 a= s used by =E2=80=98list-properties-at-point-minor-mode=E2=80=99: (id-or-nil [overlay-marker, name, value])" (let ((make-row (lambda (prop) (let* ((name (symbol-name (car prop))) (value (format "%s" (cadr prop))) (overlay (cddr prop)) (overlay-marker (if overlay "x" ""))) (list nil (vector overlay-marker name value))))) (props (property-browser-get-char-properties-and-overlays pos))) (mapcar make-row props))) (defun property-browser-post-command-hook-handler () "Update the buffer showing the properties at point." (let ((at-point-buffer (property-browser-get-at-point-buffer-create)) (rows (property-browser-make-rows-from-properties-at (point)))) (property-browser-update-at-point-buffer at-point-buffer rows))) (defun property-browser-get-property-symbols-in-range (start end) "Return properties set in the current buffer between START and END. The returned list contains just the property symbols not the values." (let* ((get-prop-symbols #'property-browser-text-and-overlay-property-symbols-at) (points (number-sequence start end)) (prop-symbols (mapcan get-prop-symbols points))) (delete-dups prop-symbols))) (defun property-browser-group-by (fn s) "Group consecutive runs of =E2=80=98equal=E2=80=99 elements in S. The function FN is called on every element of S to produce a value that is used for the comparison of equality." (let ((acc-fn (lambda (acc elem) (if (null acc) (list (list elem)) (let ((head (car acc)) (tail (cdr acc))) (if (equal (funcall fn (car head)) (funcall fn elem)) (cons (cons elem head) tail) (cons (list elem) (cons (seq-reverse head) tail)))))))) (let* ((groups (seq-reduce acc-fn s nil)) ;; The last group added at the head will still be in ;; reverse order after =E2=80=98seq-reduce=E2=80=99 finished. (head (seq-reverse (car groups))) (tail (cdr groups))) (seq-reverse (cons head tail))))) (defun property-browser-single-property-values-in-range (prop start end) "Return all values of PROP between START and END in the current buffer. The result is a list of alists. Each alist has the form: `((start . ,start) (end . ,end) (value . ,value) (overlay . ,overlay)) The overlay value is either nil for a text property or the overlay from which the value was taken from." (let* ((get-prop-and-overlay (lambda (pos) (cons pos (get-char-property-and-overlay pos prop)))) (get-pos #'car) (get-value #'cadr) (get-overlay #'cddr) (value-and-overlay #'cdr) (range (number-sequence start end)) (props (mapcar get-prop-and-overlay range)) (grouped (property-browser-group-by value-and-overlay props)) (condense-group-extracting-start-and-end (lambda (g) (let* ((first (first g)) (last (car (last g))) (start (funcall get-pos first)) (end (funcall get-pos last)) (value (funcall get-value first)) (overlay (funcall get-overlay first))) (list (cons 'start start) (cons 'end end) (cons 'value value) (cons 'overlay overlay)))))) (mapcar condense-group-extracting-start-and-end grouped))) (defun property-browser-property-values-in-range (start end) "Return properties between START and END in the current buffer. The return value is an alist of the form: (('prop . ('start . start-pos) ('end . end-pos) ('value . value) ('overlay . overlay))) Duplicated =E2=80=9Cprop=E2=80=9D keys are possible. In this case =E2=80= =9Cvalue=E2=80=9D or =E2=80=9Coverlay=E2=80=9D may be different, or the property does not extend= over the range in one continues block." (let ((get-props (lambda (prop) (cons prop (property-browser-single-property-values-in-range prop start end)))) (prop-symbols (property-browser-get-property-names-in-range start end))) (mapcar get-props prop-symbols))) (defun property-browser-compare-numerical (a b) "Compare two =E2=80=98tabulated-list-mode=E2=80=99 elements numerical. The comparison is done honouring the current =E2=80=98tabulated-list-mode=E2=80=99 sort column." (let* ((sort-column (car tabulated-list-sort-key)) (n (tabulated-list--column-number sort-column))) (< (string-to-number (aref (cadr a) n)) (string-to-number (aref (cadr b) n))))) (defun property-browser-compare-string (a b) "Compare two =E2=80=98tabulated-list-mode=E2=80=99 elements as strings. The comparison is done honouring the current =E2=80=98tabulated-list-mode=E2=80=99 sort column." ;; FIXME: Isn=CA=BCt this the default and, therefore, redundant? (let* ((sort-column (car tabulated-list-sort-key)) (n (tabulated-list--column-number sort-column))) (string< (aref (cadr a) n) (aref (cadr b) n)))) (defun property-browser-get-list-in-range-buffer-create () "Return for the current buffer the buffer to show properties in. See the =E2=80=98list-properties-in-range=E2=80=99 function." (let ((buffer (get-buffer-create (concat "*properties-in-range-" (buffer-name) "*")))) (with-current-buffer buffer (let* ((table-format [("Name" 15 t) ("Value" 40 property-browser-compare-string) ("Start" 6 property-browser-compare-numerical) ("End" 6 property-browser-compare-numerical) ("O" 3)])) ; overlay-marker (tabulated-list-mode) (setq tabulated-list-format table-format) (tabulated-list-init-header))) buffer)) ;;;; Interactive Commands (defun property-browser-display-at-point-buffer () "Display the properties-at-point buffer for the current buffer." (interactive) (let ((at-point-buffer property-browser-at-point-buffer)) (if (buffer-live-p at-point-buffer) (display-buffer at-point-buffer) (user-error "Property browser buffer dead or non-existant.")))) (defun list-properties-in-range (start end) "List properties in the current region." (interactive "r") (let* ((make-row (lambda (name d) (let* ((start (format "%s" (cdr (assq 'start d)))) (end (format "%s" (cdr (assq 'end d)))) (value (format "%s" (cdr (assq 'value d)))) (overlay (cdr (assq 'overlay d))) (overlay-mark (if (overlayp overlay) "x" ""))) (list nil (vector name value start end overlay-mark))))) (props (property-browser-property-values-in-range start end)) (rows (cl-loop for (prop-symbol . data) in props for prop-name =3D (symbol-name prop-symbol) nconc (cl-loop for d in data collect (funcall make-row prop-name d)))) (buffer (property-browser-get-list-in-range-buffer-create))) (with-current-buffer buffer (setq tabulated-list-entries rows) (tabulated-list-print)) (display-buffer buffer))) (define-minor-mode list-properties-at-point-minor-mode "List properties at point." :lighter " pbrowse" (cond (list-properties-at-point-minor-mode (property-browser-generate-new-at-point-buffer) (property-browser-display-at-point-buffer) (add-hook 'post-command-hook #'property-browser-post-command-hook-handler nil :local)) (t (when (buffer-live-p property-browser-at-point-buffer) (kill-buffer property-browser-at-point-buffer)) (setq-local property-browser-at-point-buffer nil) (remove-hook 'post-command-hook #'property-browser-post-command-hook-handler :local)))) ;;;; Footer (provide 'property-browser) ;;; property-browser.el ends here --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable > more of the x for which this is possibly an xy problem: as noted, i am > trying to copy visible text from buffer a to b. and i want buffer b > to have text properties that include markers to jump to a for specific > lines. kinda like magit jumps to files. I don=CA=BCt know how magit does it. Maybe it uses the line information provided in the diff hunks? You could read it=CA=BCs source. > and so, those text properties in buffer b would include, in b, the > markers in a. or... something. you can probably tell i am a little > lost here. but with such a funciton like yours, i would put the > markers in a, put the text properties redundantly in a, then copy over > using the function. the redundant text properties seems wrong > however. it seems efficient. What is in those text properties that creates the need to copy and duplicate them? > so idk. one alternative seems to be to go line by line through a, > insert markers in a, then create a string for each line with a text > property containing the marker, then insert that string into b, then > continue with the next matching visible line in a. > > which seems inefficient. idk if this is a common problem: copy parts > of buffer a to buffer b such that you can RET to get to a. I don=CA=BCt know either. If no one else chimes in and your search engine does not provide you with an answer, just start writing some code and playing around with your ideas to develop a better understanding of the problem and possible solutions. Don=CA=BCt start worrying to much about efficiency at the beginning unless your Emacs or your Computer grind to a halt. --=20 Felix Dietrich --=-=-=--