Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
2701 views
1
; ess-roxy.el --- convenient editing of in-code roxygen documentation
2
;;
3
;; Copyright (C) 2009--2012 Henning Redestig, A.J. Rossini, Richard
4
;; M. Heiberger, Martin Maechler, Kurt Hornik, Rodney Sparapani, Stephen
5
;; Eglen and Vitalie Spinu.
6
;;
7
;; Author: Henning Redestig <henning.red * go0glemail c-m>
8
;; Keywords: convenience, tools
9
;;
10
;; This file is part of ESS
11
;;
12
;; This program is free software; you can redistribute it and/or
13
;; modify it under the terms of the GNU General Public License as
14
;; published by the Free Software Foundation; either version 3 of the
15
;; License, or (at your option) any later version.
16
;;
17
;; This program is distributed in the hope that it will be useful, but
18
;; WITHOUT ANY WARRANTY; without even the implied warranty of
19
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20
;; General Public License for more details.
21
;;
22
;; You should have received a copy of the GNU General Public License
23
;; along with this program. If not, see
24
;; <http://www.gnu.org/licenses/>.
25
26
27
;;; Commentary:
28
29
;; Lots of inspiration from doc-mode,
30
;; http://nschum.de/src/emacs/doc-mode/
31
;;
32
;; Features::
33
;;
34
;; - basic highlighting
35
;; - generating and updating templates from function definition and customized default template
36
;; - C-c C-o C-o :: update template
37
;; - navigating and filling roxygen fields
38
;; - C-c TAB, M-q, C-a, ENTER, M-h :: advised tag completion, fill-paragraph,
39
;; move-beginning-of-line, newline-and-indent, mark-paragraph
40
;; - C-c C-o n,p :: next, previous roxygen entry
41
;; - C-c C-o C-c :: Unroxygen region. Convenient for editing examples.
42
;; - folding visibility using hs-minor-mode
43
;; - TAB :: advised ess-ident-command, hide entry if in roxygen doc.
44
;; - preview
45
;; - C-c C-o C-r :: create a preview of the Rd file as generated
46
;; using roxygen
47
;; - C-c C-o C-t :: create a preview of the Rd HTML file as generated
48
;; using roxygen and the tools package
49
;; - C-c C-o t :: create a preview of the Rd text file
50
;;
51
;; Known issues:
52
;;
53
;; - hideshow mode does not work very well. In particular, if ordinary
54
;; comments precede a roxygen entry, then both will be hidden in the
55
;; same overlay from start and not unfoldable using TAB since the
56
;; roxygen prefix is not present. The planned solution is implement
57
;; a replacement for hideshow.
58
;; - only limited functionality for S4 documentation.
59
60
;; this *is* enabled now via ess-mode-hook in ./ess-site.el
61
62
;;; Code:
63
64
(require 'ess-custom)
65
(require 'hideshow)
66
(autoload 'Rd-preview-help "ess-rd" "[autoload]" t)
67
68
;; ------------------
69
(defvar ess-roxy-mode-map
70
(let ((map (make-sparse-keymap)))
71
(if ess-roxy-hide-show-p
72
(define-key map (kbd "C-c C-o h") 'ess-roxy-hide-all))
73
;; short version (*first*: -> key binding shown in menu):
74
(define-key map (kbd "C-c C-o n") 'ess-roxy-next-entry)
75
(define-key map (kbd "C-c C-o p") 'ess-roxy-previous-entry)
76
;; For consistency (e.g. C-c C-o C-h !): kept here *in* addition to above
77
(define-key map (kbd "C-c C-o C-o") 'ess-roxy-update-entry)
78
(define-key map (kbd "C-c C-o C-r") 'ess-roxy-preview-Rd)
79
(define-key map (kbd "C-c C-o C-w") 'ess-roxy-preview-HTML)
80
(define-key map (kbd "C-c C-o C-t") 'ess-roxy-preview-text)
81
(define-key map (kbd "C-c C-o C-c") 'ess-roxy-toggle-roxy-region)
82
map)
83
)
84
85
;; (defvar ess-roxy-font-lock-keywords nil)
86
87
(defvar ess-roxy-font-lock-keywords
88
`((,(concat ess-roxy-re " *\\([@\\]"
89
(regexp-opt ess-roxy-tags-param t)
90
"\\)\\>")
91
(1 'font-lock-keyword-face prepend))
92
(,(concat ess-roxy-re " *\\([@\\]"
93
(regexp-opt '("param" "importFrom" "importClassesFrom"
94
"importMethodsFrom") t)
95
"\\)\\>\\(?:[ \t]+\\(\\sw+\\)\\)?")
96
(1 'font-lock-keyword-face prepend)
97
(3 'font-lock-variable-name-face prepend))
98
(,(concat "[@\\]" (regexp-opt ess-roxy-tags-noparam t) "\\>")
99
(0 'font-lock-variable-name-face prepend))
100
(,(concat ess-roxy-re)
101
(0 'bold prepend))))
102
103
(define-minor-mode ess-roxy-mode
104
"Minor mode for editing in-code documentation."
105
;; :lighter " Rox"
106
:keymap ess-roxy-mode-map
107
(if ess-roxy-mode
108
(progn
109
(unless (featurep 'xemacs) ;; does not exist in xemacs:
110
(font-lock-add-keywords nil ess-roxy-font-lock-keywords))
111
(if (and (featurep 'emacs) (>= emacs-major-version 24))
112
(add-to-list 'completion-at-point-functions 'ess-roxy-tag-completion)
113
(add-to-list 'comint-dynamic-complete-functions 'ess-roxy-complete-tag))
114
(if ess-roxy-hide-show-p
115
(progn
116
;(setq hs-c-start-regexp "s")
117
(if (condition-case nil
118
(if (and (symbolp hs-minor-mode)
119
(symbol-value hs-minor-mode))
120
nil t) (error t) )
121
(progn
122
(hs-minor-mode)))
123
(if ess-roxy-start-hidden-p
124
(ess-roxy-hide-all)))))
125
(if ess-roxy-hide-show-p
126
(if hs-minor-mode
127
(progn
128
(hs-show-all)
129
(hs-minor-mode))))
130
(unless (featurep 'xemacs)
131
(font-lock-remove-keywords nil ess-roxy-font-lock-keywords)))
132
(when font-lock-mode
133
(font-lock-fontify-buffer))
134
;; for auto fill functionality
135
(make-local-variable 'adaptive-fill-regexp)
136
(setq adaptive-fill-regexp (concat ess-roxy-re adaptive-fill-regexp))
137
(make-local-variable 'adaptive-fill-first-line-regexp)
138
(setq adaptive-fill-first-line-regexp (concat ess-roxy-re
139
adaptive-fill-first-line-regexp))
140
(make-local-variable 'paragraph-start)
141
(setq paragraph-start (concat "\\(" ess-roxy-re "\\)*" paragraph-start))
142
(make-local-variable 'paragraph-separate)
143
(setq paragraph-separate (concat "\\(" ess-roxy-re "\\)*" paragraph-separate))
144
(add-hook 'ess-presend-filter-functions 'ess-roxy-remove-roxy-re nil 'local)
145
)
146
147
148
;; (setq hs-c-start-regexp ess-roxy-str)
149
;; (make-variable-buffer-local 'hs-c-start-regexp)
150
151
;; Function definitions
152
(defun ess-roxy-beg-of-entry ()
153
"Get point number at start of current entry, 0 if not in entry"
154
(save-excursion
155
(let (beg)
156
(beginning-of-line)
157
(setq beg -1)
158
(if (not (ess-roxy-entry-p))
159
(setq beg 0)
160
(setq beg (point)))
161
(while (and (= (forward-line -1) 0) (ess-roxy-entry-p))
162
(setq beg (point)))
163
beg)))
164
165
(defun ess-roxy-in-header-p ()
166
"true if point is the description / details field"
167
(save-excursion
168
(let ((res t)
169
(cont (ess-roxy-entry-p)))
170
(beginning-of-line)
171
(while cont
172
(if (looking-at (concat ess-roxy-re " *[@].+"))
173
(progn (setq res nil)
174
(setq cont nil)))
175
(setq cont (and (= (forward-line -1) 0) (ess-roxy-entry-p)))
176
)res)))
177
178
(defun ess-roxy-beg-of-field ()
179
"Get point number at beginning of current field, 0 if not in entry"
180
(save-excursion
181
(let (cont beg)
182
(beginning-of-line)
183
(setq beg 0)
184
(setq cont t)
185
(while (and (ess-roxy-entry-p) cont)
186
(setq beg (point))
187
(if (looking-at (concat ess-roxy-re " *[@].+"))
188
(setq cont nil))
189
(if (ess-roxy-in-header-p)
190
(if (looking-at (concat ess-roxy-re " *$"))
191
(progn
192
(forward-line 1)
193
(setq beg (point))
194
(setq cont nil))))
195
(if cont (setq cont (= (forward-line -1) 0))))
196
beg)))
197
198
(defun ess-roxy-end-of-entry ()
199
" get point number at end of current entry, 0 if not in entry"
200
(save-excursion
201
(let ((end))
202
(end-of-line)
203
(setq end -1)
204
(if (not (ess-roxy-entry-p))
205
(setq end 0)
206
(setq end (point)))
207
(while (and (= (forward-line 1) 0) (ess-roxy-entry-p))
208
(end-of-line)
209
(setq end (point)))
210
end)))
211
212
(defun ess-roxy-end-of-field ()
213
"get point number at end of current field, 0 if not in entry"
214
(save-excursion
215
(let ((end nil)
216
(cont nil))
217
(setq end 0)
218
(if (ess-roxy-entry-p) (progn (end-of-line) (setq end (point))))
219
(beginning-of-line)
220
(forward-line 1)
221
(setq cont t)
222
(while (and (ess-roxy-entry-p) cont)
223
(save-excursion
224
(end-of-line)
225
(setq end (point)))
226
(if (or (and (ess-roxy-in-header-p)
227
(looking-at (concat ess-roxy-re " *$")))
228
(looking-at (concat ess-roxy-re " *[@].+")))
229
(progn
230
(forward-line -1)
231
(end-of-line)
232
(setq end (point))
233
(setq cont nil)))
234
(if cont (setq cont (= (forward-line 1) 0))))
235
end)))
236
237
(defun ess-roxy-entry-p ()
238
"True if point is in a roxy entry"
239
(save-excursion
240
(beginning-of-line)
241
(looking-at (concat ess-roxy-re))))
242
243
(defun ess-roxy-narrow-to-field ()
244
"Go to to the start of current field"
245
(interactive)
246
(let ((beg (ess-roxy-beg-of-field))
247
(end (ess-roxy-end-of-field)))
248
(narrow-to-region beg end)))
249
250
(defun ess-roxy-fill-field ()
251
"Fill the current paragraph in the current roxygen field."
252
(interactive)
253
(if (ess-roxy-entry-p)
254
(save-excursion
255
(let ((beg (ess-roxy-beg-of-field))
256
(end (ess-roxy-end-of-field))
257
(fill-prefix (concat (ess-roxy-guess-str) " "))
258
(beg-par (point-min))
259
(end-par (point-max)))
260
(save-excursion
261
(if (re-search-backward (concat ess-roxy-re " *$") beg t)
262
(setq beg-par (match-end 0))))
263
(save-excursion
264
(if (re-search-forward (concat ess-roxy-re " *$") end t)
265
(setq end-par (- (match-beginning 0) 1))))
266
(fill-region (max beg beg-par) (min end end-par))))))
267
268
(defun ess-roxy-goto-func-def ()
269
"put point at start of function either that the point is in or
270
below the current roxygen entry, error otherwise"
271
(if (ess-roxy-entry-p)
272
(progn
273
(ess-roxy-goto-end-of-entry)
274
(forward-line 1)
275
(beginning-of-line))
276
(goto-char (car (ess-end-of-function)))))
277
278
(defun ess-roxy-get-args-list-from-def ()
279
"get args list for current function"
280
(save-excursion
281
(ess-roxy-goto-func-def)
282
(let* ((args (ess-roxy-get-function-args)))
283
(mapcar (lambda (x) (cons x '(""))) args))))
284
285
(defun ess-roxy-insert-args (args &optional here)
286
"Insert an args list to the end of the roxygen entry for the
287
function at point. if here is supplied start inputting
288
`here'. Finish at end of line."
289
(let* ((arg-des nil)
290
(roxy-str (ess-roxy-guess-str)))
291
(if (or (not here) (< here 1))
292
(progn
293
(ess-roxy-goto-end-of-entry)
294
(beginning-of-line)
295
(if (not (looking-at "\="))
296
(progn
297
(end-of-line))))
298
(goto-char here))
299
(while (stringp (car (car args)))
300
(setq arg-des (pop args))
301
(unless (string= (car arg-des) "")
302
(progn
303
(insert (concat "\n"
304
roxy-str " @param " (car arg-des) " "))
305
(insert
306
(ess-replace-in-string (concat (car (cdr arg-des))) "\n"
307
(concat "\n" roxy-str)))
308
(if ess-roxy-fill-param-p
309
(ess-roxy-fill-field))
310
)))))
311
312
(defun ess-roxy-merge-args (fun ent)
313
"Take two args lists (alists) and return their union. Result
314
holds all keys from both fun and ent but no duplicates and
315
association from ent are preferred over entries from fun. Also,
316
drop entries from ent that are not in fun and are associated with
317
the empty string."
318
(let ((res-arg nil)
319
(arg-des))
320
(while (stringp (car (car fun)))
321
(setq arg-des (pop fun))
322
(if (assoc (car arg-des) ent)
323
(setq res-arg
324
(cons (cons (car arg-des) (cdr (assoc (car arg-des) ent))) res-arg))
325
(setq res-arg (cons (cons (car arg-des) '("")) res-arg))))
326
(while (stringp (car (car ent)))
327
(setq arg-des (pop ent))
328
(if (and (not (assoc (car arg-des) res-arg)) (not (string= (car (cdr arg-des)) "")))
329
(setq res-arg (cons (cons (car arg-des) (cdr arg-des)) res-arg))))
330
(nreverse res-arg)))
331
332
(defun ess-roxy-update-entry ()
333
"Update the entry at the point or the entry above the function
334
which the point is in. Add a template empty roxygen documentation
335
if no roxygen entry is available. The template can be customized
336
via the variable `ess-roxy-template-alist'. The parameter
337
descriptions can are filled if `ess-roxy-fill-param-p' is
338
non-nil."
339
(interactive)
340
(save-excursion
341
(let* ((args-fun (ess-roxy-get-args-list-from-def))
342
(args-ent (ess-roxy-get-args-list-from-entry))
343
(args (ess-roxy-merge-args args-fun args-ent))
344
(roxy-str (ess-roxy-guess-str))
345
(line-break "")
346
here key template tag-def)
347
(ess-roxy-goto-func-def)
348
(if (not (= (forward-line -1) 0))
349
(progn
350
(insert "\n")
351
(forward-line -1)))
352
(if (and (not (looking-at "^\n")) (not (ess-roxy-entry-p)))
353
(progn
354
(end-of-line)
355
(insert "\n")))
356
(if (ess-roxy-entry-p)
357
(progn
358
(setq here (1- (ess-roxy-delete-args)))
359
(ess-roxy-insert-args args here))
360
(setq template (copy-sequence ess-roxy-template-alist))
361
(while (stringp (car (car template)))
362
(setq tag-def (pop template))
363
(if (string= (car tag-def) "param")
364
(ess-roxy-insert-args args (point))
365
(if (string= (car tag-def) "description")
366
(insert (concat line-break roxy-str " "
367
(cdr tag-def) "\n" roxy-str))
368
(if (string= (car tag-def) "details")
369
(insert (concat line-break roxy-str " " (cdr tag-def)))
370
(insert (concat line-break roxy-str " @"
371
(car tag-def) " " (cdr tag-def))))
372
))
373
(setq line-break "\n")
374
)))))
375
376
(defun ess-roxy-goto-end-of-entry ()
377
"Put point at the top of the entry at point or above the
378
function at point. Return t if the point is left in a roxygen
379
entry, otherwise nil. Error if point is not in function or
380
roxygen entry."
381
(if (not (ess-roxy-entry-p))
382
(progn
383
(goto-char (nth 0 (ess-end-of-function)))
384
(forward-line -1)))
385
(if (ess-roxy-entry-p)
386
(progn
387
(goto-char (ess-roxy-end-of-entry))
388
t) (forward-line) nil))
389
390
(defun ess-roxy-goto-beg-of-entry ()
391
"put point at the top of the entry at point or above the
392
function at point. Return t if the point is left in a roxygen
393
entry, otherwise nil. Error if point is not in function or
394
roxygen entry."
395
(if (not (ess-roxy-entry-p))
396
(progn
397
(goto-char (nth 0 (ess-end-of-function)))
398
(forward-line -1)))
399
(if (ess-roxy-entry-p)
400
(progn
401
(goto-char (ess-roxy-beg-of-entry))
402
t) (forward-line) nil))
403
404
(defun ess-roxy-delete-args ()
405
"remove all args from the entry at point or above the function
406
at point. Return 0 if no deletions were made other wise the point
407
at where the last deletion ended"
408
(save-excursion
409
(let* ((args nil)
410
(cont t)
411
(field-beg 0)
412
entry-beg entry-end field-end)
413
(ess-roxy-goto-end-of-entry)
414
(setq entry-beg (ess-roxy-beg-of-entry))
415
(setq entry-end (ess-roxy-end-of-entry))
416
(goto-char entry-end)
417
(beginning-of-line)
418
(while (and (<= entry-beg (point)) (> entry-beg 0) cont)
419
(if (looking-at
420
(concat ess-roxy-re " *@param"))
421
(progn
422
(setq field-beg (ess-roxy-beg-of-field))
423
(setq field-end (ess-roxy-end-of-field))
424
(delete-region field-beg (+ field-end 1))))
425
(setq cont nil)
426
(if (= (forward-line -1) 0)
427
(setq cont t)))
428
field-beg)))
429
430
(defun ess-roxy-get-args-list-from-entry ()
431
"fill an args list from the entry above the function where the
432
point is"
433
(save-excursion
434
(let* (args entry-beg field-beg field-end args-text arg-name desc)
435
(if (ess-roxy-goto-end-of-entry)
436
(progn
437
(setq roxy-str (ess-roxy-guess-str))
438
(beginning-of-line)
439
(setq entry-beg (ess-roxy-beg-of-entry))
440
(while (and (< entry-beg (point)) (> entry-beg 0))
441
(if (looking-at
442
(concat ess-roxy-re " *@param"))
443
(progn
444
(setq field-beg (ess-roxy-beg-of-field))
445
(setq field-end (ess-roxy-end-of-field))
446
(setq args-text (buffer-substring-no-properties
447
field-beg field-end))
448
(setq args-text
449
(ess-replace-in-string args-text roxy-str ""))
450
(setq args-text
451
(ess-replace-in-string
452
args-text "[[:space:]]*@param *" ""))
453
;; (setq args-text
454
;; (ess-replace-in-string args-text "\n" ""))
455
(string-match "[^[:space:]]*" args-text)
456
(setq arg-name (match-string 0 args-text))
457
(setq desc (replace-regexp-in-string
458
(concat "^" (regexp-quote arg-name) " *") "" args-text))
459
(setq args (cons (list (concat arg-name)
460
(concat desc)) args))))
461
(forward-line -1))
462
args)
463
nil))))
464
465
(defun ess-roxy-toggle-roxy-region (beg end)
466
"Remove prefix roxy string in this region if point is in a roxy
467
region, otherwise prefix all lines with the roxy
468
string. Convenient for editing example fields."
469
(interactive "r")
470
(unless (use-region-p)
471
(error "region is not active"))
472
(ess-roxy-roxy-region beg end (ess-roxy-entry-p)))
473
474
(defun ess-roxy-roxy-region (beg end &optional on)
475
(save-excursion
476
(let (RE to-string
477
(roxy-str (ess-roxy-guess-str)))
478
(narrow-to-region beg (- end 1))
479
(if on
480
(progn (setq RE (concat ess-roxy-re " +?"))
481
(setq to-string ""))
482
(setq RE "^")
483
(setq to-string (concat roxy-str " ")))
484
(goto-char beg)
485
(while (re-search-forward RE (point-max) 'noerror)
486
(replace-match to-string))
487
(widen))))
488
489
(defun ess-roxy-preview ()
490
"Use a (possibly newly) connected R session and the roxygen package
491
`ess-roxy-package' to generate the Rd code for entry at point, place it
492
in a temporary buffer and return that buffer."
493
(let ((beg (ess-roxy-beg-of-entry))
494
(tmpf (make-temp-file "ess-roxy"))
495
(roxy-buf (get-buffer-create " *RoxygenPreview*"))
496
(out-rd-roclet
497
(cond ((string= "roxygen" ess-roxy-package)
498
"make.Rd2.roclet()$parse")
499
;; must not line break strings to avoid getting +s in the output
500
((string= "roxygen2" ess-roxy-package)
501
"(function(P) { if(compareVersion(paste(packageVersion('roxygen2')), '3.0.0') < 0) { ..results <- roxygen2:::roc_process(rd_roclet(), parse.files(P), \"\");cat(vapply(..results, FUN.VALUE=character(1), function(x) { roxygen2:::rd_out_cache$compute(x, format(x))})) } else {..results <- roc_proc_text(rd_roclet(), readChar(P, file.info(P)$size));cat(vapply(..results, format, FUN.VALUE = character(1))) } })")
502
(t (error "need to hard code the roclet output call for roxygen package '%s'"
503
ess-roxy-package))))
504
)
505
(if (= beg 0)
506
(error "Point is not in a Roxygen entry"))
507
(save-excursion
508
(goto-char (ess-roxy-end-of-entry))
509
(forward-line 1)
510
(if (ess-end-of-function nil t)
511
(append-to-file beg (point) tmpf)
512
(while (and (forward-line 1) (not (looking-at "^$"))
513
(not (looking-at ess-roxy-re))))
514
(append-to-file beg (point) tmpf))
515
(ess-force-buffer-current)
516
(ess-command (concat "print(suppressWarnings(require(" ess-roxy-package
517
", quietly=TRUE)))\n") roxy-buf)
518
(with-current-buffer roxy-buf
519
(goto-char 1)
520
(if (search-forward-regexp "FALSE" nil t)
521
(error (concat "Failed to load the " ess-roxy-package " package; "
522
"in R, try install.packages(\"" ess-roxy-package "\")"))))
523
(ess-command (concat out-rd-roclet "(\"" tmpf "\")\n") roxy-buf))
524
(delete-file tmpf)
525
roxy-buf))
526
527
(defun ess-roxy-preview-HTML (&optional visit-instead-of-browse)
528
"Use a (possibly newly) connected R session and the roxygen package to
529
generate a HTML page for the roxygen entry at point and open that
530
buffer in a browser. Visit the HTML file instead of showing it in
531
a browser if `visit-instead-of-browse' is non-nil."
532
(interactive "P")
533
(let* ((roxy-buf (ess-roxy-preview))
534
(rd-tmp-file (make-temp-file "ess-roxy-" nil ".Rd"))
535
(html-tmp-file (make-temp-file "ess-roxy-" nil ".html"))
536
(rd-to-html (concat "Rd2HTML(\"" rd-tmp-file "\",\""
537
html-tmp-file "\", stages=c(\"render\"))"))
538
)
539
(with-current-buffer roxy-buf
540
(set-visited-file-name rd-tmp-file)
541
(save-buffer)
542
(kill-buffer roxy-buf))
543
(ess-force-buffer-current)
544
(ess-command "print(suppressWarnings(require(tools, quietly=TRUE)))\n")
545
(if visit-instead-of-browse
546
(progn
547
(ess-command (concat rd-to-html "\n"))
548
(find-file html-tmp-file))
549
(ess-command (concat "browseURL(" rd-to-html ")\n")))))
550
551
(defun ess-roxy-preview-text ()
552
"Use the connected R session and the roxygen package to
553
generate the text help page of the roxygen entry at point."
554
(interactive)
555
(with-current-buffer (ess-roxy-preview)
556
(Rd-preview-help)))
557
558
(defun ess-roxy-preview-Rd (&optional name-file)
559
"Use the connected R session and the roxygen package to
560
generate the Rd code for the roxygen entry at point. If called
561
with a non-nil `name-file' (e.g. universal argument C-u),
562
also set the visited file name of the created buffer to
563
facilitate saving that file."
564
(interactive "P")
565
(let ((roxy-buf (ess-roxy-preview)))
566
(pop-to-buffer roxy-buf)
567
(if name-file
568
(save-excursion
569
(goto-char 1)
570
(search-forward-regexp "name{\\(.+\\)}")
571
(set-visited-file-name (concat (match-string 1) ".Rd"))))
572
(Rd-mode)))
573
574
(defun ess-roxy-guess-str (&optional not-here)
575
"guess the prefix used in the current roxygen block. If
576
`not-here' is non-nil, guess the prefix for nearest roxygen
577
block before the point"
578
(save-excursion
579
(if (ess-roxy-entry-p)
580
(progn
581
(goto-char (point-at-bol))
582
(search-forward-regexp ess-roxy-re))
583
(if not-here
584
(search-backward-regexp ess-roxy-re)))
585
(if (or not-here (ess-roxy-entry-p))
586
(match-string 0)
587
ess-roxy-str)))
588
589
(defun ess-roxy-hide-block ()
590
"hide current roxygen comment block"
591
(interactive)
592
(save-excursion
593
(let ((end-of-entry (ess-roxy-end-of-entry))
594
(beg-of-entry (ess-roxy-beg-of-entry)))
595
(hs-hide-block-at-point nil (list beg-of-entry end-of-entry)))))
596
597
(defun ess-roxy-toggle-hiding ()
598
"Toggle hiding/showing of a block.
599
See `hs-show-block' and `ess-roxy-hide-block'."
600
(interactive)
601
(hs-life-goes-on
602
(if (hs-overlay-at (point-at-eol))
603
(hs-show-block)
604
(ess-roxy-hide-block))))
605
606
(defun ess-roxy-show-all ()
607
"Hide all Roxygen entries in current buffer. "
608
(interactive)
609
(ess-roxy-hide-all t))
610
611
(defun ess-roxy-hide-all (&optional show)
612
"Hide all Roxygen entries in current buffer. "
613
(interactive)
614
(hs-life-goes-on
615
(save-excursion
616
(goto-char (point-min))
617
(while (re-search-forward (concat ess-roxy-re) (point-max) t 1)
618
(let ((end-of-entry (ess-roxy-end-of-entry)))
619
(if show
620
(hs-show-block)
621
(ess-roxy-hide-block))
622
(goto-char end-of-entry)
623
(forward-line 1))))))
624
625
(defun ess-roxy-previous-entry ()
626
"Go to beginning of previous Roxygen entry. "
627
(interactive)
628
(if (ess-roxy-entry-p)
629
(progn
630
(goto-char (ess-roxy-beg-of-entry))
631
(forward-line -1)))
632
(search-backward-regexp ess-roxy-re (point-min) t 1)
633
(goto-char (ess-roxy-beg-of-entry)))
634
635
(defun ess-roxy-next-entry ()
636
"Go to beginning of next Roxygen entry. "
637
(interactive)
638
(if (ess-roxy-entry-p)
639
(progn
640
(goto-char (ess-roxy-end-of-entry))
641
(forward-line 1)))
642
(search-forward-regexp ess-roxy-re (point-max) t 1)
643
(goto-char (ess-roxy-beg-of-entry)))
644
645
(defun ess-roxy-get-function-args ()
646
"Return the arguments specified for the current function as a
647
list of strings."
648
(save-excursion
649
(let ((args-txt
650
(progn
651
(ess-beginning-of-function)
652
(buffer-substring-no-properties
653
(progn
654
(search-forward-regexp "\\([=,-]+ *function *\\|^\s*function\\)" nil nil 1)
655
(+ (point) 1))
656
(progn
657
(ess-roxy-match-paren)
658
(point))))))
659
(setq args-txt (replace-regexp-in-string "#+[^\"']*\n" "" args-txt))
660
(setq args-txt (replace-regexp-in-string "([^)]+)" "" args-txt))
661
(setq args-txt (replace-regexp-in-string "=[^,]+" "" args-txt))
662
(setq args-txt (replace-regexp-in-string "[ \t\n]+" "" args-txt))
663
(split-string args-txt ","))))
664
665
(defun ess-roxy-match-paren ()
666
"Go to the matching parenthesis"
667
(cond ((looking-at "\\s\(") (forward-list 1) (backward-char 1))
668
((looking-at "\\s\)") (forward-char 1) (backward-list 1))))
669
670
(defun ess-roxy-complete-tag ()
671
"complete the tag at point"
672
(let ((token-string (thing-at-point 'symbol)))
673
(when (and token-string (string-match "@.+" token-string))
674
(comint-dynamic-simple-complete
675
(replace-regexp-in-string "^@" "" token-string)
676
(append ess-roxy-tags-noparam ess-roxy-tags-param)))))
677
678
(defun ess-roxy-tag-completion ()
679
"Completion data for emacs >= 24"
680
(when (save-excursion (re-search-backward "@\\<\\(\\w*\\)" (point-at-bol) t))
681
(let ((token (match-string-no-properties 1))
682
(beg (match-beginning 1))
683
(end (match-end 1)))
684
(when (and end (= end (point)))
685
(list beg end (append ess-roxy-tags-noparam ess-roxy-tags-param) :exclusive 'no)))))
686
687
(defun ess-roxy-remove-roxy-re (string)
688
"Remove the `ess-roxy-str' before sending to R process. Useful
689
for sending code from example section. This function is placed
690
in `ess-presend-filter-functions'.
691
"
692
(if (ess-roxy-entry-p)
693
(replace-regexp-in-string ess-roxy-re "" string)
694
string))
695
(add-hook 'ess-presend-filter-functions 'ess-roxy-remove-roxy-re nil)
696
697
(defadvice ess-eval-line-and-step (around ess-eval-line-and-step-roxy)
698
"evaluate line but do not skip over comment (roxy) lines"
699
(if (ess-roxy-entry-p)
700
(let ((simple-next t))
701
ad-do-it)
702
ad-do-it))
703
704
(defadvice mark-paragraph (around ess-roxy-mark-field)
705
"mark this field"
706
(if (and (ess-roxy-entry-p) (not mark-active))
707
(progn
708
(push-mark (point))
709
(push-mark (1+ (ess-roxy-end-of-field)) nil t)
710
(goto-char (ess-roxy-beg-of-field)))
711
ad-do-it))
712
713
(defadvice ess-indent-command (around ess-roxy-toggle-hiding)
714
"hide this block if we are at the beginning of the line"
715
(if (and (= (point) (point-at-bol)) (ess-roxy-entry-p) 'ess-roxy-hide-show-p)
716
(progn (ess-roxy-toggle-hiding))
717
ad-do-it))
718
719
(defadvice fill-paragraph (around ess-roxy-fill-advise)
720
"Fill the current roxygen field."
721
(if (ess-roxy-entry-p)
722
(ess-roxy-fill-field)
723
ad-do-it))
724
725
(defadvice move-beginning-of-line (around ess-roxy-beginning-of-line)
726
"move to start"
727
(if (and (ess-roxy-entry-p)
728
(not (looking-back (concat ess-roxy-re " *\\="))))
729
(progn
730
(end-of-line)
731
(re-search-backward (concat ess-roxy-re " *") (point-at-bol))
732
(goto-char (match-end 0)))
733
ad-do-it))
734
735
(defadvice newline-and-indent (around ess-roxy-newline)
736
"Insert a newline in a roxygen field."
737
(cond
738
;; Not in roxy entry; do nothing
739
((not (ess-roxy-entry-p))
740
ad-do-it)
741
;; Point at beginning of first line of entry; do nothing
742
((= (point) (ess-roxy-beg-of-entry))
743
ad-do-it)
744
;; Otherwise: skip over roxy comment string if necessary and then
745
;; newline and then inset new roxy comment string
746
(t
747
(let ((point-after-roxy-string
748
(save-excursion (forward-line 0)
749
(move-beginning-of-line nil)
750
(point))))
751
(goto-char (max (point) point-after-roxy-string)))
752
ad-do-it
753
(insert (concat (ess-roxy-guess-str t) " ")))))
754
755
(provide 'ess-roxy)
756
757
;;; ess-roxy.el ends here
758
759