Copiez en profondeur une chaîne dans Elisp?

9

J'ai une chaîne propertized. Je veux en faire une copie complète pour ajouter plus de propriétés, tout en préservant les propriétés dans la chaîne d'origine. Comment puis-je faire cela (facilement)?

Exemple

Évaluez un par un:

(setq test-str-1
      #(";; This `is' a test"
        0 3 (fontified nil face font-lock-comment-delimiter-face)
        3 9 (fontified nil face font-lock-comment-face)
        9 11 (fontified nil face (font-lock-constant-face font-lock-comment-face))
        11 19 (fontified nil face font-lock-comment-face)))
(setq test-str-2 (concat test-str-1))
(add-face-text-property 0 (length test-str-2) 'foobar t test-str-2)

Et le résultat:

test-str-2
;; =>
#(";; This `is' a test" 0 3 (fontified nil face (font-lock-comment-delimiter-face foobar))
  3 9 (fontified nil face (font-lock-comment-face foobar))
  9 11 (fontified nil face (font-lock-constant-face font-lock-comment-face foobar))
  11 19 (fontified nil face (font-lock-comment-face foobar)))
test-str-1
;; =>
#(";; This `is' a test" 0 3 (face font-lock-comment-delimiter-face fontified nil)
  3 9 (face font-lock-comment-face fontified nil)
  9 11 (face (font-lock-constant-face font-lock-comment-face foobar) ; <= foobar is here
        fontified nil)
  11 19 (face font-lock-comment-face fontified nil))
abo-abo
la source
2
Je signalerais cela comme un bug dans add-face-text-property. Il ne doit pas modifier de manière destructive la liste, car il échoue lorsque cette liste est référencée par d'autres.
Lindydancer
1
OK, a signalé le bogue sur debbugs.gnu.org/cgi/bugreport.cgi?bug=20153
abo-abo
Merci d'avoir signalé le bogue. Dommage que personne n'y ait encore répondu. Il serait bon de corriger cette fonction utilitaire (codée en C).
Drew

Réponses:

7

Vous pouvez utiliser la fonction font-lock-append-text-propertypour ajouter la propriété text. Il ne modifie pas la valeur de manière destructive.

Par exemple:

(setq test-str-1
      #(";; This `is' a test"
        0 3 (fontified nil face font-lock-comment-delimiter-face)
        3 9 (fontified nil face font-lock-comment-face)
        9 11 (fontified nil face (font-lock-constant-face font-lock-comment-face))
        11 19 (fontified nil face font-lock-comment-face)))
(setq test-str-2 (concat test-str-1))
(font-lock-append-text-property 0 (length test-str-2) 'face '(foobar t) test-str-2)


test-str-1
#(";; This `is' a test"
  0 3 (face font-lock-comment-delimiter-face fontified nil)
  3 9 (face font-lock-comment-face fontified nil)
  9 11 (face (font-lock-constant-face font-lock-comment-face) fontified nil)
  11 19 (face font-lock-comment-face fontified nil))

test-str-2
#(";; This `is' a test"
  0 3 (fontified nil face (font-lock-comment-delimiter-face foobar t))
  3 9 (fontified nil face (font-lock-comment-face foobar t))
  9 11 (fontified nil face (font-lock-constant-face font-lock-comment-face foobar t))
  11 19 (fontified nil face (font-lock-comment-face foobar t)))

Ici, en test-str-1, a conservé sa valeur d'origine.

Lindydancer
la source
4

J'ai trouvé que vous pouvez le faire en itérant sur les propriétés du texte, en copiant les données de propriété sous-jacentes et en remplaçant les propriétés existantes par de nouvelles copies.

(defun deep-copy-text-properties (str)
  (with-temp-buffer
    (insert str)
    (goto-char 1)
    (while (not (eobp))
      (set-text-properties (point)
                           (goto-char (next-char-property-change (point) (point-max)))
                           ;; copy-tree is the important part
                           (copy-tree (text-properties-at (1- (point))))))
    (buffer-string)))

Dans mes tests, c'était environ 20% plus rapide que votre readsolution. J'ai également écrit une version qui n'utilisait pas de tampon temporaire et j'ai modifié les propriétés d'une chaîne qui était moins de code mais plus lente.

En regardant le code C, il copie les listes de propriétés, avec copy_sequence qui reconstruira la structure de la liste mais ne copiera pas les éléments par valeur, de sorte que les propriétés comme face dans votre exemple qui ont une valeur de liste sont copiées par référence et modifiées. Bug ou pas, je ne sais pas

Jordon Biondo
la source
2

Vous pouvez utiliser (concat the-original-string).

Par exemple:

(let ((s "TEXT"))
  (set-text-properties 2 3 '(:foreground "blue") s)
  (let ((q (concat s)))
    (add-text-properties 2 3 '(:background "red") q)
    (cons s q)))
;; Returns:
(#("TEXT" 2 3 (:foreground "blue")) . #("TEXT" 2 3 (:foreground "blue" :background "red")))
Lindydancer
la source
1
Ça ne marche pas, je vais ajouter un exemple.
abo-abo
1
L'astuce consiste à avoir une liste imbriquée dans les propriétés, comme je le fais. Alors concatça ne marche pas.
abo-abo
@ abo-abo. Ok, maintenant je vois. Je n'ai pas remarqué cela dans votre exemple supplémentaire. Dans ce cas, je n'ai pas de réponse, mais je pense qu'il y a un réel besoin pour une telle fonction. (Un problème potentiel est qu'il est impossible de savoir si une propriété inconnue peut s'attendre à se référer à un objet partagé d'une certaine sorte.)
Lindydancer
1

Trouvé une solution de contournement (pas très efficace):

(setq test-str-2
      (read (prin1-to-string test-str-1)))
abo-abo
la source
2
La solution de contournement échoue si les propriétés contiennent le #caractère.
abo-abo
voulez-vous dire si le caractère # fait partie du nom du symbole? Ou signifie-t-il des propriétés qui sont des tampons ou d'autres données non imprimables? Si c'est le premier, vous devez signaler un bogue.
Malabarba
tampons dans les propriétés
abo-abo