Emacs 使用ispell/aspell检查大小写单词的拼写

Emacs 使用ispell/aspell检查大小写单词的拼写,emacs,aspell,camelcasing,ispell,Emacs,Aspell,Camelcasing,Ispell,我需要检查一个包含许多大小写单词的大文件的拼写。我想让ispell或aspell检查每个单词是否拼写正确 因此,对于这个词: 科学进步 我希望它能提出以下建议: 科学进步 有没有办法做到这一点?(我的意思是,在Emacs缓冲区上运行它时。)注意,我不一定希望它建议完整的替代方案。但是,如果它知道Progrez不被识别,我希望至少能够替换该部分,或者将该单词添加到我的私人词典中,而不是将每个大小写单词都包含在词典中。您应该解析大小写单词并将其拆分,然后检查每个标记的单个拼写,并根据每个拼写错误的标

我需要检查一个包含许多大小写单词的大文件的拼写。我想让ispell或aspell检查每个单词是否拼写正确

因此,对于这个词:

科学进步

我希望它能提出以下建议:

科学进步


有没有办法做到这一点?(我的意思是,在Emacs缓冲区上运行它时。)注意,我不一定希望它建议完整的替代方案。但是,如果它知道Progrez不被识别,我希望至少能够替换该部分,或者将该单词添加到我的私人词典中,而不是将每个大小写单词都包含在词典中。

您应该解析大小写单词并将其拆分,然后检查每个标记的单个拼写,并根据每个拼写错误的标记的单个建议组合一个建议。考虑到每个拼写错误的代币都可能有多个建议,这对我来说有点低效。

我接受了@phils的建议,并深入研究了一下。事实证明,如果您获得并重新配置某些ispell,如下所示:

(defun ispell-get-word (following)
  (when following
    (camelCase-forward-word 1))
  (let* ((start (progn (camelCase-backward-word 1)
                       (point)))
         (end (progn (camelCase-forward-word 1)
                     (point))))
    (list (buffer-substring-no-properties start end)
          start end)))
然后,在这种情况下,像thisone这样的单个驼峰大小写的单词实际上会被正确地拼写检查。(除非你在一个文档的开头——我刚刚发现。)

因此,这显然不是一个完整的解决方案,但至少它是某种东西

aspell中有“-run-together”选项。Hunspell无法检查大小写单词

如果您阅读aspell的代码,您会发现它的算法实际上并没有将camelcase单词拆分为子单词列表。也许这个算法更快,但它会错误地将包含两个字符子单词的单词报告为打字错误。不要浪费时间调整其他aspell选项。我试过了,但没有成功

