├── examples ├── .gitignore ├── [bracket].org └── links.org ├── .gitattributes ├── .gitignore ├── README-ja.org ├── README.org ├── todo.org ├── LICENSE └── org-link-completion.el /examples/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.org encoding=utf-8 2 | *.el encoding=utf-8 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.elc 3 | README.html 4 | README-ja.html 5 | todo.html 6 | -------------------------------------------------------------------------------- /examples/[bracket].org: -------------------------------------------------------------------------------- 1 | #+TITLE: Bracket File Name 2 | 3 | * Heading 1 4 | 5 | * Heading 2 6 | 7 | * Heading Escape \[left[desu\1\\2\\\3\\\\4]right\]yo\ 8 | -------------------------------------------------------------------------------- /examples/links.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Link Examples 2 | #+STARTUP: showall 3 | 4 | * Untyped Link 5 | :PROPERTIES: 6 | :CUSTOM_ID: untyped-link 7 | :END: 8 | 9 | - Internal Links 10 | - Custom ID 11 | - [[# 12 | - [[#custom-id-example-1][ 13 | - [[#custom-id-example-1][Custom ID Example 1]] 14 | - [[#custom-id-escape\\\[ 15 | - Heading 16 | - [[* 17 | - [[*Target Example][ 18 | - [[*Target Example][Target Example Section]] 19 | - [[*Heading Escape\\\[ 20 | - Coderef 21 | - [[( 22 | - [[(back)][ 23 | - [[(back2)][ 24 | - [[(hello-world)]["Hello, \\[[World]@@-:@@]// !!"))]] (Export Snippet Hack) 25 | - [[(back2)][escape- 26 | - Search 27 | - [[ 28 | - [[Apple][ 29 | - [[Apple][--Apple--]] 30 | - [[Ap 31 | - [[Apricot][Tab 32 | - [[Orange][ 33 | - [[Danraku][ 34 | - [[My 35 | - [[My Target][ 36 | - [[Unknown Target][ 37 | - [[Target Escape\\\[ 38 | - [[element-escape-\\\[ 39 | - [[neverusedtypename: 40 | - [[neverusedtypename:foobar]] 41 | - [[.emacs][ 42 | - [[\[type:\\\[p 43 | - [[\[type:\\\[path\]]] 44 | - External Links 45 | - Relative Path 46 | - [[./ 47 | - [[./li 48 | - [[../ 49 | - [[../][ 50 | - [[../../org-link-completion/ 51 | - [[../../org-link-completion/README.org][ 52 | - [[./links.org:: 53 | - [[./links.org::# 54 | - [[./links.org::* 55 | - [[./links.org::( 56 | - [[./links.org::/ 57 | - [[./links.org::Target Escape\\\[ 58 | - [[./links.org::#custom-id-escape\\\[ 59 | - [[./links.org::*Heading Escape\\\[ 60 | - [[./links.org::(escape-\[br\\\[ac\k\\\]et\]) 61 | - [[./links.org::/\[a-z\]+/] 62 | - [[./\[bracket\].org::* 63 | - [[./\[bracket\].org][ 64 | - Absolute Path 65 | - [[/ 66 | - [[\ 67 | - [[/home/][ 68 | - [[\Users\ 69 | - [[~ 70 | - [[~USER 71 | - [[~USER/ 72 | - [[~USER\ 73 | - [[~/.emacs.d/] 74 | - [[~\.emacs.d/] 75 | - [[c:/home/ 76 | - [[c:\home\ 77 | - [[~/emacs/lisp/org-link-completion/examples/links.org:: 78 | - [[~/emacs/lisp/org-link-completion/examples/links.org::# 79 | - [[~/emacs/lisp/org-link-completion/examples/links.org::* 80 | - [[~/emacs/lisp/org-link-completion/examples/links.org::( 81 | - [[~/emacs/lisp/org-link-completion/examples/links.org::/ 82 | - Not a Path 83 | - [[links.org 84 | - [[. 85 | - [[.. 86 | - [[.\li 87 | - [[..\org 88 | - [[c: 89 | - [[c:home 90 | 91 | * Typed Link 92 | :PROPERTIES: 93 | :CUSTOM_ID: typed-link 94 | :END: 95 | 96 | - [[ 97 | - file 98 | - [[file:links.org 99 | - [[file:links.org][ 100 | - [[file:../README.org 101 | - [[file:../README.org][ 102 | - [[file:/home/ 103 | - [[file:/home/][ 104 | - [[file:\[bracket\]] 105 | - [[file:c:\Users\\] 106 | - [[file:c:\Users\::Public 107 | - [[file:links.org::Danraku 108 | - [[file:../README.org::*Setup] 109 | - [[file:../README.org::#license 110 | - [[file:::*Link Target 111 | - [[file:.emacs][ 112 | - [[file:][ 113 | - [[file:links.org:: 114 | - [[file:links.org::# 115 | - [[file:links.org::* 116 | - [[file:links.org::( 117 | - [[file:links.org::/ 118 | - [[file:../README.org:: 119 | - [[file:../README.org::# 120 | - [[file:../README.org::* 121 | - [[file:../README.org::( 122 | - [[file:./\[bracket\].org:: 123 | - [[file:./\[bracket\].org::# 124 | - [[file:./\[bracket\].org::* 125 | - [[file:./\[bracket\].org::( 126 | - [[file:./\[bracket\].org::/ 127 | - [[file::: 128 | - [[file:::# 129 | - [[file:::* 130 | - [[file:::( 131 | - [[file:::/ 132 | - [[file:::/Shinonomesou .*Senburi-zoku$/]] 133 | - [[file:::/^#\+TITLE:/]] 134 | - id 135 | - [[id: 136 | - [[id:Entry with ID 137 | - [[id:fba836c2-5ae8-4f2a-a559-bc5dbbe90865][ 138 | - [[id:8117e6ec-de24-48df-9986-727e8ed08761][ 139 | - [[id:8117e6ec-de24-48df-9986-727e8ed08761][ID:8117e6ec-de24-48df-9986-727e8ed08761]] 140 | - help 141 | - [[help: 142 | - [[help:track-m 143 | - [[help:org-link-parameters][ 144 | - [[help:org-link-parameters][Link Parameters]] 145 | - elisp 146 | - [[elisp: 147 | - [[elisp:(mess 148 | - [[elisp:(message "Hello, World")][ 149 | - [[elisp:(message "Hello, World")][Show "Hello, World"]] 150 | - info 151 | - [[info: 152 | - [[info:elisp# 153 | - [[info:org#Hyperlinks][ 154 | - [[info:org#Hyperlinks][Hyperlinks - Org Manual]] 155 | - https 156 | - [[https: 157 | - [[https://misohena.jp/blog/2024-02-23-org-link-completion-el.html][ 158 | - [[https://misohena.jp/blog/]] 159 | - [[https://www.gnu 160 | - [[https://www.gnu.org/software/emacs/][ 161 | 162 | * Unsupported 163 | - Outside Text 164 | - .[ 165 | - [. 166 | - [[Link Target]. 167 | - [[Link Target][description]. 168 | - [[Link Target][description]]. 169 | - Contains Line Breaks 170 | - [[Link 171 | Target][description]] 172 | - [[Link Target][desc 173 | ription]] 174 | - Description contains [[ 175 | - [[*Unsupported][If description contains [[, completion is not possible 176 | - [[*Unsupported][If description contains [[, completion is not possible on the right side of it]] 177 | 178 | <>This is a dedicated target. 179 | 180 | * Not Link 181 | - [[My Target\][description]] 182 | - [[My [Target][description]] 183 | - [[My ]Target][description]] 184 | 185 | * Link Target 186 | ** Paragraph 187 | 188 | <>これは段落ですよ。 189 | 190 | 私のターゲットですよ。<> 191 | 192 | <<<ラジオターゲット>>>と区別できるのかな。ラジオターゲットって面白いね。 193 | 194 | <<ラジオターゲット>>←にラジオターゲットという名前のdedicated targetがあるんだけど。[[ラジオターゲット]]からリンクしちゃうもんね。 195 | 196 | 三単語。<> 197 | 198 | <<[type:\[path]>> type:path syntax. 199 | 200 | ** List 201 | - Apple 15 <> 202 | - Orange 12 203 | - Apricot 23 204 | 205 | description list 206 | - Senburi :: Rindou-ka Senburi-zoku 207 | - Akebonosou :: Rindou-ka Senburi-zoku 208 | - Shinonomesou :: Rindou-ka Senburi-zoku 209 | 210 | ** Table 211 | #+NAME: table-1 212 | | Name | Quantity | Note | 213 | |---------+----------+------------| 214 | | Apple | 15 | | 215 | | Orange | 12 | <> | 216 | | Apricot | 23 | <> | 217 | 218 | ** Custom ID 1 219 | :PROPERTIES: 220 | :CUSTOM_ID: custom-id-example-1 221 | :END: 222 | 223 | ** Custom ID 2 224 | :PROPERTIES: 225 | :CUSTOM_ID: custom-id-example-2 226 | :END: 227 | 228 | ** Custom ID with Escape Chars 229 | :PROPERTIES: 230 | :CUSTOM_ID: custom-id-escape\[left[desu\1\\2\\\3\\\\4]right\]yo\ 231 | :END: 232 | 233 | ** Heading Escape\[left[desu\1\\2\\\3\\\\4]right\]yo\ 234 | 235 | ** Source Blocks 236 | :PROPERTIES: 237 | :CUSTOM_ID: source-blocks 238 | :END: 239 | 240 | #+NAME: coderef-example 241 | #+begin_src elisp -n -r 242 | (forward-char) 243 | (forward-char) 244 | (backward-char) (ref:back) 245 | (forward-char) 246 | #+end_src 247 | 248 | [[(back)][(backward-char)の所]]だけ左に動きます。 249 | 250 | #+begin_src elisp -n -r 251 | (forward-char) 252 | (forward-char) 253 | (backward-char) (ref:back2) 254 | (backward-char) (ref:back3) 255 | (forward-char) 256 | #+end_src 257 | 258 | #+begin_src elisp -n -r 259 | (let ((text 260 | "Hello, \\[[World]]// !!")) (ref:hello-world) 261 | (print text)) 262 | #+end_src 263 | 264 | #+begin_src elisp -n -r -l "[REFID:%s]" 265 | (let ((text 266 | "Konnichiwa, \\[[Sekai]]// !!")) [REFID:konnichiwa-sekai] 267 | (print text)) 268 | #+end_src 269 | 270 | [[(konnichiwa-sekai)][(konnichiwa-sekai)行目]] 271 | 272 | #+name: element-escape-\[left[desu\1\\2\\\3\\\\4]right\]yo\ 273 | #+begin_src elisp -n -r 274 | (+ 275 | 1 (ref:escape-[br\[ac\k\]et]) 276 | 2 (ref:escape-[br\[ac\k\]et\]) 277 | 3) (ref:escape-/o_o\) 278 | #+end_src 279 | 280 | 1. [[(escape-\[br\\\[ac\k\\\]et\])]] 281 | 2. [[(escape-\[br\\\[ac\k\\\]et\\\])]] 282 | 3. [[(escape-/o_o\)]] <= Not \\) 283 | 284 | ** Example Block 285 | 286 | #+begin_example -n -r -l "" 287 | これは例です。 288 | 2行目です。 289 | 3行目です。 290 | #+end_example 291 | 292 | [[(in example block)][(in example block)行目]] 293 | 294 | ** Entry with ID1 295 | :PROPERTIES: 296 | :ID: 8117e6ec-de24-48df-9986-727e8ed08761 297 | :END: 298 | 299 | ** Entry with ID2 300 | :PROPERTIES: 301 | :ID: fba836c2-5ae8-4f2a-a559-bc5dbbe90865 302 | :END: 303 | 304 | ** Entry with ID Property 305 | :PROPERTIES: 306 | :ID: f4a621a7-412e-4986-9932-7aaa18c94ee9 307 | :END: 308 | 309 | ** Entry with ID Property 310 | :PROPERTIES: 311 | :ID: a80163af-a84d-41fa-a1e6-104125a5c9c0 312 | :END: 313 | Same Heading Text 314 | 315 | ** HTTPS 316 | 317 | - [[https://github.com/misohena/org-link-completion][misohena/org-link-completion: Complete the link type, path and description part of links at point in org-mode buffer.]] 318 | - [[https://github.com/misohena/org-link-completion/blob/main/README-ja.org][org-link-completion/README-ja.org at main · misohena/org-link-completion]] 319 | - [[https://misohena.jp/blog/2024-02-23-org-link-completion-el.html][org-link-completion.el | Misohena Blog]] 320 | - [[https://misohena.jp/blog/2024-02-23-org-link-completion-el.html][2024年2月23日の記事]] 321 | - [[https://orgmode.org/manual/Hyperlinks.html][Hyperlinks (The Org Manual)]] 322 | - [[https://orgmode.org/manual/Literal-Examples.html][Literal Examples (The Org Manual)]] 323 | -------------------------------------------------------------------------------- /README-ja.org: -------------------------------------------------------------------------------- 1 | #+TITLE: org-modeリンクのバッファ内補完 2 | #+AUTHOR: AKIYAMA Kouhei 3 | 4 | * 概要 5 | :PROPERTIES: 6 | :CUSTOM_ID: overview 7 | :END: 8 | 9 | このEmacs Lispライブラリは、org-modeにおけるリンク表記内の各部で completion-at-point (M-TAB、C-M-i、またはESC TAB) コマンドによる補完ができるようにします。 10 | 11 | リンク内のほぼ全ての場所で補完ができます。デフォルトでは次の場所が補完可能です。 12 | 13 | - [[ */link-type/* : 14 | - [[ */searchtarget/* 15 | - [[# */custom-id/* 16 | - [[# /custom-id/ ][ */description/* 17 | - [[* */heading/* 18 | - [[* /heading/ ][ */description/* 19 | - [[( */coderef/* ) 20 | - [[( /coderef/ )][ */description/* 21 | - [[ */search target/* 22 | - [[ /search target/ ][ */description/* 23 | - [[ *./dir/file* 24 | - [[ *../dir/file* 25 | - [[ */dir/file* 26 | - [[ *~/dir/file* 27 | - [[ *~USER/dir/file* 28 | - [[ *c:/dir/file* 29 | - [[ *\dir\file* 30 | - [[/dir/file:: */search target/* (上述の ./ ../ / ~ c:/ \ を含む) 31 | - [[/dir/file::# */custom-id/* 32 | - [[/dir/file::* */heading/* 33 | - [[/dir/file::( */coderef/* 34 | - [[/dir/file][ */description/* 35 | - [[file: */file/* 36 | - [[file+sys: */file/* 37 | - [[file+emacs: */file/* 38 | - [[file: /file/:: */search target/* (上述の file+sys: file+emacs: を含む) 39 | - [[file: /file/::# */custom-id/* 40 | - [[file: /file/::* */heading/* 41 | - [[file: /file/::( */coderef/* 42 | - [[file: /file/ ][ */description/* 43 | - [[id: */id/* 44 | - [[id: /id/ ][ */description/* 45 | - [[help: */function-or-variable/* 46 | - [[help: /function-or-variable/ ][ */description/* 47 | - [[elisp: */expression/* 48 | - [[elisp: /expression/ ][ */description/* 49 | - [[info: */infofile/* 50 | - [[info: /infofile/ # */nodename/* 51 | - [[info: /infofile/ # /nodename/ ][ */description/* 52 | - [[ /unknown-type/ : */path/* 53 | - [[ /unknown-type/ : /path/ ][ */description/* 54 | 55 | 補完候補はリンク先やその周辺、他の同種のリンク、構文上の制約、リンクのパス部分(説明部分を補完するとき)、お気に入り登録したリンク等から収集します。 56 | 57 | これら全ての部位で補完関数を変更・追加できるようになっています。 58 | 59 | 対応していないファイルタイプについても、自由に補完関数を追加できます。 60 | 61 | このライブラリを使用して補完関数を提供する別のライブラリには次のものがあります。 62 | 63 | - [[https://github.com/misohena/org-elisp-link][org-elisp-link.el]] :: Emacs Lispの言語要素へのリンクタイプ ~elisp-library:~, ~elisp-function:~, ~elisp-variable:~, ~elisp-face:~ を追加します。 ~:follow~, ~:export~, ~:store~, ~:activate-func~, ~:complete~ といった標準的なプロパティの他に、本ライブラリが使う ~:capf-path~ や ~:capf-desc~ プロパティも定義しています。 64 | 65 | また、使用例として、私が使っているブログ投稿へのリンクを補完する関数を後ほど紹介します。 66 | 67 | 様々なリンクの補完を試せるように[[file:examples/links.org][examples/links.org]]が用意されています。 68 | 69 | * セットアップ 70 | :PROPERTIES: 71 | :CUSTOM_ID: setup 72 | :END: 73 | 74 | org-link-completion.elをload-pathの通った場所に配置して、init.elに次のコードを追加してください。 75 | 76 | #+begin_src elisp 77 | (autoload 'org-link-completion-setup "org-link-completion" nil t) 78 | (with-eval-after-load "org" 79 | (org-link-completion-setup)) 80 | #+end_src 81 | 82 | org-link-completion-setup関数の中身を見て、必要な要素だけを抜き出してセットアップすることもできます。例えば次のように: 83 | 84 | #+begin_src elisp 85 | (with-eval-after-load "org" 86 | ;; org-modeのバッファに補完関数を追加する。 87 | (add-hook 'org-mode-hook 88 | (lambda () 89 | (add-hook 'completion-at-point-functions 90 | 'org-link-completion-at-point nil t))) 91 | 92 | ;; file: file+sys: file+desc のパス部分、説明部分の補完を追加する。 93 | (dolist (type '("file" "file+sys" "file+emacs")) 94 | (org-link-set-parameters 95 | type 96 | :capf-path 'org-link-completion-path-file 97 | :capf-desc 'org-link-completion-desc-file))) 98 | #+end_src 99 | 100 | * org-link-completion-at-point関数が呼び出す関数 101 | :PROPERTIES: 102 | :CUSTOM_ID: routing 103 | :END: 104 | 105 | ~org-link-completion-at-point~ 関数は、リンクの内部を補完するときに一番最初に呼び出されます。 106 | 107 | この関数はまず最初にポイントがある場所の周辺を解析し、リンクの各部の位置とポイントがどの部位を指しているかを特定します。 108 | 109 | 本ライブラリではリンク内の各部位を次のように分類しています。 110 | 111 | : [[:][ 112 | 113 | これら各部のバッファ内での位置と、ポイントがどの部位にあるかを特定します。(これらの情報は解析中 ~org-link-completion-pos~ 変数に保持されます) 114 | 115 | その後これらの情報を元により具体的な処理を行う関数を次のように決定し、呼び出します。 116 | 117 | - ポイントが // の部分にある => ~org-link-completion-type-function~ 変数(デフォルト: ~org-link-completion-type~ 関数) 118 | 119 | - // が空: 120 | - ポイントが // の部分にある => ~org-link-completion-path-untyped-function~ 変数(デフォルト: ~org-link-completion-path-untyped~ 関数) 121 | - ポイントが // の部分にある => ~org-link-completion-desc-untyped-function~ 変数(デフォルト: ~org-link-completion-desc-untyped~ 関数) 122 | 123 | - // が有効なリンクタイプ(~org-link-parameters~ 変数内で定義されている): 124 | ~org-link-parameters~ 変数の次のプロパティに設定されている関数を呼び出す: 125 | - ポイントが // の部分にある => リンクタイプ // の ~:capf-path~ プロパティ 126 | - ポイントが // の部分にある => リンクタイプ // の ~:capf-desc~ プロパティ 127 | - 上記のプロパティが無い場合 => ~:completino-at-point~ プロパティ 128 | (このプロパティに設定する関数はポイントがある部位によって動作を変える必要があります) 129 | 130 | - // に対する補完関数が見つからない: 131 | 132 | - ポイントが // の部分にある => ~org-link-completion-path-unknown-type-function~ 変数(デフォルト: ~org-link-completion-path-unknown-type~ 関数) 133 | - ポイントが // の部分にある => ~org-link-completion-desc-unknown-type-function~ 変数(デフォルト: ~org-link-completion-desc-unknown-type~ 関数) 134 | 135 | ~org-link-completion-at-point~ 関数から呼び出される関数には引数は渡されません。ただし、解析した情報を ~org-link-completion-pos~ 変数にキャッシュしてから呼び出します。呼び出された関数は必要に応じてその情報を参照するか、無視して再解析することも出来ます。 136 | 137 | 呼び出される関数は ~completion-at-point-functions~ に登録される関数と同じ形式を返す必要があります。詳しくはEmacs Lispマニュアルを参照してください。 138 | 139 | [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Completion-in-Buffers.html][Completion in Buffers (GNU Emacs Lisp Reference Manual)]] ([[https://ayatakesi.github.io/lispref/29.2/html/Completion-in-Buffers.html][ayatakesiさんの日本語訳(29.2)]]) 140 | 141 | * リンクの解析と結果の取得 142 | :PROPERTIES: 143 | :CUSTOM_ID: parsing 144 | :END: 145 | 146 | リンク解析は ~org-link-completion-parse-at-point~ 関数が行います。 147 | 148 | この関数は引数を取らず、ポイントがある場所の前後を調べて、ポイントがどの部位にあるかとポイントより前にある各部位の範囲を返します。 149 | 150 | #+begin_src elisp 151 | (WHERE TYPE-BEG TYPE-END [ PATH-BEG PATH-END [ DESC-BEG DESC-END ] ]) 152 | ;; WHERE ::= type | path | desc 153 | #+end_src 154 | 155 | ~org-link-completion-at-point~ 関数から呼び出される関数は通常この関数を直接呼び出す必要はありません。 ~org-link-completion-pos~ 変数にキャッシュされた結果が格納されているのでそこから取り出すことが出来ます。ただし、キャッシュされた値がない場合に備えたコードを書くことも可能です。 156 | 157 | #+begin_src elisp 158 | (when-let ((pos (or org-link-completion-pos 159 | ;; キャッシュが無ければ自分で解析する 160 | (org-link-completion-parse-at-point)))) 161 | ;; 補完候補を返す処理 162 | ) 163 | #+end_src 164 | 165 | 解析結果の各要素を取得するには、専用のアクセッサマクロを使用してください。 166 | 167 | #+begin_src elisp 168 | (when-let ((pos (or org-link-completion-pos 169 | (org-link-completion-parse-at-point)))) 170 | (let ((where (org-link-completion-pos-ref pos where)) ;;(nth 0 pos)に展開される 171 | (path-beg (org-link-completion-pos-ref pos path-beg)) ;;(nth 3 pos)に展開される 172 | (path-end (org-link-completion-pos-ref pos path-end))) ;;(nth 4 pos)に展開される 173 | (when (eq where 'path) 174 | (list 175 | path-beg path-end 176 | ;; ここに候補のリストを書く 177 | )))) 178 | #+end_src 179 | 180 | これらの処理をより簡単に書くためのマクロも用意されています。次のコードは上と等価です。 181 | 182 | #+begin_src elisp 183 | (org-link-completion-parse-let :path (path-beg path-end) 184 | (list 185 | path-beg path-end 186 | ;; ここに候補のリストを書く 187 | )) 188 | #+end_src 189 | 190 | * 自分用のブログ専用リンクタイプの作成例 191 | :PROPERTIES: 192 | :CUSTOM_ID: example-blog-type 193 | :END: 194 | 195 | 私はブログを書くのにOrg2blogを使っているのですが、ブログのポストへのリンクを表す専用のリンクタイプを定義しています。これを使うとorg-modeファイル内で次のように書けます。 196 | 197 | #+begin_src org 198 | 以前[[blog:2024-02-23-org-link-completion-at-point][org-modeのリンク部分でバッファ内補完する]]という記事を書きました。 199 | #+end_src 200 | 201 | このリンク上でC-c C-oを押すとそのorgファイルに飛びますし、エクスポートするとWeb上のURLが出力されます。C-c lによるリンクのストアにも対応していますし、C-c C-lを使ったときのパスの補完や説明部分のデフォルト値生成にも対応しています。 202 | 203 | しかしバッファ内での補完、つまりcompletion-at-pointには対応していませんでした。なので、それに対応させてみようと思います。 204 | 205 | ブログは次のようなリストで管理されています。 206 | 207 | #+begin_src elisp 208 | (defvar my-blog-list 209 | '((:link-type "blog" 210 | :post-url "https://example.com/blog/%s.html" 211 | :local-dir "~/org/blog/" 212 | :title "My Main Blog") 213 | (:link-type "subblog" 214 | :post-url "https://example.com/subblog/%s.html" 215 | :local-dir "~/org/subblog/" 216 | :title "My Sub Blog"))) 217 | 218 | (defun my-blog-from-link-type (link-type) 219 | "org-modeのリンクタイプからブログの情報を返す。" 220 | (when (stringp link-type) 221 | (seq-find (lambda (blog) 222 | (string= (plist-get blog :link-type) link-type)) 223 | my-blog-list))) 224 | #+end_src 225 | 226 | ブログは複数あるのでmy-blog-listには複数のブログを定義できるようになっています。一つは ~blog:~ というリンクタイプを使い、もう一つは ~subblog:~ というリンクタイプを使うものとします(:link-typeプロパティ)。 227 | 228 | ブログの元ファイルはorg-modeで書かれており、パーマリンク名に拡張子(.org)を付けたファイル名で特定のディレクトリ下に全て格納されています(:local_dirプロパティ)。 229 | 230 | 従って、リンクのパス部分を補完するという事は、ブログの元ファイルが格納されているディレクトリから.orgファイルを列挙し、そのファイル名から拡張子を取り除いたものを補完候補にすれば良さそうです。それを行うのが次のコードです。 231 | 232 | #+begin_src elisp 233 | (defun my-org-blog-link-capf-path () 234 | "ポイント上のリンクのパス部分を補完します。 235 | 236 | 次のような場所でC-M-iを押したときに呼び出されることを想定しています: 237 | [[blog:(ここ) 238 | [[subblog:(ここ)" 239 | (org-elisp-link-capf-parse-let :path (type path-beg path-end) 240 | (let ((blog (my-blog-from-link-type type))) 241 | (when blog 242 | (list 243 | path-beg path-end 244 | (cl-loop for file in (directory-files (plist-get blog :local-dir)) 245 | when (string-match "\\`\\(.+\\)\\.org\\'" file) 246 | collect (match-string 1 file)) 247 | :company-kind (lambda (_) 'file)))))) 248 | #+end_src 249 | 250 | 実際にこの関数をorg-link-parametersに登録すると ~blog:~ リンクタイプのパス部分でC-M-iによる補完が出来るようになります。 251 | 252 | #+begin_src elisp 253 | (dolist (blog my-blog-list) 254 | (org-link-set-parameters (plist-get blog :link-type) 255 | :capf-path #'my-org-blog-link-capf-path)) 256 | #+end_src 257 | 258 | 次に説明部分の補完を実装します。説明部分ではどのような候補を出せば良いでしょうか。私は投稿のタイトルが補完されてほしいと思いました。ブログのタイトル付きとそうでないものの二種類に加えて元のパーマリンクも候補に出そうと思います。 259 | 260 | #+begin_src elisp 261 | (defun my-org-blog-link-capf-desc () 262 | "ポイント上のリンクの説明部分を補完します。 263 | 264 | 次のような場所でC-M-iを押したときに呼び出されることを想定しています: 265 | [[blog:][(ここ) 266 | [[subblog:][(ここ)" 267 | (org-elisp-link-capf-parse-let :desc (type path desc-beg desc-end) 268 | (let* ((blog (my-blog-from-link-type type))) 269 | (when blog 270 | (let* ((title (let* ((dir (plist-get blog :local-dir)) 271 | (file (expand-file-name (concat path ".org") dir))) 272 | (my-org-blog-org-file-title file)))) 273 | (list 274 | desc-beg desc-end 275 | (append 276 | (when title 277 | (list title 278 | (concat title " | " (plist-get blog :title)))) 279 | (list path)))))))) 280 | 281 | (defun my-org-blog-org-file-title (file) 282 | "org-modeで記述されているFILEからタイトルを取得します。" 283 | (when (file-regular-p file) 284 | (with-temp-buffer 285 | (insert-file-contents file nil nil 16384) ;; きっと先頭の方にあるでしょう。 286 | (goto-char (point-min)) 287 | (let ((case-fold-search t)) 288 | (when (re-search-forward 289 | "^#\\+TITLE: *\\(.*\\)$" nil t) 290 | (match-string-no-properties 1)))))) 291 | #+end_src 292 | 293 | 投稿のタイトルは.orgファイルの先頭部分にある ~#+TITLE:~ と書いてある所から抽出してみました。このコードでは行っていませんが、Emacsで開いていたらバッファから取り出すようにもした方が良いかもしれません。 294 | 295 | これも先ほどと同じようにorg-link-parametersに登録します。 296 | 297 | #+begin_src elisp 298 | (dolist (blog my-blog-list) 299 | (org-link-set-parameters (plist-get blog :link-type) 300 | :capf-desc #'my-org-blog-link-capf-desc)) 301 | #+end_src 302 | 303 | 他の操作(:follow、:store、:export、:complete、:insert-description)は、このライブラリの趣旨から外れるので割愛します。皆さん好きなように書いてみてください。 304 | 305 | * ライセンス 306 | :PROPERTIES: 307 | :CUSTOM_ID: license 308 | :END: 309 | 310 | このソフトウェアはGPLv3の元で使用できます。このソフトウェアは自由に使用・変更・配布できます。 311 | 312 | どこかのパッケージアーカイブにこのソフトウェアを登録したい場合は、このリポジトリをフォークしてそのパッケージアーカイブに適合するように修正を加え、ご自身で登録申請をしてください。そして必要な維持作業をしてください。私の許可は必要ありません。 313 | 314 | 改良版を公開するのも歓迎します。そちらの方が私のものよりも良ければ私もそれを使うようにするかもしれません。私は突然開発が出来なくなるかもしれませんし、継続的な開発は何ら保障できません。このソフトウェアは私が欲しいものを作った結果なので、皆さんが欲しいものは自ら付け足してください。 315 | 316 | 私は英語がとても苦手ですので、英語での継続的なコミュニケーションは期待しないでください。 317 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: In-buffer completion for org-mode links 2 | #+AUTHOR: AKIYAMA Kouhei 3 | 4 | (This document was machine translated from [[file:README-ja.org][README-ja.org]]) 5 | 6 | * Overview 7 | :PROPERTIES: 8 | :CUSTOM_ID: overview 9 | :END: 10 | 11 | This Emacs Lisp library allows completion at various points in a link in org-mode using the completion-at-point (M-TAB, C-M-i, or ESC TAB) command. 12 | 13 | You can complete almost anywhere within a link. By default, the following locations can be completed: 14 | 15 | - [[ */link-type/* : 16 | - [[ */searchtarget/* 17 | - [[# */custom-id/* 18 | - [[# /custom-id/ ][ */description/* 19 | - [[* */heading/* 20 | - [[* /heading/ ][ */description/* 21 | - [[( */coderef/* ) 22 | - [[( /coderef/ )][ */description/* 23 | - [[ */search target/* 24 | - [[ /search target/ ][ */description/* 25 | - [[ *./dir/file* 26 | - [[ *../dir/file* 27 | - [[ */dir/file* 28 | - [[ *~/dir/file* 29 | - [[ *~USER/dir/file* 30 | - [[ *c:/dir/file* 31 | - [[ *\dir\file* 32 | - [[/dir/file:: */search target/* (Include ./ ../ / ~ c:/ \) 33 | - [[/dir/file::# */custom-id/* 34 | - [[/dir/file::* */heading/* 35 | - [[/dir/file::( */coderef/* 36 | - [[/dir/file][ */description/* 37 | - [[file: */file/* 38 | - [[file+sys: */file/* 39 | - [[file+emacs: */file/* 40 | - [[file: /file/:: */search target/* (Include file+sys: file+emacs:) 41 | - [[file: /file/::# */custom-id/* 42 | - [[file: /file/::* */heading/* 43 | - [[file: /file/::( */coderef/* 44 | - [[file: /file/ ][ */description/* 45 | - [[id: */id/* 46 | - [[id: /id/ ][ */description/* 47 | - [[help: */function-or-variable/* 48 | - [[help: /function-or-variable/ ][ */description/* 49 | - [[elisp: */expression/* 50 | - [[elisp: /expression/ ][ */description/* 51 | - [[info: */infofile/* 52 | - [[info: /infofile/ # */nodename/* 53 | - [[info: /infofile/ # /nodename/ ][ */description/* 54 | - [[ /unknown-type/ : */path/* 55 | - [[ /unknown-type/ : /path/ ][ */description/* 56 | 57 | Completion candidates are collected from the link destination and its surroundings, other similar links, syntactic constraints, the path part of the link (when completing the description part), links added to favorites, etc. 58 | 59 | Completion functions can be changed and added to all of these parts. 60 | 61 | You can freely add completion functions even for unsupported file types. 62 | 63 | Another library that uses this library to provide completion functions is: 64 | 65 | - [[https://github.com/misohena/org-elisp-link][org-elisp-link.el]] :: Define link types that refer to definition of Emacs Lisp language elements ( ~elisp-library:~, ~elisp-function:~, ~elisp-variable:~, ~elisp-face:~). In addition to standard properties such as ~:follow~, ~:export~, ~:store~, ~:activate-func~, ~:complete~, this library also defines ~:capf-path~ and ~:capf-desc~ properties. 66 | 67 | Also, as a usage example, I'll show you a function I use later that completes links to blog posts. 68 | 69 | [[file:examples/links.org][examples/links.org]] is provided so that you can try out various link completions. 70 | 71 | * Setup 72 | :PROPERTIES: 73 | :CUSTOM_ID: setup 74 | :END: 75 | 76 | Place org-link-completion.el in load-path and add the following code to init.el. 77 | 78 | #+begin_src elisp 79 | (autoload 'org-link-completion-setup "org-link-completion" nil t) 80 | (with-eval-after-load "org" 81 | (org-link-completion-setup)) 82 | #+end_src 83 | 84 | You can also look at the contents of the org-link-completion-setup function and extract and set up only the necessary elements. For example: 85 | 86 | #+begin_src elisp 87 | (with-eval-after-load "org" 88 | ;; Add a completion function to the org-mode buffer. 89 | (add-hook 'org-mode-hook 90 | (lambda () 91 | (add-hook 'completion-at-point-functions 92 | 'org-link-completion-at-point nil t))) 93 | 94 | ;; Add completion for the path and description parts of file:, file+sys:, file+desc. 95 | (dolist (type '("file" "file+sys" "file+emacs")) 96 | (org-link-set-parameters 97 | type 98 | :capf-path 'org-link-completion-path-file 99 | :capf-desc 'org-link-completion-desc-file))) 100 | #+end_src 101 | 102 | * Functions called by org-link-completion-at-point function 103 | :PROPERTIES: 104 | :CUSTOM_ID: routing 105 | :END: 106 | 107 | The ~org-link-completion-at-point~ function is called first when completing a link. 108 | 109 | This function first analyzes the area around the point and determines the location of each part of the link and which part the point points to. 110 | 111 | In this library, each part of the link is classified as follows. 112 | 113 | : [[:][ 114 | 115 | Determine the position of each of these parts within the buffer and where the point is located. (This information is retained in the ~org-link-completion-pos~ variable during analysis) 116 | 117 | Then, based on this information, determine and call a function that performs more specific processing as shown below. 118 | 119 | - Point is in // => call ~org-link-completion-type-function~ variable (default: ~org-link-completion-type~ function) 120 | 121 | - // is empty: 122 | - Point is in // => call ~org-link-completion-path-untyped-function~ variable (default: ~org-link-completion-path-untyped~ function) 123 | - Point is in // => call ~org-link-completion-desc-untyped-function~ variable (default: ~org-link-completion-desc-untyped~ function) 124 | 125 | - // is a valid link type (defined in ~org-link-parameters~ variable): 126 | Call the function set to the following properties of the ~org-link-parameters~ variable: 127 | - Point is in // => ~:capf-path~ property of link type // 128 | - Point is in // => ~:capf-desc~ property of link type // 129 | - If the above properties are missing => ~:completino-at-point~ property 130 | (The function set for this property must change its behavior depending on the part where the point is.) 131 | 132 | - No completion function found for //: 133 | 134 | - point is on // => call ~org-link-completion-path-unknown-type-function~ variable (default: ~org-link-completion-path-unknown-type~ function) 135 | - point is on // => call ~org-link-completion-desc-unknown-type-function~ variable (default: ~org-link-completion-desc-unknown-type~ function) 136 | 137 | No arguments are passed to functions called from the ~org-link-completion-at-point~ function. However, it caches the parsed information in the ~org-link-completion-pos~ variable before calling it. The called function can refer to that information or ignore it and re-analyze it as needed. 138 | 139 | The called function must return the same format as the function registered with ~completion-at-point-functions~. Please refer to the elisp manual for details. 140 | 141 | [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Completion-in-Buffers.html][Completion in Buffers (GNU Emacs Lisp Reference Manual)]] ([[https://ayatakesi.github.io/lispref/29.2/html/Completion-in-Buffers.html][ayatakesi's Japanese translation (29.2)]]) 142 | 143 | * Parsing links and getting results 144 | :PROPERTIES: 145 | :CUSTOM_ID: parsing 146 | :END: 147 | 148 | Link analysis is performed by the ~org-link-completion-parse-at-point~ function. 149 | 150 | This function takes no arguments, looks before and after the point, and returns the region where the point is and the range of each region before the point. 151 | 152 | #+begin_src elisp 153 | (WHERE TYPE-BEG TYPE-END [ PATH-BEG PATH-END [ DESC-BEG DESC-END ] ]) 154 | ;; WHERE ::= type | path | desc 155 | #+end_src 156 | 157 | Functions called from the ~org-link-completion-at-point~ usually do not need to call this function directly. The cached result is stored in the ~org-link-completion-pos~ variable, so you can retrieve it from there. However, it is possible to write code for the case where there are no cached values. 158 | 159 | #+begin_src elisp 160 | (when-let ((pos (or org-link-completion-pos 161 | ;; If there is no cache, analyze it yourself 162 | (org-link-completion-parse-at-point)))) 163 | ;; Processing that returns completion candidates 164 | ) 165 | #+end_src 166 | 167 | Use dedicated accessor macros to retrieve each element of the analysis result. 168 | 169 | #+begin_src elisp 170 | (when-let ((pos (or org-link-completion-pos 171 | (org-link-completion-parse-at-point)))) 172 | (let ((where (org-link-completion-pos-ref pos where)) ;; Expands to (nth 0 pos) 173 | (path-beg (org-link-completion-pos-ref pos path-beg)) ;; Expands to (nth 3 pos) 174 | (path-end (org-link-completion-pos-ref pos path-end))) ;; Expands to (nth 4 pos) 175 | (when (eq where 'path) 176 | (list 177 | path-beg path-end 178 | ;; Write a list of suggestions here 179 | )))) 180 | #+end_src 181 | 182 | Macros are also available to make writing these processes easier. The following code is equivalent to the above. 183 | 184 | #+begin_src elisp 185 | (org-link-completion-parse-let :path (path-beg path-end) 186 | (list 187 | path-beg path-end 188 | ;; Write a list of suggestions here 189 | )) 190 | #+end_src 191 | 192 | * Example of creating a link type for your own blog 193 | :PROPERTIES: 194 | :CUSTOM_ID: example-blog-type 195 | :END: 196 | 197 | I'm using Org2blog to write a blog, and I've defined a special link type to represent links to blog posts. Using this, I can write the following in an org-mode file. 198 | 199 | #+begin_src org 200 | I previously wrote an article called [[blog:2024-02-23-org-link-completion-at-point][Completion in buffer in link part of org-mode]]. 201 | #+end_src 202 | 203 | Pressing C-c C-o on this link will jump to that org file, and exporting will output the URL on the web. It also supports storing links with C-c l, as well as completing paths and generating default values for descriptions when using C-c C-l. 204 | 205 | However, it did not support completion within the buffer, that is, completion-at-point. So I'll try to accommodate that. 206 | 207 | Blogs are managed in the following list: 208 | 209 | #+begin_src elisp 210 | (defvar my-blog-list 211 | '((:link-type "blog" 212 | :post-url "https://example.com/blog/%s.html" 213 | :local-dir "~/org/blog/" 214 | :title "My Main Blog") 215 | (:link-type "subblog" 216 | :post-url "https://example.com/subblog/%s.html" 217 | :local-dir "~/org/subblog/" 218 | :title "My Sub Blog"))) 219 | 220 | (defun my-blog-from-link-type (link-type) 221 | "Return blog information from link type in org-mode." 222 | (when (stringp link-type) 223 | (seq-find (lambda (blog) 224 | (string= (plist-get blog :link-type) link-type)) 225 | my-blog-list))) 226 | #+end_src 227 | 228 | Since there are multiple blogs, multiple blogs can be defined in my-blog-list. One uses the link type ~blog:~ and the other uses the link type ~subblog:~ (~:link-type~ property). 229 | 230 | The original blog files are written in org-mode, and are all stored under a specific directory (~:local_dir~ property) with file names that include the permalink name with an extension (.org). 231 | 232 | Therefore, to complete the path part of the link, it seems to be a good idea to enumerate the .org files from the directory where the original blog file is stored, and remove the extension from the file name and use it as a completion candidate. The following code does that. 233 | 234 | #+begin_src elisp 235 | (defun my-org-blog-link-capf-path () 236 | "Complete the path part of the link on point. 237 | 238 | I expect it to be called when you press C-M-i somewhere like this: 239 | [[blog:(here) 240 | [[subblog:(here)" 241 | (org-elisp-link-capf-parse-let :path (type path-beg path-end) 242 | (let ((blog (my-blog-from-link-type type))) 243 | (when blog 244 | (list 245 | path-beg path-end 246 | (cl-loop for file in (directory-files (plist-get blog :local-dir)) 247 | when (string-match "\\`\\(.+\\)\\.org\\'" file) 248 | collect (match-string 1 file)) 249 | :company-kind (lambda (_) 'file)))))) 250 | #+end_src 251 | 252 | Registering this function in org-link-parameters enables completion using C-M-i for the path part of ~blog:~ links. 253 | 254 | #+begin_src elisp 255 | (dolist (blog my-blog-list) 256 | (org-link-set-parameters (plist-get blog :link-type) 257 | :capf-path #'my-org-blog-link-capf-path)) 258 | #+end_src 259 | 260 | Next, I'll implement completion for the description part. What kind of candidates should be provided for the description part? I thought that I would like the titles of the posts to be completed. In addition to two types of candidates, those with blog titles and those without, I also plan to include the original permalinks as candidates. 261 | 262 | #+begin_src elisp 263 | (defun my-org-blog-link-capf-desc () 264 | "Complete the description part of the link on the point. 265 | 266 | I expect it to be called when you press C-M-i somewhere like this: 267 | [[blog:][(here) 268 | [[subblog:][(here)" 269 | (org-elisp-link-capf-parse-let :desc (type path desc-beg desc-end) 270 | (let* ((blog (my-blog-from-link-type type))) 271 | (when blog 272 | (let* ((title (let* ((dir (plist-get blog :local-dir)) 273 | (file (expand-file-name (concat path ".org") dir))) 274 | (my-org-blog-org-file-title file)))) 275 | (list 276 | desc-beg desc-end 277 | (append 278 | (when title 279 | (list title 280 | (concat title " | " (plist-get blog :title)))) 281 | (list path)))))))) 282 | 283 | (defun my-org-blog-org-file-title (file) 284 | "Get title from FILE written in org-mode." 285 | (when (file-regular-p file) 286 | (with-temp-buffer 287 | (insert-file-contents file nil nil 16384) ;; It's probably near the top. 288 | (goto-char (point-min)) 289 | (let ((case-fold-search t)) 290 | (when (re-search-forward 291 | "^#\\+TITLE: *\\(.*\\)$" nil t) 292 | (match-string-no-properties 1)))))) 293 | #+end_src 294 | 295 | I extracted the title of the post from the beginning of the .org file, where it says ~#+TITLE:~. Although this code does not do this, if it is opened in Emacs, it may be a good idea to also extract it from the buffer. 296 | 297 | Register this in org-link-parameters as before. 298 | 299 | #+begin_src elisp 300 | (dolist (blog my-blog-list) 301 | (org-link-set-parameters (plist-get blog :link-type) 302 | :capf-desc #'my-org-blog-link-capf-desc)) 303 | #+end_src 304 | 305 | Other operations (:follow, :store, :export, :complete, :insert-description) are omitted as they are outside the purpose of this library. Please feel free to write as you like. 306 | 307 | * License 308 | :PROPERTIES: 309 | :CUSTOM_ID: license 310 | :END: 311 | 312 | This software is licensed under GPLv3. You are free to use, modify and distribute this software. 313 | 314 | If you wish to register this software in any package archive, please fork this repository, make the necessary modifications to fit the package archive's requirements, and submit the registration on your own. Also continue with the necessary maintenance. You don't need my permission. 315 | 316 | I also welcome you to publish your improved version. If that works better than mine, I might start using it too. I may suddenly be unable to develop, and I cannot guarantee any continued development. This software is the result of what I want, so please add what you want yourself. 317 | 318 | I am not proficient in English, so please do not expect continuous communication in English. 319 | -------------------------------------------------------------------------------- /todo.org: -------------------------------------------------------------------------------- 1 | #+TITLE: やりたいことリスト 2 | 3 | * Inbox 4 | ** TODO エスケープシーケンスが絡んだ解析不能パターンを何とかする 5 | ~[[file:\[bracket\]][~ ←が解析できない。おそらく ] が二回出現するから? 6 | 7 | ** TODO タイプ無しリンクでも他リンクからの補完をすべきでは? 8 | 現在 org-link-completion-collect-path-from-other-links はタイプが空だと何もしない。内部リンクやタイプなしファイルリンクで他リンクからの補完が出来ない。 9 | 10 | ** TODO Example Blockの検索方法が正しいか検証する 11 | とりあえずorg-babel-src-block-regexpの_srcを_exampleに置換して間に合わせたが、それで大丈夫か確認する。 12 | org-fontify-meta-lines-and-blocks-1でのやり方と一致していれば問題ないと思う。 13 | 後はorg-elementのやり方を見るとか。 14 | 15 | ** TODO plainリンクやangleリンクに対応するかどうか 16 | ** TODO テキストプロパティを見て解析を高速化することは出来る? 17 | ** TODO リンクの開始点を必ずしも正確に特定できない問題は解決できる? 18 | 左に戻りながら調べて最初に現れた[[で停止するようになっている。なので、descriptionの中に[[があったらそこがリンクの始まりだと誤認してしまう。それが嫌ならずいぶん前から解析をしなければならない(行頭からとも限らない。複数行に跨がるリンクは存在しうるから)。大人しくorg-elementを使った方が? もちろんポイントのあるリンクは完成しているとは限らないので直接的には調べられない。 19 | 20 | ** TODO 説明部の[[も[@@-:@@[にすべき? 21 | 本来は不要だが、org-link-completionはリンクの先頭を誤認してしまうので。 22 | ただ、行頭からスキャンしていけば済む話ではある。 23 | ([[*リンクの開始点を必ずしも正確に特定できない問題は解決できる?][リンクの開始点を必ずしも正確に特定できない問題は解決できる?]]) 24 | 25 | ** TODO ソースコードブロックの中で反応しないようにする 26 | ただしorg-modeのソースコードブロックは除く?? 27 | ** TODO 全体的にパスの末尾を補完するかどうか 28 | - [[My Target] 29 | - [[#CUSTOM_ID] 30 | - [[*Heading] 31 | - [[(coderef)] ←)がある 32 | - [[ ←オプションがあり得る 33 | - [[file: ←オプションがあり得る 34 | - [[id:][ ←descriptionを書かないことはあまり無さそう 35 | - [[help:] 36 | - [[elisp:] ←式を閉じたら自動で]が入ってくれてもいいのよ 37 | - [[info:#] 38 | - [[https:] 39 | 40 | 基本的には、末尾がハッキリしているなら ] を入れてしまっても良い。 41 | しかしいくつかの形式は入れられないので、それとの一貫性が必要なのかが問題。 42 | ]の後はそのまま]で閉じる場合もあるし、[でdescriptionへ続く場合もある。 43 | idはdescriptionを入れる可能性がひときわ高いと思う。idだけでは人間には意味をなさないし。 44 | 45 | 正直そこまで補完しなくても……という気はするのだけど。 46 | 47 | オプションで変えられるようにする? 48 | 49 | 注意: )や]まで含めたり含めなかったりするなら、補完範囲の末尾をpath-endから変えなくてはならないと思う。 50 | 51 | ** TODO coderefの)を補完する 52 | そもそも一般的に補完候補を選択した後にそれ以上の文字列を追加するというのは良いのだろうか。ファイル名補完なんかではディレクトリの場合次の/まで候補に出している。 53 | まぁ、)]まで入力して問題ないだろう。そうなると他も]が問題になる。[までは入れない方が良い。 54 | 55 | ** TODO coderefの補完範囲の終端は)までにすべきでは? 56 | (1+ path-beg)(または(1+ option-beg))からpath-endまでとしているが、 57 | )を含めるべきではないなら)があるときは一つ前にすべきでは? 58 | [[*coderefの)を補完する][coderefの)を補完する]]をどうするかにもよる。 59 | ** TODO idを補完した後に][を挿入したり、説明部を補完した後に]]を挿入したい 60 | ** TODO そもそもidはpathの補完で説明部まで補完していいのでは? 61 | リンクの末尾まで候補文字列に入れてしまえば一発でリンクが完成して便利。 62 | alistの補完で頭を悩ませる必要も無い。 63 | もちろん説明部を変えたい場合は逆に手間だけど(BackSpaceして]に対応する[に戻るだけではある)。 64 | 65 | ** TODO pathの時点でどこまで補完するかの設定を追加する 66 | オプションで説明部まで含めてリンクの最後まで補完できるようにする。 67 | 68 | ** TODO pathの補完終了時に][を挿入する 69 | ** TODO ]と[の間等で補完できるようにする 70 | すでにあるならそれを補完すれば良い。 71 | - |[[ => +[[を補完する+ 何もしない? 72 | - [|[ => [を補完する 73 | - ]|[ => [を補完する 74 | - ]|] => ]を補完する 75 | - ]]| => 何もしない 76 | 無いなら 77 | - | => リンクと分からない 78 | - [| => リンクと分からない 79 | - ]| => [か]を補完する 80 | - ]]| => 何もしない 81 | 82 | ** TODO htmlファイルのタイトルを補完する 83 | ** TODO httpやhttpsでタイトルを補完する 84 | http、https経由でhtmlやorgファイルをダウンロードしてファイルと同じように解析する。 85 | さすがにやり過ぎなのでデフォルトで無効にする? 86 | 87 | ** TODO fileの説明部分などで重複が発生しているのをどうするか 88 | 直す? アノテーションを付けるなら重複していても仕方ないかもしれないけど。 89 | ** TODO org-link-completion-path-file-functionsは引数無しにすべき? 90 | ここに指定する関数だけ引数を取る。 91 | 最初は org-link-completion-pos と同じようにしようと思ったが、想像以上に煩雑になったので普通に引数で書いたらシンプルになったという経緯がある。 92 | 93 | 引数: 94 | - path-beg 95 | - path-end 96 | - option-beg 97 | - file 98 | 99 | 一番の問題はoption-beg(やfile)をどうやって引き渡すかだろう。 100 | もっと一般化された仕組みがあると良いのだけど。 101 | - データの任意のスロットを静的に名前でlet出来る仕組み。 102 | - 解析データを追加できる仕組み。 103 | - 継承関係を把握できるような仕組み。 104 | 105 | ** TODO org-link-completion-call-with-file-findを廃止する 106 | org-link-completion-call-with-fileを使えば問題ないはず。 107 | 108 | ** TODO infoの説明部分の補完でTopの時はファイルのタイトルにする 109 | ~[[info:magit#Top][~ ←このタイミングで、Magit User Manual とかを候補に出すべき。 110 | 111 | ** TODO 候補をキャッシュする一般的な仕組みを作る 112 | idタイプのためにキャッシュを保持する仕組みを作った。 113 | completion-in-region-modeの終了時にキャッシュをクリアする。または、最後に呼び出されてからタイムアウト時間が経過したら次回キャッシュをクリアしてから再度始める。 114 | [[elisp-function:completion-table-with-cache]]よりは良い仕組みだと思う。 115 | 他でも使えると思うので、一般的な仕組みを用意したい。 116 | 117 | ** TODO お気に入りリンクが一部のタイプのパスに適用されない 118 | file、id、help、elisp、infoといった専用の補完関数を用意してあるタイプのパスに適用されない。 119 | fileはやっかい。 120 | 121 | ** TODO お気に入りリンクを専用のファイルに保存できるようにする 122 | 方が良いかもしれない。でも勝手に作られるのを嫌がる人もいるだろう。 123 | customizeの方が手軽だと思う人もいるかもしれない。 124 | 選択出来るようにするしか。 125 | ** TODO いくつかpath内のエスケープシーケンスが問題を引き起こすケースがある 126 | [[*エスケープシーケンスを含むパスを正しく補完できない][エスケープシーケンスを含むパスを正しく補完できない]]で大半は潰したが、問題が残っている。 127 | - fileタイプで[[elisp-function:read-file-name-internal]]関数を使っている所 128 | - helpタイプで[[elisp-function:elisp--completion-local-symbols]]関数を使っている所 129 | - elispタイプで[[elisp-function:elisp-completion-at-point]]関数を使っている所 130 | - 末尾で無くなることによって、これまで必要だった\が不要になる所 131 | [[file:C:\Users\\]]は有効なパスだが、[[file:C:\Users\::8]] のように::を付けた瞬間に末尾に\を一つ減らさなければならない。 132 | 133 | * Finished 134 | ** DONE 「#」で始まる内部リンクを補完する(customid) 135 | CLOSED: [2024-02-24 Sat 13:30] 136 | ** DONE 「*」で始まる内部リンクを補完する(見出し) 137 | CLOSED: [2024-02-24 Sat 13:30] 138 | ** DONE #や*以外の内部リンクを補完する 139 | CLOSED: [2024-02-24 Sat 21:53] 140 | 次の順番で探すらしい。 141 | 1. dedicated target << と >> で囲まれた単語 142 | 2. 要素の名前 (#+NAME:) 143 | 3. 見出し(ただし[[elisp-variable:org-link-search-must-match-exact-headline]]の影響を受ける) 144 | 4. 全単語 145 | 146 | 見出しは「*」を使うべきなので補完しないことにする。全単語も論外。 147 | 148 | # [[TODO #や*以外のページ内リンクを補完する]] 149 | 見出しを探す動作は今ひとつ分からない。 150 | org-link-search-must-match-exact-headlineがデフォルトの'query-to-createだとジャンプはするのに新しい見出しを作るか聞いてくる。 151 | 152 | リンクタイプの補完と被るのが困り処。 153 | ** DONE タイプが省略されたファイル名を補完する 154 | CLOSED: [2024-02-24 Sat 13:34] 155 | / や ./ 、 ~/ c:/等 で始まるものはファイル名として補完する。 156 | 単にファイル名だけだと内部リンクになる。 157 | ** DONE 内部リンクの説明部分を補完する 158 | CLOSED: [2024-02-24 Sat 22:45] 159 | 補完候補: 160 | - リンクの文字列そのもの(*や#を取り除く) 161 | - +段落のテキスト+ 162 | - その行のテキスト 163 | - 見出し 164 | ** DONE あらゆるリンクの説明部分を他のリンクから推測する 165 | CLOSED: [2024-02-25 Sun 01:47] 166 | パス部分と一致する他のリンクを探して、その説明部分を補完候補にする。 167 | ** DONE あらゆるリンクのパス部分を他のリンクから推測する 168 | CLOSED: [2024-02-25 Sun 01:47] 169 | タイプ部分と一致する他のリンクを探して、そのパス部分を補完候補にする。 170 | ** DONE 関数名や変数名を整える 171 | CLOSED: [2024-02-25 Sun 13:37] 172 | - -capf-は意味が無いので取り除く 173 | - 関数名の-defaultは取り除く 174 | - 関数を入れる変数名に-functionを付ける 175 | - 変数名の-functionsと-functionが似すぎているので-kind-functionsにする 176 | ** DONE Example Blockに対するcoderefが補完できないのを直す 177 | CLOSED: [2024-02-25 Sun 23:26] 178 | org-element-typeはexample-blockを受け入れてもorg-babel-src-block-regexpを使っているのだから当然。でもexample-blockを検索する正規表現そのものは見当たらない。org-fontify-meta-lines-and-blocks-1のやり方はちょっと面倒だしなぁ。 179 | org-babel-src-block-regexpの_srcを_exampleに置換したらダメ?→とりあえずそうした。 180 | ** DONE カスタマイズグループが補完関数だらけで見づらいので何とかする 181 | CLOSED: [2024-02-25 Sun 23:38] 182 | 補完関数だけを入れたサブグループを作る。 183 | ** DONE タイプの解析部分とタイプ無しファイルパスの判定部分を直す 184 | CLOSED: [2024-02-26 Mon 19:14] 185 | 186 | ドライブレターの扱いに問題がある。 187 | 188 | [[elisp-function:org-element-link-parser]]を見ると絶対パス ~(file-name-absolute-p raw-link)~ または相対パス ./ または ../ のときファイルリンクだと判定している。つまり c: の後に / が無い場合は ./ でも ../ でもない相対パスなので、ファイルとして扱われない。 189 | 190 | つまり[[d:data/]]や[[c:Users]]や[[c:./todo.org]]のようなものはEmacsはともかくorg-modeのファイルリンクとしては扱われない。一方で[[c:\home]]のように/では無く\でも問題ないことになる。 191 | 192 | c:の後に/や\を許容するか、大人しく[[elisp-function:file-name-absolute-p]]を使用するか。 193 | 194 | そもそも頭に. / ~ が付いているケースももっとちゃんと調べた方が良い。 195 | 196 | 大人しくorg-element.elを使いなさいってこった。[[elisp-function:org-element-link-parser]]は直接呼び出しても大丈夫なのかな? 197 | まぁ、[[elisp-function:org-element-link-parser]]をよく読んで出来るだけ仕様に忠実にできたらそれに越したことはないし、こんな些細な所で違っていても別に問題はほとんどない。 198 | 199 | ~[[c:home]]~ と書いたらこれはcというリンクタイプになると思いきや内部リンクになる! 何で!? ……ああ、リンクタイプはorg-link-types-reとマッチしていなければならないのか。つまり登録されているリンクタイプ名しか許容されない。それ以外は内部リンクになる。でもこれの場合は未完成のリンクタイプを考慮しなければならない。とは言え、カーソルが:よりもずっと右にあったら許容する理由もない気がする。 200 | 201 | まとめると 202 | - [[elisp-function:org-link-completion-parse-at-point][org-link-completion-parse-at-point]] は 定義済みタイプのみタイプと認識すべき。 203 | (ただし、ポイントがタイプ部分にあるときは未完成のタイプとして許容すべき) 204 | これによって c: も必然的にタイプでは無くなる。 205 | ~[[unknowntype~ はこれまで通りtypeだが、 ~[[unknowntype:foobar~ の ~unknowntype:~ 部分はタイプではなく内部リンク(の一部)になる。カスタマイズ変数があっても良い。 206 | - [[elisp-function:org-link-completion-untyped-link-kind]] は[[elisp-function:file-name-absolute-p][file-name-absolute-p]]を使うべき。 207 | ~ ~USERID ~USERID/* ~USERID\* ~/* ~\* /* \* c:/* c:\* だけがファイルパスになる。 208 | ** DONE parse-letのwhereにnilを指定しているところを出来るだけ無くす 209 | CLOSED: [2024-02-26 Mon 22:33] 210 | path-begやpath-endがnilの場所(つまりwhere=type)でpathを参照するとbuffer-substring-no-propertiesがエラーを出すはず。後からチェックできないので危険。 211 | 212 | 論理的に考えて、説明部分でしか使わないはず。 213 | pathから得られる情報を使ってpathやtypeを書き替えるわけがない。 214 | 215 | ただ一つ例外は[[elisp-function:org-link-completion-collect-path-from-other-links]]。これはtypeによってpathを補完するから。typeは必ず存在するので問題なし。 216 | ** DONE ファイル名の説明部分を補完する 217 | CLOSED: [2024-02-26 Mon 22:40] 218 | 補完候補: 219 | - orgファイルの場合はTITLE 220 | - ファイル名だけ 221 | - 拡張子を除いたベース名だけ 222 | - 絶対パス 223 | - +htmlファイルの場合はtitle要素+ これはまた後で。 224 | ** DONE 空文字列の候補を出すところをいくつか直す 225 | CLOSED: [2024-02-26 Mon 22:50] 226 | - org-link-completion-collect-description-from-other-links (他のdescription) 227 | - org-link-completion-collect-stripped-internal-link-path (記号を取り除いたパス) 228 | - org-link-completion-collect-path (パスそのもの) 229 | - org-link-completion-get-heading (見出し) 230 | ** DONE 空文字列の候補を出すところがあるかもしれないのでチェックする 231 | CLOSED: [2024-02-26 Mon 23:47] 232 | いくつか直したがまだ残っているかも。 233 | ** DONE fileの説明部分の補完などでアノテーションを付ける 234 | CLOSED: [2024-02-27 Tue 01:39] 235 | どこから持ってきたテキストなのか分かりづらい。 236 | ** DONE 同じ見出しが何度も収集されてしまう問題を修正する 237 | CLOSED: [2024-02-27 Tue 10:39] 238 | org-outline-regexpは行頭に限定されていない! 239 | ** DONE fileタイプで::以降の記法を補完する 240 | CLOSED: [2024-02-27 Tue 10:38] 241 | [[https://orgmode.org/manual/Search-Options.html][Search Options (The Org Manual)]] 242 | - ::NNN 243 | - ::My Target 244 | - ::*headline 245 | - ::#custom-id 246 | - ::/regexp/ 247 | 248 | 空のファイル名は現在のファイルを検索する。[[file:::fileタイプで]] [[fileタイプで]] どちらでも良い。 249 | ** DONE idタイプを補完できるか検討する 250 | CLOSED: [2024-02-28 Wed 00:09] 251 | 問題は見出しを選んでもらってIDを入力するようなことが出来るかどうか。 252 | 次が参考になる? 253 | [[https://emacs.stackexchange.com/questions/74547/completing-read-search-also-in-annotations][completion - completing-read, search also in annotations - Emacs Stack Exchange]] 254 | 最後のコードは少し有望かもしれない。 255 | 256 | 後は[[elisp-library:org-id]]をよく読むしか。 257 | [[elisp-variable:org-id-locations][org-id-locations]]から全IDを補完させることは出来そうだ(nilならロードする必要あり)。 258 | その時に記録されている全ファイルから見出しを集めてくることも出来る。 259 | さすがにキャッシュくらいはした方がいいかもしれない。 260 | そこに上の手法で見出しからIDを補完する。 261 | 262 | 出来ればIDが付いていない見出しも選んだらIDが付くようにしたいが、それは出来なくても仕方が無い。必要なら大人しくorg-id-store-linkを使えという話。 263 | ** DONE idタイプの補完で現在のファイルの候補を先頭にする 264 | CLOSED: [2024-02-28 Wed 08:56] 265 | - ついでに絶対パスの取扱を修正する。 266 | - カレントバッファのファイル名取得はハマリどころが多いので関数にする。 267 | ** DONE helpタイプを補完できるか検討する 268 | CLOSED: [2024-02-28 Wed 18:16] 269 | そもそもhelpリンクはhelpの何をターゲットに出来るのかよく知らなかったのだけど、[[elisp-function:org-link--open-help]]を見ると単に関数と変数だけのようだ。describe-functionとdescribe-variableで開く。それならorg-elisp-linkと同じように補完できる。開くのか関数→変数の順。つまりシンボルが重複したらkindは関数を優先すべき。 270 | ** DONE elispタイプを補完する 271 | CLOSED: [2024-02-28 Wed 21:01] 272 | [[elisp-function:elisp-completion-at-point]]を呼ぶだけで実現出来ると思う。これは内部で[[elisp-function:with-syntax-table][with-syntax-table]]を使ってelispのsyntax-tableにしてから動いているので、他のモードでもちゃんと補完できるのでは無いか。→一通り試した限り問題ない。 273 | ** DONE infoタイプを補完できるか検討する 274 | CLOSED: [2024-02-29 Thu 15:04] 275 | [[elisp-library:ol-info][ol-info.el]]に実装がある。 276 | 277 | 形式は「info: (file-name-nondirectory Info-current-file) # Info-current-node」となっている。 278 | 279 | [[elisp-function:Info-speedbar-hierarchy-buttons]] や [[elisp-function:Info-speedbar-fetch-file-nodes]] という関数があって、それが参考になるかもしれない。 280 | 281 | [[elisp-function:Info-speedbar-fetch-file-nodes]]がやっているのは: 282 | 1. テンポラリバッファを作る 283 | 2. Info-modeを立ち上げる 284 | 3. (Info-find-node )でノードをバッファに読み込む(最初は"dir" "Top") 285 | 4. 正規表現で検索してサブノードを列挙する 286 | 1. 一つ目の行頭 * ~: まで読み飛ばす。dirだと * Menu: となっている。 287 | 2. 次以降の * ~: を検索する(コロン以降のテキストはinvisibleになっている)。 288 | 1. ~の部分をnameとする。 289 | 2. - 空白(…)― => (…)― 290 | - 空白(…). => (…)Top 291 | - 空白△. => ( thisfile )△ 292 | - それ以外 => ( thisfile )~ 293 | まぁ、この関数を呼んでしまえば済む気がする。 294 | 295 | #+begin_src elisp 296 | (cl-loop for (key . value) in (Info-speedbar-fetch-file-nodes "(dir)Top") 297 | collect (cons (substring-no-properties key) (substring-no-properties value))) 298 | #+end_src 299 | 300 | ~#~ の前か後かで処理を分けるべき。 301 | ~#~ の前ならファイル名だけを補完する。 302 | ~#~ の後ならノード名を補完する。 303 | 304 | 問題点: 305 | 1. fileタイプのoption-begと同じ、解析情報の保持の問題 306 | 2. idタイプと同じ、検索に使うタイトルと補完すべきファイル名が一致しない問題 307 | 3. #の後は階層毎に検索して最終的なノード名を得るのが望ましいが、難しい、ないし、ユーザーに分かりづらい問題(Emacs/Basic/Inserting Textと指定したら emacs#Inserting Text となるような補完をしたい) 308 | 4. 階層を無視して直接ノード名を指定する場合、全ノード名を一括で取得する方法 309 | 310 | 1と2は力業で何とかなる問題。もちろん何か補助する仕組みが出来ればそれに越したことはない。4も調べたら分かるだろう。問題は3。つまり、ユーザーに一覧として何を表示して、何を入力してもらうか。 311 | 312 | 最初はidタイプと同じ手法でファイル名とそのタイトルで検索してファイル名を補完すべき。問題は#以降。 313 | 314 | - #の後ではトップノード一覧 315 | - 有効なノード名の後では、そのノード名と、サブノード一覧 316 | 317 | というのはどうだろう。 318 | 319 | うーん、結局単純なケースがうまくいかない。例えば ファイル名# の後に深い場所にあるノード名を途中まで入力して補完する場合、結局全ノードから候補を探すしかない。 320 | 321 | とりあえず全ノードを名を一括で取得してそれを補完候補にする方法で実装する。 322 | ** DONE 好きなリンクを候補に加える仕組みを作る 323 | CLOSED: [2024-02-29 Thu 18:41] 324 | org-link-completion-favorit-links 325 | ** CANCELLED collectorsを使う補完関数を作りやすくする? 326 | CLOSED: [2024-02-29 Thu 23:31] 327 | defcustomとdefunの両方を定義する必要があり、その内容も全て大部分が似通っている。 328 | org-link-completion-define-capf-with-collectorsみたいな名前のマクロでも作る? 329 | 利点は色々ある。コード量が短くなり、コピペによるミスが減り(実際-helpを作ったのに-idが残っていたりした)、全体に対する修正時も一括で出来る。 330 | 問題は知らない人がぱっと見で理解しづらいということ。カスタマイズ変数と関数があることが分かりづらい。 331 | 332 | →マクロを作ってみたけどやっぱり見づらいのでやめた。代わりにorg-link-completion-capf-desc-with-collectors関数を作ってcollectorsを使った説明部の補完関数の作成を短いコードで出来るようにした。 333 | ** DONE alist補完でcompletion-stylesを尊重する 334 | CLOSED: [2024-03-01 Fri 14:26] 335 | org-link-completion-table-with-alist-searchでの補完は強制的に部分一致になってしまっている。 +keyとvalueを分けてall-completionsを実行すれば実現出来る?+ 336 | 337 | [[elisp-function:completion-all-completions]]を使う必要がある。結果は少し特殊なので注意が必要。リストの最後のcdrにはbase-sizeという整数値が入る。 338 | 339 | より正しく動作させるためには、all-completionsだけでなくtry-completionの方も修正する必要がある。try-completionが全候補の中の一つに正確にマッチする文字列を返してしまうと、そこで補完が完了してしまう。値と正確に一致したときに値がキーにならない。なので、そういうときは、値が正確に一致するなら対応するキーを返す。 340 | ** DONE エスケープシーケンスを含むパスを正しく補完できない 341 | CLOSED: [2024-03-01 Fri 20:13] 342 | 例えば <<[type:\[path]>> ←へ飛ぶ正しいリンクは [[\[type:\\\[path\]]] だが、[[で補完しても[[[type:\[path]となってしまう。 343 | 他にもfile他でpathの中にエスケープシーケンスがあった場合にできないかも? 344 | path-from-other-linksのように他のpathから直接コピーしてくるものは大丈夫なはず。 345 | 他のソースから持ってくる場合は、path、descそれぞれにあったエスケープ処理をすべき。 346 | 347 | 容疑者は全てのパスを補完する部分。 348 | 349 | - Untyped 350 | - [[elisp-function:org-link-completion-collect-custom-id]] => エスケープした 351 | - [[elisp-function:org-link-completion-collect-heading]] => エスケープした 352 | - [[elisp-function:org-link-completion-collect-coderef]] => エスケープした(org-src-coderef-regexpが生成する正規表現にマッチしないので意味が無いかもしれない。マッチしないので候補の一覧に出ない。無理矢理作れば一応ジャンプ自体は出来る) 353 | - [[elisp-function:org-link-completion-collect-dedicated-target]] => エスケープした 354 | - [[elisp-function:org-link-completion-collect-element-names]] => エスケープした 355 | - Typed 356 | - [[elisp-function:org-link-completion-collect-path-from-other-links]] => 問題なし 357 | - [[elisp-function:org-link-completion-collect-path-from-favorite-links]] => エスケープした 358 | - [[elisp-function:org-link-completion-path-file]] => ファイル名を取得するときにunescapeする。でないと[]を含むファイル名の中身を参照できない。 359 | - [[elisp-function:org-link-completion-path-file-file]] => × *read-file-name-internalを使っているのでエスケープするのは困難。* 360 | - [[elisp-function:org-link-completion-path-file-custom-id]] => 上で対策済み 361 | - [[elisp-function:org-link-completion-path-file-heading]] => 上で対策済み 362 | - [[elisp-function:org-link-completion-path-file-coderef]] => 上で対策済み 363 | - [[elisp-function:org-link-completion-path-file-search]] => 上で対策済み 364 | - [[elisp-function:org-link-completion-file-without-options]] => ファイル名を取得するときにunescapeする。でないと[]を含むファイル名の中身を参照できない。 365 | - [[elisp-function:org-link-completion-path-id]] => 一応エスケープした 366 | - [[elisp-function:org-link-completion-path-help]] => *難しいので保留* 367 | - [[elisp-function:org-link-completion-path-elisp]] => *難しいので保留* 368 | - [[elisp-function:org-link-completion-path-info]] => エスケープ・アンエスケープした 369 | - [[elisp-function:org-link-completion-collect-heading-by-id]] => unescape 370 | - [[elisp-function:org-link-completion-collect-path]] => unescape 371 | - descriptionをpathから持ってくる場合unescapeが必要 => parse-let で pathを取得しているところを一通り調べて対処した 372 | 373 | *注意: 部分的にエスケープすると正しい結果が得られない場合がある。* 374 | 375 | ::