├── examples ├── .gitignore ├── picture1.jpg ├── orgcite.bib └── all-types.org ├── .gitignore ├── screenshot ├── org-cmenu-insert.png ├── org-cmenu-pretty.png ├── org-cmenu-table.gif ├── org-cmenu-image-link.gif └── org-cmenu_plain-list.png ├── org-cmenu-typedoc.el ├── README-ja.org ├── README.org ├── org-cmenu-setup.el ├── org-cmenu.el └── org-cmenu-tools.el /examples/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.elc 3 | README.html 4 | README-ja.html 5 | -------------------------------------------------------------------------------- /examples/picture1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misohena/org-cmenu/main/examples/picture1.jpg -------------------------------------------------------------------------------- /screenshot/org-cmenu-insert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misohena/org-cmenu/main/screenshot/org-cmenu-insert.png -------------------------------------------------------------------------------- /screenshot/org-cmenu-pretty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misohena/org-cmenu/main/screenshot/org-cmenu-pretty.png -------------------------------------------------------------------------------- /screenshot/org-cmenu-table.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misohena/org-cmenu/main/screenshot/org-cmenu-table.gif -------------------------------------------------------------------------------- /screenshot/org-cmenu-image-link.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misohena/org-cmenu/main/screenshot/org-cmenu-image-link.gif -------------------------------------------------------------------------------- /screenshot/org-cmenu_plain-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misohena/org-cmenu/main/screenshot/org-cmenu_plain-list.png -------------------------------------------------------------------------------- /examples/orgcite.bib: -------------------------------------------------------------------------------- 1 | @article{OrgCitations, 2 | author={org, mode and Syntax, Citation and List, Mailing and Effort, Time}, 3 | journal={Journal of Plain Text Formats}, 4 | title={Elegant Citations with Org-Mode}, 5 | year={2021}, 6 | month={7}, 7 | volume={42}, 8 | number={1}, 9 | pages={2-3}} 10 | -------------------------------------------------------------------------------- /org-cmenu-typedoc.el: -------------------------------------------------------------------------------- 1 | ;;; org-cmenu-typedoc.el --- Org Syntax Type Help -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2021 AKIYAMA Kouhei 4 | 5 | ;; Author: AKIYAMA Kouhei 6 | ;; Keywords: Org 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | 23 | ;; This file is a list of correspondence between each syntax element 24 | ;; type and the URL of the manual. 25 | 26 | ;; Usage: 27 | ;; (org-cmenu-browse-type-document 'timestamp) 28 | 29 | ;;; Code: 30 | 31 | (require 'org-element) 32 | 33 | ;; ref: 34 | ;; - https://orgmode.org/org.html 35 | ;; - https://orgmode.org/manual/Main-Index.html 36 | 37 | (defconst org-cmenu-type-document-alist 38 | '(;; Elements 39 | (babel-call . "https://orgmode.org/manual/Evaluating-Code-Blocks.html") 40 | (center-block ."https://orgmode.org/manual/Paragraphs.html") 41 | (clock . "https://orgmode.org/manual/Clocking-commands.html") 42 | (comment . "https://orgmode.org/manual/Comment-Lines.html") 43 | (comment-block . "https://orgmode.org/manual/Comment-Lines.html") 44 | (diary-sexp . "https://orgmode.org/manual/Timestamps.html") 45 | (drawer . "https://orgmode.org/manual/Drawers.html") 46 | (dynamic-block . "https://orgmode.org/manual/Dynamic-Blocks.html") 47 | (example-block . "https://orgmode.org/manual/Literal-Examples.html") 48 | (export-block . "https://orgmode.org/manual/Quoting-HTML-tags.html") 49 | (fixed-width . "https://orgmode.org/manual/Literal-Examples.html") 50 | (footnote-definition . "https://orgmode.org/manual/Creating-Footnotes.html") 51 | (headline . "https://orgmode.org/manual/Headlines.html") 52 | (horizontal-rule . "https://orgmode.org/manual/Horizontal-Rules.html") 53 | (inlinetask . "https://git.savannah.gnu.org/cgit/emacs/org-mode.git/tree/lisp/org-inlinetask.el") 54 | (item . "https://orgmode.org/manual/Plain-Lists.html") 55 | (keyword 56 | . (lambda (datum) 57 | (pcase (org-element-property :key datum) 58 | ((or "ARCHIVE" "CATEGORY" "COLUMNS" "CONSTANTS" "FILETAGS" "LINK" 59 | "PRIORITIES" "PROPERTY" "SETUPFILE" "STARTUP" 60 | "TAGS" "TODO" "SEQ_TODO" "TYP_TODO") 61 | "https://orgmode.org/manual/In_002dbuffer-Settings.html") 62 | ((pred (lambda (kw) (string-prefix-p "HTML" kw))) 63 | "https://orgmode.org/manual/HTML-specific-export-settings.html") 64 | ("INFOJS_OPT" 65 | "https://orgmode.org/manual/JavaScript-support.html") 66 | (_ "https://orgmode.org/manual/Export-Settings.html")))) 67 | (latex-environment . "https://orgmode.org/manual/LaTeX-fragments.html") 68 | (node-property . "https://orgmode.org/manual/Property-Syntax.html") 69 | (paragraph . "https://orgmode.org/manual/Paragraphs.html") 70 | (plain-list . "https://orgmode.org/manual/Plain-Lists.html") 71 | (planning . "https://orgmode.org/manual/Deadlines-and-Scheduling.html") 72 | (property-drawer . "https://orgmode.org/manual/Drawers.html") 73 | (quote-block . "https://orgmode.org/manual/Paragraphs.html") 74 | (section . "https://orgmode.org/manual/Headlines.html") 75 | (special-block . "https://orgmode.org/worg/org-contrib/org-special-blocks.html") 76 | (src-block . "https://orgmode.org/manual/Working-with-Source-Code.html") 77 | (table . "https://orgmode.org/manual/Tables.html") 78 | (table-row . "https://orgmode.org/manual/Tables.html") 79 | (verse-block . "https://orgmode.org/manual/Paragraphs.html") 80 | ;; Objects 81 | (bold . "https://orgmode.org/manual/Emphasis-and-Monospace.html") 82 | (citation . "https://orgmode.org/manual/Citation-handling.html") 83 | (citation-reference . "https://orgmode.org/manual/Citation-handling.html") 84 | (code . "https://orgmode.org/manual/Emphasis-and-Monospace.html") 85 | (entity . "https://orgmode.org/manual/Special-Symbols.html") 86 | (export-snippet . "https://orgmode.org/manual/Quoting-HTML-tags.html") 87 | (footnote-reference . "https://orgmode.org/manual/Creating-Footnotes.html") 88 | (inline-babel-call . "https://orgmode.org/manual/Evaluating-Code-Blocks.html") 89 | (inline-src-block . "https://orgmode.org/manual/Structure-of-Code-Blocks.html") 90 | (italic . "https://orgmode.org/manual/Emphasis-and-Monospace.html") 91 | (line-break . "https://orgmode.org/manual/Paragraphs.html") 92 | (latex-fragment . "https://orgmode.org/manual/LaTeX-fragments.html") 93 | (link . "https://orgmode.org/manual/Hyperlinks.html") 94 | (macro . "https://orgmode.org/manual/Macro-Replacement.html") 95 | (radio-target . "https://orgmode.org/manual/Radio-Targets.html") 96 | (statistics-cookie . "https://orgmode.org/manual/Checkboxes.html") 97 | (strike-through . "https://orgmode.org/manual/Emphasis-and-Monospace.html") 98 | (subscript . "https://orgmode.org/manual/Subscripts-and-Superscripts.html") 99 | (superscript . "https://orgmode.org/manual/Subscripts-and-Superscripts.html") 100 | (table-cell . "https://orgmode.org/manual/Tables.html") 101 | (target . "https://orgmode.org/manual/Internal-Links.html") 102 | (timestamp . "https://orgmode.org/manual/Timestamps.html") 103 | (underline . "https://orgmode.org/manual/Emphasis-and-Monospace.html") 104 | (verbatim . "https://orgmode.org/manual/Emphasis-and-Monospace.html"))) 105 | 106 | (defconst org-cmenu-affiliated-keyword-document-alist 107 | '(("CAPTION" . "https://orgmode.org/manual/Captions.html"))) 108 | 109 | (put 'org-cmenu-browse-type-document 'org-cmenu 110 | '(:target all)) 111 | (defun org-cmenu-browse-type-document (type-or-datum) 112 | (interactive 113 | (list 114 | (intern (completing-read "Type: " org-cmenu-type-document-alist)))) 115 | 116 | (let* ((datum (if (listp type-or-datum) type-or-datum)) 117 | (type (if datum (org-element-type datum) type-or-datum)) 118 | (url-or-fun 119 | (or 120 | ;; From Affiliated Keyword 121 | (when-let ((aff-kw (org-cmenu-current-affiliated-keyword-at-point datum))) 122 | (alist-get 123 | aff-kw 124 | org-cmenu-affiliated-keyword-document-alist 125 | nil nil #'string=)) 126 | ;; From Type 127 | (alist-get type org-cmenu-type-document-alist)))) 128 | ;; Open Browser 129 | (if url-or-fun 130 | (browse-url 131 | (if (functionp url-or-fun) 132 | (funcall url-or-fun datum) 133 | url-or-fun)) 134 | (error "Unknown type %s" type)))) 135 | 136 | (defun org-cmenu-current-affiliated-keyword-at-point (&optional datum pos) 137 | (let* ((datum (or datum (org-element-at-point))) 138 | (datum-beg (org-element-property :begin datum)) 139 | (datum-post-aff (org-element-property :post-affiliated datum)) 140 | (pos (or pos (point)))) 141 | (when (and datum-beg 142 | datum-post-aff 143 | (<= datum-beg pos) 144 | (< pos datum-post-aff)) 145 | (save-excursion 146 | (forward-line 0) 147 | (when (looking-at 148 | "[ \t]*#\\+\\([a-zA-Z_-]+\\)") 149 | (upcase (match-string-no-properties 1))))))) 150 | 151 | (provide 'org-cmenu-typedoc) 152 | ;;; org-cmenu-typedoc.el ends here 153 | -------------------------------------------------------------------------------- /examples/all-types.org: -------------------------------------------------------------------------------- 1 | 2 | #+TITLE: Types of All Syntax Elements in Org Mode 3 | 4 | * All Types 5 | 6 | - org-version :: src_elisp[:eval no-export]{(org-version)} {{{results(=9.5.1=)}}} 7 | - org-element-all-elements :: src_elisp[:eval no-export :results pp]{org-element-all-elements} {{{results(=(babel-call center-block clock comment comment-block diary-sexp drawer dynamic-block example-block export-block fixed-width footnote-definition headline horizontal-rule inlinetask item keyword latex-environment node-property paragraph plain-list planning property-drawer quote-block section special-block src-block table table-row verse-block)=)}}} 8 | - org-element-all-objects: :: src_elisp[:eval no-export :results pp]{org-element-all-objects} {{{results(=(bold citation citation-reference code entity export-snippet footnote-reference inline-babel-call inline-src-block italic line-break latex-fragment link macro radio-target statistics-cookie strike-through subscript superscript table-cell target timestamp underline verbatim)=)}}} 9 | 10 | * all-elements 11 | ** babel-call 12 | 13 | #+call: test-fun() 14 | 15 | #+RESULTS: 16 | : 3 17 | 18 | ** center-block 19 | #+begin_center 20 | Paragraph line1 21 | Paragraph line2 22 | #+end_center 23 | ** clock 24 | *** STARTED Clock 25 | :LOGBOOK: 26 | - State "STARTED" from [2021-12-20 Mon 19:52] 27 | CLOCK: [2021-12-20 Mon 19:52]--[2021-12-20 Mon 19:53] => 0:01 28 | :END: 29 | 30 | A line that starts with CLOCK:. 31 | 32 | ** comment 33 | # comment line1 34 | # comment line2 35 | 36 | ** comment-block 37 | #+begin_comment 38 | comment line1 39 | comment line2 40 | #+end_comment 41 | ** diary-sexp 42 | %%(diary-sunrise-sunset) 43 | 44 | ** drawer 45 | Still outside the drawer 46 | :DRAWERNAME: 47 | This is inside the drawer. 48 | :END: 49 | After the drawer. 50 | 51 | ** dynamic-block 52 | #+BEGIN: block-update-time :format "on %m/%d/%Y at %H:%M" 53 | Last block update at: on 12/20/2021 at 17:40 54 | #+END: 55 | 56 | #+begin_src elisp :eval no-export 57 | (defun org-dblock-write:block-update-time (params) 58 | (let ((fmt (or (plist-get params :format) "%d. %m. %Y"))) 59 | (insert "Last block update at: " 60 | (format-time-string fmt)))) 61 | #+end_src 62 | 63 | ** example-block 64 | #+begin_example 65 | Example 66 | #+end_example 67 | 68 | ** export-block 69 | #+begin_export html 70 | test 71 | #+end_export 72 | 73 | ** fixed-width 74 | : Fixed Width Line1 75 | : Fixed Width Line2 76 | 77 | ** footnote-definition 78 | 79 | See [[*footnote-reference]]. 80 | 81 | [fn:test1] Foot note definition test1. 82 | 83 | [fn:test2] Foot note definition test2. This follows [fn:test1]. 84 | 85 | This paragraph is included in the test2 definition. 86 | 87 | ** headline 88 | Contents of Headline 89 | 90 | *** SubHeadline1 :EMACS: 91 | Contents of SubHeadline1. 92 | 93 | **** SubSub1 94 | Contents of SubSub1 95 | 96 | *** SubHeadline2 97 | 98 | Contents of SubHeadline2. 99 | 100 | ** horizontal-rule 101 | 102 | ----- 103 | Horizontal Rule 104 | ----- 105 | 106 | ** inlinetask 107 | *************** test 108 | *************** END 109 | 110 | (require 'org-inlinetask) 111 | ** keyword 112 | 113 | #+OPTIONS: ^:- 114 | 115 | ** latex-environment 116 | 117 | \begin{equation} 118 | x=\sqrt{b} 119 | \end{equation} 120 | 121 | ** node-property 122 | :PROPERTIES: 123 | :LOCATION: This is a Node Property 124 | :END: 125 | 126 | ** paragraph 127 | 128 | This is a paragraph 1. Line1. 129 | Line1. 130 | 131 | This is a paragraph 2. 132 | 133 | ** plain-list 134 | 135 | #+attr_html: :class list1 136 | - Ringo 137 | - Mikan 138 | #+attr_html: :class list2 139 | - 140 | #+attr_html: :class paragraph1 141 | Mikan A 142 | Line2 143 | - Mikan B [[https://google.com/]] 144 | - Mikan C 145 | Paragraph1 146 | 147 | Paragraph2 148 | - Ichigo 149 | - CheckBoxes 150 | - [ ] A 151 | - [ ] B 152 | - Last 153 | 154 | simple list 155 | 156 | - item 3 157 | - item 9 158 | - item 1 159 | - item 5 160 | - item 5-4 161 | - item 5-11 162 | - item 5-8 163 | - item 2 164 | - item 0 165 | 166 | Ordered 167 | 168 | 1. item1 169 | 2. item2 170 | 3. item3 171 | 172 | Description 173 | 174 | - key1 :: desc1 175 | - key2 :: desc2 176 | - key3 :: desc3 177 | 178 | ** item 179 | 180 | 1. item1 181 | 2. item2 182 | 3. item3 183 | 184 | ** planning 185 | *** DONE SCHEDULE 186 | CLOSED: [2021-12-20 Mon 19:18] DEADLINE: <2021-12-20 Mon> SCHEDULED: <2021-12-20 Mon> 187 | 188 | The line containing ~CLOSED:~ ~DEADLINE:~ ~SCHEDULED:~ is called planning line. 189 | 190 | ** property-drawer 191 | :PROPERTIES: 192 | :LOCATION: Tokyo 193 | :END: 194 | 195 | ** quote-block 196 | #+begin_quote 197 | Paragraph1 Line1 198 | Paragraph1 Line1 199 | Paragraph1 Line1 200 | 201 | Paragraph2 Line1 202 | #+end_quote 203 | ** section 204 | ** special-block 205 | 206 | #+begin_supesyaru 207 | paragraph 208 | #+end_supesyaru 209 | 210 | ** src-block 211 | 212 | #+name: test-fun 213 | #+begin_src elisp :eval no-export 214 | (+ 1 2) 215 | #+end_src 216 | 217 | ** table, table-row 218 | 219 | Indented table: 220 | | a | b | c | 221 | |----+----+---| 222 | | 1 | 2 | 3 | 223 | | 4 | 5 | 6 | 224 | | 7 | 8 | 9 | 225 | |----+----+---| 226 | | 12 | 15 | | 227 | | 1 | 1 | 1 | 228 | #+TBLFM: @5$1=vsum(@I..@II)::@5$2=vsum(@I..@II)::@6=1 229 | 230 | The smallest table that org-elements recognizes (no table-cell): 231 | | 232 | 233 | 234 | The following table has 3 rows. The 1st and 3rd rows are empty (no columns). 235 | 236 | | 237 | |0 238 | | 239 | 240 | The follwing table has 3 empty rows. 241 | 242 | | 243 | | 244 | | 245 | 246 | A smallest table with cells: 247 | |0 248 | 249 | | 0 250 | 251 | A table that is not neatly aligned: 252 | #+NAME: tbl 253 | | a|b |c| d | 254 | |-- 255 | |0| 1| 2 |3 | 256 | | 4|5| 6| 7 257 | |8 258 | ||9|10|11| 259 | | 12 | 13 | 260 | 261 | | a| 262 | |-- 263 | |0| 1 264 | | 4|5| 6| 265 | |8 266 | ||9|10|11| 267 | | 12 | 13 | 268 | 269 | dlines: 270 | 271 | |---+---| 272 | | 1 | 2 | 273 | | 3 | 4 | 274 | |---+---| 275 | 276 | hline only: 277 | 278 | |---| 279 | 280 | Column Width 281 | 282 | | | | | | 283 | | Long Column | Very Long Column | Very Very Long Column | Columnnn | 284 | |-------------+------------------+-----------------------+----------| 285 | | 1234567890 | 12345678901234 | 1234567890123456789 | Bannann | 286 | | 9876543210 | 23456789012301 | 3456789012345678912 | Appllle | 287 | | AAAAA | BBBBB | | Orangee | 288 | | CCCCC | DDDDD | | | 289 | | | | | | 290 | | | | | | 291 | |-------------+------------------+-----------------------+----------| 292 | 293 | ** verse-block 294 | 295 | #+begin_verse 296 | Verse Block Line1 297 | Verse Block Line2 298 | Verse Block Line3 299 | #+end_verse 300 | 301 | * all-objects 302 | ** bold, underline, italic, verbatim, code, strike-through 303 | 304 | *bold* , _underline_ , /italic/ , =verbatim= , ~code~ , +strike-through+ 305 | 306 | ** subscript, superscript 307 | 308 | ABCD_{subscript} , ABCD_subscript 309 | 310 | ABCD^{superscript} , ABCD^superscript 311 | 312 | ** inline-babel-call, inline-src-block 313 | 314 | call_test-fun() {{{results(=3=)}}} is a inline babel call. 315 | 316 | src_elisp[:var x=2 :eval no-export]{(sqrt x)} {{{results(=1.4142135623730951=)}}} is a inline src block. 317 | 318 | ** line-break 319 | 320 | first-line\\ 321 | second-line 322 | 323 | ** entity 324 | 325 | alpha=\alpha{} , beta=\beta{} 326 | 327 | ** link 328 | 329 | 330 | 331 | file:./picture1.jpg 332 | 333 | [[*All Types][All Types(Internal Link)]] 334 | 335 | #+ATTR_HTML: :style border: solid 2px red; padding: 2px; display: inline-block 336 | Links in the line: [[*link][1st link]], [[*link][2nd link]], [[*link][3rd link]]. Attributes apply to the paragraph and the first link in the paragraph. 337 | 338 | Standalone Image: 339 | 340 | #+CAPTION: Fuji-san!! 341 | #+ATTR_HTML: :width 600 342 | [[file:picture1.jpg]] 343 | 344 | Inline Image: 345 | 346 | #+ATTR_HTML: :width 100 :style border:solid 2px red 347 | This [[file:picture1.jpg]] is the highest mountain in Japan. 348 | 349 | ** footnote-reference 350 | 351 | Inline definition [fn:: This is a inline definition of this footnote] Example. Named test3 [fn:test3: This is a named test3 inline definition of this footnote] definition. 352 | 353 | Test1 [fn:test1] and Test2 [fn:test2]. 354 | 355 | Test3 [fn:test3]. 356 | 357 | ** table-cell 358 | 359 | See [[*table, table-row]]. 360 | 361 | ** timestamp 362 | 363 | [2021-12-26 Sun] 364 | 365 | <2021-12-26 Sun> 366 | 367 | [2021-12-26 Sun 13:14] 368 | 369 | <2021-12-26 Sun 13:14> 370 | 371 | <2021-12-26 Sun>--<2021-12-27 Mon> 372 | 373 | ** target 374 | 375 | Go to [[My Target]]. 376 | 377 | 1. 1st 378 | 2. <>This is my target. 379 | 380 | ** radio-target 381 | 382 | Go to My Radio Target. 383 | 384 | This is <<>> 385 | 386 | ** macro 387 | 388 | {{{date}}} 389 | 390 | #+MACRO: poem Rose is $1, violet's $2. Life's ordered: Org assists you. 391 | 392 | {{{poem(red,blue)}}} 393 | 394 | #+MACRO: gnustamp (eval (concat "GNU/" (capitalize $1))) 395 | 396 | {{{gnustamp(linux)}}} 397 | 398 | ** export-snippet 399 | 400 | brackets: @@html:[[@@ foo @@html:]]@@ 401 | 402 | ** latex-fragment 403 | 404 | If $a^2=b$ and \( b=2 \), then the solution must be 405 | either $$ a=+\sqrt{2} $$ or \[ a=-\sqrt{2} \]. 406 | 407 | ** statistics-cookie 408 | 409 | - [-] call people [1/3] 410 | - [ ] Peter 411 | - [X] Sarah 412 | - [ ] Sam 413 | - [X] order food 414 | - [ ] think about what music to play 415 | 416 | ** citation, citation-reference 417 | (from: [[https://blog.tecosaur.com/tmio/2021-07-31-citations.html][July 2021 - This Month in Org]]) 418 | 419 | [cite/l/b:see @OrgCitations pp. 7 for fun] 420 | 421 | #+bibliography: orgcite.bib 422 | 423 | #+print_bibliography: 424 | -------------------------------------------------------------------------------- /README-ja.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Emacs org-mode のためのコンテキストメニュー 2 | #+SUBTITLE: 現在の構文要素に応じた操作メニューを表示する 3 | 4 | * 目的 5 | 6 | このEmacs Lispコード(org-cmenu)は、現在のポイントが指しているorg-modeの構文要素に応じたキー操作メニューを表示する仕組みを提供する。 7 | 8 | 構文要素のタイプは org-element.el によって解析・判別され、そのタイプに応じたメニューは[[https://github.com/magit/transient/blob/master/lisp/transient.el][transient.el]]によって表示される。org-cmenuはその間に立ち、次の役割を担う。 9 | 10 | - どの[[#syntax-element-types][構文要素タイプ]]にどのようなメニューを表示するか管理する。(org-cmenu.el) 11 | - org-elementやtransientと連携してメニューを表示し、必要に応じて呼び出し先のコマンドとの調整を行う。(org-cmenu.el) 12 | - メニューから使うためのorg-mode標準に欠けているコマンドを提供する。(org-cmenu-tools.el) 13 | 14 | * スクリーンショット 15 | 16 | 画像リンクに対して、キャプションとHTML属性を設定し、コメントアウトし、再びコメント化解除をする例: 17 | 18 | #+CAPTION: Set CAPTION, ATTR_HTML, comment, and uncomment for image links. 19 | [[file:./screenshot/org-cmenu-image-link.gif]] 20 | 21 | 表をいろいろ操作する例: 22 | 23 | #+CAPTION: Cut and paste or move part of the table. 24 | [[file:./screenshot/org-cmenu-table.gif]] 25 | 26 | #+CAPTION: Plain List Menu 27 | [[file:screenshot/org-cmenu_plain-list.png]] 28 | 29 | #+CAPTION: Insert menu 30 | [[file:screenshot/org-cmenu-insert.png]] 31 | 32 | #+CAPTION: Entity Menu 33 | [[file:screenshot/org-cmenu-pretty.png]] 34 | 35 | * デフォルトのメニューで実装している機能 36 | 37 | 現在の所、デフォルトのメニュー(org-cmenu-setup.elで定義)で実装している機能は次の通り。 38 | 39 | - 要素に対する 40 | - 範囲選択 41 | - カット 42 | - コピー 43 | - org-modeマニュアルを開く 44 | - コメント化/コメント解除 45 | - 要素の内容に対する 46 | - 範囲選択 47 | - カット 48 | - コピー 49 | - 中身をむき出しにする(囲っている部分を削除) 50 | - Affiliated Keywords (#+ATTR_HTMLや#+CAPTION、#+NAME等、要素の前にくっつくキーワード) 51 | - 属性の追加(#+ATTR_ORG, ATTR_HTML, ATTR_LATEX) 52 | - 名前の追加(#+NAME) 53 | - 表題の追加(#+CAPTION) 54 | - 見出しに対するorg-speed-command的な操作 55 | - カーソル移動 56 | - 可視性制御 57 | - サブツリー移動 58 | - サブツリークローン 59 | - ソート 60 | - アーカイブ 61 | - TODO、優先度、タグ、プロパティ変更 62 | - クロック操作 63 | - ソースブロック関連(#+CALL、#+BEGIN_SRC、call_、src_)に対する 64 | - 実行 65 | - 結果の削除 66 | - その他 Babel speed keys(C-c C-v prefix) メニュー 67 | - リスト全体やリスト項目に対する 68 | - S式コピー 69 | - 見出し化 70 | - 整形 71 | - チェックボックスON/OFF 72 | - チェックボックス作成/削除 73 | - プロパティドロワーに対する 74 | - プロパティの設定 75 | - 表に対する 76 | - セル単位の上下左右移動 77 | - 矩形リージョンに対するカット、コピーとそのペースト 78 | - セル、行、列の移動 79 | - セル、行、列の範囲選択(マーク) 80 | - 転置 81 | - TAB、S-TABによる幅の縮小と展開 82 | - 整形 83 | - S式コピー 84 | - 行、列の追加、削除 85 | - 水平線の追加 86 | - 式の編集等 87 | - 行、列、表全体の総和計算 88 | - 下付き・上付き文字に対する 89 | - インライン表示のON/OFF 90 | - 文字エンティティに対する 91 | - インライン表示のON/OFF 92 | - リスト表示 93 | - リンクに対する 94 | - パスと説明の編集 95 | - 開く(デフォルト、システム優先、Emacs優先) 96 | - パスのコピー 97 | - ファイル情報の表示 98 | - Statistics Cookie ([1/3]みたいなやつ) 99 | - 更新 100 | - バッファ全体に対する 101 | - オプションキーワードの追加 102 | - タイトル情報の追加 103 | - 段落、テーブルセル、リストアイテム、各種ブロック等に対する各種追加 104 | - 太字、下線、イタリック、逐語、コード、取消線 105 | - 上付き、下付き文字 106 | - インラインCALL、インラインSRC 107 | - 文字エンティティの追加(候補選択、逆引き可能) 108 | - リンク 109 | - ターゲット(<< >>) 110 | - ラジオターゲット(<<< >>>) 111 | - マクロ 112 | - エクスポートスニペット 113 | - 強制改行 114 | - 脚注 115 | - ドロワー 116 | - 各種ブロック 117 | - CALL 118 | - マクロ定義 119 | - 固定幅 120 | - 水平線 121 | - オプションキーワード 122 | 123 | * 設定例 124 | 125 | #+begin_src elisp 126 | (autoload 'org-cmenu "org-cmenu") 127 | (add-hook 'org-mode-hook 128 | (lambda () 129 | ;; メニューを開くキーを設定する 130 | (define-key org-mode-map (kbd "C-^") #'org-cmenu))) ;;キー設定はお好みで。例えば "C-c m" (Menuのm)とか "S-" (Windowsのコンテキストメニューのキー)とか 131 | 132 | (with-eval-after-load "org-cmenu" 133 | ;; メニューの内容を定義する 134 | (require 'org-cmenu-setup) ;; or your setup file 135 | 136 | ;; --------------------------------- 137 | ;; 【カスタムコマンドを追加する例】 138 | 139 | ;; HTMLのdata属性を追加する二つのコマンドを追加する例 140 | (org-cmenu-add-commands 141 | '(:basic "Affiliated Keyword") 142 | '(("ad1" "My Data 1" 143 | (lambda (datum) 144 | (org-cmenu-add-affiliated-keyword "ATTR_HTML" datum) 145 | (insert ":data-my-important1 Very Important Data 1!"))) 146 | ("ad2" "My Data 2" 147 | (lambda (datum) 148 | (org-cmenu-add-affiliated-keyword "ATTR_HTML" datum) 149 | (insert ":data-my-important2 Very Important Data 2!")))) 150 | '(aff-elements ;; affiliated keywordを持つことが出来る要素が対象 151 | :exclude (table) ;;ただしtableは除く 152 | :pred org-cmenu-element-or-first-link-p) ;;elementか段落内の最初のリンクのみ有効 153 | 'with-datum) ;; 構文要素情報を第一引数に渡す 154 | 155 | ;; コマンドを削除する例 156 | (org-cmenu-remove-command 157 | 'all 158 | '(:basic "Affiliated Keyword") "al") ;; attr_latex 159 | 160 | ;; グループを削除する例 161 | ;; (org-cmenu-remove-group 162 | ;; 'all 163 | ;; '(:basic "Affiliated Keyword")) 164 | ) 165 | #+end_src 166 | 167 | * カスタマイズ 168 | 169 | メニューの内容は org-cmenu-setup.el によって定義されている。このファイルがメニューを定義した後に項目を追加・削除してカスタマイズできる。 170 | 171 | また、メニューの内容が大幅に変わる場合は org-cmenu-setup.el をコピーして自分専用のsetupファイルを作成することもできる。その際は、setupファイルの中で次のことを行うと良い。 172 | 173 | - (require 'org-cmenu) 174 | - (org-cmenu-reset) ;;必要に応じて 175 | - org-cmenu-add-groupを呼び出してグループを追加する (必須では無い。グループに特別なプロパティを設定する必要がある場合のみ明示的に呼び出す) 176 | - org-cmenu-add-commandsを呼び出して構文タイプとグループの組み合わせに対してコマンドを追加する 177 | 178 | ** 設定のリセット 179 | 180 | (org-cmenu-reset) を評価するとメニューの内容が全てクリアされる。メニューの内容を確実に保証したい場合や、最初から設定をやり直したくなったときに使用すると良い。 181 | 182 | ** コマンド(関数)の追加方法 183 | 184 | メニューにコマンドを追加するには、次の要素を指定する必要がある。 185 | 186 | - 追加先の[[#syntax-element-types][構文要素タイプ]] 187 | - 追加先のグループ 188 | - 追加するコマンド、割り当てキー、表示タイトル 189 | - コマンドの呼び出し方 190 | 191 | *** 追加先の[[#syntax-element-types][構文要素タイプ]]を指定する 192 | 193 | org-cmenuは構文要素のタイプ毎にメニューの定義を作成する。例えばlink用のメニュー、paragraph用のメニュー、table-cell用のメニュー、等々。 194 | 195 | コマンドを追加するタイプ(メニュー)は /target-spec/ で指定する。 196 | 197 | /target-spec/ はつぎのいずれかである。 198 | 199 | - /type/ : 一つの[[#syntax-element-types][構文要素タイプ]](symbol)を指定する 200 | - ( /type/... /:key/ /value/ /:key/ /value/ ....) : 一つ以上の[[#syntax-element-types][構文要素タイプ]](symbol)を指定し、残りで追加の情報を指定する。 201 | 202 | 具体的な例: 203 | 204 | - 'all :: 全ての[[#syntax-element-types][構文要素タイプ]] 205 | - 'elements :: 全ての非行内要素 206 | - 'objects :: 全ての行内要素 207 | - 'paragraph :: 段落要素のみ 208 | - '(paragraph table-cell) :: 段落とテーブルセル 209 | - '(all :exclude (table table-row table-cell)) :: テーブル類を除いた全て 210 | 211 | *** 追加先のグループを指定する 212 | 213 | メニューの内容はグループの入れ子(ツリー)によって表現されている。このグループはtransientのグループに対応している。 214 | 215 | 第一レベルのグループは上から下へ配置される。第二レベルのグループは左から右へ配置される(transientによって)。 216 | 217 | グループは識別子を持っている。識別子はequal関数で比較できればどんな型の値でも良い。ただし、文字列の場合はグループのタイトルとして表示に使用される。その他、シンボルなどの場合は識別のみに使用され表示には使用されない。 218 | 219 | グループは階層を持っているので、どのグループへ追加するかは group-path (グループ識別子のリスト)で指定する必要がある。 220 | 221 | 具体的な例: 222 | - '("Common") 223 | - '("Table" "Navigation") 224 | - '(:table "Table Navi") ;; :table は文字列ではないので識別には使われるがタイトルには使われない 225 | 226 | コマンドを追加する際、存在しないグループはその都度作成され末尾に追加される。 227 | 228 | *** コマンドの呼び出し規約 229 | 230 | コマンドを追加するにあたって、そのコマンドをどのように呼び出すのかを指定する必要がある。 231 | 232 | - 'no-wrap :: そのまま呼び出す。 233 | - 'with-datum :: 現在選択中の構文要素を第一引数にして呼び出す。 234 | - 'at-begin :: ポイントを現在選択中の構文要素の先頭に移動して呼び出す。 235 | - 'at-post-affiliated :: ポイントを現在選択中の構文要素のaffiliated keywordの直後に移動して呼び出す。 236 | 237 | 例えば次のような状況を考える。 238 | 239 | #+begin_src org 240 | 1. Item1 241 | 2. Item2 242 | 3. Item3 243 | - Item3-1 *現在のポイントここ* 244 | - Item3-2 245 | #+end_src 246 | 247 | 現在のポイントは、plain-list(ordered)の中のitemの中のplain-list(unordered)の中のitemの中のparagraphの中のboldの中にある。 248 | 249 | ユーザーは、現在のポイントを包む全ての親要素(plain-list, item, plain-list, item, paragraph, body)へメニューを切り替えることができる。従ってコマンドは現在どの要素が選択されているのか知らなければ正しい処理を行えない場合がある。 250 | 251 | 例えば構文要素全体をカットするコマンドは、現在選択されているの要素がboldなら*から*までをカットすれば良い。しかしplain-listが選択されているならそのコマンドは「- Item3-1」と「- Item3-2」の二行をカットしなければならない。そういった場合には、'with-datumを指定して第一引数に構文要素の情報を引き渡して貰うか、または、 ~(org-cmenu-target-datum)~ 関数を使用して取得する必要がある。 252 | 253 | #+begin_src elisp 254 | (defun my-cut-element (datum) 255 | (kill-region 256 | (org-element-property :begin datum) 257 | (org-element-property :end datum))) 258 | 259 | (defun my-copy-element (datum) 260 | (kill-ring-save 261 | (org-element-property :begin datum) 262 | (org-element-property :end datum))) 263 | 264 | (org-cmenu-add-commands 265 | '("Common") 266 | '(("x" "Cut Element" my-cut-element) 267 | ("c" "Copy Element" my-copy-element)) 268 | 'all 269 | 'with-datum) 270 | #+end_src 271 | 272 | 逆に対象となる構文要素の情報が不要な場合もある。例えば次のような状況を考える。 273 | 274 | #+begin_src org 275 | | abcdef | *現在のポイントここ* | 276 | | ABCDEF | 123456 | 277 | #+end_src 278 | 279 | 現在のポイントが指す構文要素は、bold、table-cell、table-row、tableとなる。 280 | 281 | table-cellの内容を下に移動するコマンド(org-table-move-cell-down。org-modeに標準で入っている)は、現在のポイントがtable-cell上にありさえすれば良い。表は入れ子に出来ないのでどのtable-cellか曖昧になる事は無い。そのような場合は対象タイプを 'table-cell にして 'no-wrap を指定すればそのままそのコマンドを使うことができる。引数が無くても現在の位置から間違いなく対象のセルが特定できる。 282 | 283 | #+begin_src elisp 284 | (org-cmenu-add-commands 285 | '("Table Cell") 286 | '(("D" "Move Down" org-table-move-cell-down)) 287 | 'table-cell 288 | 'no-wrap) 289 | #+end_src 290 | 291 | * 構文要素タイプ 292 | :PROPERTIES: 293 | :CUSTOM_ID: syntax-element-types 294 | :END: 295 | 296 | org-element.el は org-mode の構文要素を次のように分類している。 297 | 298 | #+begin_example elisp 299 | (defconst org-element-all-elements 300 | '(babel-call center-block clock comment comment-block diary-sexp drawer 301 | dynamic-block example-block export-block fixed-width 302 | footnote-definition headline horizontal-rule inlinetask item 303 | keyword latex-environment node-property paragraph plain-list 304 | planning property-drawer quote-block section 305 | special-block src-block table table-row verse-block) 306 | "Complete list of element types.") 307 | 308 | (defconst org-element-all-objects 309 | '(bold citation citation-reference code entity export-snippet 310 | footnote-reference inline-babel-call inline-src-block italic line-break 311 | latex-fragment link macro radio-target statistics-cookie strike-through 312 | subscript superscript table-cell target timestamp underline verbatim) 313 | "Complete list of object types.") 314 | #+end_example 315 | 316 | org-element.el 内では、objectが行内要素、elementが非行内要素を指しており、その両方を含むあらゆる要素はdatumと呼ばれていることが多い。 317 | 318 | 各要素タイプの具体的な例は [[https://raw.githubusercontent.com/misohena/org-cmenu/main/examples/all-types.org][examples/all-types.org]] を参照のこと。[[https://github.com/misohena/org-cmenu/blob/main/org-cmenu-typedoc.el][org-cmenu-typedoc.el]]にはタイプ名とorg-modeマニュアルへのURLの対応リストが書いてある。org-cmenuのメニューから "?" を押すと選択中の構文要素の説明がブラウザで開くので参考にして欲しい。 319 | 320 | org-cmenuではこれらのタイプ名シンボルが使用できるほか、次の別名も使用できる。 321 | 322 | - all :: org-element-all-elements と org-element-all-objects の各タイプ 323 | - elements :: org-element-all-elements の各タイプ 324 | - objects :: org-element-all-objects の各タイプ 325 | - aff-elements :: Affiliated Keywordsを持てるelement 326 | - com-elements :: コメントアウトできるelement 327 | - contents :: 内容を持つことができる全タイプ(org-cmenu-contents-range 関数を参照) 328 | - buffer :: バッファ全体を表す 329 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Context Sensitive Menu for Emacs Org Mode 2 | #+SUBTITLE: Display the operation menu according to the current syntax element 3 | 4 | (This document has been translated by Google Translate. The original document is [[file:README-ja.org][here]]) 5 | 6 | * Purpose 7 | 8 | This Emacs Lisp code (org-cmenu) provides a mechanism to display a key operation menu according to the current syntax element. 9 | 10 | The type of syntax element is parsed and determined by [[https://orgmode.org/worg/dev/org-element-api.html][org-element.el]], and the menu corresponding to that type is displayed by [[https://github.com/magit/transient/blob/master/lisp/transient.el][transient.el]]. org-cmenu stands in the meantime and plays the following role. 11 | 12 | - Manage what menus are displayed for which [[#syntax-element-types][syntax element types]]. (org-cmenu.el) 13 | - Display the menu in cooperation with org-element and transient, and make adjustments with the called command if necessary. (org-cmenu.el) 14 | - Provides commands that are missing from the org-mode standard for use from menus. (org-cmenu-tools.el) 15 | 16 | * Screenshot 17 | 18 | Example of setting captions and HTML attributes for image links, commenting them out, and uncommenting them again: 19 | 20 | #+CAPTION: Set CAPTION, ATTR_HTML, comment, and uncomment for image links. 21 | #+ATTR_HTML: :width 400 22 | [[file:./screenshot/org-cmenu-image-link.gif]] 23 | 24 | Example of working with a table: 25 | 26 | #+CAPTION: Cut and paste or move part of the table. 27 | #+ATTR_HTML: :width 580 28 | [[file:./screenshot/org-cmenu-table.gif]] 29 | 30 | #+CAPTION: Plain List Menu 31 | [[file:screenshot/org-cmenu_plain-list.png]] 32 | 33 | #+CAPTION: Insert menu 34 | [[file:screenshot/org-cmenu-insert.png]] 35 | 36 | #+CAPTION: Entity Menu 37 | [[file:screenshot/org-cmenu-pretty.png]] 38 | 39 | 40 | * Features implemented in the default menu 41 | 42 | Currently, the functions implemented in the default menu (defined in org-cmenu-setup.el) are as follows. 43 | 44 | - For elements 45 | - Range selection 46 | - Cut 47 | - Copy 48 | - Open the org-mode manual 49 | - Comment / Uncomment 50 | - For contents of the element 51 | - Range selection 52 | - Cut 53 | - Copy 54 | - Exposing the contents (remove the enclosed part) 55 | - Affiliated Keywords (#+ATTR_HTML, #+CAPTION, #+NAME, etc.) 56 | - Add attributes (#+ATTR_ORG, ATTR_HTML, ATTR_LATEX) 57 | - Add name (#+NAME) 58 | - Add caption (#+CAPTION) 59 | - org-speed-command-like operation for headings 60 | - Move cursor 61 | - Visibility control 62 | - Move subtree 63 | - Subtree clone 64 | - Sort 65 | - Archive 66 | - TODO, priority, tag, property change 67 | - Clock operation 68 | - For source block related (#+CALL, #+BEGIN_SRC, call_, src_) 69 | - execution 70 | - Delete result 71 | - Other Babel speed keys (C-c C-v prefix) menu 72 | - For the entire list or list items 73 | - S-expression copy 74 | - Heading 75 | - Shaping 76 | - Checkbox ON / OFF 77 | - Create / delete checkbox 78 | - For property drawers 79 | - Property settings 80 | - For the table 81 | - Move up / down / left / right in cell units 82 | - Cut, copy and paste to rectangular region 83 | - Move cells, rows and columns 84 | - Select cell, row, and column range (mark) 85 | - Transpose 86 | - Width reduction and expansion by TAB and S-TAB 87 | - Shaping 88 | - S-expression copy 89 | - Add / Remove Rows and Columns 90 | - Addition of horizontal line 91 | - Editing expressions, etc. 92 | - Sum total of rows, columns and tables 93 | - For subscripts and superscripts 94 | - ON / OFF of inline display 95 | - For character entities 96 | - ON / OFF of inline display 97 | - List display 98 | - For links 99 | - Edit path and description 100 | - Open (default, system priority, Emacs priority) 101 | - Copy of path 102 | - Display file information 103 | - Statistics Cookie (like [1/3]) 104 | - Update 105 | - For the entire buffer 106 | - Addition of option keyword 107 | - Addition of title information 108 | - Various additions to paragraphs, table cells, list items, various blocks, etc. 109 | - Bold, underlined, italic, verbatim, code, strikethrough 110 | - Superscript, subscript 111 | - Inline CALL, Inline SRC 112 | - Add character entity (candidate selection, reverse lookup possible) 113 | - Link 114 | - Target (<< >>) 115 | - Radio target (<<< >>>) 116 | - Macro 117 | - Export snippet 118 | - Forced line breaks 119 | - Footnote 120 | - Drawer 121 | - Various blocks 122 | - CALL 123 | - Macro definition 124 | - Fixed width 125 | - Horizontal Rule 126 | - Option keyword 127 | * Setup 128 | 129 | #+begin_src elisp 130 | (autoload 'org-cmenu "org-cmenu") 131 | (add-hook 'org-mode-hook 132 | (lambda () 133 | ;; Set the key to open the menu 134 | ;; Assign your favorite key. For example, "C-c m" (Menu m) or "S-" (Windows context menu key) 135 | (define-key org-mode-map (kbd "C-^") #'org-cmenu))) 136 | 137 | (with-eval-after-load "org-cmenu" 138 | ;; Define the contents of the menu 139 | (require 'org-cmenu-setup) ;; or your setup file 140 | 141 | ;; --------------------------------- 142 | ;; [Example of adding a custom command] 143 | 144 | ;; Example of adding two commands to add HTML data attribute 145 | (org-cmenu-add-commands 146 | '(:basic "Affiliated Keyword") 147 | '(("ad1" "My Data 1" 148 | (lambda (datum) 149 | (org-cmenu-add-affiliated-keyword "ATTR_HTML" datum) 150 | (insert ":data-my-important1 Very Important Data 1!"))) 151 | ("ad2" "My Data 2" 152 | (lambda (datum) 153 | (org-cmenu-add-affiliated-keyword "ATTR_HTML" datum) 154 | (insert ":data-my-important2 Very Important Data 2!")))) 155 | '(aff-elements ;; Targets elements that can have affiliated keywords 156 | :exclude (table) ;;However, table is excluded 157 | :pred org-cmenu-element-or-first-link-p) ;;Only valid for element or the first link in a paragraph 158 | 'with-datum) ;; Pass syntax element information as the first argument 159 | 160 | ;; Example of deleting a command 161 | (org-cmenu-remove-command 162 | 'all 163 | '(:basic "Affiliated Keyword") "al") ;; attr_latex 164 | 165 | ;; Example of deleting a group 166 | ;; (org-cmenu-remove-group 167 | ;; 'all 168 | ;; '(:basic "Affiliated Keyword")) 169 | ) 170 | #+end_src 171 | 172 | * Customize 173 | 174 | The contents of the menu are defined by org-cmenu-setup.el. Items can be added / removed and customized after this file defines the menu. 175 | 176 | You can also copy org-cmenu-setup.el to create your own setup file if the menu content changes significantly. In that case, you should do the following in the setup file. 177 | 178 | - (require 'org-cmenu) 179 | - (org-cmenu-reset) ;; as needed 180 | - Call org-cmenu-add-group to add a group (not required, explicitly only if you need to set special properties for the group) 181 | - Call org-cmenu-add-commands to add commands for syntax type and group combinations 182 | 183 | ** Reset Settings 184 | 185 | Evaluating (org-cmenu-reset) clears all menu contents. Use this when you want to guarantee the contents of the menu or when you want to start over from the beginning. 186 | 187 | ** How to Add Commands 188 | 189 | To add a command to the menu, you need to specify the following elements: 190 | 191 | - Target [[#syntax-element-types][syntax element type]] 192 | - Target group 193 | - Commands to add, assigned keys, description 194 | - How to call a command 195 | 196 | *** Specify the target syntax element type 197 | 198 | org-cmenu creates a menu definition for each type of syntax element. For example, a menu for link, a menu for paragraph, a menu for table-cell, and so on. 199 | 200 | The type (menu) to which the command is added is specified by /target-spec/. 201 | 202 | /target-spec/ is one of the following: 203 | 204 | - /type/ :: Specify one [[#syntax-element-types][syntax element type]] (symbol) 205 | - (/type/ ... /:key/ /value/ /:key/ /value/ ...) :: Specify one or more [[#syntax-element-types][syntax element types]] (symbols) and specify additional information in the rest. 206 | 207 | Examples: 208 | 209 | - 'all :: All [[#syntax-element-types][syntax element type]] 210 | - 'elements :: All misconduct elements 211 | - 'objects :: All in-line elements 212 | - 'paragraph :: Paragraph elements only 213 | - '(paragraph table-cell) :: paragraphs and table cells 214 | - '(all: exclude (table table-row table-cell)) :: All except tables 215 | 216 | *** Specify the group to add to 217 | 218 | The contents of the menu are represented by group nesting (trees). This group corresponds to the transient group. 219 | 220 | First level groups are arranged from top to bottom. Second level groups are arranged from left to right (by transient). 221 | 222 | The group has an identifier. The identifier can be any type of value as long as it can be compared with the equal function. However, in the case of a string, it is used for display as the title of the group. In addition, in the case of symbols, etc., they are used only for identification and not for display. 223 | 224 | Since groups have a hierarchy, you need to specify in group-path (list of group identifiers) which group to add to. 225 | 226 | Specific example: 227 | - '("Common") 228 | - '("Table" "Navigation") 229 | - '(:table "Table Navi") ;; :table is not a string, so it is used for identification but not for the title 230 | 231 | Each time you add a command, a non-existent group is created and added to the end. 232 | 233 | *** Command Calling Convention 234 | 235 | When you add a command, you need to specify how to call it. 236 | 237 | - 'no-wrap :: Call as it is. 238 | - 'with-datum :: Call with the currently selected syntax element as the first argument. 239 | - 'at-begin :: Move the point to the beginning of the currently selected syntax element and call it. 240 | - 'at-post-affiliated :: Move and call the point immediately after the affiliated keyword of the currently selected syntax element. 241 | 242 | For example, consider the following situation. 243 | 244 | # + begin_src org 245 | 1. Item1 246 | 2. Item2 247 | 3. Item3 248 | - Item3-1 *Current point here* 249 | - Item3-2 250 | # + end_src 251 | 252 | The current point is in the bold in the paragraph in the item in the plain-list (unordered) in the item in the plain-list (ordered). 253 | 254 | Users can switch the menu to all parent elements (plain-list, item, plain-list, item, paragraph, body) that wrap the current point. Therefore, the command may not be able to perform correct processing without knowing which element is currently selected. 255 | 256 | For example, a command that cuts the entire syntax element can cut * to * if the currently selected element is bold. But if plain-list is selected, the command must cut the two lines "-Item 3-1" and "-Item 3-2". In such a case, you need to specify'with-datum to pass the information of the syntax element to the first argument, or use the (org-cmenu-target-datum) function to get it. 257 | 258 | #+begin_src elisp 259 | (defun my-cut-element (datum) 260 | (kill-region 261 | (org-element-property :begin datum) 262 | (org-element-property :end datum))) 263 | 264 | (defun my-copy-element (datum) 265 | (kill-ring-save 266 | (org-element-property :begin datum) 267 | (org-element-property :end datum))) 268 | 269 | (org-cmenu-add-commands 270 | '("Common") 271 | '(("x" "Cut Element" my-cut-element) 272 | ("c" "Copy Element" my-copy-element)) 273 | 'all 274 | 'with-datum) 275 | #+end_src 276 | 277 | On the contrary, the information of the target syntax element may not be necessary. For example, consider the following situation. 278 | 279 | #+begin_src org 280 | abcdef | *Current point here* | 281 | ABCDEF | 123456 | 282 | #+end_src 283 | 284 | The syntax elements pointed to by the current point are bold, table-cell, table-row, and table. 285 | 286 | The command to move the contents of table-cell down (org-table-move-cell-down, which is included as standard in org-mode) only needs to have the current point on table-cell. Tables cannot be nested, so there is no ambiguity about which table-cell. In such a case, you can use the command as it is by setting the target type to'table-cell and specifying'no-wrap. Even if there is no argument, the target cell can be definitely identified from the current position. 287 | 288 | #+begin_src elisp 289 | (org-cmenu-add-commands 290 | '("Table Cell") 291 | '(("D" "Move Down" org-table-move-cell-down)) 292 | 'table-cell 293 | 'no-wrap) 294 | #+end_src 295 | 296 | * Syntax Element Types 297 | :PROPERTIES: 298 | :CUSTOM_ID: syntax-element-types 299 | :END: 300 | 301 | org-element.el classifies the syntax elements of org-mode as follows: 302 | 303 | #+begin_example elisp 304 | (defconst org-element-all-elements 305 | '(babel-call center-block clock comment comment-block diary-sexp drawer 306 | dynamic-block example-block export-block fixed-width 307 | footnote-definition headline horizontal-rule inlinetask item 308 | keyword latex-environment node-property paragraph plain-list 309 | planning property-drawer quote-block section 310 | special-block src-block table table-row verse-block) 311 | "Complete list of element types.") 312 | 313 | (defconst org-element-all-objects 314 | '(bold citation citation-reference code entity export-snippet 315 | footnote-reference inline-babel-call inline-src-block italic line-break 316 | latex-fragment link macro radio-target statistics-cookie strike-through 317 | subscript superscript table-cell target timestamp underline verbatim) 318 | "Complete list of object types.") 319 | #+end_example 320 | 321 | In org-element.el, `object' refers to an inline element and `element' refers to a non-inline element, and any element that contains both is often called `datum'. 322 | 323 | See [[https://raw.githubusercontent.com/misohena/org-cmenu/main/examples/all-types.org][examples/all-types.org]] for specific examples of each element type. [[https://github.com/misohena/org-cmenu/blob/main/org-cmenu-typedoc.el][org-cmenu-typedoc.el]] contains a list of correspondences between type names and URLs to the org-mode manual. If you press "?" From the menu of org-cmenu, the explanation of the selected syntax element will open in the browser, so please refer to it. 324 | 325 | In addition to using these type name symbols in org-cmenu, you can also use the following aliases: 326 | 327 | - all :: org-element-all-elements and org-element-all-objects types 328 | - elements :: org-element-all-elements types 329 | - objects :: org-element-all-objects types 330 | - aff-elements :: elements with Affiliated Keywords 331 | - com-elements :: elements that can be commented out 332 | - contents :: All types that can have contents (see org-cmenu-contents-range function) 333 | - buffer :: represents the entire buffer 334 | -------------------------------------------------------------------------------- /org-cmenu-setup.el: -------------------------------------------------------------------------------- 1 | ;;; org-cmenu-setup.el --- Context Menu for Org-Mode -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2021 AKIYAMA Kouhei 4 | 5 | ;; Author: AKIYAMA Kouhei 6 | ;; Keywords: 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | 23 | ;; This file defines the menu contents for each element type. 24 | 25 | ;;; Code: 26 | 27 | (require 'org) 28 | (require 'org-cmenu) 29 | (require 'org-cmenu-tools) 30 | (require 'org-cmenu-typedoc) 31 | 32 | (defvar org-cmenu-setup-reset t) 33 | (when org-cmenu-setup-reset 34 | (org-cmenu-reset)) 35 | 36 | ;;;; All 37 | 38 | (org-cmenu-add-group 'all '(:hidden) :hide t) 39 | 40 | (org-cmenu-add-commands 41 | '(:hidden) 42 | '(("C-l" "Recenter" recenter :transient t) 43 | ("C-^" "Parent" org-cmenu-select-parent) 44 | ("C-\\" "Child" org-cmenu-select-child)) 45 | 'all 46 | 'no-wrap) 47 | 48 | (org-cmenu-add-commands 49 | '(:basic "Outside") 50 | '((";" "Comment" org-cmenu-comment-enclosing-element))) 51 | 52 | (org-cmenu-add-commands 53 | '(:basic "Whole") 54 | '(("m" "Mark" org-cmenu-mark-datum) 55 | ("C-w" "Kill" org-cmenu-kill-datum) 56 | ("M-w" "Copy" org-cmenu-copy-datum) 57 | ("N" "Narrow" org-cmenu-narrow-to-datum) 58 | ("N" "Widen" org-cmenu-widen-buffer) 59 | ("?" "Manual" org-cmenu-browse-type-document) 60 | (";" "Comment" org-cmenu-comment-element))) 61 | 62 | (org-cmenu-add-commands 63 | '(:basic "Contents") 64 | '(("[ m" "Mark" org-cmenu-mark-contents) 65 | ("[ C-w" "Kill" org-cmenu-kill-contents) 66 | ("[ M-w" "Copy" org-cmenu-copy-contents) 67 | ("[ N" "Narrow/Widen" org-cmenu-toggle-narrow-contents) 68 | ("[ e" "Expose" org-cmenu-expose-contents))) 69 | 70 | (org-cmenu-add-commands 71 | '(:basic "Affiliated Keyword") 72 | '(("ao" "ATTR_ORG" org-cmenu-add-attr-org) 73 | ("ah" "ATTR_HTML" org-cmenu-add-attr-html) 74 | ("al" "ATTR_LATEX" org-cmenu-add-attr-latex) 75 | ("an" "NAME" org-cmenu-add-name) 76 | ("ac" "CAPTION" org-cmenu-add-caption))) 77 | 78 | ;;;; Element 79 | ;;;;; section 80 | ;;;;; headline 81 | 82 | ;; ref: 83 | ;; - org-org-menu (in org.el) 84 | ;; - org-keys.el 85 | ;; - (org-speed-command-help) 86 | 87 | ;;@todo Fix getting out of the headline 88 | (org-cmenu-add-commands 89 | '(:headline "Navi") 90 | '(("u" "Up" outline-up-heading :transient t) 91 | ("p" "Prev" outline-previous-visible-heading :transient t) 92 | ("n" "Next" outline-next-visible-heading :transient t) 93 | ("b" "Prev(SameLv)" outline-backward-same-level :transient t) 94 | ("f" "Next(SameLv)" outline-forward-same-level :transient t) 95 | ("j" "GoTo" org-goto)) 96 | '(headline) 97 | 'no-wrap) 98 | 99 | (org-cmenu-add-commands 100 | '(:headline2 "Visibility") 101 | '(("TAB" "Cycle" org-cycle :transient t) 102 | ("S-TAB" "Cycle" org-shifttab :transient t)) 103 | '(headline) 104 | 'no-wrap) 105 | 106 | (org-cmenu-add-commands 107 | '(:headline "Structure") 108 | '(("M-p" "Subtree Up" org-metaup :transient t) 109 | ("M-n" "Subtree Down" org-metadown :transient t) 110 | ("M-b" "<=Heading" org-metaleft :transient t) 111 | ("M-f" " Heading=>" org-metaright :transient t) 112 | ("M-B" "<=Subtree" org-shiftmetaleft :transient t) 113 | ("M-F" " Subtree=>" org-shiftmetaright :transient t) 114 | ) 115 | '(headline) 116 | 'no-wrap) 117 | 118 | (org-cmenu-add-string 119 | '(headline) 120 | '(:headline :structure2) 121 | " ") 122 | (org-cmenu-add-commands 123 | '(:headline :structure2) 124 | '(("l" "Clone" org-clone-subtree-with-time-shift :transient t) 125 | ("s" "Sort" org-sort) 126 | ("w" "Refile" org-refile) 127 | ("$" "Archive" org-archive-subtree-default-with-confirmation)) 128 | '(headline) 129 | 'no-wrap) 130 | 131 | (org-cmenu-add-commands 132 | '(:headline "Data") 133 | '(("t" "TODO" org-todo :transient t) 134 | ("," "Priority" org-priority :transient t) 135 | (":" "Tag" org-set-tags-command :transient t) 136 | ("P" "Property" org-set-property :transient t) 137 | ("a" "Archive Tag" org-toggle-archive-tag :transient t)) 138 | '(headline) 139 | 'no-wrap) 140 | 141 | (org-cmenu-add-commands 142 | '(:headline "Clock") 143 | '(("ci" "In" org-clock-in :transient t) 144 | ("co" "Out" org-clock-out :transient t) 145 | ("cc" "Cancel" org-clock-cancel :transient t) 146 | ("cs" "Switch" (lambda () (interactive) (org-clock-in '(4))) :transient t)) 147 | '(headline) 148 | 'no-wrap) 149 | 150 | (org-cmenu-add-group '(headline) '(:headline-hidden) :hide t) 151 | 152 | (org-cmenu-add-commands 153 | '(:headline-hidden) 154 | '(("C-u" "Up" outline-up-heading :transient t) 155 | ("C-p" "Prev" outline-previous-visible-heading :transient t) 156 | ("C-n" "Next" outline-next-visible-heading :transient t) 157 | ("C-b" "Prev(SameLv)" outline-backward-same-level :transient t) 158 | ("C-f" "Next(SameLv)" outline-forward-same-level :transient t) 159 | ("" "Cycle" org-shifttab :transient t)) 160 | '(headline) 161 | 'no-wrap) 162 | 163 | ;;;;; babel-call 164 | (org-cmenu-add-commands 165 | '(:babel-call "Babel Call") 166 | '(("e" "Execute" org-babel-execute-maybe) 167 | ("k" "Remove Result" org-babel-remove-result-one-or-many)) 168 | '(babel-call) 169 | 'no-wrap) 170 | 171 | ;;;;; center-block, quote-block, verse-block 172 | ;;;;; clock 173 | ;;;;; comment 174 | ;; uncomment => comment 175 | ;;;;; comment-block 176 | ;; uncomment => expose 177 | ;;;;; dynamic-block 178 | ;;;;; example-block 179 | ;;;;; export-block 180 | ;;;;; special-block 181 | ;;;;; fixed-width 182 | ;;;;; footnote-definition 183 | ;;;;; horizontal-rule 184 | ;;;;; inlinetask 185 | ;;;;; plain-list 186 | (org-cmenu-add-commands 187 | '(:list "List") 188 | `(("S" "Copy as S-Exp" org-cmenu-plain-list-copy-as-sexp) 189 | ("t" "Make Subtree" org-cmenu-plain-list-make-subtree) 190 | ("s" "Sort" ,(org-cmenu-wrap-command-at-post-affiliated #'org-sort-list)) 191 | ("r" "Repair" org-cmenu-plain-list-repair) 192 | ("b" "Checkbox On/Off" 193 | (lambda () (interactive) 194 | (org-cmenu-mark-datum (org-cmenu-target-datum)) 195 | (org-toggle-checkbox))) 196 | ("B" "Checkbox Add/Remove" 197 | (lambda () (interactive) 198 | (org-cmenu-mark-datum (org-cmenu-target-datum)) 199 | (org-toggle-checkbox '(4))))) 200 | ;;@todo convert ordered-unordered 201 | 'plain-list 202 | 'no-wrap) 203 | 204 | ;;;;; item 205 | (org-cmenu-add-commands 206 | '(:item "Item") 207 | `(("b" "Checkbox On/Off" org-toggle-checkbox) 208 | ("B" "Checkbox Add/Remove" (lambda () (interactive) (org-toggle-checkbox '(4))))) 209 | 'item 210 | 'no-wrap) 211 | 212 | ;;;;; keyword 213 | ;;;;; diary-sexp 214 | ;;;;; latex-environment 215 | ;;;;; paragraph 216 | ;;;;; planning 217 | ;; org-todo 218 | ;; org-schedule 219 | ;; org-deadline 220 | ;; org-cancel-repeater 221 | 222 | ;;;;; drawer 223 | ;;;;; property-drawer 224 | (org-cmenu-add-commands 225 | '(:property-drawer "Property Drawer") 226 | '(("p" "Set Property" org-set-property)) 227 | 'property-drawer 228 | 'no-wrap) 229 | 230 | ;;;;; node-property 231 | (org-cmenu-add-commands 232 | '(:node-property "Node Property") 233 | '(("p" "Set Property" org-set-property)) 234 | 'node-property 235 | 'no-wrap) 236 | 237 | ;;;;; src-block 238 | (transient-define-prefix org-cmenu-transient-prefix-for-babel () 239 | "The key prefix for Babel interactive key-bindings." 240 | [["Edit" 241 | ("j" "Insert Header Arg" org-babel-insert-header-arg) 242 | ("v" "Expand" org-babel-expand-src-block) 243 | ("d" "Demarcate" org-babel-demarcate-block) 244 | ("x" "Do Key Sequence" org-babel-do-key-sequence-in-edit-buffer)] 245 | ["Move" 246 | ("p" "Previous" org-babel-previous-src-block :transient t) 247 | ("n" "Next" org-babel-next-src-block :transient t) 248 | ("u" "Goto Head" org-babel-goto-src-block-head) 249 | ;;("g" "Goto Named Block" org-babel-goto-named-src-block) 250 | ;;("r" "Goto Named Result" org-babel-goto-named-result) 251 | ("C-M-h" "Mark Block" org-babel-mark-block)] 252 | ["Execute & Result" 253 | ("e" "Execute" org-babel-execute-maybe) 254 | ;;("b" "Execute Buffer" org-babel-execute-buffer) 255 | ;;("s" "Execute Subtree" org-babel-execute-subtree) 256 | ("o" "Open Result" org-babel-open-src-block-result) 257 | ("k" "Remove Result" org-babel-remove-result-one-or-many)]] 258 | [["Info" 259 | ("i" "View Info" org-babel-view-src-block-info) 260 | ("c" "Check" org-babel-check-src-block) 261 | ("a" "SHA1 Hash" org-babel-sha1-hash)] 262 | ["Session" 263 | ("l" "Load in Session" org-babel-load-in-session) 264 | ("Z" "Switch to Session" org-babel-switch-to-session) 265 | ("z" "Switch to Session With Code" org-babel-switch-to-session-with-code)] 266 | ["Tangle" 267 | ("t" "Tangle" org-babel-tangle) 268 | ;;("f" "Tangle File" org-babel-tangle-file) 269 | ;;("i" "LOB Ingest" org-babel-lob-ingest) 270 | ;;("h" "Describe Bindings" org-babel-describe-bindings) 271 | ]]) 272 | (org-cmenu-add-commands 273 | '(:src-block "Src Block") 274 | '(("v" "Babel Keys" org-cmenu-transient-prefix-for-babel)) 275 | 'src-block 276 | 'no-wrap) 277 | 278 | ;;;;; table, table-row, table-cell(object) 279 | 280 | ;; @todo Add Gnu Plot support? 281 | 282 | (org-cmenu-add-group '(table table-row table-cell) '(:table-hidden) :hide t) 283 | 284 | (org-cmenu-add-commands 285 | '(:table "Table") 286 | '(("}" "Toggle Coordinate" org-table-toggle-coordinate-overlays :transient t) 287 | ("{" "Toggle Formula Dbg" org-table-toggle-formula-debugger) 288 | ("'" "Edit Formula" org-table-edit-formulas) 289 | ("tM" "Mark Fields" org-cmenu-table-mark-all :transient t) 290 | ("t+" "Sum" org-cmenu-table-sum-all :transient t) 291 | ("ts" "Sort" org-table-sort-lines) 292 | ("tt" "Transpose" org-table-transpose-table-at-point) 293 | ("S-TAB" "Cycle Width" org-cmenu-table-cycle-column-width :transient t)) 294 | '(table table-row table-cell) 295 | 'no-wrap) 296 | 297 | (org-cmenu-add-commands 298 | '(:table-hidden) 299 | '(("" "Cycle Width" org-cmenu-table-cycle-column-width :transient t)) 300 | '(table table-row table-cell) 301 | 'no-wrap) 302 | 303 | (org-cmenu-add-commands 304 | '(:table "Table2") 305 | '(("A" "Align" org-table-align) 306 | ("e" "Export File" org-table-export) 307 | ("S" "Copy as S-Exp" org-cmenu-table-copy-as-sexp)) 308 | '(table :forced t) 309 | 'no-wrap) 310 | 311 | ;; Line (Row/Column) 312 | 313 | (transient-define-prefix org-cmenu-table-move-line () 314 | "Move table line left/right/up/down." 315 | ["Move Line" 316 | [("p" "Row Up" org-table-move-row-up :transient t) 317 | ("n" "Row Down" org-table-move-row-down :transient t) 318 | ("b" "Column Left" org-table-move-column-left :transient t) 319 | ("f" "Column Right" org-table-move-column-right :transient t)] 320 | [("C-p" "Row Up" org-table-move-row-up :transient t) 321 | ("C-n" "Row Down" org-table-move-row-down :transient t) 322 | ("C-b" "Column Left" org-table-move-column-left :transient t) 323 | ("C-f" "Column Right" org-table-move-column-right :transient t)]] 324 | (interactive) 325 | (when-let ((key-str (this-command-keys)) 326 | (cmd (pcase (key-description (substring key-str -1)) 327 | ((or "p" "C-p") #'org-table-move-row-up) 328 | ((or "n" "C-n") #'org-table-move-row-down) 329 | ((or "b" "C-b") #'org-table-move-column-left) 330 | ((or "f" "C-f") #'org-table-move-column-right)))) 331 | (call-interactively cmd)) 332 | (transient-setup 'org-cmenu-table-move-line)) 333 | 334 | ;; Row 335 | 336 | (transient-define-prefix org-cmenu-table-move-row () 337 | "Move table row up/down." 338 | ["Move Row" 339 | [("p" "Up" org-table-move-row-up :transient t) 340 | ("n" "Down" org-table-move-row-down :transient t)] 341 | [("C-p" "Up" org-table-move-row-up :transient t) 342 | ("C-n" "Down" org-table-move-row-down :transient t)]] 343 | (interactive) 344 | (when-let ((key-str (this-command-keys)) 345 | (cmd (pcase (key-description (substring key-str -1)) 346 | ((or "p" "C-p") #'org-table-move-row-up) 347 | ((or "n" "C-n") #'org-table-move-row-down)))) 348 | (call-interactively cmd)) 349 | (transient-setup 'org-cmenu-table-move-row)) 350 | 351 | (org-cmenu-add-commands 352 | '(:table "Row") 353 | '(("rp" "Move Up" org-cmenu-table-move-row) 354 | ("rn" "Move Down" org-cmenu-table-move-row) 355 | ("rm" "Mark Fields" org-cmenu-table-mark-row :transient t) 356 | ("r+" "Sum" org-cmenu-table-sum-row :transient t) 357 | ("ri" "Insert" org-table-insert-row :transient t) 358 | ("rk" "Kill" org-table-kill-row :transient t) 359 | ("_" "HLine" org-table-insert-hline :transient t) 360 | ("-" "HLine & Down" org-table-hline-and-move :transient t)) 361 | ;;("r=" "Set Formula" ;;@todo Set row formula @N= 362 | '(table-row table-cell) 363 | 'no-wrap) 364 | (org-cmenu-add-commands 365 | '(:table-hidden) 366 | '(("r C-p" "Move Up" org-cmenu-table-move-row) 367 | ("r C-n" "Move Down" org-cmenu-table-move-row) 368 | ("l C-p" "Move Up" org-cmenu-table-move-line) 369 | ("l C-n" "Move Down" org-cmenu-table-move-line) 370 | ("" "Move Up" org-table-move-row-up :transient t) 371 | ("" "Move Left" org-table-move-row-down :transient t) 372 | ("" "Kill" org-table-kill-row :transient t) 373 | ("" "Insert" org-table-insert-row :transient t)) 374 | '(table-cell) 375 | 'no-wrap) 376 | 377 | ;; Column 378 | 379 | (transient-define-prefix org-cmenu-table-move-column () 380 | "Move table column left/right." 381 | ["Move Column" 382 | [("b" "Left" org-table-move-column-left :transient t) 383 | ("f" "Right" org-table-move-column-right :transient t)] 384 | [("C-b" "Left" org-table-move-column-left :transient t) 385 | ("C-f" "Right" org-table-move-column-right :transient t)]] 386 | (interactive) 387 | (when-let ((key-str (this-command-keys)) 388 | (cmd (pcase (key-description (substring key-str -1)) 389 | ((or "b" "C-b") #'org-table-move-column-left) 390 | ((or "f" "C-f") #'org-table-move-column-right)))) 391 | (call-interactively cmd)) 392 | (transient-setup 'org-cmenu-table-move-column)) 393 | 394 | (org-cmenu-add-commands 395 | '(:table "Column") 396 | ;;NOTE: "c" key is not assigned to "comment" in table-cell. 397 | '(("cf" "Move Right" org-cmenu-table-move-column) 398 | ("cb" "Move Left" org-cmenu-table-move-column) 399 | ("cm" "Mark Fields" org-cmenu-table-mark-column :transient t) 400 | ("c+" "Sum" org-table-sum :transient t) 401 | ("ci" "Insert" org-table-insert-column :transient t) 402 | ("cd" "Delete" org-table-delete-column :transient t) 403 | ("TAB" "Toggle Width" org-table-toggle-column-width :transient t) 404 | ("c=" "Set Formula" org-table-eval-formula :transient t)) 405 | ;; insert column width 406 | '(table-cell) 407 | 'no-wrap) 408 | (org-cmenu-add-commands 409 | '(:table-hidden) 410 | '(("c C-f" "Move Right" org-cmenu-table-move-column) 411 | ("c C-b" "Move Left" org-cmenu-table-move-column) 412 | ("l C-f" "Move Right" org-cmenu-table-move-line) 413 | ("l C-b" "Move Left" org-cmenu-table-move-line) 414 | ("" "Move Right" org-table-move-column-right :transient t) 415 | ("" "Move Left" org-table-move-column-left :transient t) 416 | ("" "Insert" org-table-insert-column :transient t) 417 | ("" "Delete" org-table-delete-column :transient t)) 418 | '(table-cell) 419 | 'no-wrap) 420 | 421 | ;; Field(Cell) 422 | 423 | (transient-define-prefix org-cmenu-table-move-cell () 424 | "Move table cell." 425 | ["Move Cell" 426 | [("b" "Left" org-table-move-cell-left :transient t) 427 | ("f" "Right" org-table-move-cell-right :transient t) 428 | ("p" "Up" org-table-move-cell-up :transient t) 429 | ("n" "Down" org-table-move-cell-down :transient t)] 430 | [("C-b" "Left" org-table-move-cell-left :transient t) 431 | ("C-f" "Right" org-table-move-cell-right :transient t) 432 | ("C-p" "Up" org-table-move-cell-up :transient t) 433 | ("C-n" "Down" org-table-move-cell-down :transient t)]] 434 | (interactive) 435 | (when-let ((key-str (this-command-keys)) 436 | (cmd (pcase (key-description (substring key-str -1)) 437 | ((or "b" "C-b") #'org-table-move-column-left) 438 | ((or "f" "C-f") #'org-table-move-column-right)))) 439 | (call-interactively cmd)) 440 | (transient-setup 'org-cmenu-table-move-cell)) 441 | 442 | 443 | (org-cmenu-add-commands 444 | '(:table "Field") 445 | '(("." "Move" org-cmenu-table-move-cell) 446 | ("m" "Mark Field" org-cmenu-table-mark-field :transient t) 447 | ("h" "Info" org-table-field-info :transient t) 448 | ("=" "Set Formula" (lambda () (interactive) (org-table-eval-formula '(4))) :transient t) 449 | (":" "Edit Formula" (lambda () (interactive) (org-table-eval-formula '(16))) :transient t) 450 | ("e" "Edit Field" org-table-edit-field) 451 | ;;@todo insert org-recalc-marks 452 | ) 453 | '(table-cell) 454 | 'no-wrap) 455 | 456 | (org-cmenu-add-commands 457 | '(:table-hidden) 458 | '(("" "Move Cell Right" org-table-move-cell-right :transient t) 459 | ("" "Move CellLeft" org-table-move-cell-left :transient t) 460 | ("" "Move Cell Up" org-table-move-cell-up :transient t) 461 | ("" "Move Cell Down" org-table-move-cell-down :transient t)) 462 | '(table-cell) 463 | 'no-wrap) 464 | 465 | ;; Region 466 | 467 | (org-cmenu-remove-group '(table-cell) '(:basic "Whole")) 468 | (org-cmenu-add-commands 469 | '(:basic "Region/Field") 470 | '(("C-SPC" "Mark" set-mark-command :transient t) 471 | ("C-w" "Cut" org-cmenu-table-cut-region :transient t) 472 | ("M-w" "Copy" org-cmenu-table-copy-region :transient t) 473 | ("C-y" "Paste" org-table-paste-rectangle :transient t) 474 | ("+" "Sum" org-table-sum :transient t) 475 | ;; ("=" "Set Formula" ;; @todo Set range formula @N$M..@N$M= or current field 476 | ) 477 | '(table-cell) 478 | 'no-wrap) 479 | 480 | ;; Navigation 481 | 482 | (org-cmenu-add-commands 483 | '(:table-hidden) 484 | '(("C-b" "Left" org-cmenu-table-previous-column :transient t) 485 | ("C-f" "Right" org-cmenu-table-next-column :transient t) 486 | ("C-p" "Up" org-cmenu-table-previous-row :transient t) 487 | ("C-n" "Down" org-cmenu-table-next-row :transient t) 488 | ("C-a" "First Column" org-cmenu-table-first-column-in-row :transient t) 489 | ("C-e" "Last Column" org-cmenu-table-last-column-in-row :transient t) 490 | ("M-<" "First Field" org-cmenu-table-first-field-in-table :transient t) 491 | ("M->" "Last Field" org-cmenu-table-last-field-in-table :transient t) 492 | ("C-v" "Scroll Up" org-cmenu-table-scroll-up :transient t) 493 | ("M-v" "Scroll Down" org-cmenu-table-scroll-down :transient t)) 494 | '(table-cell) 495 | 'no-wrap) 496 | 497 | (org-cmenu-add-string '(table-cell) '(:basic "Navigation") 498 | "C-b,f,p,n") 499 | (org-cmenu-add-string '(table-cell) '(:basic "Navigation") 500 | "C-a,e") 501 | (org-cmenu-add-string '(table-cell) '(:basic "Navigation") 502 | "M-<,>") 503 | (org-cmenu-add-string '(table-cell) '(:basic "Navigation") 504 | "C-v,M-v") 505 | 506 | ;;;; Object 507 | ;;;;; bold, underline, italic, verbatim, code, strike-through 508 | ;;;;; subscript, superscript 509 | 510 | (org-cmenu-add-commands 511 | '(:subscript "Subscript/Superscript") 512 | '(("p" "Pretty" org-toggle-pretty-entities)) 513 | '(subscript superscript) 514 | 'no-wrap) 515 | 516 | ;;;;; line-break 517 | ;;;;; citation 518 | ;;;;; citation-reference 519 | ;;;;; entity 520 | 521 | (org-cmenu-add-commands 522 | '(:entity "Entity") 523 | '(("p" "Pretty" org-toggle-pretty-entities) 524 | ("h" "List" org-entities-help)) 525 | '(entity) 526 | 'no-wrap) 527 | 528 | ;;;;; export-snippet 529 | ;;;;; footnote-reference 530 | ;;;;; inline-babel-call 531 | (org-cmenu-add-commands 532 | '(:babel-call "Babel Call") 533 | '(("e" "Execute" org-babel-execute-maybe) 534 | ("k" "Remove Result" org-babel-remove-inline-result)) 535 | '(inline-babel-call) 536 | 'no-wrap) 537 | 538 | ;;;;; inline-src-block 539 | (org-cmenu-add-commands 540 | '(:inline-src-block "Inline Src Block") 541 | '(("e" "Execute" org-babel-execute-maybe) 542 | ("k" "Remove Result" org-babel-remove-inline-result) 543 | ("i" "Info" org-babel-view-src-block-info) 544 | ("a" "SHA1 Hash" org-babel-sha1-hash) 545 | ) 546 | '(inline-src-block) 547 | 'no-wrap) 548 | 549 | ;;;;; link 550 | (org-cmenu-add-commands 551 | '(:link "Link") 552 | '(("e" "Edit Path/Desc" org-insert-link) 553 | ("o" "Open" org-cmenu-link-open-by-default) 554 | ("x" "Open by system" org-cmenu-link-open-by-system) 555 | ("i" "Open by emacs" org-cmenu-link-open-by-emacs) 556 | ("cp" "Copy Path" org-cmenu-link-copy-path) 557 | ("TAB" "Toggle Link Display" org-toggle-link-display)) 558 | '(link) 559 | 'no-wrap) 560 | 561 | (org-cmenu-add-commands 562 | '(:link "File") 563 | '(("d" "Open Directory" org-cmenu-link-open-directory) 564 | ("cf" "Copy File Name" org-cmenu-link-copy-file-name) 565 | ("fi" "Info" org-cmenu-link-show-file-info) 566 | ("fr" "Rename" org-cmenu-link-rename-file)) 567 | '(link) 568 | 'no-wrap) 569 | 570 | (org-cmenu-add-commands 571 | '(:link "Navigation") 572 | '(("p" "Prev Link" org-previous-link :transient t) 573 | ("n" "Next Link" org-next-link :transient t)) 574 | '(link) 575 | 'no-wrap) 576 | 577 | (org-cmenu-add-group '(link) '(:link-hidden) :hide t) 578 | 579 | (org-cmenu-add-commands 580 | '(:link-hidden) 581 | '(("C-p" "Prev Link" org-previous-link :transient t) 582 | ("C-n" "Next Link" org-next-link :transient t)) 583 | '(link) 584 | 'no-wrap) 585 | 586 | ;;;;; latex-fragment 587 | ;;;;; macro 588 | 589 | ;;@todo search macro definition 590 | 591 | ;;;;; radio-target 592 | 593 | ;;@todo search (org-occur?) target text 594 | 595 | ;;;;; statistics-cookie 596 | 597 | (org-cmenu-add-commands 598 | '(:statistics-cookie "Statistics Cookie") 599 | '(("u" "Update" org-update-statistics-cookies)) 600 | '(statistics-cookie) 601 | 'no-wrap) 602 | 603 | ;;;;; target 604 | 605 | ;;@todo search (org-occur?) target text 606 | 607 | ;;;;; timestamp 608 | 609 | ;;;; Insert 610 | 611 | (transient-define-prefix org-cmenu-insert () 612 | "Insert" 613 | [["Emphasis" 614 | ("b" "Bold" org-cmenu-insert-bold) 615 | ("u" "Underline" org-cmenu-insert-underline) 616 | ("i" "Italic" org-cmenu-insert-italic) 617 | ("v" "Verbatim" org-cmenu-insert-verbatim) 618 | ("c" "Code" org-cmenu-insert-code) 619 | ("+" "Strike" org-cmenu-insert-strike-through)] 620 | ["Super/Subscript" 621 | ("_" "Subscript" org-cmenu-insert-subscript) 622 | ("^" "Superscript" org-cmenu-insert-superscript) 623 | "" 624 | "Babel" 625 | ("C" "Inline Call" org-cmenu-insert-inline-babel-call) 626 | ("S" "Inline Src" org-cmenu-insert-inline-src-block)] 627 | ["Others" 628 | ("e" "Entity" org-cmenu-insert-entity) 629 | ("l" "Link" org-insert-link) 630 | ("t" "Target" org-cmenu-insert-target) 631 | ("r" "Radio Target" org-cmenu-insert-radio-target) 632 | ("m" "Macro" org-cmenu-insert-macro) 633 | ("@" "Export Snippet" org-cmenu-insert-export-snippet) 634 | ("f" "Footnote" org-footnote-new) 635 | ("RET" "Line Break" org-cmenu-insert-line-break) ;;exclude table-cell 636 | ] 637 | ["Elements" 638 | :if (lambda () (eq (org-element-type (org-cmenu-target-datum)) 'section)) 639 | ("d" "Drawer" org-insert-drawer) 640 | ("," "#+BEGIN_?" org-insert-structure-template) 641 | ("a" "#+CALL" org-cmenu-insert-babel-call) 642 | ("M" "#+MACRO" org-cmenu-insert-macro-definition) 643 | (":" "Fixed Width" org-cmenu-insert-fixed-width) 644 | ("x" "Dynamic Block" org-dynamic-block-insert-dblock) 645 | ("-" "Horizontal" org-cmenu-insert-horizontal-rule) 646 | ("o" "Option" org-cmenu-insert-option-keyword)] 647 | ]) 648 | 649 | (org-cmenu-add-commands 650 | '(:basic "Insert") 651 | '(("i" "Insert" org-cmenu-insert)) 652 | '(section paragraph table-cell item 653 | center-block quote-block special-block dynamic-block 654 | drawer footnote-definition) 655 | 'no-wrap) 656 | 657 | ;;@todo Add commands to decorate region. bold, italic, etc. 658 | 659 | ;;@todo Add commands to convert region to block. 660 | 661 | ;;;; Buffer 662 | 663 | (org-cmenu-add-commands 664 | '(:buffer "Insert") 665 | '(("i+" "#+" org-cmenu-insert-option-keyword-at-top) 666 | ("it" "#+TITLE:" org-cmenu-insert-title-info)) 667 | '(buffer) 668 | 'no-wrap) 669 | 670 | 671 | (provide 'org-cmenu-setup) 672 | ;;; org-cmenu-setup.el ends here 673 | -------------------------------------------------------------------------------- /org-cmenu.el: -------------------------------------------------------------------------------- 1 | ;;; org-cmenu.el --- Context Menu for Org-Mode -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2021 AKIYAMA Kouhei 4 | 5 | ;; Author: AKIYAMA Kouhei 6 | ;; Keywords: 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | 23 | ;; This file provides a mechanism for defining and displaying menus. 24 | 25 | ;; - Define Menu 26 | ;; - Reset 27 | ;; - org-cmenu-reset 28 | ;; - Command 29 | ;; - org-cmenu-add-commands 30 | ;; - org-cmenu-add-command 31 | ;; - org-cmenu-remove-command 32 | ;; - Command Adapter 33 | ;; - org-cmenu-wrap-command 34 | ;; - org-cmenu-target-datum 35 | ;; - Group 36 | ;; - org-cmenu-add-group 37 | ;; - org-cmenu-set-group-property 38 | ;; - org-cmenu-remove-group 39 | ;; - String 40 | ;; - org-cmenu-add-string 41 | ;; - Display Menu 42 | ;; - org-cmenu 43 | ;; - org-cmenu-update-transient-prefixes 44 | 45 | ;;; Code: 46 | 47 | (require 'subr-x) 48 | (require 'cl-lib) 49 | (require 'transient) 50 | (require 'org-element) 51 | 52 | ;;;; Customize 53 | 54 | (defgroup org-cmenu nil 55 | "Context Menu for Org-Mode." 56 | :prefix "org-cmenu-" 57 | :group 'org) 58 | 59 | (defcustom org-cmenu-update-transient-prefix-everytime t 60 | "If non-nil, redefine the transient prefix each time the menu is displayed. 61 | 62 | If nil you should use `org-cmenu-update-transient-prefixes' function." 63 | :type 'boolean 64 | :group 'org-cmenu) 65 | 66 | ;;;; Reset 67 | 68 | (defun org-cmenu-reset () 69 | (interactive) 70 | (org-cmenu-reset-type-aliases) 71 | (org-cmenu-reset-types)) 72 | 73 | ;;;; Syntax Element Type Aliases 74 | 75 | (defvar org-cmenu-type-aliases nil) 76 | 77 | (defun org-cmenu-reset-type-aliases () 78 | (setq org-cmenu-type-aliases nil) 79 | (org-cmenu-define-standard-type-aliases)) 80 | 81 | (defun org-cmenu-type-alias-to-ids (alias) 82 | (alist-get alias org-cmenu-type-aliases)) 83 | 84 | (defun org-cmenu-define-type-alias (alias type-ids) 85 | (setf (alist-get alias org-cmenu-type-aliases) type-ids)) 86 | 87 | (defun org-cmenu-define-standard-type-aliases () 88 | ;; Buffer pseudo element (see: org-cmenu-element-lineage) 89 | (org-cmenu-define-type-alias 'buffer '(buffer org-data)) 90 | ;; Each elements (see: org-element-all-elements) 91 | (dolist (type org-element-all-elements) 92 | (org-cmenu-define-type-alias type (list type))) 93 | ;; Each objects (see: org-element-all-objects) 94 | (dolist (type org-element-all-objects) 95 | (org-cmenu-define-type-alias type (list type))) 96 | ;; All elements and all objects (all) 97 | (org-cmenu-define-type-alias 'all 98 | (append 99 | org-element-all-elements 100 | org-element-all-objects)) 101 | ;; All elements (elements) 102 | (org-cmenu-define-type-alias 'elements org-element-all-elements) 103 | ;; All objects (objects) 104 | (org-cmenu-define-type-alias 'objects org-element-all-objects) 105 | ;; Elements that can have affiliated keywords (aff-elements) 106 | (org-cmenu-define-type-alias 'aff-elements 107 | (seq-difference 108 | org-element-all-elements 109 | '(item table-row table-cell 110 | section headline) 111 | #'eq)) 112 | ;; Elements that can be comment (com-elements) 113 | (org-cmenu-define-type-alias 'com-elements 114 | (append 115 | '(buffer) 116 | (seq-difference 117 | org-element-all-elements 118 | '(item table-row table-cell) 119 | #'eq))) 120 | ;; Elements that have contents (contents) 121 | (org-cmenu-define-type-alias 122 | 'contents 123 | '(;; Special Support (see: org-cmenu-contents-range) 124 | code verbatim 125 | src-block comment-block example-block export-block 126 | ;; Elements 127 | center-block drawer 128 | dynamic-block 129 | footnote-definition headline inlinetask item 130 | paragraph plain-list 131 | property-drawer quote-block section 132 | special-block table table-row verse-block 133 | ;; Objects 134 | bold citation 135 | footnote-reference italic 136 | link radio-target strike-through 137 | subscript superscript table-cell underline ))) 138 | 139 | (org-cmenu-define-standard-type-aliases) 140 | 141 | ;;;; Syntax Element Type List 142 | 143 | (defvar org-cmenu-types nil) 144 | 145 | (defun org-cmenu-reset-types () 146 | (setq org-cmenu-types nil)) 147 | 148 | (defun org-cmenu-get-type (type-id) 149 | (assq type-id org-cmenu-types)) 150 | 151 | (defun org-cmenu-add-type (type-id) 152 | (let ((type (org-cmenu-type-create type-id))) 153 | (push type org-cmenu-types) 154 | type)) 155 | 156 | (defun org-cmenu-get-type-create (type-id) 157 | (or (org-cmenu-get-type type-id) 158 | (org-cmenu-add-type type-id))) 159 | 160 | (defun org-cmenu-add-command-to-types (group-path command target-types 161 | &optional remove-duplicates-p) 162 | (dolist (type-id target-types) 163 | (org-cmenu-type-add-command 164 | (org-cmenu-get-type-create type-id) 165 | group-path command remove-duplicates-p))) 166 | 167 | ;;;; Syntax Element Type 168 | 169 | (defun org-cmenu-type-create (type-id) 170 | ;; (type-id:symbol groups:list) 171 | (list type-id nil)) 172 | 173 | (defmacro org-cmenu-type-id (type-sym) 174 | `(car ,type-sym)) 175 | 176 | (defmacro org-cmenu-type-groups (type-sym) 177 | `(cadr ,type-sym)) 178 | 179 | (defun org-cmenu-type-find-group (type group-id) 180 | (seq-find 181 | (lambda (group) 182 | (org-cmenu-group-equal-id group group-id)) 183 | (org-cmenu-type-groups type))) 184 | 185 | (defun org-cmenu-type-add-group (type group-id) 186 | (let ((group (org-cmenu-group-create group-id))) 187 | ;; push back 188 | (setf (org-cmenu-type-groups type) 189 | (nconc (org-cmenu-type-groups type) 190 | (list group))) 191 | group)) 192 | 193 | (defun org-cmenu-type-remove-group (type group-id) 194 | (setf (org-cmenu-type-groups type) 195 | (seq-remove 196 | (lambda (group) 197 | (org-cmenu-group-equal-id group group-id)) 198 | (org-cmenu-type-groups type)))) 199 | 200 | (defun org-cmenu-type-get-group (type group-path) 201 | (unless (consp group-path) 202 | (setq group-path (list group-path))) 203 | 204 | (let* ((group (org-cmenu-type-find-group type (car group-path)))) 205 | (pop group-path) 206 | 207 | (while (and group group-path) 208 | (setq group (org-cmenu-group-find-subgroup group (car group-path))) 209 | (pop group-path)) 210 | 211 | group)) 212 | 213 | (defun org-cmenu-type-get-group-create (type group-path) 214 | (unless (consp group-path) 215 | (setq group-path (list group-path))) 216 | 217 | (let* ((group-id (car group-path)) 218 | (group (or (org-cmenu-type-find-group type group-id) 219 | (org-cmenu-type-add-group type group-id)))) 220 | (pop group-path) 221 | 222 | (while group-path 223 | (setq group-id (car group-path)) 224 | (setq group (or (org-cmenu-group-find-subgroup group group-id) 225 | (org-cmenu-group-add-subgroup group group-id))) 226 | (pop group-path)) 227 | 228 | group)) 229 | 230 | (defun org-cmenu-type-add-command (type group-path command remove-duplicates-p) 231 | (org-cmenu-group-add-command 232 | (org-cmenu-type-get-group-create type group-path) 233 | command 234 | remove-duplicates-p)) 235 | 236 | (defun org-cmenu-type-remove-command (type group-path key) 237 | (when-let ((group (org-cmenu-type-get-group type group-path))) 238 | (org-cmenu-group-remove-command group key))) 239 | 240 | (defun org-cmenu-type-remove-group-by-path (type group-path) 241 | (when group-path 242 | (let* ((rpath (reverse group-path)) 243 | (last-group-id (car rpath)) 244 | (path-to-parent (nreverse (cdr rpath)))) 245 | 246 | (if (null path-to-parent) 247 | (org-cmenu-type-remove-group type last-group-id) 248 | (when-let ((parent-group (org-cmenu-type-get-group type path-to-parent))) 249 | (org-cmenu-group-remove-subgroup parent-group last-group-id)))))) 250 | 251 | 252 | ;;;; Group 253 | 254 | (defun org-cmenu-group-create (group-id) 255 | ;; (:group group-id:string props:plist elements:list) 256 | (list :group group-id nil nil)) 257 | 258 | (defun org-cmenu-group-p (object) 259 | (eq (car-safe object) :group)) 260 | 261 | (defmacro org-cmenu-group-id (group-sym) 262 | `(cadr ,group-sym)) 263 | 264 | (defmacro org-cmenu-group-props (group-sym) 265 | `(caddr ,group-sym)) 266 | 267 | (defmacro org-cmenu-group-elements (group-sym) 268 | `(cadddr ,group-sym)) 269 | 270 | (defun org-cmenu-group-equal-id (object group-id) 271 | (and (org-cmenu-group-p object) 272 | (equal (org-cmenu-group-id object) group-id))) 273 | 274 | (defun org-cmenu-group-find-subgroup (group subgroup-id) 275 | (seq-find 276 | (lambda (elm) 277 | (org-cmenu-group-equal-id elm subgroup-id)) 278 | (org-cmenu-group-elements group))) 279 | 280 | (defun org-cmenu-group-add-subgroup (group subgroup-id) 281 | (let ((subgroup (org-cmenu-group-create subgroup-id))) 282 | ;; push back 283 | (setf (org-cmenu-group-elements group) 284 | (nconc (org-cmenu-group-elements group) 285 | (list subgroup))) 286 | subgroup)) 287 | 288 | (defun org-cmenu-group-add-command (group command &optional remove-duplicates-p) 289 | (setf (org-cmenu-group-elements group) 290 | (nconc 291 | (if remove-duplicates-p 292 | ;; Remove commands with duplicate keys from GROUP 293 | (seq-remove (lambda (elm) 294 | (org-cmenu-command-equal-key 295 | elm 296 | (org-cmenu-command-get-key command))) 297 | (org-cmenu-group-elements group)) 298 | (org-cmenu-group-elements group)) 299 | ;; Add COMMAND to the end 300 | (cons command nil))) 301 | command) 302 | 303 | (defun org-cmenu-group-add-string (group str) 304 | (setf (org-cmenu-group-elements group) 305 | (nconc (org-cmenu-group-elements group) 306 | (list str))) 307 | str) 308 | 309 | (defun org-cmenu-group-remove-subgroup (group subgroup-id) 310 | (setf (org-cmenu-group-elements group) 311 | (seq-remove 312 | (lambda (elm) 313 | (org-cmenu-group-equal-id elm subgroup-id)) 314 | (org-cmenu-group-elements group)))) 315 | 316 | (defun org-cmenu-group-remove-command (group key) 317 | (setf (org-cmenu-group-elements group) 318 | (seq-remove 319 | (lambda (elm) 320 | (org-cmenu-command-equal-key elm key)) 321 | (org-cmenu-group-elements group)))) 322 | 323 | (defun org-cmenu-group-remove-string (group str) 324 | (setf (org-cmenu-group-elements group) 325 | (seq-remove 326 | (lambda (elm) 327 | (and (stringp elm) 328 | (string= elm str))) 329 | (org-cmenu-group-elements group)))) 330 | 331 | (defun org-cmenu-group-set-property (group key value) 332 | (setf (org-cmenu-group-props group) 333 | (plist-put (org-cmenu-group-props group) key value))) 334 | 335 | (defun org-cmenu-group-transient-props (group) 336 | (cl-loop 337 | for p on (org-cmenu-group-props group) by #'cddr 338 | nconc 339 | (pcase p 340 | ;; :hide boolean => :hide (lambda () boolean) 341 | (`(:hide ,(and (pred booleanp) value) . ,_) 342 | (list :hide `(lambda () ,value))) 343 | ;; key value 344 | (`(,key ,value . ,_) 345 | (list key value))))) 346 | 347 | (defun org-cmenu-group-to-transient-spec (group) 348 | (apply 349 | #'vector 350 | ;; https://magit.vc/manual/transient.html#Group-Specifications 351 | (delq 352 | nil 353 | (append 354 | ;; Description (string) 355 | (let ((group-id (org-cmenu-group-id group))) 356 | (when (stringp group-id) (list group-id))) 357 | ;; Properties 358 | (org-cmenu-group-transient-props group) 359 | ;; Elements 360 | (mapcar 361 | (lambda (elm) 362 | (cond 363 | ((org-cmenu-group-p elm) 364 | (org-cmenu-group-to-transient-spec elm)) 365 | ((org-cmenu-command-p elm) 366 | elm) 367 | ((stringp elm) 368 | elm))) 369 | (org-cmenu-group-elements group)))))) 370 | 371 | ;;;; Command 372 | 373 | (defun org-cmenu-command-create (key description func &rest properties) 374 | ;; (key:string description:string func:function . properties:plist) 375 | (nconc (list key description func) properties)) 376 | 377 | (defun org-cmenu-command-p (object) 378 | (and (listp object) 379 | (stringp (car object)))) 380 | 381 | (defmacro org-cmenu-command-key (command-sym) 382 | `(car ,command-sym)) 383 | 384 | (defmacro org-cmenu-command-description (command-sym) 385 | `(cadr ,command-sym)) 386 | 387 | (defmacro org-cmenu-command-function (command-sym) 388 | `(caddr ,command-sym)) 389 | 390 | (defmacro org-cmenu-command-properties (command-sym) 391 | `(cdddr ,command-sym)) 392 | 393 | (defun org-cmenu-command-equal-key (object key) 394 | (and (org-cmenu-command-p object) 395 | (equal (org-cmenu-command-key object) key))) 396 | 397 | (defun org-cmenu-command-get-key (command) 398 | (org-cmenu-command-key command)) 399 | 400 | ;;;; Modify transient.el 401 | 402 | ;; ;; Add pre-exit-hook feature 403 | 404 | ;; (defvar org-cmenu-transient-pre-exit-hook nil) 405 | 406 | ;; (defun org-cmenu-transient-pre-exit (old-fun &rest rest) 407 | ;; (prog1 (apply old-fun rest) 408 | ;; (run-hooks 'org-cmenu-transient-pre-exit-hook))) 409 | 410 | ;; (advice-add #'transient--pre-exit 411 | ;; :around 412 | ;; #'org-cmenu-transient-pre-exit) 413 | 414 | ;;;; org-element Extension 415 | 416 | (defun org-cmenu-element-end (datum) 417 | ;;@todo The post-blank of some elements represents the number of lines. 418 | ;; keyword type: 419 | ;; #+TITLE: title 420 | ;; <= Even if there are many blanks here, post-blank will be 1 421 | ;; next element 422 | (- (org-element-property :end datum) 423 | (or (org-element-property :post-blank datum) 0))) 424 | 425 | (defun org-cmenu-element-contains-point-p (datum pos) 426 | (let ((begin (org-element-property :begin datum)) 427 | (end (org-cmenu-element-end datum))) 428 | (and 429 | (<= begin pos) 430 | (or (< pos end) 431 | ;; plain-list item 432 | ;; - item 433 | ;; section 434 | (and (= pos end) 435 | (not (= (line-beginning-position) (point)))))))) 436 | 437 | (defun org-cmenu-element-point-on-first-line-p (datum pos) 438 | (save-excursion 439 | (goto-char (or (org-element-property :post-affiliated datum) 440 | (org-element-property :begin datum))) 441 | (<= (point) pos (line-end-position)))) 442 | 443 | (defun org-cmenu-element-current-section () 444 | (save-excursion 445 | ;; Move to next line of heading or point-min. 446 | (condition-case nil 447 | (progn 448 | (org-back-to-heading t) 449 | (forward-line)) 450 | (error 451 | (goto-char (point-min)))) 452 | ;; Return the section element at point. 453 | (org-element-section-parser nil))) 454 | 455 | (defun org-cmenu-element-headlines-path (with-self) 456 | "Return a list of elements from current headline to top level headline." 457 | (save-excursion 458 | (let (path) 459 | (when (ignore-errors (org-back-to-heading t)) 460 | (when with-self 461 | (push (org-element-at-point) path)) 462 | (while (org-up-heading-safe) 463 | (push (org-element-at-point) path))) 464 | (nreverse path)))) 465 | 466 | (defun org-cmenu-element-buffer () 467 | "Return a buffer pseudo element." 468 | (let ((beg (point-min)) 469 | (end (point-max))) 470 | (list 471 | 'buffer 472 | (list 473 | :begin beg 474 | :post-affiliated beg 475 | :contents-begin beg 476 | :contents-end end 477 | :post-blank 0 478 | :end end)))) 479 | 480 | (defun org-cmenu-element-lineage () 481 | (let ((path (org-element-lineage (org-element-context) nil t)) 482 | (pos (point))) 483 | ;; Exclude elements whose POS intersects only post-blank 484 | (while (and path 485 | (pcase (org-element-type (car path)) 486 | ;; only the first line is recognized as a headline. 487 | ('headline (not (org-cmenu-element-point-on-first-line-p 488 | (car path) pos))) 489 | ;; exclude post-blank 490 | (_ (not (org-cmenu-element-contains-point-p 491 | (car path) pos))))) 492 | (setq path (cdr path))) 493 | 494 | (unless (eq (org-element-type (car (last path))) 'org-data) 495 | (let ((on-headline-p (eq (org-element-type (car path)) 'headline))) 496 | ;; Add a section element 497 | (unless on-headline-p 498 | (setq path 499 | (append 500 | path 501 | (list (org-cmenu-element-current-section))))) 502 | 503 | ;; Add headline elements 504 | (setq path 505 | (append 506 | path 507 | (org-cmenu-element-headlines-path (not on-headline-p)))) 508 | 509 | ;; Add buffer elements 510 | (setq path 511 | (append 512 | path 513 | (list (org-cmenu-element-buffer)))))) 514 | path)) 515 | 516 | ;;;; Menu 517 | 518 | (defvar org-cmenu-pointed-path-dirty nil) ;; org-cmenu-reset-context, org-cmenu-on-pre-command 519 | (defvar org-cmenu-pointed-path nil) ;; org-cmenu-reset-context 520 | (defvar org-cmenu-target-datum nil) ;; org-cmenu-reset-context, org-cmenu-open-internal 521 | ;;org-cmenu-on-setup ~ org-cmenu-on-pre-command 522 | (defvar org-cmenu-open-p nil) 523 | (defvar org-cmenu-mark-active-p nil) 524 | (defvar org-cmenu-saved-point nil) 525 | (defvar org-cmenu-saved-mark nil) 526 | 527 | (defun org-cmenu-pointed-path-string () 528 | "Return the current path string for menu display." 529 | (mapconcat 530 | (lambda (d) 531 | (propertize 532 | (format "%s" (org-element-type d)) 533 | 'face 534 | (if (eq d org-cmenu-target-datum) 535 | 'success 536 | 'transient-heading))) 537 | (reverse org-cmenu-pointed-path) 538 | " > ")) 539 | 540 | (defun org-cmenu-next-element (elt lst) 541 | (cl-loop for p on lst 542 | do (when (eq (car p) elt) 543 | (cl-return (cadr p))))) 544 | 545 | (defun org-cmenu-target-root () 546 | (car (last org-cmenu-pointed-path))) 547 | 548 | (defun org-cmenu-target-parent () 549 | "Return the parent of the current datum." 550 | (org-cmenu-next-element org-cmenu-target-datum org-cmenu-pointed-path)) 551 | 552 | (defun org-cmenu-target-child () 553 | "Return the child of the current datum." 554 | (org-cmenu-next-element org-cmenu-target-datum (reverse org-cmenu-pointed-path))) 555 | 556 | (defun org-cmenu-target-leaf () 557 | (car org-cmenu-pointed-path)) 558 | 559 | (defun org-cmenu-select-root () 560 | (interactive) 561 | (if-let ((root (org-cmenu-target-root))) 562 | (org-cmenu-open-internal root) 563 | (org-cmenu-open-internal org-cmenu-target-datum))) 564 | 565 | (defun org-cmenu-select-parent () 566 | (interactive) 567 | (if-let ((parent (org-cmenu-target-parent))) 568 | (org-cmenu-open-internal parent) 569 | (org-cmenu-open-internal org-cmenu-target-datum))) 570 | 571 | (defun org-cmenu-select-child () 572 | (interactive) 573 | (if-let ((child (org-cmenu-target-child))) 574 | (org-cmenu-open-internal child) 575 | (org-cmenu-open-internal org-cmenu-target-datum))) 576 | 577 | (defun org-cmenu-select-leaf () 578 | (interactive) 579 | (if-let ((leaf (org-cmenu-target-leaf))) 580 | (org-cmenu-open-internal leaf) 581 | (org-cmenu-open-internal org-cmenu-target-datum))) 582 | 583 | (defun org-cmenu-define-transient-prefix-for-type (type-id) 584 | (let ((type (org-cmenu-get-type type-id)) 585 | (prefix-name (intern 586 | (format "org-cmenu-transient-prefix-%s" type-id)))) 587 | (unless type 588 | (error "Unknown type %s" type-id)) 589 | (eval 590 | `(transient-define-prefix ,prefix-name () 591 | ,(format "Operations for %s" type-id) 592 | [:description 593 | org-cmenu-on-setup ;;HACK!! I want to callback when returned from subprefix! 594 | [("~" "Root" org-cmenu-select-root :if org-cmenu-target-parent)] 595 | [("^" "Parent" org-cmenu-select-parent :if org-cmenu-target-parent)] 596 | [("\\" "Child" org-cmenu-select-child :if org-cmenu-target-child)] 597 | [("|" "Leaf" org-cmenu-select-leaf :if org-cmenu-target-child)] 598 | [("q" "Quit" transient-quit-one)] 599 | ] 600 | ;; Groups 601 | ,@(cl-loop for group in (org-cmenu-type-groups type) 602 | collect (org-cmenu-group-to-transient-spec group)) 603 | ;; ;; Body 604 | ;; (interactive) 605 | ;; (transient-setup ',prefix-name) 606 | )))) 607 | 608 | (defun org-cmenu-update-transient-prefixes () 609 | "Define transient prefixes for all elements and all objects." 610 | (mapc #'org-cmenu-define-transient-prefix-for-type 611 | (org-cmenu-type-alias-to-ids 'all))) 612 | 613 | ;; Save/Restore Mark/Point 614 | 615 | (defun org-cmenu-save-mark-and-point () 616 | (setq org-cmenu-saved-point (point)) 617 | (setq org-cmenu-saved-mark 618 | ;;(save-mark-and-excursion--save) 619 | (cons 620 | (let ((mark (mark-marker))) 621 | (and (marker-position mark) (copy-marker mark))) 622 | (and mark-active (not deactivate-mark)))) ;;If deactivate-mark is t, turn off mark-active in commandn loop. 623 | ;;(message "Save mark(%s) and point(%s)" org-cmenu-saved-mark org-cmenu-saved-point) 624 | ) 625 | 626 | (defun org-cmenu-restore-mark-and-point () 627 | ;;(message "Restore mark(%s) and point(%s)" org-cmenu-saved-mark org-cmenu-saved-point) 628 | (goto-char org-cmenu-saved-point) 629 | (save-mark-and-excursion--restore org-cmenu-saved-mark)) 630 | 631 | ;; Highlight 632 | 633 | (defvar-local org-cmenu-highlight-ov nil) 634 | 635 | (defface org-cmenu-highlight 636 | '((t 637 | :extend t 638 | :inherit highlight)) 639 | "Hilight syntax element." 640 | :group 'org-cmenu) 641 | 642 | (defun org-cmenu-highlight-datum (datum) 643 | (org-cmenu-unhighlight-datum) 644 | (setq org-cmenu-highlight-ov 645 | (make-overlay 646 | (org-element-property :begin datum) 647 | (org-cmenu-element-end datum))) 648 | (overlay-put org-cmenu-highlight-ov 649 | 'face 650 | 'org-cmenu-highlight)) 651 | 652 | (defun org-cmenu-unhighlight-datum () 653 | (when org-cmenu-highlight-ov 654 | (delete-overlay org-cmenu-highlight-ov) 655 | (setq org-cmenu-highlight-ov nil))) 656 | 657 | ;; Open/Close 658 | 659 | ;; org-cmenu => on-setup 660 | ;; => on-pre-command 661 | ;; => org-cmenu-select-[parent|child] => open-internal => on-setup 662 | ;; => transient-* => on-setup 663 | ;; => COMMAND(:transient t) => on-setup 664 | ;; => COMMAND => end 665 | ;; => on-setup (?) 666 | 667 | (defun org-cmenu-on-setup () 668 | ;;(message "[cmenu]on-setup open-p=%s pointed-path-dirty=%s this-command=%s last-command=%s" org-cmenu-open-p org-cmenu-pointed-path-dirty this-command last-command) 669 | (unless org-cmenu-open-p 670 | ;; Fix path and target. 671 | (when org-cmenu-pointed-path-dirty 672 | (let ((new-path (org-cmenu-element-lineage)) 673 | (menu-type (org-element-type org-cmenu-target-datum))) ;;current menu type 674 | 675 | (cond 676 | ((equal new-path org-cmenu-pointed-path) 677 | ;;(message "[cmenu]Completely same path structure! Do nothing") 678 | ) 679 | 680 | ((let* ((index (seq-position (reverse org-cmenu-pointed-path) 681 | org-cmenu-target-datum #'eq)) 682 | (new-target-same-index (nth index (reverse new-path)))) 683 | (when (eq (org-element-type new-target-same-index) 684 | menu-type) ;;same type 685 | ;;(message "[cmenu]Changed but continue same index and same type. Set new path and target") 686 | (org-cmenu-reset-context new-path new-target-same-index) 687 | t))) 688 | 689 | ((let ((new-target-same-type 690 | (seq-find (lambda (d) (eq (org-element-type d) menu-type)) 691 | new-path))) 692 | (when new-target-same-type 693 | ;;(message "[cmenu]Lost path but continue same type datum. Set new path and target") 694 | (org-cmenu-reset-context new-path new-target-same-type) 695 | t))) 696 | 697 | (t 698 | ;;@todo Safely exit the menu 699 | (error "Menu type mismatch"))))) 700 | 701 | (setq org-cmenu-open-p t) 702 | ;; Save mark and point. 703 | (org-cmenu-save-mark-and-point) 704 | ;; Hilight datum 705 | (setq org-cmenu-mark-active-p (and mark-active (not deactivate-mark))) 706 | (unless org-cmenu-mark-active-p 707 | (org-cmenu-highlight-datum org-cmenu-target-datum)) 708 | ;; Add hook 709 | ;;(add-hook 'org-cmenu-transient-pre-exit-hook #'org-cmenu-on-pre-exit)) 710 | (add-hook 'pre-command-hook #'org-cmenu-on-pre-command)) 711 | ;; Return path string. 712 | (org-cmenu-pointed-path-string)) 713 | 714 | (defun org-cmenu-on-pre-command () 715 | ;;(message "[cmenu]on-pre-command this-command=%s last-command" this-command last-command) 716 | (when org-cmenu-open-p 717 | (unless org-cmenu-mark-active-p 718 | (org-cmenu-unhighlight-datum)) 719 | (setq org-cmenu-mark-active-p nil) 720 | (setq org-cmenu-open-p nil) 721 | (setq org-cmenu-pointed-path-dirty t) 722 | (org-cmenu-restore-mark-and-point) 723 | ;;(remove-hook 'org-cmenu-transient-pre-exit-hook #'org-cmenu-on-pre-exit) 724 | (remove-hook 'pre-command-hook #'org-cmenu-on-pre-command))) 725 | 726 | (defun org-cmenu-open-internal (datum) 727 | (when org-cmenu-pointed-path 728 | (let ((type-id (org-element-type datum))) 729 | ;; Update transient prefix 730 | (when org-cmenu-update-transient-prefix-everytime 731 | (org-cmenu-define-transient-prefix-for-type type-id)) 732 | 733 | ;; Set target datum 734 | (setq org-cmenu-target-datum datum) 735 | 736 | ;; Invoke transient prefix 737 | (call-interactively 738 | (intern (format "org-cmenu-transient-prefix-%s" type-id)))))) 739 | 740 | ;; Beginning of Menu 741 | 742 | (defun org-cmenu () 743 | "Open a menu for the syntax element pointed by the current point." 744 | (interactive) 745 | 746 | (let* ((path (org-cmenu-element-lineage)) 747 | (datum (car path))) 748 | (unless datum 749 | (error "No elements")) 750 | (org-cmenu-reset-context path datum) 751 | (org-cmenu-open-internal datum))) 752 | 753 | (defun org-cmenu-reset-context (path target-datum) 754 | ;; Set current point information. 755 | (setq org-cmenu-pointed-path path) 756 | (setq org-cmenu-pointed-path-dirty nil) 757 | (setq org-cmenu-target-datum target-datum)) 758 | 759 | ;;;; Wrap Command 760 | 761 | (defun org-cmenu-target-datum () 762 | "Return the current target datum (element or object)." 763 | org-cmenu-target-datum) 764 | 765 | (defun org-cmenu-make-wrap-command-function (original func) 766 | ;; Avoid transient.el problem. 767 | ;; transient.el defines a function with description as the function name. 768 | ;; (transient::) 769 | ;; If there are multiple commands with the exact same description, 770 | ;; they will collide and only the last command will be valid. 771 | ;; e.g. 772 | ;; (transient-define-prefix talk () 773 | ;; "Talk" 774 | ;; ["Dog" ("d" "Talk" (lambda () (interactive) (message "bowwow")))] 775 | ;; ["Cat" ("c" "Talk" (lambda () (interactive) (message "meow")))]) 776 | ;; (talk) => d => meow 777 | ;; see: "(format "transient:%s:%s"" part in transient--parse-suffix. 778 | (let ((fname (intern (format "org-cmenu-wrap:%s" 779 | (if (symbolp original) original (gensym)))))) 780 | (fset fname func) 781 | fname)) 782 | 783 | (defun org-cmenu-wrap-command-with-datum (func) 784 | (org-cmenu-make-wrap-command-function 785 | func 786 | (lambda () (interactive) (funcall func (org-cmenu-target-datum))))) 787 | 788 | (defun org-cmenu-wrap-command-at-begin (func) 789 | (org-cmenu-make-wrap-command-function 790 | func 791 | (lambda () 792 | (interactive) 793 | (goto-char (org-element-property :begin (org-cmenu-target-datum))) 794 | (funcall func)))) 795 | 796 | (defun org-cmenu-wrap-command-at-post-affiliated (func) 797 | (org-cmenu-make-wrap-command-function 798 | func 799 | (lambda () 800 | (interactive) 801 | (goto-char (org-element-property :post-affiliated (org-cmenu-target-datum))) 802 | (funcall func)))) 803 | 804 | (defun org-cmenu-no-wrap-command (func) 805 | func) 806 | 807 | (defun org-cmenu-wrap-command (command wrapping-method) 808 | (funcall 809 | (pcase wrapping-method 810 | ('no-wrap #'org-cmenu-no-wrap-command) 811 | ('with-datum #'org-cmenu-wrap-command-with-datum) 812 | ('nil #'org-cmenu-wrap-command-with-datum) 813 | ('at-begin #'org-cmenu-wrap-command-at-begin) 814 | ('at-post-affiliated #'org-cmenu-wrap-command-at-post-affiliated) 815 | ((pred functionp) wrapping-method) 816 | (_ #'org-cmenu-no-wrap-command)) 817 | command)) 818 | 819 | ;;;; Target Types 820 | 821 | (defun org-cmenu-after-keyword (list) 822 | (while (and list (not (keywordp (car list)))) 823 | (setq list (cdr list))) 824 | list) 825 | 826 | (defun org-cmenu-resolve-target-spec (target-spec) 827 | (let (target-types 828 | target-props) 829 | 830 | ;; target-spec: 831 | ;; type 832 | ;; (type ... :keyword value ...) 833 | (cond 834 | ((null target-spec)) 835 | ((symbolp target-spec) 836 | (setq target-types (list target-spec))) 837 | ((listp target-spec) 838 | (setq target-types 839 | (seq-take-while (lambda (e) (not (keywordp e))) target-spec)) 840 | (setq target-props 841 | (org-cmenu-after-keyword target-spec)))) 842 | 843 | ;; Resolve aliases 844 | (setq target-types 845 | (delq nil 846 | (seq-uniq 847 | (seq-mapcat #'org-cmenu-type-alias-to-ids target-types) 848 | #'eq))) 849 | 850 | ;; Exclude spec 851 | (when-let ((exclude (plist-get target-props :exclude))) 852 | (setq target-types (seq-difference 853 | target-types 854 | (if (listp exclude) 855 | exclude 856 | (list exclude))))) 857 | 858 | (cons target-types target-props))) 859 | 860 | (defun org-cmenu-add-command (group-path command &optional 861 | target-spec wrapping-method 862 | remove-duplicates-p) 863 | (let* ((command (copy-sequence command)) 864 | (func (org-cmenu-command-function command)) 865 | (func-props (and (symbolp func) (get func 'org-cmenu))) 866 | (target-spec (cond 867 | ((plist-get (and (listp target-spec) 868 | (org-cmenu-after-keyword target-spec)) 869 | :forced) 870 | target-spec) 871 | ((plist-member func-props :target) 872 | (plist-get func-props :target)) 873 | (t 874 | target-spec))) 875 | (target (org-cmenu-resolve-target-spec target-spec)) 876 | (target-types (car target)) 877 | (target-props (cdr target))) 878 | 879 | (unless target-types 880 | (error "No target types command %s" func)) 881 | 882 | ;; Wrap Command 883 | (setf (org-cmenu-command-function command) 884 | (org-cmenu-wrap-command 885 | func 886 | (if func-props (or (plist-get func-props :call) 887 | 'with-datum) 888 | wrapping-method))) 889 | 890 | ;; Condition 891 | (when-let ((pred (plist-get target-props :pred))) 892 | (setf (org-cmenu-command-properties command) 893 | (nconc 894 | (list :if `(lambda () (funcall (quote ,pred) org-cmenu-target-datum))) 895 | (org-cmenu-command-properties command)))) 896 | 897 | (org-cmenu-add-command-to-types group-path command target-types remove-duplicates-p))) 898 | 899 | (defun org-cmenu-add-commands (group-path commands &optional 900 | target-spec wrapping-method 901 | remove-duplicates-p) 902 | (dolist (command commands) 903 | (org-cmenu-add-command group-path command target-spec wrapping-method 904 | remove-duplicates-p))) 905 | 906 | (defun org-cmenu-apply-target-types (target-spec func) 907 | (let* ((target (org-cmenu-resolve-target-spec target-spec)) 908 | (target-types (car target))) 909 | (unless target-types 910 | (error "No target types %s" target-spec)) 911 | (dolist (type-id target-types) 912 | (funcall func type-id)))) 913 | 914 | (defun org-cmenu-remove-command (target-spec group-path key) 915 | (org-cmenu-apply-target-types 916 | target-spec 917 | (lambda (type-id) 918 | (org-cmenu-type-remove-command (org-cmenu-get-type type-id) 919 | group-path 920 | key)))) 921 | 922 | (defun org-cmenu-add-group (target-spec group-path &rest props) 923 | "Add all groups on GROUP-PATH to all types specified by TARGET-SPEC." 924 | (org-cmenu-apply-target-types 925 | target-spec 926 | (lambda (type-id) 927 | (let ((group (org-cmenu-type-get-group-create 928 | (org-cmenu-get-type-create type-id) 929 | group-path))) 930 | (cl-loop for (key value) on props by 'cddr 931 | do (org-cmenu-group-set-property group key value)))))) 932 | 933 | (defun org-cmenu-remove-group (target-spec group-path) 934 | (org-cmenu-apply-target-types 935 | target-spec 936 | (lambda (type-id) 937 | (org-cmenu-type-remove-group-by-path (org-cmenu-get-type type-id) 938 | group-path)))) 939 | 940 | (defun org-cmenu-set-group-property (target-spec group-path key value) 941 | (org-cmenu-apply-target-types 942 | target-spec 943 | (lambda (type-id) 944 | (when-let ((type (org-cmenu-get-type type-id)) 945 | (group (org-cmenu-type-get-group type group-path))) 946 | (org-cmenu-group-set-property group key value))))) 947 | 948 | (defun org-cmenu-add-string (target-spec group-path str) 949 | "Add the string STR to the groups specified by GROUP-PATH of 950 | all types specified by TARGET-SPEC." 951 | (org-cmenu-apply-target-types 952 | target-spec 953 | (lambda (type-id) 954 | (let ((group (org-cmenu-type-get-group-create 955 | (org-cmenu-get-type-create type-id) 956 | group-path))) 957 | (org-cmenu-group-add-string group str))))) 958 | 959 | (provide 'org-cmenu) 960 | ;;; org-cmenu.el ends here 961 | -------------------------------------------------------------------------------- /org-cmenu-tools.el: -------------------------------------------------------------------------------- 1 | ;;; org-cmenu-tools.el --- Tools for Org Mode -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2021 AKIYAMA Kouhei 4 | 5 | ;; Author: AKIYAMA Kouhei 6 | ;; Keywords: outline 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | 23 | ;; Define useful commands that are missing in org-mode. 24 | 25 | ;; This file does not depend directly on org-cmenu. 26 | 27 | ;;; Code: 28 | 29 | (require 'org) 30 | (require 'org-element) 31 | 32 | ;;;; Common 33 | 34 | (defun org-cmenu-enclosing-element (element ignore-types) 35 | (seq-find 36 | (lambda (e) (and 37 | ;; ignore-types is used when you want to exclude a specific type. 38 | (not (memq (org-element-type e) ignore-types)) 39 | ;; It seems that a inline thing becomes 'object and 40 | ;; becomes something like 'element that wraps 41 | ;; something. 42 | (eq (org-element-class e) 'element))) 43 | ;; A list of elements from ELEMENT to the root. 44 | ;; For example 45 | ;;'(link paragraph item plain-list). 46 | (org-element-lineage (or element (org-element-context)) nil t))) 47 | 48 | (defun org-cmenu-under-section-p (datum) 49 | (member 50 | (org-element-type (org-element-property :parent datum)) 51 | '(nil section))) 52 | 53 | ;;;; Predicates 54 | 55 | (defun org-cmenu-buffer-narrowed-p (&rest _) 56 | (buffer-narrowed-p)) 57 | 58 | (defun org-cmenu-buffer-not-narrowed-p (&rest _) 59 | (not (buffer-narrowed-p))) 60 | 61 | (defun org-cmenu-element-p (datum) 62 | "Return t if DATUM is a element (not an inline object)." 63 | (eq (org-element-class datum) 'element)) 64 | 65 | (defun org-cmenu-first-link-p (datum) 66 | "Return t if DATUM is the first link in the parent element." 67 | (and (eq (org-element-type datum) 'link) 68 | (if-let ((parent (org-element-property :parent datum))) 69 | (equal 70 | ;; Do not use org-element-parse-secondary-string. 71 | ;; Causes strange behavior in org2blog buffer. 72 | ;; (+ (org-element-property :begin parent) 73 | ;; (org-element-parse-secondary-string 74 | ;; (save-excursion 75 | ;; (save-restriction 76 | ;; (widen) 77 | ;; (buffer-substring 78 | ;; (org-element-property :begin parent) 79 | ;; (org-element-property :end parent)))) 80 | ;; '(link) 81 | ;; (org-element-property :parent parent)) 82 | ;; -1) 83 | (org-element-property 84 | :begin 85 | (seq-find 86 | (lambda (e) (eq (org-element-type e) 'link)) 87 | (org-element--parse-objects 88 | (org-element-property :begin parent) 89 | (org-element-property :end parent) 90 | nil 91 | '(link) 92 | parent))) 93 | (org-element-property :begin datum)) 94 | t))) 95 | 96 | (defun org-cmenu-element-or-first-link-p (datum) 97 | "Return t if DATUM is not a link or is the first link in the parent element." 98 | (or (org-cmenu-element-p datum) 99 | (org-cmenu-first-link-p datum))) 100 | 101 | (defun org-cmenu-standalone-link-p (datum) 102 | "Return t if DATUM is a standalone link." 103 | (and (eq (org-element-type datum) 'link) 104 | (let ((parent (org-element-property :parent datum))) 105 | (or 106 | (null parent) 107 | (and 108 | parent 109 | (eq (org-element-type parent) 'paragraph) 110 | (save-excursion 111 | (save-restriction 112 | (widen) 113 | (not 114 | (or 115 | (org-string-nw-p 116 | (buffer-substring 117 | (org-element-property :post-affiliated parent) 118 | (org-element-property :begin datum))) 119 | (org-string-nw-p 120 | (buffer-substring 121 | (org-element-property :end datum) 122 | (org-element-property :end parent)))))))))))) 123 | 124 | (defun org-cmenu-element-or-standalone-link-p (datum) 125 | (or (not (eq (org-element-type datum) 'link)) 126 | (org-cmenu-standalone-link-p datum))) 127 | 128 | (defun org-cmenu-file-link-p (link) 129 | (and (eq (org-element-type link) 'link) 130 | (equal (org-element-property :type link) "file"))) 131 | 132 | (defun org-cmenu-exists-file-link-p (link) 133 | (and (org-cmenu-file-link-p link) 134 | (file-exists-p (org-element-property :path link)))) 135 | 136 | (defun org-cmenu-exists-image-file-first-link-p (link) 137 | (and 138 | (org-cmenu-first-link-p link);;see: "HACK:" comment in org-html-link 139 | (equal (org-element-property :type link) "file") 140 | (let ((path (org-element-property :path link))) 141 | (and 142 | (string-match-p (image-file-name-regexp) path) 143 | (file-exists-p path))))) 144 | 145 | ;;;; Affiliated Keywords 146 | 147 | ;; Add affiliated KEYWORD to the current ELEMENT. 148 | ;; 149 | ;; - Insertion point is :post-affilicated or :begin 150 | ;; - If there is something (item, etc.) to the left of the insertion point, insert KEYWORD after line-break and indent. Keywords must always start at the beginning of the line. 151 | ;; - Even if there is nothing, insert KEYWORD after indent. 152 | ;; - Some elements cannot have KEYWORD. item, table-row, table-cell. 153 | ;; - Objects cannot have KEYWORD. 154 | ;; - Some link objects can have KEYWORD. For example, the attributes for the first link in paragraph and the captions for the standalone image. 155 | 156 | (defun org-cmenu-add-affiliated-keyword (keyword &optional element) 157 | "Add affiliated KEYWORD to ELEMENT." 158 | 159 | ;; Find the target element 160 | (setq element 161 | (if element 162 | ;; Skip objects (Object can't have keywords) 163 | (seq-find 164 | (lambda (e) (not (eq (org-element-class e) 'object))) 165 | (org-element-lineage element nil t)) 166 | ;; Find element 167 | (org-cmenu-enclosing-element nil '(item table-row table-cell)))) 168 | (when (null element) 169 | (error "Target element not found")) 170 | 171 | ;; Insert keyword before post-affiliated 172 | (let* ((begin (org-element-property :begin element)) 173 | (end (org-element-property :end element)) 174 | (post-aff (org-element-property :post-affiliated element)) 175 | (downcase-p (and post-aff 176 | (save-excursion 177 | (goto-char begin) 178 | (let ((case-fold-search nil)) 179 | (looking-at "[ \t]*#\\+[a-z]"))))) 180 | (after-str "")) 181 | 182 | ;; Insert indent 183 | (goto-char begin) 184 | (if (and (not (equal begin post-aff)) 185 | (re-search-forward "^\\([ \t]*\\)#\\+" (or post-aff end) t)) 186 | ;; If affiliated keywords already exist, use it's indentation. 187 | (progn 188 | (goto-char post-aff) 189 | (insert (match-string 1))) 190 | (goto-char (or post-aff begin)) 191 | (if (looking-back "^[ \t]*" (line-beginning-position)) 192 | ;; There is only a blank before the insertion point. 193 | ;; e.g. . 194 | ;; - Paragraph1 195 | ;; 196 | ;; . #+keyword: <=Insert (. means :begin of Paragraph2) 197 | ;; Paragraph2 198 | (progn 199 | (setq after-str (match-string 0)) 200 | (when (looking-at "[ \t]*") 201 | (insert (match-string 0)))) 202 | ;; There is somthing before the insertion point. 203 | ;; e.g. here 204 | ;; - . <=:begein of Paragraph1 205 | ;; #+keyword: <=Insert 206 | ;; Paragraph1 207 | (let ((column-str (make-string (current-column) ? ))) 208 | (insert "\n" column-str) 209 | (setq after-str column-str)))) 210 | 211 | ;; Insert keyword 212 | (insert "#+" (if downcase-p (downcase keyword) keyword) ": ") 213 | (save-excursion 214 | (insert "\n" after-str)))) 215 | 216 | (put 'org-cmenu-add-attr-org 'org-cmenu 217 | '(:target (aff-elements link :pred org-cmenu-element-or-first-link-p))) 218 | (defun org-cmenu-add-attr-org (&optional element) 219 | "Add #+ATTR_ORG: keyword to the element around point." 220 | (interactive) 221 | (org-cmenu-add-affiliated-keyword "ATTR_ORG" element) 222 | (insert ":")) 223 | 224 | (put 'org-cmenu-add-attr-html 'org-cmenu 225 | '(:target (aff-elements link :pred org-cmenu-element-or-first-link-p))) 226 | (defun org-cmenu-add-attr-html (&optional element) 227 | "Add #+ATTR_HTML: keyword to the element around point." 228 | (interactive) 229 | (org-cmenu-add-affiliated-keyword "ATTR_HTML" element) 230 | (insert ":")) 231 | 232 | (put 'org-cmenu-add-attr-latex 'org-cmenu 233 | '(:target (aff-elements link :pred org-cmenu-element-or-first-link-p))) 234 | (defun org-cmenu-add-attr-latex (&optional element) 235 | "Add #+ATTR_LATEX: keyword to the element around point." 236 | (interactive) 237 | (org-cmenu-add-affiliated-keyword "ATTR_LATEX" element) 238 | (insert ":")) 239 | 240 | (put 'org-cmenu-add-caption 'org-cmenu 241 | '(:target (aff-elements link :pred org-cmenu-element-or-standalone-link-p))) 242 | (defun org-cmenu-add-caption (&optional element) 243 | "Add #+CAPTION: keyword to the element around point." 244 | (interactive) 245 | (org-cmenu-add-affiliated-keyword "CAPTION" element)) 246 | 247 | (put 'org-cmenu-add-name 'org-cmenu '(:target aff-elements)) 248 | (defun org-cmenu-add-name (&optional element) 249 | "Add #+NAME: keyword to the element around point." 250 | (interactive) 251 | (org-cmenu-add-affiliated-keyword "NAME" element)) 252 | 253 | ;;;; Comment 254 | 255 | (defconst org-cmenu-types-can-comment-out 256 | (cons 257 | 'buffer 258 | (seq-difference org-element-all-elements '(item table-row)))) 259 | 260 | (defconst org-cmenu-types-cannot-comment-out 261 | (append org-element-all-objects '(item table-row))) 262 | 263 | (put 'org-cmenu-comment-element 'org-cmenu 264 | `(:target ,org-cmenu-types-can-comment-out)) 265 | (defun org-cmenu-comment-element (&optional element) 266 | "Comment the element around point." 267 | (interactive (list (org-element-lineage (org-element-at-point) 268 | org-cmenu-types-can-comment-out t))) 269 | 270 | (unless element 271 | (error "No element")) 272 | 273 | (unless (memq (org-element-type element) 274 | org-cmenu-types-can-comment-out) 275 | ;; (error "%s is a type that cannot be commented out" 276 | ;; (org-element-type element)) 277 | (setq element 278 | (org-element-lineage element org-cmenu-types-can-comment-out t)) 279 | (unless element 280 | (error "There are no elements that can be commented out"))) 281 | 282 | (let ((begin (org-element-property :begin element)) 283 | (end (org-element-property :end element))) 284 | ;; Insert line-break 285 | (save-excursion 286 | (goto-char begin) 287 | (unless (looking-back "^[ \t]*" (line-beginning-position)) 288 | ;; e.g. - Paragraph 289 | (let ((column (current-column))) 290 | (insert "\n" (make-string column ? )) 291 | (setq begin (+ begin 1 column) 292 | end (+ end 1 column))))) 293 | 294 | ;; Comment 295 | (let ((comment-empty-lines t)) 296 | (comment-region begin end)))) 297 | 298 | (put 'org-cmenu-comment-enclosing-element 'org-cmenu 299 | `(:target ,org-cmenu-types-cannot-comment-out)) 300 | (defun org-cmenu-comment-enclosing-element (datum) 301 | (org-cmenu-comment-element datum)) 302 | 303 | ;;;; Region 304 | 305 | (put 'org-cmenu-mark-datum 'org-cmenu '(:target (all buffer))) 306 | (defun org-cmenu-mark-datum (&optional datum) 307 | (interactive) 308 | (unless datum 309 | (setq datum (org-element-context))) 310 | (unless datum 311 | (error "No datum")) 312 | (let ((begin (org-element-property :begin datum)) 313 | (end (org-element-property :end datum)) 314 | (post-blank (org-element-property :post-blank datum))) 315 | (push-mark) 316 | (goto-char end) 317 | ;; :fixed-width returns too many post-blanks 318 | ;; (forward-line (- (or (org-element-property :post-blank datum) 0))) 319 | (cl-loop repeat (or post-blank 0) 320 | while (looking-back "^[ \t]*\n[ \t]*" begin) 321 | do (forward-line -1)) 322 | (push-mark (point) nil t) 323 | (goto-char begin))) 324 | 325 | (put 'org-cmenu-kill-datum 'org-cmenu '(:target (all buffer))) 326 | (defun org-cmenu-kill-datum (&optional datum) 327 | (interactive) 328 | (org-cmenu-mark-datum datum) 329 | (call-interactively #'kill-region)) 330 | 331 | (put 'org-cmenu-copy-datum 'org-cmenu '(:target (all buffer))) 332 | (defun org-cmenu-copy-datum (&optional datum) 333 | (interactive) 334 | (org-cmenu-mark-datum datum) 335 | (call-interactively #'kill-ring-save)) 336 | 337 | (put 'org-cmenu-toggle-narrow-datum 'org-cmenu '(:target all)) 338 | (defun org-cmenu-toggle-narrow-datum (&optional datum) 339 | (interactive) 340 | (if (buffer-narrowed-p) 341 | (widen) 342 | (org-cmenu-narrow-to-datum datum))) 343 | 344 | (put 'org-cmenu-narrow-to-datum 'org-cmenu 345 | '(:target (all :pred org-cmenu-buffer-not-narrowed-p))) 346 | (defun org-cmenu-narrow-to-datum (datum) 347 | (interactive (list (org-element-context))) 348 | (narrow-to-region 349 | (org-element-property :begin datum) 350 | (org-element-property :end datum))) 351 | 352 | (put 'org-cmenu-widen-buffer 'org-cmenu 353 | '(:target (all buffer :pred org-cmenu-buffer-narrowed-p) :call no-wrap)) 354 | (defun org-cmenu-widen-buffer () 355 | (interactive) 356 | (widen)) 357 | 358 | ;;;;; Contents 359 | 360 | (defun org-cmenu-has-non-empty-contents-p (datum) 361 | (let* ((range (ignore-errors (org-cmenu-contents-range datum))) 362 | (begin (car range)) 363 | (end (cdr range))) 364 | (and begin end (< begin end)))) 365 | 366 | (put 'org-cmenu-mark-contents 'org-cmenu 367 | '(:target (contents :pred org-cmenu-has-non-empty-contents-p))) 368 | (defun org-cmenu-mark-contents (&optional datum) 369 | (interactive) 370 | (unless datum 371 | (setq datum (org-element-context))) 372 | (unless datum 373 | (error "No datum")) 374 | (let* ((range (org-cmenu-contents-range datum)) 375 | (begin (car range)) 376 | (end (cdr range))) 377 | ;; Some elements (e.g. comment, fixed-width) cannot only mark contents. 378 | (push-mark) 379 | (goto-char end) 380 | (push-mark (point) nil t) 381 | (goto-char begin))) 382 | 383 | (put 'org-cmenu-kill-contents 'org-cmenu 384 | '(:target (contents :pred org-cmenu-has-non-empty-contents-p))) 385 | (defun org-cmenu-kill-contents (&optional datum) 386 | (interactive) 387 | ;;@todo Support comment, fixed-width 388 | (org-cmenu-mark-contents datum) 389 | (call-interactively #'kill-region)) 390 | 391 | (put 'org-cmenu-copy-contents 'org-cmenu 392 | '(:target (contents :pred org-cmenu-has-non-empty-contents-p))) 393 | (defun org-cmenu-copy-contents (&optional datum) 394 | (interactive) 395 | ;;@todo Support comment, fixed-width 396 | (org-cmenu-mark-contents datum) 397 | (call-interactively #'kill-ring-save)) 398 | 399 | (put 'org-cmenu-toggle-narrow-contents 'org-cmenu 400 | '(:target (contents :pred org-cmenu-has-non-empty-contents-p))) 401 | (defun org-cmenu-toggle-narrow-contents (&optional datum) 402 | (interactive) 403 | (if (buffer-narrowed-p) 404 | (widen) 405 | (let* ((range (org-cmenu-contents-range datum)) 406 | (begin (car range)) 407 | (end (cdr range))) 408 | (narrow-to-region begin end)))) 409 | 410 | (defun org-cmenu-find-contents-range (datum begin-regexp end-regexp) 411 | (let ((begin (org-element-property :begin datum)) 412 | (end (org-element-property :end datum)) 413 | c-begin 414 | c-end) 415 | (save-excursion 416 | (goto-char (or (org-element-property :post-affiliated datum) 417 | begin)) 418 | (unless (re-search-forward begin-regexp end t) 419 | (error "No begin text")) 420 | (setq c-begin (point)) 421 | (goto-char end) 422 | (unless (re-search-backward end-regexp begin t) 423 | (error "No end text")) 424 | (setq c-end (point))) 425 | (cons c-begin c-end))) 426 | 427 | (defun org-cmenu-contents-range (datum) 428 | (if-let ((c-begin (org-element-property :contents-begin datum)) 429 | (c-end (org-element-property :contents-end datum))) 430 | (cons c-begin c-end) 431 | (pcase (org-element-type datum) 432 | ('src-block 433 | (org-cmenu-find-contents-range datum 434 | "[ \t]*#\\+BEGIN_SRC[^\n]*\n" 435 | "^[ \t]*#\\+END_SRC")) 436 | ('comment-block 437 | (org-cmenu-find-contents-range datum 438 | "[ \t]*#\\+BEGIN_COMMENT[^\n]*\n" 439 | "^[ \t]*#\\+END_COMMENT")) 440 | ('example-block 441 | (org-cmenu-find-contents-range datum 442 | "[ \t]*#\\+BEGIN_EXAMPLE[^\n]*\n" 443 | "^[ \t]*#\\+END_EXAMPLE")) 444 | ('export-block 445 | (org-cmenu-find-contents-range datum 446 | "[ \t]*#\\+BEGIN_EXPORT[^\n]*\n" 447 | "^[ \t]*#\\+END_EXPORT")) 448 | ('code 449 | (org-cmenu-find-contents-range datum "~" "~")) 450 | ('verbatim 451 | (org-cmenu-find-contents-range datum "=" "=")) 452 | ;;@todo support (single line) comment 453 | ;;@todo support (single line) fixed-width 454 | ;;@todo support inline-babel-call? 455 | ;;@todo support inline-src-block? 456 | ;;@todo support babel-call? 457 | 458 | ;;@todo support diary-sexp? 459 | ;;@todo support latex-environment? 460 | ;;@todo support node-property? 461 | ;;@todo support citation-reference? 462 | ;;@todo support latex-fragment? 463 | 464 | ;; Not support: 465 | ;; - Elements 466 | ;; comment(multiline), fixed-width(multiline) 467 | ;; keyword, planning, clock, horizontal-rule 468 | ;; - Objects 469 | ;; entity, export-snippet, line-break, macro, 470 | ;; statistics-cookie, target, timestamp 471 | (_ 472 | (error "No contents range information (type=%s)" 473 | (org-element-type datum)))))) 474 | 475 | ;;;; Expose Contents 476 | 477 | (defun org-cmenu-back-before-spaces () 478 | (while (member (char-before) '(? ?\n ?\t)) 479 | (backward-char))) 480 | 481 | (defun org-cmenu-pos-before-spaces (pos) 482 | (save-excursion 483 | (goto-char pos) 484 | (org-cmenu-back-before-spaces) 485 | (point))) 486 | 487 | (defun org-cmenu-can-expose-p (datum) 488 | (let* ((c-range (ignore-errors (org-cmenu-contents-range datum))) 489 | (c-begin (car c-range)) 490 | (c-end (cdr c-range)) 491 | (begin (org-element-property :begin datum)) 492 | (end (org-element-property :end datum)) 493 | (post-blank (org-element-property :post-blank datum))) 494 | (and 495 | c-begin 496 | c-end 497 | (or 498 | (< begin c-begin) 499 | (< c-end (- end post-blank)))))) 500 | 501 | (put 'org-cmenu-expose-contents 'org-cmenu 502 | '(:target (contents 503 | :pred org-cmenu-can-expose-p 504 | :exclude (table table-row table-cell)))) 505 | (defun org-cmenu-expose-contents (&optional datum) 506 | (interactive) 507 | (unless datum 508 | (setq datum (org-element-context))) 509 | (unless datum 510 | (error "No datum")) 511 | ;;@todo Support comment, fixed-width 512 | (let* ((c-range (org-cmenu-contents-range datum)) ;;error if no :contents-begin 513 | (c-begin (car c-range)) 514 | (c-end (cdr c-range)) 515 | (begin (org-element-property :begin datum)) 516 | (end (org-element-property :end datum)) 517 | (post-blank (org-element-property :post-blank datum))) 518 | (delete-region c-end (- end post-blank)) 519 | (delete-region begin c-begin))) 520 | 521 | ;;;; Link 522 | 523 | (put 'org-cmenu-link-open-by-default 'org-cmenu '(:target link)) 524 | (defun org-cmenu-link-open-by-default (link) 525 | (org-link-open link)) 526 | 527 | (put 'org-cmenu-link-open-by-emacs 'org-cmenu '(:target link)) 528 | (defun org-cmenu-link-open-by-emacs (link) 529 | (org-link-open link '(4))) 530 | 531 | (put 'org-cmenu-link-open-by-system 'org-cmenu '(:target link)) 532 | (defun org-cmenu-link-open-by-system (link) 533 | (org-link-open link '(16))) 534 | 535 | (put 'org-cmenu-link-open-directory 'org-cmenu 536 | '(:target (link :pred org-cmenu-file-link-p)));;@todo org-cmenu-exists-directory-p ? 537 | (defun org-cmenu-link-open-directory (link) 538 | (let ((type (org-element-property :type link)) 539 | (path (org-element-property :path link))) 540 | (when (and (equal type "file") 541 | path 542 | (file-exists-p path)) 543 | (setq path (expand-file-name path)) 544 | (find-file (file-name-directory path)) 545 | (when (eq major-mode 'dired-mode) 546 | (with-no-warnings 547 | (dired-goto-file path)))))) 548 | 549 | (put 'org-cmenu-link-copy-path 'org-cmenu '(:target link)) 550 | (defun org-cmenu-link-copy-path (link) 551 | (when-let ((path (org-element-property :path link))) 552 | (kill-new path) 553 | (message "%s" path))) 554 | 555 | (put 'org-cmenu-link-copy-file-name 'org-cmenu 556 | '(:target (link :pred org-cmenu-file-link-p))) 557 | (defun org-cmenu-link-copy-file-name (link) 558 | (when-let ((type (org-element-property :type link)) 559 | (path (org-element-property :path link))) 560 | (when (equal type "file") 561 | (let ((filename (file-name-nondirectory path))) 562 | (kill-new filename) 563 | (message "%s" filename))))) 564 | 565 | (defvar org-cmenu-file-info-function-map 566 | (nconc 567 | (when-let 568 | ((command (cond 569 | ((executable-find "exiftool") "exiftool %s") 570 | ((executable-find "identify") "identify -verbose %s")))) 571 | (list (cons (image-file-name-regexp) command))) 572 | (list (cons "" "stat %s")))) 573 | 574 | (defun org-cmenu-show-file-info (path) 575 | (let ((func (cdr (seq-find 576 | (lambda (item) (string-match-p (car item) path)) 577 | org-cmenu-file-info-function-map)))) 578 | (cond 579 | ((functionp func) 580 | (funcall func path)) 581 | ((stringp func) 582 | (org-cmenu-shell-command-popup 583 | (format func (shell-quote-argument path)) 584 | "*File Info*" "*File Info Error*"))))) 585 | 586 | (put 'org-cmenu-link-show-file-info 'org-cmenu 587 | '(:target (link :pred org-cmenu-exists-file-link-p))) 588 | (defun org-cmenu-link-show-file-info (link) 589 | (let ((type (org-element-property :type link)) 590 | (path (org-element-property :path link))) 591 | (unless (equal type "file") 592 | (error "type=%s" type)) 593 | (unless (file-exists-p path) 594 | (error "File not exists : %s" path)) 595 | 596 | (org-cmenu-show-file-info path))) 597 | 598 | (put 'org-cmenu-link-rename-file 'org-cmenu 599 | '(:target (link :pred org-cmenu-exists-file-link-p))) 600 | (defun org-cmenu-link-rename-file (link) 601 | (let* ((type (org-element-property :type link)) 602 | (path (org-element-property :path link)) 603 | (abs-p (file-name-absolute-p path)) 604 | (abs-path (expand-file-name path))) 605 | (unless (equal type "file") 606 | (error "type=%s" type)) 607 | (unless (file-exists-p abs-path) 608 | (error "File not exists : %s" abs-path)) 609 | 610 | (let* ((new-file 611 | (read-file-name "File Name: " 612 | (file-name-directory abs-path) 613 | nil 614 | nil 615 | (file-name-nondirectory abs-path))) 616 | (new-file 617 | (if (string-empty-p (file-name-nondirectory new-file)) 618 | (concat new-file (file-name-nondirectory abs-path)) 619 | new-file)) 620 | (new-link-path 621 | (concat "file:" 622 | (if abs-p 623 | (expand-file-name new-file) 624 | (file-relative-name new-file))))) 625 | (rename-file abs-path new-file) 626 | (org-cmenu-link-replace-at-point new-link-path 'path) 627 | (message "Changed the file name to %s\n(%s)" 628 | new-file new-link-path)))) 629 | 630 | (defun org-cmenu-link-replace-at-point (new-text part) 631 | (cond 632 | ((org-in-regexp org-link-bracket-re 1) 633 | (pcase part 634 | ('link (replace-match (org-link-escape new-text) t t nil 0)) 635 | ('path (replace-match (org-link-escape new-text) t t nil 1)) 636 | ('description (replace-match new-text t t nil 2))) 637 | t) 638 | ((org-in-regexp org-link-angle-re) 639 | (pcase part 640 | ('link (replace-match new-text t t nil 0) t) 641 | ('path (replace-match (concat "<" new-text ">") t t nil 0) t) 642 | ('description nil))) 643 | ((org-in-regexp org-link-plain-re) 644 | (pcase part 645 | ('link (replace-match new-text t t nil 0) t) 646 | ('path (replace-match new-text t t nil 0) t) 647 | ('description nil))))) 648 | 649 | ;;@todo impl 650 | ;; (defun org-cmenu-file-link-open-source (link) 651 | ;; (interactive) 652 | ;; ) 653 | 654 | ;;@todo impl 655 | ;; (defun org-cmenu-file-link-open-map (link) 656 | ;; (interactive) 657 | ;; ) 658 | 659 | (defun org-cmenu-shell-command-popup (command output-buffer error-buffer) 660 | "Execute COMMAND and pop up the resulting buffer." 661 | (let* ((kill-buffers 662 | (lambda () 663 | (when (get-buffer output-buffer) (kill-buffer output-buffer)) 664 | (when (get-buffer error-buffer) (kill-buffer error-buffer)))) 665 | (quit 666 | (lambda () 667 | (interactive) 668 | (quit-window) 669 | (funcall kill-buffers))) 670 | (init-buffer 671 | (lambda (buffer-name) 672 | (when (get-buffer buffer-name) 673 | (with-current-buffer buffer-name 674 | (read-only-mode) 675 | (local-set-key "q" quit))))) 676 | (result-code 677 | (progn 678 | (funcall kill-buffers) 679 | (let ((max-mini-window-height 0)) 680 | (shell-command command output-buffer error-buffer))))) 681 | (funcall init-buffer output-buffer) 682 | (funcall init-buffer error-buffer) 683 | (pop-to-buffer (if (equal result-code 0) output-buffer error-buffer)) 684 | result-code)) 685 | 686 | ;;;; Plain List 687 | 688 | (put 'org-cmenu-plain-list-make-subtree 'org-cmenu 689 | '(:target (plain-list :pred org-cmenu-under-section-p))) 690 | (defun org-cmenu-plain-list-make-subtree (_datum) 691 | (save-excursion 692 | (save-restriction 693 | (org-list-make-subtree)))) 694 | 695 | (put 'org-cmenu-plain-list-repair 'org-cmenu '(:target plain-list)) 696 | (defun org-cmenu-plain-list-repair (datum) 697 | (save-excursion 698 | (save-restriction 699 | (org-cmenu-narrow-to-datum datum) 700 | (org-list-repair)))) 701 | 702 | (put 'org-cmenu-plain-list-copy-as-sexp 'org-cmenu '(:target plain-list)) 703 | (defun org-cmenu-plain-list-copy-as-sexp (datum) 704 | (save-excursion 705 | (save-restriction 706 | (org-cmenu-narrow-to-datum datum) 707 | (goto-char (org-element-property :post-affiliated datum)) 708 | (kill-new (pp (org-list-to-lisp)))))) 709 | 710 | ;;;; Table 711 | ;;;;; S-Exp 712 | (put 'org-cmenu-table-copy-as-sexp 'org-cmenu 713 | '(:target (table table-row table-cell))) 714 | (defun org-cmenu-table-copy-as-sexp (datum) 715 | (save-excursion 716 | (goto-char (or (org-element-property :post-affiliated datum) 717 | (org-element-property :begin datum))) 718 | (kill-new (pp (org-cmenu-table-to-lisp-no-properties))))) 719 | 720 | (defun org-cmenu-table-to-lisp-no-properties () 721 | (interactive) 722 | (let ((rows (org-table-to-lisp))) 723 | (dolist (row rows) 724 | (when (listp row) 725 | (cl-loop for cell on row 726 | do (when (stringp (car cell)) 727 | (setcar cell (substring-no-properties (car cell))))))) 728 | rows)) 729 | 730 | ;;;;; Move Point (Internal) 731 | 732 | ;; goto row 733 | 734 | (defun org-cmenu-table-goto-row-bol (n) 735 | "Go to the beginning (on |) of the Nth row. 736 | 737 | Return t when the line exists, nil if it does not exist." 738 | (let ((saved-point (point)) 739 | (end (org-table-end)) 740 | (i 0)) 741 | (goto-char (org-table-begin)) 742 | (while (and (< i n) 743 | (re-search-forward org-table-dataline-regexp end t)) 744 | (setq i (1+ i))) 745 | (backward-char 2) ;; |[^-] 746 | 747 | (if (= i n) 748 | t 749 | (goto-char saved-point) 750 | nil))) 751 | 752 | (defun org-cmenu-table-goto-first-row-bol () 753 | "Go to the beginning (on |) of the first row." 754 | (org-cmenu-table-goto-row-bol 1)) 755 | 756 | (defun org-cmenu-table-goto-first-non-empty-row-bol () 757 | "Go to the beginning (on |) of the first non empty row." 758 | (let ((saved-point (point))) 759 | (goto-char (org-table-begin)) 760 | (if (re-search-forward "^[ \t]*|[^-\n]" (org-table-end) t) 761 | (progn 762 | (backward-char 2) ;; |[^-\n] 763 | t) 764 | (goto-char saved-point) 765 | nil))) 766 | 767 | (defun org-cmenu-table-goto-first-row-nth-column (n) 768 | (let ((saved-point (point)) 769 | (end (org-table-end)) 770 | found) 771 | (goto-char (org-table-begin)) 772 | (while (and (re-search-forward "^[ \t]*|[^-\n]" end t) 773 | (not (setq found (org-cmenu-table-goto-column n))))) 774 | (unless found 775 | (goto-char saved-point)) 776 | found)) 777 | 778 | (defun org-cmenu-table-goto-last-row-bol () 779 | "Go to the beginning (on |) of the last row." 780 | (let ((saved-point (point))) 781 | (goto-char (org-table-end)) 782 | (if (re-search-backward org-table-dataline-regexp (org-table-begin) t) 783 | t 784 | (goto-char saved-point) 785 | nil))) 786 | 787 | (defun org-cmenu-table-goto-last-non-empty-row-bol () 788 | "Go to the beginning (on |) of the last non empty row." 789 | (let ((saved-point (point))) 790 | (goto-char (org-table-end)) 791 | (if (re-search-backward "^[ \t]*|[^-\n]" (org-table-begin) t) 792 | t 793 | (goto-char saved-point) 794 | nil))) 795 | 796 | (defun org-cmenu-table-goto-last-row-nth-column (n) 797 | (let ((saved-point (point)) 798 | (begin (org-table-begin)) 799 | found) 800 | (goto-char (org-table-end)) 801 | (while (and (re-search-backward "^[ \t]*|[^-\n]" begin t) 802 | (not (setq found (org-cmenu-table-goto-column n)))) 803 | (beginning-of-line)) 804 | (unless found 805 | (goto-char saved-point)) 806 | found)) 807 | 808 | ;; goto column 809 | 810 | (defun org-cmenu-table-goto-column (n) 811 | "Move the cursor to the Nth column in the current table line." 812 | ;;NOTE: org-table-goto-column will move to place that is not table-cell, 813 | ;; so do not use it. 814 | (beginning-of-line) 815 | (cl-loop repeat n 816 | always (re-search-forward "\\(|[ \t]?\\)[ \t]*[^ \t\n]" 817 | (line-end-position) t) 818 | do (goto-char (match-end 1)))) 819 | 820 | (defun org-cmenu-table-has-column-p (n) 821 | "Return t if the current row has the Nth column." 822 | (save-excursion 823 | (org-cmenu-table-goto-column n))) 824 | 825 | ;; goto in field 826 | 827 | (defun org-cmenu-table-goto-end-of-field () 828 | (when (re-search-forward "\\([ \t]?|\\|$\\)" (line-end-position) t) 829 | (goto-char (match-beginning 1)))) 830 | 831 | ;; Predicate 832 | 833 | (defun org-cmenu-table-empty-row-p () 834 | (save-excursion 835 | (when (org-at-table-p) 836 | (beginning-of-line) 837 | (not (looking-at "^[ \t]*|[^-\n]"))))) 838 | 839 | ;;;;; Move Point 840 | 841 | (put 'org-cmenu-table-previous-column 'org-cmenu 842 | '(:target table-cell :call no-wrap)) 843 | (defun org-cmenu-table-previous-column () 844 | (interactive) 845 | (when (org-at-table-p) 846 | (when (re-search-backward "\\(|[ \t]?\\)[^|\n]*|" 847 | (line-beginning-position) t) 848 | (goto-char (match-end 1))))) 849 | 850 | (put 'org-cmenu-table-next-column 'org-cmenu 851 | '(:target table-cell :call no-wrap)) 852 | (defun org-cmenu-table-next-column () 853 | (interactive) 854 | (when (org-at-table-p) 855 | (when (re-search-forward "|[ \t]?\\([ \t]*[^\n]\\)" (line-end-position) t) 856 | (goto-char (match-beginning 1))))) 857 | 858 | (put 'org-cmenu-table-previous-row 'org-cmenu 859 | '(:target table-cell :call no-wrap)) 860 | (defun org-cmenu-table-previous-row () 861 | (interactive) 862 | (when (org-at-table-p) 863 | (let ((col (org-table-current-column))) 864 | (when (re-search-backward "^[ \t]*|[^-\n][^\n]*\n[^\n]*" 865 | (org-table-begin) t) 866 | (org-cmenu-table-goto-column col))))) 867 | 868 | (put 'org-cmenu-table-next-row 'org-cmenu 869 | '(:target table-cell :call no-wrap)) 870 | (defun org-cmenu-table-next-row () 871 | (interactive) 872 | (when (org-at-table-p) 873 | (let ((col (org-table-current-column))) 874 | (when (re-search-forward org-table-dataline-regexp (org-table-end) t) 875 | (org-cmenu-table-goto-column col))))) 876 | 877 | (put 'org-cmenu-table-first-column-in-row 'org-cmenu 878 | '(:target table-cell :call no-wrap)) 879 | (defun org-cmenu-table-first-column-in-row () 880 | (interactive) 881 | (when (org-at-table-p) 882 | (beginning-of-line) 883 | (org-cmenu-table-next-column))) 884 | 885 | (put 'org-cmenu-table-last-column-in-row 'org-cmenu 886 | '(:target table-cell :call no-wrap)) 887 | (defun org-cmenu-table-last-column-in-row () 888 | (interactive) 889 | (while (org-cmenu-table-next-column))) 890 | 891 | ;; 892 | 893 | (put 'org-cmenu-table-first-row 'org-cmenu 894 | '(:target table-cell :call no-wrap)) 895 | (defun org-cmenu-table-first-row () 896 | (interactive) 897 | (when (org-at-table-p) 898 | (let ((col (org-table-current-column))) 899 | (when (org-cmenu-table-goto-first-row-bol) 900 | (org-cmenu-table-goto-column col))))) 901 | 902 | (put 'org-cmenu-table-last-row 'org-cmenu 903 | '(:target table-cell :call no-wrap)) 904 | (defun org-cmenu-table-last-row () 905 | (interactive) 906 | (when (org-at-table-p) 907 | (let ((col (org-table-current-column))) 908 | (when (org-cmenu-table-goto-last-row-bol) 909 | (org-cmenu-table-goto-column col))))) 910 | 911 | (put 'org-cmenu-table-first-field-in-table 'org-cmenu 912 | '(:target table-cell :call no-wrap)) 913 | (defun org-cmenu-table-first-field-in-table () 914 | (interactive) 915 | (when (and (org-at-table-p) 916 | (org-cmenu-table-goto-first-non-empty-row-bol)) 917 | (org-cmenu-table-goto-column 1))) 918 | 919 | (put 'org-cmenu-table-last-field-in-table 'org-cmenu 920 | '(:target table-cell :call no-wrap)) 921 | (defun org-cmenu-table-last-field-in-table () 922 | (interactive) 923 | (when (and (org-at-table-p) 924 | (org-cmenu-table-goto-last-non-empty-row-bol)) 925 | (org-cmenu-table-last-column-in-row))) 926 | 927 | ;;;;; Scroll 928 | 929 | (defun org-cmenu-table-scroll-up (&optional arg) 930 | (interactive "^P") 931 | (when (org-at-table-p) 932 | (let* ((table-end (org-table-end)) 933 | (start-line-pos (line-beginning-position)) 934 | (start-window-start (window-start)) 935 | ;;(start-window-end (window-end)) 936 | (start-col (org-table-current-column))) 937 | (scroll-up-command arg) 938 | (when (> (point) table-end) 939 | (goto-char table-end)) 940 | (while (and 941 | (> (line-beginning-position) start-line-pos) 942 | (not (org-cmenu-table-has-column-p start-col))) 943 | (forward-line -1)) 944 | (org-cmenu-table-goto-column start-col) 945 | (when (< (point) (window-start)) 946 | (set-window-start (selected-window) start-window-start))))) 947 | 948 | (defun org-cmenu-table-scroll-down (&optional arg) 949 | (interactive "^P") 950 | (when (org-at-table-p) 951 | (let* ((table-begin (org-table-begin)) 952 | (start-line-pos (line-beginning-position)) 953 | (start-window-start (window-start)) 954 | ;;(start-window-end (window-end)) 955 | (start-col (org-table-current-column))) 956 | (scroll-down-command arg) 957 | (when (< (point) table-begin) 958 | (goto-char table-begin)) 959 | (while (and 960 | (< (line-beginning-position) start-line-pos) 961 | (not (org-cmenu-table-has-column-p start-col))) 962 | (forward-line 1)) 963 | (org-cmenu-table-goto-column start-col) 964 | (when (>= (point) start-window-start) 965 | (set-window-start (selected-window) start-window-start))))) 966 | 967 | ;;;;; Mark 968 | 969 | (put 'org-cmenu-table-mark-all 'org-cmenu 970 | '(:target (table table-row table-cell) :call no-wrap)) 971 | (defun org-cmenu-table-mark-all () 972 | (interactive) 973 | (when (org-at-table-p) 974 | ;;@todo check the last row has a maximum column size. 975 | (org-cmenu-table-first-field-in-table) 976 | (push-mark (point) nil t) 977 | (org-cmenu-table-last-field-in-table) 978 | (org-cmenu-table-goto-end-of-field) 979 | t)) 980 | 981 | (put 'org-cmenu-table-mark-row 'org-cmenu 982 | '(:target (table-row table-cell) :call no-wrap)) 983 | (defun org-cmenu-table-mark-row () 984 | (interactive) 985 | (when (org-at-table-p) 986 | (when (org-cmenu-table-empty-row-p) 987 | (error "The current row is empty")) 988 | 989 | (org-cmenu-table-first-column-in-row) 990 | (push-mark (point) nil t) 991 | (org-cmenu-table-last-column-in-row) 992 | (org-cmenu-table-goto-end-of-field) 993 | t)) 994 | 995 | (put 'org-cmenu-table-mark-column 'org-cmenu 996 | '(:target table-cell :call no-wrap)) 997 | (defun org-cmenu-table-mark-column () 998 | (interactive) 999 | (when (org-at-table-p) 1000 | (let ((col (org-table-current-column))) 1001 | (when (org-cmenu-table-goto-first-row-nth-column col) 1002 | (push-mark (point) nil t) 1003 | (org-cmenu-table-goto-last-row-nth-column col) 1004 | (org-cmenu-table-goto-end-of-field) 1005 | t)))) 1006 | 1007 | (put 'org-cmenu-table-mark-field 'org-cmenu 1008 | '(:target table-cell :call no-wrap)) 1009 | (defun org-cmenu-table-mark-field () 1010 | (interactive) 1011 | (when (org-at-table-p) 1012 | (org-cmenu-table-goto-end-of-field) 1013 | (push-mark (point) nil t) 1014 | (when (re-search-backward "| ?" (line-beginning-position) t) 1015 | (goto-char (match-end 0))))) 1016 | 1017 | 1018 | ;;;;; Region 1019 | 1020 | (put 'org-cmenu-table-cut-region 'org-cmenu 1021 | '(:target table-cell :call no-wrap)) 1022 | (defun org-cmenu-table-cut-region () 1023 | (interactive) 1024 | (org-table-save-field 1025 | (call-interactively #'org-table-cut-region))) 1026 | 1027 | (put 'org-cmenu-table-copy-region 'org-cmenu 1028 | '(:target table-cell :call no-wrap)) 1029 | (defun org-cmenu-table-copy-region () 1030 | (interactive) 1031 | (org-table-save-field 1032 | (call-interactively #'org-table-copy-region)) 1033 | (setq deactivate-mark t)) 1034 | 1035 | ;;;;; Sum 1036 | 1037 | (defun org-cmenu-table-sum-all () 1038 | (interactive) 1039 | (when (org-at-table-p) 1040 | ;;@todo check the last row has a maximum column size. 1041 | ;;@todo There are many cases where it cannot be calculated correctly. 1042 | (save-mark-and-excursion 1043 | (org-cmenu-table-mark-all) 1044 | (call-interactively #'org-table-sum)))) 1045 | 1046 | (defun org-cmenu-table-sum-row () 1047 | (interactive) 1048 | (when (org-at-table-p) 1049 | (save-mark-and-excursion 1050 | (org-cmenu-table-mark-row) 1051 | (call-interactively #'org-table-sum)))) 1052 | 1053 | (defun org-cmenu-table-sum-column () 1054 | (interactive) 1055 | (when (org-at-table-p) 1056 | (save-mark-and-excursion 1057 | (org-cmenu-table-mark-column) 1058 | (call-interactively #'org-table-sum)))) 1059 | 1060 | ;;;;; Shrink/Expand 1061 | 1062 | (defvar org-cmenu-table-cycle-column-width--count 0) 1063 | (defun org-cmenu-table-cycle-column-width () 1064 | (interactive) 1065 | ;;(message "last=%s this=%s" last-command this-command) 1066 | (setq org-cmenu-table-cycle-column-width--count 1067 | (if (eq last-command this-command) 1068 | (1+ org-cmenu-table-cycle-column-width--count) 1069 | 0)) 1070 | (pcase (% org-cmenu-table-cycle-column-width--count 4) 1071 | (0 (if (org-cmenu-table-has-column-width-spec-p) 1072 | (org-table-shrink) 1073 | (org-cmenu-table-shrink-all-column))) 1074 | (1 (org-table-expand)) 1075 | (2 (org-cmenu-table-shrink-all-column)) 1076 | (3 (org-table-expand)))) 1077 | 1078 | (defun org-cmenu-table-has-column-width-spec-p () 1079 | (when (org-at-table-p) 1080 | (save-excursion 1081 | (goto-char (org-table-begin)) 1082 | (re-search-forward "|[ \t]*<[lrc]?[0-9]+>[ \t]*\\(|\\|$\\)" (org-table-end) t)))) 1083 | 1084 | (defun org-cmenu-table-shrink-all-column () 1085 | (interactive) 1086 | (when (org-at-table-p) 1087 | (org-table-expand) 1088 | (org-table-toggle-column-width "-"))) 1089 | 1090 | ;;;; Insert 1091 | ;;;;; Insert Objects 1092 | 1093 | (defun org-cmenu-insert-begin-end (beg-str &optional end-str) 1094 | (unless end-str 1095 | (setq end-str beg-str)) 1096 | (insert beg-str end-str) 1097 | (backward-char (length end-str))) 1098 | 1099 | (defun org-cmenu-insert-bold () 1100 | (interactive) 1101 | (org-emphasize ?*)) 1102 | 1103 | (defun org-cmenu-insert-underline () 1104 | (interactive) 1105 | (org-emphasize ?_)) 1106 | 1107 | (defun org-cmenu-insert-italic () 1108 | (interactive) 1109 | (org-emphasize ?/)) 1110 | 1111 | (defun org-cmenu-insert-verbatim () 1112 | (interactive) 1113 | (org-emphasize ?=)) 1114 | 1115 | (defun org-cmenu-insert-code () 1116 | (interactive) 1117 | (org-emphasize ?~)) 1118 | 1119 | (defun org-cmenu-insert-strike-through () 1120 | (interactive) 1121 | (org-emphasize ?+)) 1122 | 1123 | (defun org-cmenu-insert-subscript () 1124 | (interactive) 1125 | (org-cmenu-insert-begin-end "_{" "}")) 1126 | 1127 | (defun org-cmenu-insert-superscript () 1128 | (interactive) 1129 | (org-cmenu-insert-begin-end "^{" "}")) 1130 | 1131 | (defun org-cmenu-insert-inline-babel-call (fname) 1132 | (interactive "sFunction Name: ") 1133 | (insert (format "call_%s()" fname))) 1134 | 1135 | (defun org-cmenu-insert-inline-src-block (lang) 1136 | (interactive "sLanguage: ") 1137 | (insert (format "src_%s[]{}" lang)) ;;@todo remove [] after implementing the command to add header arguments to inline-src-block. org-babel-insert-header-arg does not support insertion into inline-src-block. 1138 | (backward-char 1)) 1139 | 1140 | (defun org-cmenu-insert-line-break () 1141 | (interactive) 1142 | (insert "\\\\\n")) 1143 | 1144 | (defun org-cmenu-entities-name-and-utf8 (entities) 1145 | "Convert ENTITIES (org-entities or org-entities-user) to list 1146 | of name and utf8." 1147 | (cl-loop for e in entities 1148 | when (listp e) 1149 | nconc (list (car e) 1150 | (nth 6 e)))) ;;utf8 1151 | 1152 | (defun org-cmenu-entities-rfind (entities name-or-utf8) 1153 | "Find the entity by NAME-OR-UTF8 from ENTITIES (org-entities or 1154 | org-entities-user)." 1155 | (seq-some 1156 | (lambda (e) 1157 | (when (listp e) 1158 | (cond 1159 | ((string= (nth 0 e) name-or-utf8) name-or-utf8) ;;name 1160 | ((string= (nth 6 e) name-or-utf8) (nth 0 e))))) ;;utf8 1161 | entities)) 1162 | 1163 | (defun org-cmenu-insert-entity (name) 1164 | (interactive 1165 | (list 1166 | (let ((name-or-utf8 1167 | (completing-read 1168 | "Entity name or symbol: " 1169 | (nconc 1170 | (org-cmenu-entities-name-and-utf8 org-entities) 1171 | (org-cmenu-entities-name-and-utf8 org-entities-user))))) 1172 | (or (org-cmenu-entities-rfind org-entities name-or-utf8) 1173 | (org-cmenu-entities-rfind org-entities-user name-or-utf8))))) 1174 | (insert (format "\\%s{}" name)) 1175 | (message "%s" (nth 6 (org-entity-get name)))) 1176 | 1177 | ;; Use org-insert-link 1178 | ;; (defun org-cmenu-insert-link () 1179 | ;; (interactive) 1180 | ;; (org-cmenu-insert-begin-end "[[" "]]")) 1181 | 1182 | (defun org-cmenu-insert-target () 1183 | (interactive) 1184 | (org-cmenu-insert-begin-end "<<" ">>")) 1185 | 1186 | (defun org-cmenu-insert-radio-target () 1187 | (interactive) 1188 | (org-cmenu-insert-begin-end "<<<" ">>>")) 1189 | 1190 | (defun org-cmenu-insert-macro () 1191 | (interactive) 1192 | ;;@todo find macro definition 1193 | (org-cmenu-insert-begin-end "{{{" "name(arg)}}}")) 1194 | 1195 | (defun org-cmenu-insert-export-snippet (backend) 1196 | (interactive 1197 | (list 1198 | (completing-read "Backend: " 1199 | (mapcar #'symbol-name org-export-backends)))) 1200 | (org-cmenu-insert-begin-end (format "@@%s:" backend) "@@")) 1201 | 1202 | 1203 | ;;;;; Insert Elements 1204 | 1205 | ;; org-insert-drawer 1206 | ;; org-footnote-new 1207 | ;; org-insert-structure-template 1208 | 1209 | (defun org-cmenu-insert-babel-call () 1210 | (interactive) 1211 | (org-cmenu-insert-begin-end "#+call: " "name(arg)")) 1212 | 1213 | (defun org-cmenu-insert-macro-definition () 1214 | (interactive) 1215 | (org-cmenu-insert-begin-end "#+MACRO: " "name string-or-sexp $1 $2\n")) 1216 | 1217 | (defun org-cmenu-insert-fixed-width () 1218 | (interactive) 1219 | (insert ": ")) 1220 | 1221 | (defun org-cmenu-insert-horizontal-rule () 1222 | (interactive) 1223 | (insert "-----\n")) 1224 | 1225 | ;;@todo Add statistics cookie 1226 | 1227 | ;;@todo Add functions to insert the following keywords. 1228 | ;; #+TEXT: 1229 | ;; #+TEXT: [TABLE-OF-CONTENTS] 1230 | ;; #+TEXT: 1231 | ;; #+TITLE, AUTHOR, DATE, EMAIL, DESCRIPTION, KEYWORDS, LANGUAGE: 1232 | ;; #+LINK_UP, LINK_HOME: url 1233 | ;; #+BIND: 1234 | ;; #+OPTIONS: 1235 | ;; #+CONSTANTS: c=299792458. pi=3.14 eps=2.4e-6 1236 | ;; #+STARTUP: 1237 | ;; #+LINK: google http://www.google.com/search?q=%s 1238 | ;; #+TODO: TODO FEEDBACK VERIFY | DONE CANCELED 1239 | ;; #+PRIORITIES: A C B 1240 | ;; #+FILETAGS: :Peter:Boss:Secret: 1241 | ;; #+TAGS: { @work(w) @home(h) @tennisclub(t) } laptop(l) pc(p) 1242 | ;; #+PROPERTY: NDisks_ALL 1 2 3 4 1243 | ;; #+COLUMNS: %25ITEM %TAGS %PRIORITY %TODO 1244 | ;; #+DRAWERS: LOGBOOK PROPERTIES FEEDSTATUS 1245 | ;; #+ARCHIVE: %s_done:: 1246 | ;; #+CATEGORY: Holiday 1247 | ;; #+INCLUDE: "~/example.el" :lines "5-10" :prefix1 "" :prefix "" :minlevel 1 src elisp 1248 | ;; #+INDEX: Application!CV 1249 | ;; LATEX_HEADER 1250 | ;; EXPORT_SELECT_TAGS 1251 | ;; EXPORT_EXCLUDE_TAGS 1252 | ;; XSLT 1253 | ;; MATHJAX 1254 | ;; STYLE 1255 | ;; INFOJS_OPT 1256 | ;;(defun org-cmenu-insert-options () 1257 | ;; ) 1258 | 1259 | (defun org-cmenu-read-option-keyword () 1260 | (completing-read "Keyword: " 1261 | (seq-uniq 1262 | (append 1263 | org-options-keywords 1264 | (mapcar (lambda (kw) (concat kw ":")) 1265 | (org-get-export-keywords)))))) 1266 | 1267 | (defun org-cmenu-insert-option-keyword (option-name 1268 | &optional 1269 | value 1270 | at-top 1271 | no-append) 1272 | (interactive 1273 | (list (org-cmenu-read-option-keyword))) 1274 | 1275 | (cond 1276 | ((and 1277 | no-append 1278 | (progn 1279 | (goto-char (point-min)) 1280 | (re-search-forward (concat "^[ \t]*#\\+" option-name "[^\n]+") nil t))) 1281 | nil) 1282 | (t 1283 | (when at-top 1284 | (goto-char (point-min)) 1285 | (when (re-search-forward "^[^#]" nil t) 1286 | (goto-char (match-beginning 0)))) 1287 | (insert "#+" option-name " " (or value "") "\n") 1288 | (backward-char) 1289 | t))) 1290 | 1291 | (defun org-cmenu-insert-option-keyword-at-top () 1292 | (interactive) 1293 | (org-cmenu-insert-option-keyword (org-cmenu-read-option-keyword) nil t)) 1294 | 1295 | (defvar org-cmenu-default-title-info 1296 | ;; see: org-pcomplete.el 1297 | '(("TITLE:" . (lambda () 1298 | (if-let ((file (buffer-file-name (buffer-base-buffer)))) 1299 | (file-name-sans-extension (file-name-nondirectory file)) 1300 | (buffer-name (buffer-base-buffer))))) 1301 | ("DATE:" . (lambda () (format-time-string (car org-time-stamp-formats)))) 1302 | ;;("EMAIL:" . (lambda () (or user-mail-address ""))) 1303 | ("AUTHOR:" . (lambda () (or user-full-name ""))))) 1304 | 1305 | (defun org-cmenu-insert-title-info () 1306 | (interactive) 1307 | (let (first-pos) 1308 | (dolist (kw-fun org-cmenu-default-title-info) 1309 | (let* ((kw (car kw-fun)) 1310 | (fun (cdr kw-fun)) 1311 | (inserted-p (org-cmenu-insert-option-keyword kw nil t t))) 1312 | (unless first-pos 1313 | (setq first-pos (point))) 1314 | (when inserted-p 1315 | (insert (funcall fun))))) 1316 | (goto-char first-pos))) 1317 | 1318 | 1319 | (provide 'org-cmenu-tools) 1320 | ;;; org-cmenu-tools.el ends here 1321 | --------------------------------------------------------------------------------