Quite often I need to capture some paragraphs in a region with regexp - and then act on each paragraph.
For example consider a problem of recovering a numberd list:
1. Some text with a blank
line. I want not to have that line break
2. Some more text. Also - with
a line break.
3. I want to have a defun which
will capture each numbered entry
and then join it
I want to write a defun which will make the previous text like that:
1. Some text with a blank line. I want not to have that line break
2. Some more text. Also - with a line break.
3. I want to have a defun which will capture each numbered entry and then join it
Here's my best try for now:
(defun joining-lines (start end)
(interactive "r")
(save-restriction
(narrow-to-region start end)
(goto-char (point-min))
(while (search-forward-regexp "\\([[:digit:]]\\. \\)\\(\\[^[:digit:]\\].*?\\)" nil t)
(replace-match "\\1\\,(replace-regexp-in-string " ;; here's a line break
" " " (match-string 2))" t nil))
)
)
It neither work - nor give an error.
Actually it would be better to have a separate defun to act on a string. This way it will be easy to expand the code to have multiple substitutions on the replace-match.
There are two issues with your code:
A period in a regexp matches "anything except newline," so your .*? will never include a newline character.
The \,(...) regexp replacement construct is only available interactively. If issue #1 were resolved, you'd get an error (error "Invalid use of '\\' in replacement text"). Programmatically, you have to write the code yourself, eg: (replace-match (concat (match-string 1) (replace-regexp-in-string "\n" " " (match-string 2)))).
I think you'd be better off not relying on regexps to do the heavy lifting here. This works for me:
(defun massage-list (start end)
(interactive "r")
(save-excursion
(save-restriction
(narrow-to-region start end)
(goto-char start)
(while (progn (forward-line) (= (point) (line-beginning-position)))
(when (not (looking-at "^[[:digit:]]+\\."))
(delete-indentation)
(beginning-of-line))))))
Try something like this code. It's not the shortest possible but rather something straigthforward.
(defun joining-lines(start end)
(interactive "r")
(let ((newline-string "~~~"))
(save-restriction
(narrow-to-region start end)
(mark-whole-buffer)
(replace-string "\n" newline-string)
(goto-char start)
(while (re-search-forward (concat newline-string "\\([[:digit:]]+. \\)") nil t)
(replace-match "\n\\1" nil nil))
(mark-whole-buffer)
(replace-string newline-string " "))))
Here's a solution using an external defun:
(defun capturing-paragraphs (start end)
(interactive "r")
(save-restriction
(narrow-to-region start end)
(goto-char (point-min))
(while (search-forward-regexp "^\\(^[[:digit:]]+\\.[^[:digit:]]+$\\)" nil t) (replace-match (multiple-find-replace-in-match) t nil))))
(defun multiple-find-replace-in-match ()
"Returns a string based on current regex match."
(let (matchedText newText)
(setq matchedText
(buffer-substring-no-properties
(match-beginning 1) (match-end 1)))
(setq newText
(replace-regexp-in-string "\n" "" matchedText) )
newText))
it works only if there's no figures in the text. But this solution is straighforward to expand - to add new replacements on a matched string.
Related
I want to add a function (para2lines) to Emacs by which I can split the current paragraph into its sentences and print them line by line in a separate buffer. Following is code in Racket/Scheme:
(define (p2l paraString)
(define lst (string-split paraString ". "))
(for ((i lst))
(displayln i)))
Testing:
(p2l "This is a test. For checking only. Only three lines.")
Output:
This is a test.
For checking only.
Only three lines.
In Emacs Lisp, I could manage following code:
(defun pl (ss)
(interactive)
(let ((lst (split-string (ss))))
(while lst
(print (pop lst)))))
But I do not know how to get the text from the paragraph with current position. How can I correct this function?
Edit: basically, I want to read it as separate lines but want to save it as paragraph.
Here's an example that might help you on your way. It will do your conversion to the current paragraph (i.e. where the cursor is positioned), rather than to a new buffer. You could modify this to pass a string to your function if that's what you require.
(defun p2l ()
"Format current paragraph into single lines."
(interactive "*")
(save-excursion
(forward-paragraph)
(let ((foo (point)))
(backward-paragraph)
(replace-regexp "\n" " " nil (1+ (point)) foo)
(backward-paragraph)
(replace-regexp "\\. ?" ".\n" nil (point) foo))))
I would just run Emacs commands or write a macro to convert a paragraph to single-sentence lines, but maybe you are really just wanting to read wrapped paragraphs as lines, thus the need to have an Emacs command.
Here's something that will grab the current paragraph, insert a new buffer *Lines*, and then convert sentences to lines.
(defun para-lines ()
"Split sentences of paragraph to lines in new buffer."
(interactive)
;; Move the paragraph to a new buffer.
(let ((b (generate-new-buffer "*Lines*")))
(with-output-to-temp-buffer b
(let ((beg (save-excursion (forward-paragraph -1) (point)))
(end (save-excursion (forward-paragraph +1) (point))))
(princ (buffer-substring-no-properties beg end))))
;; Switch to new buffer
(with-current-buffer b
;; Since the name starts with "*", shut off Help Mode
(fundamental-mode)
;; Make sure buffer is writable
(setq buffer-read-only nil)
;; From the start of the buffer
(goto-char (point-min))
;; While not at the end of the buffer
(while (< (point) (point-max))
(forward-sentence 1)
;; Delete spaces between sentences before making new new line
(delete-horizontal-space)
;; Don't add a new line, if already at the end of the line
(unless (= (line-end-position) (point))
(newline))))))
To avoid using forward-sentence, and just use a regular expression, use re-search-forward. For instance, to match semi-colons as well as periods.
(defun para-lines ()
"Split sentences of paragraph to lines in new buffer."
(interactive)
;; Move the paragraph to a new buffer.
(let ((b (generate-new-buffer "*Lines*")))
(with-output-to-temp-buffer b
(let ((beg (save-excursion (forward-paragraph -1) (point)))
(end (save-excursion (forward-paragraph +1) (point))))
(princ (buffer-substring-no-properties beg end))))
;; Switch to new buffer
(with-current-buffer b
;; Since the name starts with "*", shut off Help Mode
(fundamental-mode)
;; Make sure buffer is writable
(setq buffer-read-only nil)
;; From the start of the buffer
(goto-char (point-min))
;; While not at the end of the buffer
(while (< (point) (point-max))
(re-search-forward "[.;]\\s-+" nil t)
;; Delete spaces between sentences before making new new line
(delete-horizontal-space)
;; Don't add a new line, if already at the end of the line
(unless (= (line-end-position) (point))
(newline))))))
I'm looking for a variant of M-x sort-lines that can handle multiline expressions properly. For instance:
this is the first line
this is the second lien
this is the (third
line
spanning multiple lines because of parens)
Any ideas?
Here is a similar question solved in a similar way
(defun end-of-chunk ()
"forward line or to ends of mid-expression."
(interactive)
(goto-char (point-at-eol))
(let ((limit (point-at-bol))
temp
expr-beg)
(while (and (setq temp (nth 1 (syntax-ppss)))
(<= limit temp))
(goto-char temp)
(setq expr-beg (point)))
(when expr-beg
(goto-char expr-beg)
(forward-sexp))))
(defun sort-lines-as-exprs (reverse beg end)
"sort lines, or whole expression if line ends mid-expression."
(interactive "P\nr")
(save-excursion
(save-restriction
(narrow-to-region beg end)
(goto-char (point-min))
(sort-subr reverse
'forward-line
'end-of-chunk))))
I want to have toggler which would add/remove ".. " (there's a space -- but I can't make it more pronounced) string in front of every line above (point). Here's my best bet:
(defun rst-comment-above (Point)
(interactive "d")
(save-excursion
(goto-char 1)
(cond
((numberp (get this-command 'state))
((replace-regexp "^\\.\\. " "" nil (point) (get this-command 'state)))
(put this-command 'state ""))
(t
(replace-regexp "^" ".. " nil (point) Point)
(put this-command 'state Point))
)))
it works for the first time, but for the second it says:
(invalid-function
(replace-regexp "^\\.\\. " "" nil (point) (get this-command (quote state))))
Edit:
#user4815162342:
So I comment the thing above:
Then I insert new lines:
Then I want to uncomment the thing, and I get:
But probably its not that important. I do not generally enter anything in the commented area. I just note that the issue. What is some what more important -- is to store the 'state of the given file across the sessions. Is it hard to implement?
The error comes from the extra set of parentheses on the line where you call replace-regexp. That line should be:
(replace-regexp "^\\.\\. " "" nil (point) (get this-command 'state))
There are several other problems with your code.
Storing the current value of point doesn't work well because you add
characters to the buffer, which makes the point move forward. This makes
(once the above syntax error is fixed) the function miss the last several
instances of "..".
The fix is to store the point marker.
You should be using (point-min) instead of hard-coding the buffer
beginning to 1, or your code will fail to work when buffer narrowing is in
effect.
Finally, replace-regexp, as its documentation states, is not meant to be
called from Lisp programs.
Here is a revised version of your function:
(defun rst-comment-above ()
(interactive)
(let ((pm (point-marker))
(prev-marker (get this-command 'rst-prev-marker)))
(save-excursion
(goto-char (point-min))
(cond ((null prev-marker)
(while (< (point) pm)
(insert "..")
(forward-line 1))
(put this-command 'rst-prev-marker pm))
(t
(while (< (point) prev-marker)
(when (looking-at "^\\.\\.")
(replace-match ""))
(forward-line 1))
(put this-command 'rst-prev-marker nil))))))
Any reason why you don't use M-; in rst-mode?
What would an Emacs macro look like which turns the following line:
# abc def
into:
# +-------------+
# | abc def |
# +-------------+
? The macro need not be general at all: It can hard code the box specs (i.e. 3 spaces before and after the comment, the frame characters (|, +, - ), and assume that the comment is a one-liner. It should, however, use whatever comment character is set for the current mode, and get the box length correct.
I'd also appreciate if you knew of an existing package which does this.
Thanks!
This does what I think you want:
(defun box-comment-region (beg end)
"do some fancy commenting"
(interactive "r")
(save-restriction
(narrow-to-region beg end)
(comment-region beg end -1) ; first, uncomment
(string-rectangle (point-min)
(progn (goto-char (point-max)) (line-beginning-position))
" | ")
(goto-char (point-min))
(let ((max-len 0))
(while (< (point) (point-max))
(end-of-line)
(setq max-len (max max-len (current-column)))
(forward-line 1))
(previous-line)
(end-of-line)
(insert (make-string (- max-len (current-column)) ?\ ))
(goto-char (point-min))
(end-of-line)
(insert (make-string (- max-len (current-column)) ?\ ))
(end-of-line)
(let ((top (point)))
(goto-char (point-max))
(previous-line)
(end-of-line)
(string-rectangle top (point) " | "))
(let ((line-seg (concat " +" (make-string (- max-len 2) ?-) "+ \n")))
(goto-char (point-max))
(insert line-seg)
(goto-char (point-min))
(insert line-seg)))
(comment-region (point-min) (point-max))))
newcomment has a function called comment-box, which produces a box comment consisting of the comment characters, i.e.:
###########
# abc def #
###########
or
/***********/
/* abc def */
/***********/
depending on the mode. The only configurability it has is the number of characters to use for the box; for example in Lisp modes you end up with:
;;;;;;;;;;;;;
;; abc def ;;
;;;;;;;;;;;;;
The comment-region-default function should give you some idea of how to do comments. newcomment's comment styles aren't sufficiently flexible to implement what you want, so I think it'd be easiest to simply draw the box then add a regular (single or multiline, in languages where there is difference) comment to the region using the existing newcomment machinery.
There is a feature called picture-mode, which might do something like that.
M-x picture-mode
rebox2 is the most comprehensive emacs box drawing extension.
The function
M-x comment-box
available as of Emacs 24.4 does this using point and mark as one would expect.
In TextMate, one can use ctrl-shift-w to wrap text in an Open/Close tag and ctrl-shift-cmd-w to wrap each line in a region in Open/Close tags. How can I implement this same functionality in Emacs using emacs lisp?
emacs
becomes
<p>emacs</p>
And ...
emacs
textmate
vi
becomes
<li>emacs</li>
<li>textmate</li>
<li>vi</li>
This answer gives you a solution for wrapping the region (once you modify it to use angle brackets).
This routine will prompt you for the tag to use, and should tag every line in the region with an open/close tag of that type:
(defun my-tag-lines (b e tag)
"'tag' every line in the region with a tag"
(interactive "r\nMTag for line: ")
(save-restriction
(narrow-to-region b e)
(save-excursion
(goto-char (point-min))
(while (< (point) (point-max))
(beginning-of-line)
(insert (format "<%s>" tag))
(end-of-line)
(insert (format "</%s>" tag))
(forward-line 1)))))
*Note: *If you wanted the tag to always be li, then remove the tag argument, remove the text \nMTag for line: from the call to interactive, and update the insert calls to just insert the "<li\>" as you would expect.
For sgml-mode deratives, mark region to tagify, type M-x sgml-tag, and type the tag name you like to use (press TAB to get list of available HTML elements). Altough, this method does not allow you to tagify each line in a region, you can work around this by recording a keyboard macro.
yasnippet is a particularly good implementation of Textmate's snippet syntax for Emacs. With that you can import all of Textmate's snippets. If you install it then, this snippet that I wrote should do what you want:
(defun wrap-region-or-point-with-html-tag (start end)
"Wraps the selected text or the point with a tag"
(interactive "r")
(let (string)
(if mark-active
(list (setq string (buffer-substring start end))
(delete-region start end)))
(yas/expand-snippet (point)
(point)
(concat "<${1:p}>" string "$0</${1:$(replace-regexp-in-string \" .*\" \"\" text)}>"))))
(global-set-key (kbd "C-W") 'wrap-region-or-point-with-html-tag)
EDIT: (Okay this is my last attempt at fixing this. It is exactly like Textmate's version. It even ignores characters after a space in the end tag)
Sorry I misread your question. This function should edit each line in the region.
(defun wrap-lines-in-region-with-html-tag (start end)
"Wraps the selected text or the point with a tag"
(interactive "r")
(let (string)
(if mark-active
(list (setq string (buffer-substring start end))
(delete-region start end)))
(yas/expand-snippet
(replace-regexp-in-string "\\(<$1>\\).*\\'" "<${1:p}>"
(mapconcat
(lambda (line) (format "%s" line))
(mapcar
(lambda (match) (concat "<$1>" match "</${1:$(replace-regexp-in-string \" .*\" \"\" text)}>"))
(split-string string "[\r\n]")) "\n") t nil 1) (point) (point))))
This variant on Trey's answer will also indent the html correctly.
(defun wrap-lines-region-html (b e tag)
"'tag' every line in the region with a tag"
(interactive "r\nMTag for line: ")
(setq p (point-marker))
(save-excursion
(goto-char b)
(while (< (point) p)
(beginning-of-line)
(indent-according-to-mode)
(insert (format "<%s>" tag))
(end-of-line)
(insert (format "</%s>" tag))
(forward-line 1))))