├── README.org
├── orly-code.el
└── orly.el
/README.org:
--------------------------------------------------------------------------------
1 | * Introduction
2 | Orly adds more Org-mode link types *and* completion for them.
3 |
4 | ** Examples of link types
5 | You can make use of Elisp links, e.g. el:org-capture. Clicking on the link will open the
6 | definition of the corresponding function/variable. This is useful when writing wikis /
7 | documentation, because you no longer have to quote a part of the docstring: instead, the
8 | full docstring is just a click away.
9 |
10 | man:ls is a similarly useful link type.
11 |
12 | ** Howto use completion
13 | After the link type is entered, press ~C-M-i~ (el:complete-symbol) to complete further.
14 |
15 | To enable completion:
16 | #+begin_src elisp
17 | (add-hook 'org-mode-hook 'orly-setup-completion)
18 | #+end_src
19 |
20 | ** Links in Org export
21 | We can setup the links to be clickable in e.g. HTML export.
22 | For instance, man:ls can point to https://linux.die.net/man/.
23 |
24 | And el:forward-char links can point to https://git.savannah.gnu.org/cgit/emacs.git/tree/
25 | or https://github.com/emacs-mirror/emacs/.
26 |
27 | ** Using code: link type
28 | #+begin_src elisp
29 | (require 'orly-code)
30 | (setq orly-repos-file "repos.org")
31 | #+end_src
32 | In =repos.org= you can write:
33 | #+begin_src org
34 | file:~/.cook.d/
35 | file:~/git/site-lisp/
36 | ...
37 | #+end_src
38 | You can fill the file with comments and other code. We only care about the lines that
39 | start with "file:".
40 |
41 | Now you get completion for each step of e.g. code:site-lisp/modes/ora-org.el#26/9f8503a.
42 | Let's break it down:
43 | - code: completes all repositories registered in el:orly-repos-file
44 | - code:site-lisp/ completes all code paths in the =site-lisp= repo
45 | - code:site-lisp/modes/ora-org.el#26 means line 26 of the corresponding file
46 | - code:site-lisp/modes/ora-org.el#26/ allows us to complete for git revision
47 | - code:site-lisp/modes/ora-org.el#26/9f8503a is the final version at git revision
48 |
49 | Advantages of this approach compared to file: links:
50 | - You can move your repo around without breaking things
51 | - You can access the link on different machines with el:orly-repos-file setup
52 | - You can reference a permalink version of the code, so that when the code is updated, the
53 | old link doesn't point to nowhere or somewhere wrong.
54 |
55 | There are also other repo link types:
56 | - code:monorepo:07b40a74bf references a commit. It will open a el:magit-diff-mode buffer
57 | when clicked
58 | - code:monorepo#16909 references a PR. It will open a el:forge-topic-mode buffer when
59 | clicked
60 |
--------------------------------------------------------------------------------
/orly-code.el:
--------------------------------------------------------------------------------
1 | ;;; orly-links.el --- Code links for Orly -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2021 Oleh Krehel
4 |
5 | ;; Author: Oleh Krehel
6 |
7 | ;; This program is free software; you can redistribute it and/or modify
8 | ;; it under the terms of the GNU General Public License as published by
9 | ;; the Free Software Foundation, either version 3 of the License, or
10 | ;; (at your option) any later version.
11 |
12 | ;; This program is distributed in the hope that it will be useful,
13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | ;; GNU General Public License for more details.
16 |
17 | ;; You should have received a copy of the GNU General Public License
18 | ;; along with this program. If not, see .
19 |
20 | ;;; Commentary:
21 |
22 | ;; This adds links like this to `org-mode':
23 | ;; - code:orly/orly.el:orly-setup-links/a309064
24 | ;; - code:orly/orly.el#54/a309064
25 | ;;
26 | ;; Advantages over the existing [file:...] link:
27 | ;; * Repositores can be moved around without changing the link
28 | ;; * More readable links: use functions instead of line numbers
29 | ;; * Visit the link on remote if needed
30 | ;; * Completion for each part of the link
31 |
32 | ;;; Code:
33 | (defvar orly-repos-file nil
34 | "An `org-mode' file name to be processed by `orly-read-repos-from-file'.
35 | Each repo should be a \"file:~/foo/bar\".
36 | All lines that don't match this will be ignored.")
37 |
38 | (defun orly-read-repos-from-file (org-fname)
39 | "Read repos alist from ORG-FNAME."
40 | (with-current-buffer (find-file-noselect org-fname)
41 | (let ((res nil))
42 | (save-excursion
43 | (goto-char (point-min))
44 | (while (re-search-forward "^file:\\(.*\\)$" nil t)
45 | (let ((fname (match-string-no-properties 1)))
46 | (push (list
47 | (file-name-nondirectory (directory-file-name fname))
48 | fname)
49 | res))))
50 | (nreverse res))))
51 |
52 | (defun orly-read-repos-from-attachments ()
53 | "Read Git repos from the current file's attachments."
54 | (let ((fs (counsel-org-files))
55 | res)
56 | (dolist (f fs)
57 | (when (and (file-directory-p f)
58 | (file-exists-p (expand-file-name ".git" f)))
59 | (push (list (file-name-nondirectory f) (expand-file-name f))
60 | res)))
61 | res))
62 |
63 | (defun orly-repos ()
64 | "Return an alist of repository names to repository locations."
65 | (append
66 | (orly-read-repos-from-file orly-repos-file)
67 | (orly-read-repos-from-attachments)))
68 |
69 | (defun orly-find-loc (loc)
70 | "Find location LOC.
71 | LOC is a line number if it starts with #.
72 | LOC is a function name if starts with :."
73 | (if (string-match "\\`#\\([0-9]+\\)\\'" loc)
74 | (goto-line (string-to-number (match-string 1 loc)))
75 | (orly-find-function (substring loc 1))))
76 |
77 | (defvar orly--last-window-configuration nil)
78 |
79 | (defun orly--ediff-restore-windows ()
80 | (set-window-configuration orly--last-window-configuration)
81 | (remove-hook 'ediff-after-quit-hook-internal 'orly--ediff-restore-windows))
82 |
83 | (defun orly-open-code-link (code-link)
84 | "Open CODE-LINK.
85 | CODE-LINK is REPO/FNAME:FUN/REV.
86 | The last 2 parts are optional."
87 | (save-some-buffers t)
88 | (let ((open-fn (cdr (assoc 'file org-link-frame-setup))))
89 | (cond ((string-match "\\`\\([^/]+\\)/\\([^:#]+\\)?\\([:#].*\\)?\\'" code-link)
90 | (let* ((repo (match-string 1 code-link))
91 | (path (match-string 2 code-link))
92 | (rest (match-string 3 code-link))
93 | (local-repo (assoc repo (orly-repos)))
94 | (fname (if local-repo
95 | (expand-file-name (or path "") (nth 1 local-repo))
96 | (error "Could not find repo: %s" repo))))
97 | (if rest
98 | (let* ((parts (split-string rest "/"))
99 | (loc (nth 0 parts)))
100 | (cl-case (length parts)
101 | (1
102 | (funcall open-fn fname)
103 | (orly-find-loc loc))
104 | (2
105 | (let ((rev (nth 1 parts)))
106 | ;; TODO: maybe modify `kill-buffer-hook' to delete this file later
107 | (switch-to-buffer (vc-find-revision fname rev 'git))
108 | (orly-find-loc loc)))
109 | (3
110 | (cond ((string= (nth 2 parts) "diff")
111 | (let ((rev (nth 1 parts)))
112 | (setq orly--last-window-configuration (current-window-configuration))
113 | (add-hook 'ediff-after-quit-hook-internal 'orly--ediff-restore-windows)
114 | (vc-version-ediff
115 | (list fname)
116 | (concat rev "~1")
117 | rev)))
118 | ;; TODO: finish
119 | ((string= (nth 2 parts) "cook")
120 | (let ((default-directory (nth 1 local-repo)))
121 | (cook-book fname (substring loc 1))))
122 | (t
123 | (error "Unexpected op: %s" (nth 2 parts)))))))
124 | (funcall open-fn fname))))
125 | ((string-match "\\`\\([^:]+\\):\\(.*\\)\\'" code-link)
126 | (let* ((repo (match-string 1 code-link))
127 | (rev (match-string 2 code-link))
128 | (local-repo (assoc repo (orly-repos)))
129 | (default-directory (nth 1 local-repo)))
130 | (magit-diff-range (concat rev "^.." rev))))
131 | ((string-match "\\`\\([^:]+\\)#\\(.*\\)\\'" code-link)
132 | (let* ((repo (match-string 1 code-link))
133 | (pr-id (match-string 2 code-link))
134 | (local-repo (assoc repo (orly-repos)))
135 | (default-directory (nth 1 local-repo)))
136 | (forge-visit (forge-get-pullreq (string-to-number pr-id)))))
137 | (t
138 | (error "Failed to parse link: '%s'" code-link)))))
139 |
140 | (cl-defgeneric orly-find-function (fun)
141 | "Find FUN in the current file."
142 | (goto-char (point-min))
143 | (search-forward fun nil t))
144 |
145 | (cl-defmethod orly-find-function (fun &context (major-mode (eql python-mode)))
146 | "Find FUN in the current file.
147 | Specialized for MAJOR-MODE `python-mode'."
148 | (goto-char (point-min))
149 | (search-forward (concat "def " fun))
150 | (require 'lpy)
151 | (lpy-back-to-special))
152 |
153 | (defun orly--complete-commits (repo-path rev)
154 | "List completions for commits in REPO-PATH.
155 | REV is passed to `all-completions'."
156 | (let* ((default-directory repo-path)
157 | (commits (nreverse
158 | (split-string
159 | (shell-command-to-string
160 | "git log -5 --pretty=format:'%h|%s'")
161 | "\n" t)))
162 | (cl nil))
163 | (dolist (commit commits)
164 | (push (split-string commit "|") cl))
165 | (list (- (point) (length rev))
166 | (point)
167 | (all-completions rev cl)
168 | :annotation-function (lambda (s) (concat " " (cadr (assoc s cl)))))))
169 |
170 | (defun orly--complete-tags (fname tag)
171 | "Complete tags in FNAME.
172 | TAG is passed to `all-completions'."
173 | (let ((tags
174 | (if (string= (file-name-nondirectory fname) "Cookbook.py")
175 | (let ((default-directory (file-name-directory fname)))
176 | (split-string
177 | (shell-command-to-string "cook --list") "\n" t " "))
178 | (with-current-buffer (find-file-noselect fname)
179 | (mapcar #'car
180 | (cl-remove-if-not
181 | (lambda (tag) (eq (semantic-tag-class tag) 'function))
182 | (counsel-semantic-tags)))))))
183 | (list (- (point) (length tag))
184 | (point)
185 | (all-completions tag tags))))
186 |
187 | (defun orly--complete-lines (fname line)
188 | (let ((lines nil)
189 | (i 1))
190 | (with-current-buffer (find-file-noselect fname)
191 | (goto-char (point-min))
192 | (while (not (eobp))
193 | (push (format "%d %s" i (buffer-substring-no-properties
194 | (line-beginning-position) (line-end-position)))
195 | lines)
196 | (cl-incf i)
197 | (forward-line 1)))
198 | (setq lines (nreverse lines))
199 | (list (- (point) (length line))
200 | (point)
201 | (cl-remove-if-not (lambda (s) (string-match-p line s)) lines))))
202 |
203 | (defun orly-completion-code ()
204 | "Completion for code: links in `org-mode'."
205 | (when (looking-back "code:\\([-:~A-Za-z0-9./_#]*\\)" (line-beginning-position))
206 | (let ((link (match-string-no-properties 1))
207 | (mb (match-beginning 1))
208 | (repos (orly-repos))
209 | rev-part tag-part)
210 | (if (string-match "\\`\\([^/]+\\)/\\([^:#]*\\)\\([:#][^/]*\\(/.*\\)?\\)?\\'" link)
211 | (let* ((repo (match-string 1 link))
212 | (repo-path (expand-file-name (nth 1 (assoc repo repos))))
213 | (fname (expand-file-name (match-string 2 link) repo-path))
214 | (git-path (locate-dominating-file fname ".git")))
215 | (cond ((setq rev-part (match-string 4 link))
216 | (orly--complete-commits git-path (substring rev-part 1)))
217 | ((setq tag-part (match-string 3 link))
218 | (if (string-prefix-p ":" tag-part)
219 | (orly--complete-tags
220 | fname
221 | (substring tag-part 1))
222 | (orly--complete-lines
223 | fname
224 | (substring tag-part 1))))
225 | (t
226 | (let* ((path (match-string 2 link))
227 | (full-path (cond ((string= path "")
228 | (file-name-as-directory repo-path))
229 | ((string= path ".")
230 | (concat repo-path path))
231 | (t
232 | (expand-file-name path repo-path))))
233 | (part (file-name-nondirectory full-path))
234 | (default-directory (file-name-directory full-path))
235 | (beg (- (+ mb (length repo) (length path) 1) (length part))))
236 | (list beg (+ beg (length part))
237 | (all-completions part #'read-file-name-internal
238 | (unless (string-match-p "\\`\\." part)
239 | (lambda (s)
240 | (not (or (string-match-p "\\`\\(\\.\\|__\\)" s)
241 | (string-match-p "~\\'" s)))))))))))
242 | (list mb (+ mb (length link))
243 | (mapcar (lambda (s) (concat s "/"))
244 | (all-completions link repos)))))))
245 |
246 | (org-link-set-parameters "code" :follow #'orly-open-code-link)
247 | (cl-pushnew 'orly-completion-code orly-completion-functions)
248 |
249 | (provide 'orly-code)
250 |
251 | ;;; orly-code.el ends here
252 |
--------------------------------------------------------------------------------
/orly.el:
--------------------------------------------------------------------------------
1 | ;;; orly.el --- Additional Org-mode link types and completion for them -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2019-2022 Oleh Krehel
4 |
5 | ;; Author: Oleh Krehel
6 | ;; URL: https://github.com/abo-abo/orly
7 | ;; Version: 0.1.0
8 | ;; Package-Requires: ((emacs "26.1") (counsel "0.11.0"))
9 | ;; Keywords: convenience
10 |
11 | ;; This program is free software; you can redistribute it and/or modify
12 | ;; it under the terms of the GNU General Public License as published by
13 | ;; the Free Software Foundation, either version 3 of the License, or
14 | ;; (at your option) any later version.
15 |
16 | ;; This program is distributed in the hope that it will be useful,
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | ;; GNU General Public License for more details.
20 |
21 | ;; You should have received a copy of the GNU General Public License
22 | ;; along with this program. If not, see .
23 |
24 | ;;; Commentary:
25 |
26 | ;; As an example, you can make use of Elisp links, e.g. el:org-capture. Clicking
27 | ;; the link will open the definition of corresponding function / variable. This
28 | ;; is useful when writing wikis / documentation, because you no longer have to
29 | ;; quote a part of the docstring: instead, the full docstring is just a click
30 | ;; away. man:ls is a similarly useful link type.
31 | ;;
32 | ;; One other nice feature is completion: after the link type is entered, press
33 | ;; "C-M-i" (`complete-symbol') to complete further.
34 | ;; To enable completion:
35 | ;;
36 | ;; (add-hook 'org-mode-hook 'orly-setup-completion)
37 | ;;
38 | ;; Finally, we can set up the links to clickable in e.g. HTML export. For
39 | ;; instance, man: links can point to https://linux.die.net/man/.
40 | ;; And el: links can point to https://git.savannah.gnu.org/cgit/emacs.git/tree/
41 | ;; or to https://github.com/emacs-mirror/emacs/.
42 |
43 | ;;; Code:
44 |
45 | (require 'org)
46 | (require 'counsel)
47 | (require 'dabbrev)
48 |
49 | (defgroup orly nil
50 | "Additional `org-mode' links and completion."
51 | :group 'convenience
52 | :prefix "orly-")
53 |
54 | (defun orly-setup-links ()
55 | (org-link-set-parameters "el" :follow #'counsel--find-symbol :export #'orly--el-export)
56 | (org-link-set-parameters "man" :follow #'man :export #'orly--man-export)
57 | (org-link-set-parameters "pdf" :follow #'orly--open-pdf)
58 | (org-link-set-parameters "exe" :follow #'orly-describe-executable))
59 |
60 | (orly-setup-links)
61 |
62 | (defvar orly-completion-functions '(orly-completion-contacts
63 | orly-completion-org-inline-sh
64 | orly-completion-symbols
65 | orly-completion-properties
66 | orly-completion-elisp
67 | orly-completion-executables
68 | orly-completion-filesystem
69 | orly-completion-refs
70 | pcomplete-completions-at-point
71 | orly-completion-dabbrev))
72 |
73 | (defun orly-completion-org-inline-sh ()
74 | (when (looking-back "src_sh{\\(.*\\)" (line-beginning-position))
75 | (let ((cmd (match-string 1))
76 | (beg (match-beginning 1)))
77 | (with-current-buffer (get-buffer "*shell def*")
78 | (let* ((start (comint-line-beginning-position))
79 | (old-text (buffer-substring-no-properties
80 | start
81 | (point))))
82 | (delete-region start (point))
83 | (insert cmd)
84 | (let ((res (bash-completion-dynamic-complete-nocomint
85 | start
86 | (point))))
87 | (list
88 | (+ beg (- (nth 0 res) start))
89 | (+ beg (- (nth 1 res) start))
90 | (nth 2 res))))))))
91 |
92 | (defun orly-completion-function ()
93 | (run-hook-with-args-until-success 'orly-completion-functions))
94 |
95 | (defun orly-setup-completion ()
96 | (cl-pushnew 'orly-completion-function completion-at-point-functions))
97 |
98 | (defun orly--guess-cmd (files)
99 | (if current-prefix-arg
100 | (dired-read-shell-command "& on %s: " nil files)
101 | (let ((prog (dired-guess-default files)))
102 | (if (consp prog)
103 | (car prog)
104 | prog))))
105 |
106 | (defun orly-autostart (file)
107 | (let ((cmd (orly--guess-cmd (list file))))
108 | (when cmd
109 | (orly-start cmd file))))
110 |
111 | (defun orly-start (cmd &rest file-list)
112 | "Run CMD on FILE-LIST using nohup."
113 | (interactive
114 | (let* ((files (dired-get-marked-files t nil))
115 | (cmd (orly--guess-cmd files)))
116 | (if (cl-search (car files) cmd)
117 | (list cmd)
118 | (cons cmd files))))
119 | (let (fname)
120 | (if (and
121 | file-list
122 | (null (cdr file-list))
123 | (not (file-directory-p (setq fname (car file-list))))
124 | (file-executable-p fname)
125 | (string-match-p "^\\(#!\\|ELF\\)" (counsel--command "head" "-1" fname)))
126 | (let ((buf (compile (concat "./" (car file-list)) t)))
127 | (select-window (cl-find buf
128 | (window-list)
129 | :key #'window-buffer))
130 | (goto-char (point-max)))
131 | (start-process
132 | cmd nil shell-file-name
133 | shell-command-switch
134 | (concat
135 | (unless (string-match-p "|" cmd)
136 | "nohup 1>/dev/null 2>/dev/null ")
137 | cmd
138 | " "
139 | (mapconcat #'shell-quote-argument file-list " "))))))
140 |
141 | (defun orly--open-pdf (path)
142 | "Example PATH to open on page 10: pdf:~/Downloads/test.pdf#10."
143 | (let ((cmd (if (string-match "\\`\\(.*\\)#\\(.*\\)\\'" path)
144 | (let ((fname (match-string 1 path))
145 | (page (match-string 2 path)))
146 | (format "evince -p %s %s" page fname))
147 | path)))
148 | (orly-start cmd)))
149 |
150 | (defun orly--man-export (path _desc format)
151 | "Export the link to a manpage."
152 | (let* ((full-names
153 | (ivy--re-filter (concat path "(")
154 | (all-completions path 'Man-completion-table)))
155 | (full-name (if (= 1 (length full-names))
156 | (substring-no-properties (car full-names))
157 | path))
158 | (group (and (string-match "(\\([^()]+\\))" full-name)
159 | (match-string 1 full-name))))
160 | (cond
161 | ((eq format 'html) (format "%s"
162 | (format "https://linux.die.net/man/%s/%s" group path)
163 | full-name))
164 | ((eq format 'latex) (format "\\textit{%s}" full-name))
165 | (t path))))
166 |
167 | (defcustom orly-el-web-address-function #'orly-el-web-address-github
168 | "Which website to use for pointing to Emacs sources."
169 | :type '(choice
170 | (const :tag "Github" orly-el-web-address-github)
171 | (const :tag "Savannah" orly-el-web-address-savannah)))
172 |
173 | (defun orly-el-web-address-github (file line)
174 | (let ((base "https://github.com/emacs-mirror/emacs/blob/")
175 | (tag "emacs-26.3"))
176 | (concat
177 | base tag
178 | "/" file
179 | "#L" line)))
180 |
181 | (defun orly-el-web-address-savannah (file line)
182 | (let ((base "https://git.savannah.gnu.org/cgit/emacs.git/tree/")
183 | (branch "emacs-26"))
184 | (concat
185 | base file
186 | "?h=" branch
187 | "#n" line)))
188 |
189 | (defun orly--el-export (path _desc format)
190 | "Export the link to a built-in Emacs function or variable."
191 | (cond
192 | ((eq format 'html)
193 | (let* ((symbol (intern-soft path))
194 | (def
195 | (condition-case nil
196 | (cond ((fboundp symbol)
197 | (find-definition-noselect symbol nil))
198 | ((boundp symbol)
199 | (find-definition-noselect symbol 'defvar))
200 | (t
201 | nil))
202 | (error nil))))
203 | (if (null def)
204 | (format "%s
" path)
205 | (or (let (file line)
206 | (with-current-buffer (car def)
207 | (when (string-match "/\\(\\(?:src\\|lisp\\)/.*\\)\\'" buffer-file-name)
208 | (setq file (match-string 1 buffer-file-name))
209 | (setq line (number-to-string (line-number-at-pos (cdr def))))
210 | (format "%s"
211 | (funcall orly-el-web-address-function file line)
212 | path))))
213 | (format "%s
" path)))))
214 | (t path)))
215 |
216 | (defun orly-completion-symbols ()
217 | (when (looking-back "=[a-zA-Z]*" (line-beginning-position))
218 | (let (cands)
219 | (save-match-data
220 | (save-excursion
221 | (goto-char (point-min))
222 | (while (re-search-forward "=\\([a-zA-Z._-]+\\)=" nil t)
223 | (cl-pushnew (match-string-no-properties 0) cands :test 'equal))
224 | cands))
225 | (when cands
226 | (list (match-beginning 0) (match-end 0) cands)))))
227 |
228 | (defvar orly-contacts-file
229 | (if (featurep 'plain-org-wiki)
230 | (cdr (assoc "contacts" (plain-org-wiki-files)))
231 | nil)
232 | "Org file name where the contacts are second level outlines.")
233 |
234 | (defun orly-to-nickname (name)
235 | (concat "@" (replace-regexp-in-string " " "_" (downcase name))))
236 |
237 | (defun orly-read-contacts-file ()
238 | (let (res)
239 | (with-current-buffer (find-file-noselect orly-contacts-file)
240 | (save-match-data
241 | (save-excursion
242 | (goto-char (point-min))
243 | (while (re-search-forward "^\\*\\* \\([A-Za-z ]+\\)$" nil t)
244 | (let* ((name (match-string-no-properties 1))
245 | (nick (orly-to-nickname name)))
246 | (push nick res))))))
247 | (nreverse res)))
248 |
249 | (defvar orly-contacts nil)
250 |
251 | (defun orly-completion-contacts ()
252 | (when (looking-back "@[a-z_]*" (line-beginning-position))
253 | (let ((cands (or orly-contacts
254 | (setq orly-contacts (orly-read-contacts-file)))))
255 | (list
256 | (match-beginning 0) (match-end 0)
257 | (all-completions (match-string-no-properties 0) cands)))))
258 |
259 | (defun orly-completion-properties ()
260 | (cond ((looking-back "^#[^ ]*" (line-beginning-position))
261 | (list (match-beginning 0)
262 | (match-end 0)
263 | (all-completions (match-string 0)
264 | '("#+title: "
265 | "#+setupfile: "
266 | "#+property: "
267 | "#+include: "
268 | "#+call: "))))
269 | ((looking-back "^#\\+property: \\(.*\\)" (line-beginning-position))
270 | (list (match-beginning 1)
271 | (match-end 1)
272 | '("header-args ")))))
273 |
274 | (defun orly-completion-elisp ()
275 | (when (looking-back "el:\\([a-zA-Z._-0-9]*\\)" (line-beginning-position))
276 | (list (match-beginning 1) (match-end 1)
277 | (all-completions
278 | (match-string-no-properties 1)
279 | obarray))))
280 |
281 | (defun orly-completion-executables ()
282 | (when (looking-back "exe:\\([a-zA-Z._-0-9]*\\)" (line-beginning-position))
283 | (list (match-beginning 1) (match-end 1)
284 | (all-completions
285 | (match-string-no-properties 1)
286 | (orly-executables)))))
287 |
288 | (defun orly-completion-filesystem ()
289 | (require 'ffap)
290 | (let (path)
291 | (when (and (setq path (ffap-string-at-point))
292 | (not (string= path "")))
293 | (when (string-match "\\`file:\\(.*\\)\\'" path)
294 | (setq path (match-string 1 path)))
295 | (let ((compl (all-completions path #'read-file-name-internal)))
296 | (when compl
297 | (let* ((str (car compl))
298 | (offset
299 | (let ((i 0)
300 | (len (length str)))
301 | (while (and (< i len)
302 | (equal (get-text-property i 'face str)
303 | 'completions-common-part))
304 | (cl-incf i))
305 | i)))
306 | (list (- (point) offset) (point) compl)))))))
307 |
308 | (defun orly-completion-refs ()
309 | (when (looking-back "\\\\\\(?:ref\\|label\\){\\([^\n{}]\\)*" (line-beginning-position))
310 | (let (cands beg end)
311 | (save-excursion
312 | (goto-char (point-min))
313 | (while (re-search-forward "\\label{\\([^}]+\\)}" nil t)
314 | (push (match-string-no-properties 1) cands)))
315 | (save-excursion
316 | (up-list)
317 | (setq end (1- (point)))
318 | (backward-list)
319 | (setq beg (1+ (point))))
320 | (list beg end
321 | (delete (buffer-substring-no-properties beg end)
322 | (nreverse cands))))))
323 |
324 | (defun orly-completion-dabbrev ()
325 | (ignore-errors
326 | (dabbrev--reset-global-variables)
327 | (let* ((dabbrev-check-other-buffers nil)
328 | (dabbrev-check-all-buffers nil)
329 | (abbrev (dabbrev--abbrev-at-point))
330 | (beg (progn (search-backward abbrev) (point)))
331 | (end (progn (search-forward abbrev) (point)))
332 | (ignore-case-p (dabbrev--ignore-case-p abbrev))
333 | (list 'uninitialized)
334 | (table
335 | (lambda (s p a)
336 | (if (eq a 'metadata)
337 | `(metadata (cycle-sort-function . ,#'identity)
338 | (category . dabbrev))
339 | (when (eq list 'uninitialized)
340 | (save-excursion
341 | ;; New abbreviation to expand.
342 | (setq dabbrev--last-abbreviation abbrev)
343 | ;; Find all expansion
344 | (let ((completion-list
345 | (dabbrev--find-all-expansions abbrev ignore-case-p))
346 | (completion-ignore-case ignore-case-p))
347 | (or (consp completion-list)
348 | (user-error "No dynamic expansion for \"%s\" found%s"
349 | abbrev
350 | (if dabbrev--check-other-buffers
351 | "" " in this-buffer")))
352 | (setq list
353 | (mapcar #'downcase completion-list)))))
354 | (complete-with-action a list s p)))))
355 | (list beg end (all-completions "" table)))))
356 |
357 | (defun orly-executables ()
358 | "Return the list of executables in `exec-path'."
359 | (let ((paths (cdr (reverse exec-path)))
360 | path
361 | completions)
362 | (while (setq path (pop paths))
363 | (let* ((dir (file-name-as-directory path))
364 | (comps (and (file-accessible-directory-p dir)
365 | (file-name-all-completions "" dir)))
366 | comp)
367 | (while (setq comp (pop comps))
368 | (if (and (not (member comp completions))
369 | (or (null shell-completion-execonly)
370 | (file-executable-p (concat dir comp))))
371 | (push comp completions)))))
372 | completions))
373 |
374 | (defun orly-describe-executable (exe)
375 | (shell-command
376 | (format
377 | "dpkg -S $(which %s) | awk '{gsub(\":\",\"\"); system(\"apt-cache show \" $1)}'"
378 | exe)))
379 |
380 | (provide 'orly)
381 | ;;; orly.el ends here
382 |
--------------------------------------------------------------------------------