所以我们有两个问题:

  • aspell报告了一些用骆驼壳拼字的拼写错误

  • hunspell将所有以驼背字母大写的单词报告为拼写错误

  • 解决这两个问题的方法是在Emacs Lisp中编写我们自己的谓词

    下面是一个为javascript编写的示例谓词:

    (defun split-camel-case (word)
      "Split camel case WORD into a list of strings.
    Ported from 'https://github.com/fatih/camelcase/blob/master/camelcase.go'."
      (let* ((case-fold-search nil)
             (len (length word))
             ;; ten sub-words is enough
             (runes [nil nil nil nil nil nil nil nil nil nil])
             (runes-length 0)
             (i 0)
             ch
             (last-class 0)
             (class 0)
             rlt)
    
        ;; split into fields based on class of character
        (while (< i len)
          (setq ch (elt word i))
          (cond
           ;; lower case
           ((and (>= ch ?a) (<= ch ?z))
            (setq class 1))
           ;; upper case
           ((and (>= ch ?A) (<= ch ?Z))
            (setq class 2))
           ((and (>= ch ?0) (<= ch ?9))
            (setq class 3))
           (t
            (setq class 4)))
    
          (cond
           ((= class last-class)
            (aset runes
                  (1- runes-length)
                  (concat (aref runes (1- runes-length)) (char-to-string ch))))
           (t
            (aset runes runes-length (char-to-string ch))
            (setq runes-length (1+ runes-length))))
          (setq last-class class)
          ;; end of while
          (setq i (1+ i)))
    
        ;; handle upper case -> lower case sequences, e.g.
        ;;     "PDFL", "oader" -> "PDF", "Loader"
        (setq i 0)
        (while (< i (1- runes-length))
          (let* ((ch-first (aref (aref runes i) 0))
                 (ch-second (aref (aref runes (1+ i)) 0)))
            (when (and (and (>= ch-first ?A) (<= ch-first ?Z))
                       (and (>= ch-second ?a) (<= ch-second ?z)))
              (aset runes (1+ i) (concat (substring (aref runes i) -1) (aref runes (1+ i))))
              (aset runes i (substring (aref runes i) 0 -1))))
          (setq i (1+ i)))
    
        ;; construct final result
        (setq i 0)
        (while (< i runes-length)
          (when (> (length (aref runes i)) 0)
            (setq rlt (add-to-list 'rlt (aref runes i) t)))
          (setq i (1+ i)))
         rlt))
    
    (defun flyspell-detect-ispell-args (&optional run-together)
      "If RUN-TOGETHER is true, spell check the CamelCase words.
    Please note RUN-TOGETHER will make aspell less capable. So it should only be used in prog-mode-hook."
      ;; force the English dictionary, support Camel Case spelling check (tested with aspell 0.6)
      (let* ((args (list "--sug-mode=ultra" "--lang=en_US"))args)
        (if run-together
            (setq args (append args '("--run-together" "--run-together-limit=16"))))
        args))
    
    ;; {{ for aspell only, hunspell does not need setup `ispell-extra-args'
    (setq ispell-program-name "aspell")
    (setq-default ispell-extra-args (flyspell-detect-ispell-args t))
    ;; }}
    
    ;; ;; {{ hunspell setup, please note we use dictionary "en_US" here
    ;; (setq ispell-program-name "hunspell")
    ;; (setq ispell-local-dictionary "en_US")
    ;; (setq ispell-local-dictionary-alist
    ;;       '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "en_US") nil utf-8)))
    ;; ;; }}
    
    (defvar extra-flyspell-predicate '(lambda (word) t)
      "A callback to check WORD.  Return t if WORD is typo.")
    
    (defun my-flyspell-predicate (word)
      "Use aspell to check WORD.  If it's typo return t."
      (let* ((cmd (cond
                   ;; aspell: `echo "helle world" | aspell pipe`
                   ((string-match-p "aspell$" ispell-program-name)
                    (format "echo \"%s\" | %s pipe"
                            word
                            ispell-program-name))
                   ;; hunspell: `echo "helle world" | hunspell -a -d en_US`
                   (t
                    (format "echo \"%s\" | %s -a -d en_US"
                            word
                            ispell-program-name))))
             (cmd-output (shell-command-to-string cmd))
             rlt)
        ;; (message "word=%s cmd=%s" word cmd)
        ;; (message "cmd-output=%s" cmd-output)
        (cond
         ((string-match-p "^&" cmd-output)
          ;; it's a typo because at least one sub-word is typo
          (setq rlt t))
         (t
          ;; not a typo
          (setq rlt nil)))
        rlt))
    
    (defun js-flyspell-verify ()
      (let* ((case-fold-search nil)
             (font-matched (memq (get-text-property (- (point) 1) 'face)
                                 '(js2-function-call
                                   js2-function-param
                                   js2-object-property
                                   js2-object-property-access
                                   font-lock-variable-name-face
                                   font-lock-string-face
                                   font-lock-function-name-face
                                   font-lock-builtin-face
                                   rjsx-text
                                   rjsx-tag
                                   rjsx-attr)))
             subwords
             word
             (rlt t))
        (cond
         ((not font-matched)
          (setq rlt nil))
         ;; ignore two character word
         ((< (length (setq word (thing-at-point 'word))) 2)
          (setq rlt nil))
         ;; handle camel case word
         ((and (setq subwords (split-camel-case word)) (> (length subwords) 1))
          (let* ((s (mapconcat (lambda (w)
                                 (cond
                                  ;; sub-word wholse length is less than three
                                  ((< (length w) 3)
                                   "")
                                   ;; special characters
                                  ((not (string-match-p "^[a-zA-Z]*$" w))
                                   "")
                                  (t
                                   w))) subwords " ")))
            (setq rlt (my-flyspell-predicate s))))
         (t
          (setq rlt (funcall extra-flyspell-predicate word))))
        rlt))
    
    (put 'js2-mode 'flyspell-mode-predicate 'js-flyspell-verify)
    
    (取消拆分驼峰格(word)
    “将大小写单词拆分为字符串列表。
    从https://github.com/fatih/camelcase/blob/master/camelcase.go'."
    (let*((箱子折叠搜索无)
    (len(长单词))
    十个副词就足够了
    (符文[零零零零零零零零零])
    (符文长度0)
    (i)第0条
    中国
    (最后一节课0)
    (0级)
    (rlt)
    ;;根据字符类别拆分为字段
    (while(=ch?a)(=ch?a)(=ch?0)(小写顺序,例如。
    “PDFL”、“加载程序”->“PDF”、“加载程序”
    (setq i 0)
    (while(=ch first?A)(=ch second?A)((长度(aref符文i))0)
    (setq rlt(添加到列表'rlt(aref符文i)t)))
    (setq i(1+i)))
    (rlt)
    (取消flyspell detect ispell参数(&可选一起运行)
    如果RUN-TOGETHER为true,请检查CamelCase单词的拼写。
    请注意,一起运行将降低aspell的能力。因此,它应仅用于prog模式挂钩。”
    ;强制使用英语词典,支持大小写拼写检查(使用aspell 0.6测试)
    (let*((args(list--sug mode=ultra“--lang=en_US”)args)
    (如果一起跑
    (setq args(附加args'(“--run-house”“--run-house limit=16”))
    args)
    {{仅对于aspell,hunspell不需要设置“ispell额外参数”
    (setq ispell程序名“aspell”)
    (setq默认ispell额外参数(flyspell detect ispell args t))
    ;; }}
    {{拼写设置,请注意我们在这里使用字典“en_US”
    ;;(setq ispell程序名“hunspell”)
    (setq ispell本地词典“en_US”)
    ;(setq ispell local dictionary)
    ‘((“en_US”[[:alpha:][]”“[^[:alpha:][][']”无(“-d”en_US”)无utf-8)))
    ;; ;; }}
    (defvar额外flyspell谓词(lambda(word)t)
    “检查单词的回调。如果单词拼写错误,则返回t。”)
    (取消我的flyspell谓词(word)
    “使用aspell检查WORD。如果键入错误,请返回t。”
    (让*)命令(条件)
    ;阿斯佩尔:`echo“helle world”|阿斯佩尔管`
    ((string-match-p“aspell$”ispell程序名)
    (格式为“echo\%s\”|%s管道)
    单词
    ispell程序名)
    |;hunspell:`echo“helle world”| hunspell-a-d en|u US`
    (t
    (格式为“echo\%s\”|%s-a-d en_US”
    单词
    ispell程序名)
    (cmd输出(shell命令到字符串cmd))
    (rlt)
    ;;(消息“word=%s cmd=%s”word cmd)
    ;;(消息“cmd output=%s”cmd output)
    (续)
    ((string-match-p“^&”cmd输出)
    这是一个拼写错误,因为至少有一个子词是拼写错误
    (setq rlt)
    (t
    不是打字错误
    (setq rlt nil)))
    (rlt)
    (defun js flyspell verify()
    (let*((箱子折叠搜索无)
    (字体匹配(memq(获取文本属性(-(点)1)”面)
    '(js2函数调用
    js2函数参数
    js2对象属性
    js2对象属性访问
    字体锁定变量名面
    字体锁定字符串面
    字体锁定函数名面
    字体锁定内置面
    rjsx文本
    rjsx标签
    rjsx attr)
    子词
    单词
    (rlt)
    (续)
    ((字体不匹配)
    (setq rlt零)
    忽略两个字符的单词