Emacs中是否有一个将命令应用于区域中每行的apply-command-to-each-line-in-region?

8

我有一堆链接保存在orgmode文件中,例如...

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

我可以通过将光标放在链接上并执行 C-c C-o 来打开每个链接,它会方便地弹出我的默认浏览器并在选项卡中打开该链接。

现在假设我有大约20个这样的链接。是否有一种方便的方法可以将此类函数应用于所选区域内的每行,而无需记录显式宏?

我想象它看起来像...

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

而这只是已经定义函数的情况。我想象一下没有的情况可能是这样的...
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

这是否存在?如果不存在,我应该如何使用Lisp实现?
4个回答

14

你应该为一行记录宏,然后使用apply-macro-to-region-lines将其应用于区域内的所有行。 C-x C-k r

或者,您可以使用multiple-cursors在每一行上创建一个光标,并使用C-c C-o打开所有光标。如果你给它机会,multiple-cursors会随着时间的推移改变你的使用模式。


C-c C-o 是多光标默认的键绑定吗?我认为最好将其命名为被调用的 defun。 - Squidly

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))))

在您的评论后进行更新--

现在听起来您想要的是不输入函数名称,而是按下一个键,然后将绑定到该键的命令应用于区域(或缓冲区)中的每一行。

像以下这样的东西就可以做到。但是,请注意,命令通常会对行具有特定的行为。例如,如果您按键C-kkill-lines),则它已经在每个行被删除后向前移动了。因为do-lines不知道您将调用什么类型的函数(命令),所以它在每次调用后都会推进到下一行。 对于诸如kill-lines之类的命令,这将导致错误:它将最终推进两行,而不是一行,从而跳过行。 换句话说,请注意do-lines的代码不能补偿它调用的特定函数可能会做出与您预期不符的操作。 相反,它执行它所说的操作。

(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))))

当我在我的选择上运行时,我得到了“C-c C-o未定义”的错误,尽管它被定义为单行。 - Mittenchops
不太明白你的意思。C-c C-o是什么?你在哪里定义它,希望它做什么?当你调用do-lines(无论是使用快捷键还是M-x)时,它会提示你输入要应用于每行的函数。按照当前的定义,你需要提供一个函数名称,而不仅仅是按下一个键。 - Drew
在我的情况下,我想要调用的函数不需要参数,所以我将funcall行更改为(funcall command)或(funcall fun),具体取决于您使用的版本。效果非常好! - jarodeells

2
在某些情况下,您可以使用Emacs Repeating来重复执行命令,使用C-x z并跟随更多的`z'。我尝试注释区域中的所有行,并且它在我的用例中运行得很好。
命令C-x z(repeat)提供了另一种重复执行Emacs命令多次的方式。
要重复执行命令超过一次,请键入额外的z:每个z都会重复执行一个命令一次。

1

在TIMTOWTDI[1]的精神下,我将指出一种在某些情况下(包括OP中的情况)效果很好的技术。

如果您想在一行以空格分隔的字符串(如URL)上运行外部命令:

  1. 选择区域
  2. 调用M-|Alt+Shift+\shell-command-on-region
  3. 使用xargs作为所需命令的前缀命令(例如xdg-openx-www-browser

例如,步骤3中输入的完整命令可能是:

xargs -n1 xdg-open

-n1 这个选项会让 xargs 每次打开给定的程序一个参数;它将对每个输入运行一次程序。 如果该命令可以同时处理多个参数,则可以省略 -n1。 例如,我有一个名为 web 的命令,它可以将多个 URL 作为参数打开,所以只需要使用 xargs web

这种方法的主要优点是,在不提前执行任何操作的情况下,它适用于任何符合 POSIX 标准的内容。 缺点包括,它仅适用于外部命令,并且需要 xargs(不是每个操作系统都默认包含)。


[1] "有多种方法可以做到这一点",最初来自Perl,但在其他地方也很有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接