summaryrefslogtreecommitdiff
path: root/modules/external-open.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-10-12 11:47:26 -0500
committerCraig Jennings <c@cjennings.net>2025-10-12 11:47:26 -0500
commit092304d9e0ccc37cc0ddaa9b136457e56a1cac20 (patch)
treeea81999b8442246c978b364dd90e8c752af50db5 /modules/external-open.el
changing repositories
Diffstat (limited to 'modules/external-open.el')
-rw-r--r--modules/external-open.el129
1 files changed, 129 insertions, 0 deletions
diff --git a/modules/external-open.el b/modules/external-open.el
new file mode 100644
index 00000000..0fe6be64
--- /dev/null
+++ b/modules/external-open.el
@@ -0,0 +1,129 @@
+;;; external-open.el --- Open Files Using Default OS Handler -*- lexical-binding: t; coding: utf-8; -*-
+;; author Craig Jennings <c@cjennings.net>
+;;
+;;; Commentary:
+;;
+;; This library provides a simple mechanism for opening files with specific
+;; extensions using your operating system’s default application rather than
+;; visiting them in an Emacs buffer. It offers:
+;;
+;; • A simple method to run a command on the current buffer's file
+;; "C-c x o" bound to cj/open-this-file-with
+;; • A customizable list =default-open-extensions= of file‐type suffixes
+;; (e.g. “pdf”, “docx”, “png”) that should be handled externally.
+;; • A function =default-open-file= (and its helper commands) which will
+;; launch the matching file in the OS’s default MIME handler.
+;; • Integration with =find-file-hook= so that any file whose extension
+;; appears in =default-open-extensions= is automatically opened externally
+;; upon visit.
+;; • Optional interactive commands for manually invoking an external open on
+;; point or on a user-chosen file.
+;;
+;;; Code:
+
+(require 'system-utils) ;; for xdg-open and others
+(require 'host-environment) ;; environment information functions
+(require 'cl-lib)
+
+(defgroup external-open nil
+ "Open certain files with the OS default handler."
+ :group 'files)
+
+(defcustom default-open-extensions
+ '(
+ ;; Video
+ "\\.3g2\\'" "\\.3gp\\'" "\\.asf\\'" "\\.avi\\'" "\\.divx\\'" "\\.dv\\'"
+ "\\.f4v\\'" "\\.flv\\'" "\\.m1v\\'" "\\.m2ts\\'" "\\.m2v\\'" "\\.m4v\\'"
+ "\\.mkv\\'" "\\.mov\\'" "\\.mpe\\'" "\\.mpeg\\'" "\\.mpg\\'" "\\.mp4\\'"
+ "\\.mts\\'" "\\.ogv\\'" "\\.rm\\'" "\\.rmvb\\'" "\\.ts\\'" "\\.vob\\'"
+ "\\.webm\\'" "\\.wmv\\'"
+
+ ;; Audio
+ "\\.aac\\'" "\\.ac3\\'" "\\.aif\\'" "\\.aifc\\'" "\\.aiff\\'"
+ "\\.alac\\'" "\\.amr\\'" "\\.ape\\'" "\\.caf\\'"
+ "\\.dff\\'" "\\.dsf\\'" "\\.flac\\'" "\\.m4a\\'" "\\.mka\\'"
+ "\\.mid\\'" "\\.midi\\'" "\\.mp2\\'" "\\.mp3\\'" "\\.oga\\'"
+ "\\.ogg\\'" "\\.opus\\'" "\\.ra\\'" "\\.spx\\'" "\\.wav\\'"
+ "\\.wave\\'" "\\.weba\\'" "\\.wma\\'"
+
+ ;; Microsoft Word
+ "\\.docx?\\'" "\\.docm\\'"
+ "\\.dotx?\\'" "\\.dotm\\'"
+ "\\.rtf\\'"
+
+ ;; Microsoft Excel
+ "\\.xlsx?\\'" "\\.xlsm\\'" "\\.xlsb\\'"
+ "\\.xltx?\\'" "\\.xltm\\'"
+
+ ;; Microsoft PowerPoint
+ "\\.pptx?\\'" "\\.pptm\\'"
+ "\\.ppsx?\\'" "\\.ppsm\\'"
+ "\\.potx?\\'" "\\.potm\\'"
+
+ ;; Microsoft OneNote / Visio / Project / Access / Publisher
+ "\\.one\\'" "\\.onepkg\\'" "\\.onetoc2\\'"
+ "\\.vsdx?\\'" "\\.vsdm\\'" "\\.vstx?\\'" "\\.vstm\\'" "\\.vssx?\\'" "\\.vssm\\'"
+ "\\.mpp\\'" "\\.mpt\\'"
+ "\\.mdb\\'" "\\.accdb\\'" "\\.accde\\'" "\\.accdr\\'" "\\.accdt\\'"
+ "\\.pub\\'"
+
+ ;; OpenDocument (LibreOffice/OpenOffice)
+ "\\.odt\\'" "\\.ott\\'"
+ "\\.ods\\'" "\\.ots\\'"
+ "\\.odp\\'" "\\.otp\\'"
+ "\\.odg\\'" "\\.otg\\'"
+ "\\.odm\\'" "\\.odf\\'"
+ ;; Flat OpenDocument variants
+ "\\.fodt\\'" "\\.fods\\'" "\\.fodp\\'"
+
+ ;; Apple iWork
+ "\\.pages\\'" "\\.numbers\\'" "\\.key\\'"
+
+ ;; Microsoft’s fixed-layout formats
+ "\\.xps\\'" "\\.oxps\\'"
+ )
+ "Regexps matching file extensions that should be opened externally."
+ :type '(repeat (regexp :tag "File extension regexp"))
+ :group 'external-open)
+
+;; ------------------------------- Open File With ------------------------------
+;; TASK: Add this to buffer custom functions
+
+(defun cj/open-this-file-with (command)
+ "Open this buffer's file with COMMAND, detached from Emacs."
+ (interactive "MOpen with program: ")
+ (unless buffer-file-name
+ (user-error "Current buffer is not visiting a file"))
+ (let ((file (expand-file-name buffer-file-name)))
+ (cond
+ ;; Windows: launch via ShellExecute so the child isn't tied to Emacs.
+ ((env-windows-p)
+ (w32-shell-execute "open" command (format "\"%s\"" file)))
+ ;; POSIX: disown with nohup + background. No child remains.
+ (t
+ (call-process-shell-command
+ (format "nohup %s %s >/dev/null 2>&1 &"
+ command (shell-quote-argument file))
+ nil 0)))))
+
+(global-set-key (kbd "C-c x o") #'cj/open-this-file-with)
+
+;; -------------------- Open Files With Default File Handler -------------------
+
+(defun cj/find-file-auto (orig-fun &rest args)
+ "If file has an extension in `default-open-extensions', open externally.
+Else call ORIG-FUN with ARGS."
+ (let* ((file (car args))
+ (case-fold-search t))
+ (if (and (stringp file)
+ (cl-some (lambda (re) (string-match-p re file))
+ default-open-extensions))
+ (cj/xdg-open file)
+ (apply orig-fun args))))
+
+;; Make advice idempotent if you reevaluate this form.
+(advice-remove 'find-file #'cj/find-file-auto)
+(advice-add 'find-file :around #'cj/find-file-auto)
+
+(provide 'external-open)
+;;; external-open.el ends here.