Created
April 19, 2026 19:33
-
-
Save lgmoneda/fccd341b587405e00e6bfa4b187a29bf to your computer and use it in GitHub Desktop.
Rich copy Markdown for Slack
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ;;; 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