├── 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 | --------------------------------------------------------------------------------