【问题标题】:How do I get Emacs to fill sentences, but not paragraphs?如何让 Emacs 填充句子而不是段落?
【发布时间】:2010-10-07 02:25:26
【问题描述】:

我在 StackOverflow 上至少有两个 seenrecommendations 在编辑 LaTeX 文档时在句子之间插入换行符。原因是这种做法有助于源代码控制、diffing 和协作编辑。

我基本信服了,只是我比较懒,不想多想。

所以我正在寻找一些 emacs 咒语来为我处理它。可能是次要模式,可能是一组需要设置的变量。

我认为我想要的是

  • 文本的软换行(比如使用longlines(set long-lines-auto-wrap 't))。这是因为我不想对合作者的编辑提出要求,并且有时会使用其他 unix 工具来检查这些文件。

我认为我想要的是

  • fill-paragraph 在看起来像是标记句子结尾的换行符之间填充。
  • auto-fill-mode 一起工作的解决方案将是一个奖励。

即:

聊天聊天。
一个新的句子
需要修复的错误包装。
嘟嘟囔囔

转化为:

聊天聊天。
一个带有错误包装的新句子,需要修复。
嘟嘟囔囔

感谢您的 cmets 和建议。


编辑:Jouni K. Seppänen 的建议将我指向LaTeX-fill-break-at-separators,这表明 emacs 几乎已经知道如何做到这一点。无论如何,我要去阅读一些代码,并会报告。再次感谢。


同一问题的更通用版本:Editor showdown: Maintain newlines at the ends of sentences。谢谢,dreeves

【问题讨论】:

  • 应该可以在每行的开头做相当于 M-^ (delete-indentation) 的不开始一个句子。但是你为什么不想要软包装呢?
  • "你为什么不想要软包装?"嗯。我不知道,真的。它困扰了我很长时间。为什么我想要软包装?
  • 你认为值得推广这个问题,以便人们可以为除 emacs 之外的其他编辑器提供解决方案吗?它也比 LaTeX 更通用,想想看。例如,这样做的所有原因也适用于 HTML 文档。
  • 我继续添加了更一般的问题。如果你想把它和这个折叠起来,我很乐意删除它。
  • 这确实是合理的,但现实生活介入了我的电脑......所以,我链接到你的版本。

标签: emacs latex fill


【解决方案1】:

这里是what I use,大部分抄袭自Luca de Alfaro

(defun fill-sentence ()
  (interactive)
  (save-excursion
    (or (eq (point) (point-max)) (forward-char))
    (forward-sentence -1)
    (indent-relative t)
    (let ((beg (point))
          (ix (string-match "LaTeX" mode-name)))
      (forward-sentence)
      (if (and ix (equal "LaTeX" (substring mode-name ix)))
          (LaTeX-fill-region-as-paragraph beg (point))
        (fill-region-as-paragraph beg (point))))))

我将它绑定到M-j

(global-set-key (kbd "M-j") 'fill-sentence)

"LaTeX" 的引用是为了支持AUCTeX。如果不使用 AUCTeX,let 可以简化为

(let (beg (point))
  (forward-sentence)
  (fill-region-as-paragraph beg (point)))

【讨论】:

  • 我使用 AUCTeX。我会试一试。
  • 你的第二个 letbeg 周围没有足够的括号
【解决方案2】:

我一直想永远这样做,我最近发现this blog post 对我来说效果很好。所以这里是我使用了几天的(稍微修改的版本)。

(defun auto-fill-by-sentences ()
  (if (looking-back (sentence-end))
      ;; Break at a sentence
      (progn
        (LaTeX-newline)
        t)
    ;; Fall back to the default
    (do-auto-fill)))
(add-hook 'LaTeX-mode-hook (lambda () (setq auto-fill-function 'auto-fill-by-sentences)))

;; Modified from http://pleasefindattached.blogspot.com/2011/12/emacsauctex-sentence-fill-greatly.html
(defadvice LaTeX-fill-region-as-paragraph (around LaTeX-sentence-filling)
  "Start each sentence on a new line."
  (let ((from (ad-get-arg 0))
        (to-marker (set-marker (make-marker) (ad-get-arg 1)))
        tmp-end)
    (while (< from (marker-position to-marker))
      (forward-sentence)
      ;; might have gone beyond to-marker---use whichever is smaller:
      (ad-set-arg 1 (setq tmp-end (min (point) (marker-position to-marker))))
      ad-do-it
      (ad-set-arg 0 (setq from (point)))
      (unless (or (looking-back "^\\s *")
                  (looking-at "\\s *$"))
        (LaTeX-newline)))
    (set-marker to-marker nil)))
(ad-activate 'LaTeX-fill-region-as-paragraph)

【讨论】:

  • 很抱歉,我对这个问题的新答案的回复很慢;我的工作队列顶部还有其他事情。当我输入新文本时,这段代码确实非常有效——这正是我所要求的。它不处理以fill-paragraph 中线开头的句子的现有文本,但如果我在每个句子的开头键入空格,它会处理它们。谢谢。
  • 你的意思是它认为句子应该用双空格分隔吗?这可以通过设置sentence-end-double-space 来改变。如果您的意思是希望它在.!? 等之后无条件中断,那么您可以更改sentence-end 的值,或者将对sentence-end 的调用更改为您喜欢的任何正则表达式。
  • 出于某种原因,这个解决方案仍然对我来说是长行 - 但是它确实在句号之后添加了一个换行符。我怎样才能让它不包含在一个句子中?
  • @icks 嗯。这与我的 .emacs 中的不同,因此它们一定在某个时候出现了分歧。尝试将 (do-auto-fill) 更改为 nil 看看是否有帮助。
【解决方案3】:

如果您在每个句子的末尾放置一个注释标记,Emacs 就知道不要在注释中移动下一行:

chat chat chat.%
A new sentence
with goofed up wrapping that needs to be fixed.%
Mumble mumble%

然后 M-q 分别填充每个句子,至少在 AUCTeX 11.85 中。 (如果您在 Emacs 中对此进行测试,似乎存在一个错误,如果这是缓冲区中的第一段并且您键入 Mq,则会收到一条错误消息。只需在文本前添加一个换行符即可解决它。)

如果您不想输入注释字符,您可以使用 LaTeX-fill-paragraph 并对其进行修改,以便行尾的句尾标点符号与 cmets 类似。

【讨论】:

  • 好的。不是我所期待的,但它肯定会奏效。
  • 这不是比关闭自动填充更麻烦吗?在这种情况下 M-q 有什么用?
  • 这将吞噬句子之间的空格。您需要...chat. % 等在% 之前有一个空格
【解决方案4】:
(defun wrap-at-sentences ()
  "Fills the current paragraph, but starts each sentence on a new line."
  (interactive)
  (save-excursion
    ;; Select the entire paragraph.
    (mark-paragraph)
    ;; Move to the start of the paragraph.
    (goto-char (region-beginning))
    ;; Record the location of the end of the paragraph.
    (setq end-of-paragraph (region-end))
    ;; Wrap lines with 'hard' newlines (i.e., real line breaks).
    (let ((use-hard-newlines 't))
      ;; Loop over each sentence in the paragraph.
      (while (< (point) end-of-paragraph)
        ;; Determine the region spanned by the sentence.
        (setq start-of-sentence (point))
        (forward-sentence)
        ;; Wrap the sentence with hard newlines.
        (fill-region start-of-sentence (point))
        ;; Delete the whitespace following the period, if any.
        (while (char-equal (char-syntax (preceding-char)) ?\s)
          (delete-char -1))
        ;; Insert a newline before the next sentence.
        (insert "\n")))))

(global-set-key (kbd "M-q") 'wrap-at-sentences)

【讨论】:

  • 为了我个人的使用,我删除了(insert "\n")。感谢那!这是迄今为止对我来说最好的解决方案。
【解决方案5】:

可能并非在所有情况下都有效,但是:

(defun my-fill-sentence ()
  "Fill sentence separated by punctuation or blank lines."
  (interactive)
  (let (start end)
    (save-excursion
      (re-search-backward "\\(^\\s-*$\\|[.?!]\\)" nil t)
      (skip-syntax-forward "^w")
      (setq start (point-at-bol)))
    (save-excursion
      (re-search-forward "\\(^\\s-*$\\|[.?!]\\)" nil t)
      (setq end (point-at-eol)))
    (save-restriction
      (narrow-to-region start end)
      (fill-paragraph nil))))

要使其与auto-fill-mode 一起使用,请将(setq normal-auto-fill-function 'my-fill-sentence) 添加到您的LaTeX 模式挂钩(我认为)。

【讨论】:

  • 看起来最后的填充段落插入了我不想要的换行符。但是 is 确实只适用于该点下的句子。
  • 它适用于您的示例...是否有其他示例可以显示问题?
  • 嗯。在同一个例子中对我不起作用。如果我把 put 放在“sentence”之后并调用它,我会很好地填充第二个句子,并在“Mumble mumble”之前添加一个换行符。查看我的“.emacs”,我发现了很多内容。我将其关闭并重试。
【解决方案6】:

我假设你知道 elisp。

您可以采取以下几种方法:

  • 挂钩到auto-fill-mode。有很多硬编码 那里有条件,所以它可能不适合你。你可以 可能玩auto-fill-function,看看你有没有 你需要的钩子。

  • 让字符(可能是.)“电”,这样当你按下 它,它插入自己,然后调用一个函数来确定如何 填满你所在的行。

  • 设置after-change-hook 以调用确定如何 填写句子。此函数将在每次 更改为缓冲区,因此有效地执行此操作。 (这个机制是 被font-lock使用,所以不用太担心。这听起来 慢,但实际上不是——人们打字慢。)

一旦你在正确的地方上钩了,你只需要实施 填充逻辑。 sentence-at-point(来自thingatpt)的来源可能是 有指导意义。

无论如何,我从来没有听说过有人这样做……但这绝对是可能的。像 Emacs 中的大多数东西一样,这只是一个简单的编程问题。

【讨论】:

  • 我从来没有给 lisp 所需的时间,所以我在这方面很弱。
  • 'k.这可能是一个完美的学习项目,因为它简单明了,但并不“容易”。
【解决方案7】:

如果其他答案是自动的,这是一种半自动的方法。 如果您要手动重新格式化,这基本上是您会重复执行的操作,但经过压缩,您可以重复按一个键。

;; - go to the end of the line,
;; - do ^d to suck the previous line onto this one, 
;; - make sure there's only one space between the now-concatenated
;;   lines, and then 
;; - jump to the end and hit space so that (with auto-fill-mode)
;;   the line nicely rewraps itself:
;;   (turn on auto-fill-mode with M-x auto-fill-mode)
(defalias 'fill-sentence
  (read-kbd-macro "C-e C-d SPC M-x just- one- space RET C-e SPC <backspace>"))

(define-key global-map [f4] 'fill-sentence)  ; or whatever key you like

【讨论】:

    【解决方案8】:

    我非常喜欢 Chris Conway 的宏,但它仅在您手动换行每个句子后才有效。我是一个懒惰的人,所以我希望 emacs 为我做这件事。今天早上我终于坐下来研究了这个问题。我现在的解决方案是破解内置宏fill-region-as-paragraph

    应用以下 hack 后,新选项 newline-after-sentence 将设置为 true。标准的M-q (fill-paragraph) 将自动填充 并在 句子之间创建换行符。请注意,仅使用 GNU Emacs 23.3.1 进行测试——使用它需要您自担风险。

    完整的宏很长,所以我不会在这里发布。这个想法是在fill-region-as-paragraph中添加以下循环

    ...
    
    ;; Insert a line break after each sentence
    (while (< (point) to)
      (forward-sentence)
      (if (< (point) to) (fill-newline)))
    
    ;; This is the actual filling loop.
    (goto-char from)
    (let (sentbeg sentend)
      (while (< (point) to)
        (setq sentbeg (point))
        (end-of-line)
        (setq sentend (point))
        (fill-one-line sentbeg sentend justify) ;; original filling loop
        (forward-line)))))
    
    ...
    

    您可以在my git repository 中找到完整的宏。一些细节也写在my blog。如果你不想看我蹩脚的英语,你可以简单地使用

    $ curl http://fermi.mycloudnas.com/cgit.cgi/fill/plain/hack.el >> ~/.emacs
    

    将 hack 附加到您的 ~/.emacs 并尝试一下。欢迎评论和错误报告。

    【讨论】:

    • 网站宕机,回路机没有存档。
    【解决方案9】:

    另一种方法是将 .tex 文件保持原样,并使用像 latexdiff 这样的工具 (在this StackExchange 帖子中描述)而不是 Unix 差异。这会生成一个带有 Word 样式的跟踪更改标记的 .tex 文件,并正确处理空格,因此您不必担心句子的结尾。

    【讨论】:

      【解决方案10】:

      我写了以下循环一个区域并插入换行符。我没有使用对我不起作用的forward-sentence,而是使用re-search-forward "[.?!][]\"')}]*\\( \\)",它会查找所有句子后面只有两个空格(正则表达式是修改后的sentence-end)。换行符是使用newline-and-indent 创建的。

      (defun fill-sentences-in-paragraph ()
        "Put a newline at the end of each sentence in paragraph."
        (interactive)
        (save-excursion
          (mark-paragraph)
          (call-interactively 'fill-sentences-in-region)))
      
      (defun fill-sentences-in-region (start end)
        "Put a newline at the end of each sentence in region."
        (interactive "*r")
        (call-interactively 'unfill-region)
        (save-excursion
          (goto-char start)
          (while (re-search-forward "[.?!][]\"')}]*\\(  \\)" end t)
            (newline-and-indent))))
      

      为了能够修复格式不正确的文本,例如示例“chat chat chat...”,fill-sentences-in-region 首先调用 unfill-region,从而消除断句空格:

         (defun unfill-region (beg end)
            "Unfill the region, joining text paragraphs into a
             single logical line.  This is useful, e.g., for use
             with 'visual-line-mode'."
            (interactive "*r")
            (let ((fill-column (point-max)))
              (fill-region beg end)))
      

      我使用visual-line-mode 并将我的默认段落填充M-q 替换为fill-sentences-in-paragraph(global-set-key "\M-q" 'fill-sentences-in-paragraph)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-10
        相关资源
        最近更新 更多