summaryrefslogtreecommitdiff
path: root/modules/external-open.el
blob: 41d842fbcae78430f1c4851f072f5f71e8097fcc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
;;; 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)

;; Declare platform-specific functions
(declare-function w32-shell-execute "w32fns.c" (operation document &optional parameters show-flag))

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

(keymap-global-set "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.