Is there an apply-command-to-each-line-in-region in emacs?
Asked Answered
W

4

8

I have a bunch of links saved in an orgmode file, say...

http://www.stackoverflow.com
http://www.google.com
http://www.github.com

I can open each one by having the cursor on the link and doing C-c C-o, and it conveniently pops up my default browser and opens that link in a tab.

Now suppose I have like 20 of these links. Is there a convenient way to apply a function like this to each line within a selected region, without recording an explicit macro?

I'd imagine it looking something like...

Select region
M-x foreach-in-region
Keystrokes to apply to each line: C-c C-o

And this is just for functions already defined. I imagine the way without would be something like...

with cursor on first line of link
F3 # to start record macro
C-c C-o 
down arrow
F4
Select region (omitting the first line, since that's now already opened in my browser)
C-x C-k r

Does this exist? If not, how would I lisp this?

Wil answered 2/8, 2015 at 1:24 Comment(0)
U
14

You should record the macro for one line, then use apply-macro-to-region-lines to execute it for all lines in region. C-x C-k r

Alternatively, you can use multiple-cursors to create a cursor on each line and C-c C-o to open all. multiple-cursors will transform your usage patterns over time for the better if you give it a chance.

Uredium answered 2/8, 2015 at 22:43 Comment(1)
Is C-c C-o a default keybinding in multiple-cursors? Better to name the defun invoked imo.Coriolanus
S
7
(defun do-lines (fun &optional start end)
  "Invoke function FUN on the text of each line from START to END."
  (interactive
   (let ((fn   (intern (completing-read "Function: " obarray 'functionp t))))
     (if (use-region-p)
         (list fn (region-beginning) (region-end))
       (list fn (point-min) (point-max)))))
  (save-excursion
    (goto-char start)
    (while (< (point) end)
      (funcall fun (buffer-substring (line-beginning-position) (line-end-position)))
      (forward-line 1))))

Update after your comment --

Now it sounds like you want to not enter a function name but hit a key, and have the command bound to that key be applied to each line in the region (or buffer).

Something like the following will do that. However, be aware that command often have particular behavior wrt lines. For example, if you were to hit key C-k (kill-lines) then it already moves forward after each line it kills. Because do-lines does not know what kind of function (command) you will invoke, it advances to the next line after each invocation. For a command such as kill-lines this will thus do the wrong thing: it will end up advancing two lines, not one, thus skipping lines. IOW, be aware that the code for do-lines cannot compensate for what a particular function it invokes might do that might not correspond to what you expect. Instead, it does what it says it does.

(defun do-lines (command &optional start end)
  "Invoke COMMAND on the text of each line from START to END."
  (interactive
   (let* ((key  (read-key-sequence-vector "Hit key sequence: "))
          (cmd  (lookup-key global-map key t)))
     (when (numberp cmd) (error "Not a valid key sequence"))
     (unless (commandp cmd) (error "Key `%s' is not defined" (key-description key)))
     (if (use-region-p)
         (list cmd (region-beginning) (region-end))
       (list cmd (point-min) (point-max)))))
  (setq start  (copy-marker start)
        end    (copy-marker end))
  (save-excursion
    (goto-char start)
    (while (< (point) end)
      (funcall command (buffer-substring (line-beginning-position) (line-end-position)))
      (forward-line 1))))
Surfboarding answered 2/8, 2015 at 1:43 Comment(3)
When I run this on my selection, I get "C-c C-o" is not defined, though defined for a single line.Wil
Dunno what you mean. What is C-c C-o? Where did you define it, and what do you expect it to do? When you invoke do-lines (however you invoke it - with a key or with M-x), it prompts you for the function to apply to each line. As defined now, you need to give it a function name, not just hit a key.Surfboarding
In my case, the function I wanted to call takes no args so I changed the funcall line to (funcall command) or (funcall fun) depending on which version you are using. Worked like a charm!Rawlinson
A
2

In some situations, you can use Emacs Repeating using C-x z following by more `z'. I was trying to comment all the lines in region and it worked nicely for my use case.

The command C-x z (repeat) provides another way to repeat an Emacs command many times

To repeat the command more than once, type additional z’s: each z repeats the command one more time

Alpinist answered 7/11, 2020 at 10:52 Comment(0)
T
1

In the spirit of TIMTOWTDI[1], I'll point out a technique that works well for some situations, including the one in the OP.

If you're looking to run an external command on a line of space-separated strings (like URLs):

  1. Select the region
  2. Invoke M-| (Alt+Shift+\, shell-command-on-region)
  3. Use xargs as a prefix command to the desired command (e.g., xdg-open, or x-www-browser)

For example, the full command entered for step 3 might be:

xargs -n1 xdg-open

The -n1 switch causes xargs to open invoke the given program with one argument at a time; it will run the program once for each input. If the command can handle multiple arguments at once, you can omit -n1. For example, I have a web command that can open multiple URLs as arguments, so just xargs web works.

The major benefit of this approach is, it works on anything POSIX-compliant without doing anything in advance. Disadvantages include, it only works on external commands, and it requires xargs (not included with every OS by default).


[1] There's More Than One Way To Do It, originally from Perl, but useful elsewhere.

Transpacific answered 30/10, 2021 at 19:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.