Skip to content

Instantly share code, notes, and snippets.

@lgmoneda
Created April 19, 2026 19:33
Show Gist options
  • Select an option

  • Save lgmoneda/fccd341b587405e00e6bfa4b187a29bf to your computer and use it in GitHub Desktop.

Select an option

Save lgmoneda/fccd341b587405e00e6bfa4b187a29bf to your computer and use it in GitHub Desktop.
Rich copy Markdown for Slack
;;; copy-markdown-paste-to-slack.el --- Rich copy Markdown for Slack -*- lexical-binding: t; -*-
(defun lmoneda/slack--copy-rich-and-plain-to-clipboard (plain-text rtf-text)
"Copy PLAIN-TEXT and RTF-TEXT to macOS clipboard as plain+rich flavors."
(unless (executable-find "osascript")
(user-error "osascript not found"))
(let* ((tmp-text-file (make-temp-file "markdown-slack-plain-" nil ".txt"))
(tmp-rtf-file (make-temp-file "markdown-slack-rich-" nil ".rtf"))
(jxa-script
"ObjC.import(\"Foundation\"); ObjC.import(\"AppKit\"); function run(argv){ const plainPath=argv[0]; const rtfPath=argv[1]; const enc=$.NSUTF8StringEncoding; const plain=$.NSString.stringWithContentsOfFileEncodingError($(plainPath), enc, null); const rtfStr=$.NSString.stringWithContentsOfFileEncodingError($(rtfPath), enc, null); const pb=$.NSPasteboard.generalPasteboard; pb.clearContents; const ok1=pb.setStringForType(plain, $.NSPasteboardTypeString); const rtfData=rtfStr.dataUsingEncoding(enc); const ok2=pb.setDataForType(rtfData, $.NSPasteboardTypeRTF); if (!(ok1 && ok2)) { throw new Error(\"pasteboard write failed\"); } return \"ok\"; }")
(exit-code nil))
(unwind-protect
(progn
(with-temp-file tmp-text-file
(insert plain-text))
(with-temp-file tmp-rtf-file
(insert rtf-text))
(setq exit-code
(call-process "osascript" nil nil nil
"-l" "JavaScript"
"-e" jxa-script
tmp-text-file tmp-rtf-file))
(unless (zerop exit-code)
(user-error "Setting clipboard rich/plain data failed")))
(when (file-exists-p tmp-text-file)
(delete-file tmp-text-file))
(when (file-exists-p tmp-rtf-file)
(delete-file tmp-rtf-file)))))
(defun lmoneda/markdown-region-copy-rich-for-slack (beg end)
"Convert selected Markdown region to rich text and copy it for Slack paste."
(interactive "r")
(unless (use-region-p)
(user-error "Select a markdown region first"))
(unless (executable-find "pandoc")
(user-error "pandoc not found"))
(let* ((selected-text (buffer-substring-no-properties beg end))
(rtf
(with-temp-buffer
(insert selected-text)
(let ((exit-code (call-process-region
(point-min) (point-max)
"pandoc" t t nil
"-f" "gfm" "-t" "rtf" "-s")))
(unless (zerop exit-code)
(user-error "pandoc conversion failed")))
(buffer-string))))
(lmoneda/slack--copy-rich-and-plain-to-clipboard selected-text rtf))
(deactivate-mark)
(message "Copied rich text to clipboard for Slack"))
(defun lmoneda/kill-ring-save-or-slack-rich (beg end &optional region)
"Copy normally with `M-w', or rich-copy Markdown with `C-u M-w'."
(interactive (list (mark) (point) 'region))
(if current-prefix-arg
(lmoneda/markdown-region-copy-rich-for-slack beg end)
(kill-ring-save beg end region)))
(defalias 'lmoneda/markdown-kill-ring-save-or-slack-rich
#'lmoneda/kill-ring-save-or-slack-rich)
(defalias 'markdown-rich-copy-region #'lmoneda/markdown-region-copy-rich-for-slack)
(defun lmoneda/agent-shell-buffer-p ()
"Return non-nil when current buffer appears to be an agent-shell buffer."
(let ((name (downcase (buffer-name))))
(or (derived-mode-p 'agent-shell-mode)
(string-match-p "\\*agent[- ]shell" name))))
(defun lmoneda/setup-agent-shell-slack-rich-copy ()
"Bind rich-copy shortcut locally when editing an agent-shell buffer."
(when (lmoneda/agent-shell-buffer-p)
(local-set-key (kbd "M-w") #'lmoneda/kill-ring-save-or-slack-rich)))
(defun lmoneda/setup-agent-shell-slack-rich-copy-for-existing-buffers ()
"Apply agent-shell rich-copy bindings to already-open buffers."
(dolist (buffer (buffer-list))
(with-current-buffer buffer
(lmoneda/setup-agent-shell-slack-rich-copy))))
(defun lmoneda/enable-slack-rich-copy-bindings ()
"Enable Markdown/agent-shell rich-copy keybindings.
- `M-w': normal copy
- `C-u M-w': rich copy for Slack
- `C-c C-S-w' in markdown: rich copy"
(interactive)
(with-eval-after-load 'markdown-mode
(define-key markdown-mode-map (kbd "M-w")
#'lmoneda/kill-ring-save-or-slack-rich)
(when (boundp 'gfm-mode-map)
(define-key gfm-mode-map (kbd "M-w")
#'lmoneda/kill-ring-save-or-slack-rich))
(define-key markdown-mode-map (kbd "C-c C-S-w")
#'lmoneda/markdown-region-copy-rich-for-slack))
(with-eval-after-load 'comint
(define-key comint-mode-map (kbd "M-w")
#'lmoneda/kill-ring-save-or-slack-rich))
(add-hook 'after-change-major-mode-hook #'lmoneda/setup-agent-shell-slack-rich-copy)
(lmoneda/setup-agent-shell-slack-rich-copy-for-existing-buffers))
(lmoneda/enable-slack-rich-copy-bindings)
(provide 'copy-markdown-paste-to-slack)
;;; copy-markdown-paste-to-slack.el ends here
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment