xenodium / chatgpt-shell

A multi-llm Emacs shell (ChatGPT, Claude, Gemini) + editing integrations
https://xenodium.com
GNU General Public License v3.0
855 stars 76 forks source link

Keep text properties in shell buffer #231

Open chep opened 2 months ago

chep commented 2 months ago

I'm using shell-maker for github copilot. I would like to have markdown management in my shell. I tried polymode but as it create buffers that I can't manage, I had weird bugs.

Now I write copilot answer in a temporary buffer using markdown mode, then copy text with properties and send it to shell-maker callback.

When the text is inserted in the buffer, all properties are lost.

Is it possible to keep properties ? I'm trying to look at the code but I haven't find anything yet.

Thanks.

xenodium commented 2 months ago

If I remember correctly, shell-maker isn't overriding it. It's possibly comint (which shell-maker delegates to) or font lock. I vaguely remember using overlays as a workaround. Would be great to get to the bottom of it if you're keen to investigate. It'd be awesome if we could patch from shell-maker. Pull requests super welcome.

chep commented 2 months ago

Font lock seems to be the problem. 'face property is removed when inserting data in the buffer. I did a crappy workaround:

(defun copilot-chat--shell-maker-font-lock-faces ()
  "Replace faces by font-lock-faces."
  (with-current-buffer copilot-chat--shell-maker-temp-buffer
    (let ((inhibit-read-only t))
      (font-lock-ensure)
      (goto-char (point-min))
      (while (< (point) (point-max))
        (let ((next-change (or (next-property-change (point) nil (point-max)) (point-max)))
               (face (get-text-property (point) 'face)))
          (when face
            (font-lock-append-text-property (point) next-change 'font-lock-face face))
          (goto-char next-change))))))

(defun copilot-chat--shell-maker-copy-faces()
  "Apply faces to the copilot chat buffer."
  (with-current-buffer copilot-chat--shell-maker-temp-buffer
    (save-restriction)
      (widen)
      (font-lock-ensure)
      (copilot-chat--shell-maker-font-lock-faces)
      (let ((content (buffer-substring (point-min) (point-max))))
        (with-current-buffer copilot-chat--buffer
          (goto-char (1+ copilot-chat--shell-maker-answer-point))
          (insert content)
          (delete-region (point) (+ (point) (length content)))
          (goto-char (point-max)))))))

In the callback, I send data as is so the user can see the answer coming. I also copy it in a temp buffer. At the end I replace all faces by font-lock-faces in my temp buffer, then I add it before the answer written by the callback. Finally I remove the deprecated answer. (sorry I'm not sure to be clear.)