batch 模式下如何 read 多行字符?

以下代码模拟终端操作(1.开启一个 Emacs 并处于等待输入状态;2.输入 "foo\nbar"):

(let* ((process-connection-type nil)
       (comint-use-prompt-regexp t)
       (comint-prompt-read-only t)
       (comint-highlight-prompt t)
       (comint-prompt-regexp "ELPL> ")
       (cmdlist
        `("/Applications/Emacs-26.1.app/Contents/MacOS/Emacs"
          "--batch"
          "--eval"
          ,(format "%S"
                   '(while t
                      (condition-case err
                          (print (eval (read (read-from-minibuffer "ELPL> "))))
                        (error
                         (print err)))))))
       (buf (apply 'make-comint-in-buffer "elpl"
               (generate-new-buffer-name "*elpl*")
               (car cmdlist) nil (cdr cmdlist))))
  (with-current-buffer buf
    (view-buffer-other-window buf)
    (insert (format "%S" "foo\nbar"))
    (comint-send-input)
    ))

从结果可以看出,输入的 "foo\nbar" 被分成 "foobar" 两部分执行了,所以出现了两个错误:

"foo
bar"
ELPL> 
(end-of-file)
ELPL> 
(void-variable bar)
ELPL> 

(一度用 (process-send-string (get-buffer-process "*elpl*") "\"foo\nbar\"") 成功发送过多行字符,现在却无法重现。之前乱糟糟的测试代码没有保存,忘记当时还做了哪些设置。又或者只是幻觉?)

貌似 (while t ... (read-from-minibuffer ...)) 是按行读的,你一次性给它发送

1
2

这个循环还是会运行两次。有三个思路

  • 有没有可能让它只运行一次?

  • 实现一个支持 Buffer 的 read,没读到一个完整 Sexp 不结束

  • 把换行改成 \n,不清楚是不是总能行

    (with-current-buffer "*elpl*"
      (insert (replace-regexp-in-string
               "\n" "\\\\n"
               (format "%S" "foo\nbar")))
      (comint-send-input))
1 个赞
(princ
 (with-temp-buffer
   (ignore-errors
     (while t
       (insert (read-from-minibuffer "") "\n")))
   (buffer-substring-no-properties (point-min) (point-max))))

试试这个,这样就可以读取到EOF

替换 \n 无法解决手动输入的问题。

这个我也尝试过。在 Emacs 中手动编辑的时候,虽然 "foo<C-j> 看起来没有立即提交,而是在下一行继续编辑。但子进程其实已经对 "foo 求值了,只不是等到回车的时候,才一起打印出来。

read-from-minibuffer 第三个参数是 keymap,默认值 minibuffer-local-map,其中预设了:

(10 . exit-minibuffer)
(13 . exit-minibuffer)

10 就是按键 C-j,看起来应该就是这个按键使得 read-from-minibuffer 退出,并且把不完整的输入送给 (eval) 求值。然而当我把 C-j 重设了也没有什么影响:

(let* ((cmdlist
        `("/Applications/Emacs-26.1.app/Contents/MacOS/Emacs"
          "--batch"
          "--eval"
          ,(format "%S"
                   '(let ((map (make-sparse-keymap))
                          (i 0))
                      (define-key map (kbd "C-j") nil)
                      (define-key map (kbd "RET") 'exit-minibuffer)
                      (print map)
                      (while t
                        (condition-case err
                            (print (eval
                                    (concat
                                     (format "[%s] " i)
                                     (read-from-minibuffer "ELPL> " nil map))))
                          (error
                           (print err)))
                        (setq i (1+ i)))))))
       (buf (apply 'make-comint-in-buffer "elpl"
               (generate-new-buffer-name "*elpl*")
               (car cmdlist) nil (cdr cmdlist))))
  (with-current-buffer buf
    (view-buffer-other-window buf)
    (insert (format "%S" "foo\nbar"))
    (comint-send-input)
    ))

结果依然是分两句执行:

"foo
bar"

(keymap (13 . exit-minibuffer) (10))
ELPL> 
"[0] \"foo"
ELPL> 
"[1] bar\""
ELPL>

这样好像无法退出循环

改一下,写个条件break掉就好了

或许是由于 read-from-minibuffer 底层写死了遇到 \n 就返回:

这样貌似可行:

(let ((s ""))
  (while t
    (setq s (concat s "\n" (read-from-minibuffer "ELPL> ")))
    (condition-case err
        (progn
          (print (eval (read s)))
          (setq s ""))
      (end-of-file)
      (error
       (setq s "")
       (print err)))))

效果:

"foo
bar"
ELPL> 
"foo
bar"
ELPL> "foo
bar
baz"
ELPL> 
"foo
bar
baz"
ELPL> (+ 1
2
3
)
ELPL> 
6
ELPL> (+ 1
ELPL> 2
ELPL> 3
ELPL> 4
ELPL> )

10
ELPL>
1 个赞

用当前的 Emacs 的话:

(concat invocation-directory invocation-name)
;; => "/Users/xcy/src/emacs-mac/mac/Emacs.app/Contents/MacOS/Emacs"
1 个赞

赞!

不过在我这边的效果是:

ELPL> (+ 1
2
3
4
)

ELPL> ELPL> ELPL> ELPL>
10

所以我把续行的 prompt 去掉了,更像是 IELM

(let ((s ""))
  (while t
    (setq s (concat s "\n" (read-from-minibuffer
                            (if (string= s "")
                                "ELPL> "
                              ""))))
    (condition-case err
        (progn
          (print (eval (read s)))
          (setq s ""))
      (end-of-file)
      (error
       (setq s "")
       (print err)))))

41%20PM

1 个赞

应该是因为我上面的换行是手打的 C-q C-j,不是用 insert