1 ;;; org-extra-link-types.el --- Add extra link types to Org Mode -*- lexical-binding: t; -*-
3 ;;; Time-stamp: <2020-08-13 19:50:23 stardiviner>
5 ;; Authors: stardiviner <numbchild@gmail.com>
6 ;; Package-Requires: ((emacs "25.1") (cl-lib "1.0") (olc "1.4.1"))
7 ;; Package-Version: 0.1
9 ;; homepage: https://repo.or.cz/org-extra-link-types.el.git
11 ;; org-extra-link-types is free software; you can redistribute it and/or modify it
12 ;; under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 3, or (at your option)
16 ;; org-extra-link-types is distributed in the hope that it will be useful, but WITHOUT
17 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
18 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
19 ;; License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
35 (defgroup org-extra-link-types nil
36 "The customize group of package org-extra-link-types."
37 :prefix
"org-extra-link-types-"
40 ;;===============================================================================
43 ;; e.g. telnet://ptt.cc
44 (org-link-set-parameters "telnet" :follow
#'telnet
)
46 ;;===============================================================================
49 ;;; e.g. chrome:chrome://whats-new/
54 ((executable-find "google-chrome")
55 (org-link-set-parameters "chrome" :follow
#'browse-url-chrome
))
56 ((executable-find "chromium")
57 (org-link-set-parameters "chrome" :follow
#'browse-url-chromium
))))
60 ((file-exists-p "/Applications/Google Chrome.app")
61 (org-link-set-parameters "chrome" :follow
#'browse-url-chrome
))
62 ((file-exists-p "/Applications/Chromium.app")
63 (org-link-set-parameters "chrome" :follow
#'browse-url-chromium
))))
66 ;; https://stackoverflow.com/questions/40674914/google-chrome-path-in-windows-10
69 ;;===============================================================================
72 (defun org-rss-link-open (uri)
73 "Open rss:// URI link."
76 (org-link-set-parameters "rss" :follow
#'org-rss-link-open
)
78 ;;===============================================================================
81 ;; e.g. [[tag:work+phonenumber-boss][Optional Description]]
83 (defun org-tag-link-open (tag)
84 "Display a list of TODO headlines with tag TAG.
85 With prefix argument, also display headlines without a TODO keyword."
86 (org-tags-view (null current-prefix-arg
) tag
))
88 (org-link-set-parameters "tag" :follow
#'org-tag-link-open
)
90 ;;===============================================================================
93 ;;; `track:' for OSM Maps
94 ;; [[track:((9.707032442092896%2052.37033874553582)(9.711474180221558%2052.375238282987))data/images/org-osm-link.svg][Open this link will generate svg, png image for track link on map]]
96 (if (featurep 'org-osm-link
)
97 (require 'org-osm-link
))
99 ;;===============================================================================
102 ;; [geo:37.786971,-122.399677;u=35]
103 ;; [[geo:58.397813,15.576063]]
104 ;; [[geo:9FCQ9HXG+4CG]]
106 ;;; Open Location Code library `olc'
107 (autoload 'olc-encode
"olc")
108 (autoload 'olc-decode
"olc")
110 (defcustom org-geo-link-application-command
"gnome-maps"
111 "Specify the program name for openning geo: link."
113 :group
'org-extra-link-types
)
115 (defun org-geo-link-open (link)
116 "Open Geography location `URI' like \"geo:25.5889136,100.2208514\" in Map application."
118 (let ((location (cond
119 ;; (string-match-p "\\,.*" "25.5889136,100.2208514")
120 ((string-match-p "\\,.*" link
)
122 ;; (string-match-p "\\+.*" "9FCQ9HXG+4CG")
123 ((string-match-p "\\+.*" link
)
124 (when (featurep 'olc
)
126 (olc-area-lat (olc-decode link
))
127 (olc-area-lon (olc-decode link
)))))
128 (t (user-error "Your link is not Geo location or Open Location Code!")))))
129 (if (executable-find org-geo-link-application-command
)
132 "*org-geo-link-open*"
133 org-geo-link-application-command
134 (shell-quote-wildcard-pattern location
))
135 (browse-url location
)))
136 (user-error "Emacs package 'olc' is not installed, please install it.")))
138 (org-link-set-parameters "geo" :follow
#'org-geo-link-open
)
140 ;;===============================================================================
143 ;;; e.g. [[video:/path/to/file.mp4::00:13:20]]
145 (defcustom org-video-link-open-command
147 ((executable-find "mpv") "mpv")
148 ((executable-find "mplayer") "mplayer")
149 ((executable-find "iina") "iina"))
150 "Specify the program for openning video: link."
152 :group
'org-extra-link-types
)
154 (defvar org-video-link-extension-list
'("avi" "rmvb" "ogg" "mp4" "mkv"))
156 (defun org-video-link-open (uri)
157 "Open video file `URI' with video player."
158 (let* ((list (split-string uri
"::"))
160 (start-timstamp (cadr list
)))
161 (pcase org-video-link-open-command
164 :command
(list "mpv" (concat "--start=" start-timstamp
)
165 (expand-file-name (org-link-unescape path
)))
166 :name
"org-video-link"
167 :buffer
" *org-video-link*"))
170 :command
(list "mplayer" "-ss" start-timstamp
171 (expand-file-name (org-link-unescape path
)))
172 :name
"org-video-link"
173 :buffer
" *org-video-link*"))
176 :command
(list "iina" (concat "--mpv-start=" start-timstamp
)
177 (expand-file-name (org-link-unescape path
)))
178 :name
"org-video-link"
179 :buffer
" *org-video-link*")))))
181 (defun org-video-complete-link (&optional arg
)
182 "Create a video link using completion."
183 (let ((file (read-file-name "Video: " nil nil nil nil
186 org-video-link-extension-list
187 (file-name-extension file
)))))
188 (pwd (file-name-as-directory (expand-file-name ".")))
189 (pwd1 (file-name-as-directory (abbreviate-file-name
190 (expand-file-name ".")))))
191 (cond ((equal arg
'(16))
193 (abbreviate-file-name (expand-file-name file
))))
195 (concat "^" (regexp-quote pwd1
) "\\(.+\\)") file
)
196 (concat "video:" (match-string 1 file
)))
198 (concat "^" (regexp-quote pwd
) "\\(.+\\)")
199 (expand-file-name file
))
201 (match-string 1 (expand-file-name file
))))
202 (t (concat "video:" file
)))))
204 (org-link-set-parameters "video"
205 :follow
#'org-video-link-open
206 :complete
#'org-video-complete-link
)
208 ;;===================================================================================================
209 ;;; [ web-browser: ] -- open Org link with web browser.
211 (defun org-web-browser--insert-description (link description
)
212 (string-trim-left link
"web-browser:"))
214 (defun org-web-browser--complete-link ()
215 "Create a file link using completion."
216 (let ((file (read-file-name "Local HTML File: ")))
217 (concat "web-browser:" file
)))
219 (defun org-web-browser--store-link ()
220 "Store a link from a local HTML file."
221 (when (member major-mode
'(html-mode html-ts-mode html-erb-mode web-mode
))
222 (let* ((link (buffer-file-name (current-buffer)))
223 (description (file-name-nondirectory link
)))
224 (org-link-store-props
227 :description description
))))
229 (defun org-web-browser--open-link (path _
)
230 "Open local HTML file with web browser through function `browse-url'."
233 (defun org-web-browser--export-link (link description backend
)
234 "Export a local HTML file LINK with DESCRIPTION.
235 BACKEND is the current export backend."
236 (org-export-file-uri link
))
238 (org-link-set-parameters
240 :insert-description
#'org-web-browser--insert-description
241 :complete
#'org-web-browser--complete-link
242 :store
#'org-web-browser--store-link
243 :follow
#'org-web-browser--open-link
244 :export
#'org-web-browser--export-link
)
246 ;;===================================================================================================
247 ;;; [ javascript: / js: ] -- Org link for executing simple JavaScript code.
249 ;;; e.g. [[javascript:console.log("hello, world");]]
251 (defun org-javascript--insert-description (link description
)
252 (string-trim-left link
"javascript:"))
254 (defun org-javascript--enable-code-completion ()
255 "Enable Corfu in the minibuffer if `completion-at-point' is bound."
256 (setq-local corfu-auto t
)
257 (setq-local corfu-auto-prefix
1)
258 (setq-local completion-at-point-functions
261 (cape-company-to-capf 'company-yasnippet
)
263 #'cape-abbrev
#'cape-dabbrev
))
266 (defmacro org-javascript--enable-code-completion-wrapper
(&rest body
)
267 "Use a wrapper macro to add & clean `string-edit-mode-hook' without affecting global environment."
269 (add-hook 'string-edit-mode-hook
#'org-javascript--enable-code-completion
)
271 (remove-hook 'string-edit-mode-hook
#'org-javascript--enable-code-completion
)
272 ;; The `link' is the JS code as variable of eval result pass to `org-insert-link'.
275 (defun org-javascript--complete-code (&optional _arg
)
276 "Complete JavaScript code in javascript: link."
277 (org-javascript--enable-code-completion-wrapper
278 (substring-no-properties
279 (read-string-from-buffer ; wrapper of `string-edit' with `callback'.
280 "Write Org javascript: link code here: "
281 "console.log(\"hello, world\");"))))
283 (defun org-javascript--store-link ()
284 "Store link from JavaScript code."
285 (when (member major-mode
'(js-mode js2-mode javascript-mode
))
286 (unless (or (region-active-p) (use-region-p))
288 (let* ((org-store-props-function
289 (if (fboundp 'org-link-store-props
)
290 'org-link-store-props
291 'org-store-link-props
))
292 (code (replace-regexp-in-string
293 "\n" " " ; replace newline with space to avoid multiple line code issue in link.
294 (buffer-substring-no-properties (region-beginning) (region-end))))
295 (link (format "javascript:%s" code
))
296 (description (concat "JavaScript code: " (s-truncate (- (window-width) 40) code
))))
298 (funcall org-store-props-function
301 :description description
))))
303 (defun org-javascript--execute-code (code &optional _
)
304 "Execute the JavaScript CODE in javascript: link."
308 (indium-eval code
(lambda (result) result
)))
309 ((featurep 'skewer-mode
)
311 (skewer-eval code
(lambda (result) result
)))
312 ((featurep 'js-comint
)
313 (js-comint-start-or-switch-to-repl)
314 (js-comint-send-string code
))))
316 (defun org-javascript--export-code (link description backend
)
317 "Export a javascript: / js: LINK code with DESCRIPTION.
318 BACKEND is the current export backend."
319 ;; Evaluate javascript code in link, display eval result output in exporting.
320 (org-javascript--execute-code link
))
322 (org-link-set-parameters
324 :insert-description
#'org-javascript--insert-description
325 :complete
#'org-javascript--complete-code
326 :store
#'org-javascript--store-link
327 :follow
#'org-javascript--execute-code
328 :export
#'org-javascript--export-code
)
330 (org-link-set-parameters
332 :insert-description
#'org-javascript--insert-description
333 :complete
#'org-javascript--complete-code
334 :store
#'org-javascript--store-link
335 :follow
#'org-javascript--execute-code
336 :export
#'org-javascript--export-code
)
338 ;;===================================================================================================
339 ;;; Add "vscode:" link type to open link in Visual Studio Code.
341 (defun org-vscode-open-path (path &optional _
)
342 "Open link path with Visual Studio Code."
346 "code" (expand-file-name path
))
347 (message "%s Visual Studio Code opened Org linked project -> \"%s\""
348 (nerd-icons-mdicon "nf-md-microsoft_visual_studio_code" :face
'nerd-icons-blue-alt
)
351 (defun org-vscode-complete-path (&optional _arg
)
352 "Complete path for vscode: link."
353 (let ((path (expand-file-name
354 (substring-no-properties
355 (read-file-name "Visual Studio Code link 'vscode:' path: ")))))
356 (concat "vscode:" path
)))
358 (org-link-set-parameters
360 :complete
#'org-vscode-complete-path
361 :follow
#'org-vscode-open-path
)
365 (provide 'org-extra-link-types
)
367 ;;; org-extra-link-types.el ends here