blob: 22e56a2902357171113bfa741e9ccdc370486750 (
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
;;; external-open.el --- Open Files Using Default OS Handler -*- lexical-binding: t; coding: utf-8; -*-
;; author Craig Jennings <c@cjennings.net>
;;
;;; Commentary:
;;
;; Layer: 2 (Core UX).
;; Category: L/D.
;; Load shape: eager.
;; Eager reason: command library with no side effects; eager only by init order.
;; A deferral candidate (autoload commands) for Phase 4.
;; Top-level side effects: none.
;; Runtime requires: host-environment, system-lib, external-open-lib, cl-lib.
;; Direct test load: yes (pure command helpers).
;;
;; 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 'host-environment) ;; environment information functions
(require 'system-lib) ;; for cj/file-from-context
(require 'external-open-lib) ;; pure dispatch helpers
(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\\'" "\\.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)
(defun cj/xdg-open (&optional filename)
"Open FILENAME (or the file at point) with the OS default handler.
Logs output and exit code to buffer *external-open.log*."
(interactive)
(let* ((file (expand-file-name
(or (cj/file-from-context filename)
(user-error "No file associated with this buffer"))))
(cmd (or (cj/external-open-command)
(user-error "External-open: unsupported host environment")))
(logbuf (get-buffer-create "*external-open.log*")))
(with-current-buffer logbuf
(goto-char (point-max))
(insert (format-time-string "[%Y-%m-%d %H:%M:%S] "))
(insert (format "Opening: %s\n" file)))
(cond
((env-windows-p)
(w32-shell-execute "open" file))
(t
(call-process cmd nil 0 nil file)
(with-current-buffer logbuf
(insert " → Launched asynchronously\n"))))
nil))
;; ------------------------------- Open File With ------------------------------
(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)))))
;; -------------------- 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))))
(defun cj/external-open-install-advice ()
"Install the `cj/find-file-auto' advice on `find-file'.
Idempotent: re-running removes any prior copy of the advice before
adding it back, so re-evaluating the module updates the advice rather
than stacking it."
(advice-remove 'find-file #'cj/find-file-auto)
(advice-add 'find-file :around #'cj/find-file-auto))
(cj/external-open-install-advice)
(provide 'external-open)
;;; external-open.el ends here.
|