Głęboko skopiować ciąg znaków w Elisp?

9

Mam ciąg znaków. Chcę zrobić jego głęboką kopię, aby dodać więcej właściwości, zachowując właściwości w oryginalnym ciągu. Jak mogę to zrobić (łatwo)?

Przykład

Oceniaj jeden po drugim:

(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)

A wynik:

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
źródło
2
Zgłosiłbym to jako błąd w add-face-text-property. Nie powinien on destrukcyjnie modyfikować listy, ponieważ kończy się niepowodzeniem, gdy do tej listy odwołują się inni.
Lindydancer
1
OK, zgłosiłem błąd na debbugs.gnu.org/cgi/bugreport.cgi?bug=20153
abo-abo
Dziękujemy za zgłoszenie błędu. Szkoda, że ​​nikt jeszcze na to nie odpowiedział. Dobrze byłoby naprawić tę funkcję narzędziową (zakodowaną w C).
Drew

Odpowiedzi:

7

Możesz użyć tej funkcji, font-lock-append-text-propertyaby dodać właściwość text. Nie zmienia destrukcyjnie wartości.

Na przykład:

(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)))

Tutaj test-str-1zachował swoją pierwotną wartość.

Lindydancer
źródło
4

Odkryłem, że możesz to zrobić, iterując właściwości tekstu, kopiując dane właściwości podstawowej i zastępując istniejące właściwości nowymi kopiami.

(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)))

W moich testach było to około 20% szybciej niż twoje readrozwiązanie. Napisałem również wersję, która nie używała bufora tymczasowego i zmodyfikowałem właściwości łańcucha, który był mniejszy, ale wolniejszy.

Patrząc na kod C, kopiuje listy właściwości, z copy_sequence, która przebuduje strukturę listy, ale nie kopiuje elementów według wartości, więc właściwości takie jak face w twoim przykładzie, które mają wartość listy, są kopiowane przez odniesienie i modyfikowane. Błąd czy nie, nie wiem

Jordon Biondo
źródło
2

Możesz użyć (concat the-original-string).

Na przykład:

(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
źródło
1
Nie działa, dodam przykład.
abo-abo
1
Sztuką jest posiadanie zagnieżdżonej listy we właściwościach, tak jak ja. To concatnie działa.
abo-abo
@ abo-abo. Ok, teraz widzę. Nie zauważyłem tego w twoim dodanym przykładzie. W takim przypadku nie mam odpowiedzi, ale myślę, że taka potrzeba jest naprawdę potrzebna. (Jednym z potencjalnych problemów jest to, że nie można się dowiedzieć, czy nieznana właściwość mogłaby oczekiwać odniesienia do jakiegoś wspólnego obiektu.)
Lindydancer
1

Znaleziono obejście (niezbyt wydajne):

(setq test-str-2
      (read (prin1-to-string test-str-1)))
abo-abo
źródło
2
Obejście problemu kończy się niepowodzeniem, jeśli właściwości zawierają #postać.
abo-abo
masz na myśli, jeśli znak # jest częścią nazwy symbolu? Czy też oznaczają właściwości, które są buforami lub innymi danymi, których nie można wydrukować? Jeśli to pierwszy, powinieneś zgłosić błąd.
Malabarba
bufory we właściwościach
abo-abo