【问题标题】:Emacs completion-at-point-functionsEmacs 完成点函数
【发布时间】:2012-04-23 04:00:56
【问题描述】:

我正在编写基于 comint-mode 的派生模式。该模式是命令行程序(GRASS gis)的接口,comint 模式完成适用于这些程序。我正在尝试通过completion-at-point-functions 添加对完成程序参数的支持。一个玩具例子是:

(setq my-commands 
      '(("ls"
         ("my-completion-1")
         ("my-completion-2"))
        ("mv"
         ("my-completion-3")
         ("my-completion-4"))))


(defun my-completion-at-point ()
  (interactive)
  (let ((pt (point)) ;; collect point
        start end)

    (save-excursion ;; collect the program name
      (comint-bol)
      (re-search-forward "\\(\\S +\\)\\s ?"))
    (if (and (>= pt (match-beginning 1))
             (<= pt (match-end 1)))
        () ;; if we're still entering the command, pass completion on to
      ;; comint-completion-at-point by returning nil

      (let ((command (match-string-no-properties 1)))
        (when (member* command my-commands :test 'string= :key 'car)
          ;; If the command is one of my-commands, use the associated completions 
          (goto-char pt)
          (re-search-backward "\\S *")
          (setq start (point))
          (re-search-forward "\\S *")
          (setq end (point))
          (list start end (cdr (assoc command my-commands)) :exclusive 'no))))))

(push 'my-completion-at-point completion-at-point-functions)

这几乎可行。我得到程序名称的正常完成。但是,如果我在命令行中输入了ls,点击制表符插入my-completion- 并且不提供这两个选项。再次点击标签会再次插入my-completion-,所以我现在有了ls my-completion-mycompletion-

我的实际代码包含几行来检查多行命令,但对完成代码没有任何更改。使用此版本的代码,我在以my-commands 中的一个程序名称开头的行上单击选项卡,我会看到一个可能的参数列表来完成命令,但缓冲区中没有插入任何内容,并且键入参数的前几个字母不会缩小列表的范围。

我已经阅读了手册,但我不知道编写completion-at-point 函数的正确方法。有什么我想念的想法吗?

我简要地看了pcomplete,但并没有真正理解“文档”,也没有取得任何进展。

【问题讨论】:

    标签: emacs elisp tab-completion


    【解决方案1】:

    问题似乎出在您找到 startend 以返回该点的参数边界的方式上。我没有花足够长的时间调试它来确定细节,但我认为如果你以交互方式调用该函数,你会看到它为 startend 返回相同的值,这意味着完成UI 不知道使用该参数从您传递的完成表中进行选择。

    将函数的最后一部分更改为以下内容似乎是一种解决方法:

    (when (member* command my-commands :test 'string= :key 'car)
      ;; If the command is one of my-commands, use the associated completions 
      (goto-char pt)
      (let ((start
             (save-excursion
               (skip-syntax-backward "^ ")
               (point))))
    
        (list start pt (cdr (assoc command my-commands)) :exclusive 'no)))))))
    

    当添加为completion-at-point-functions 的元素时,这给出了预期的结果。

    在这里,我使用了skip-syntax-backward 而不是正则表达式搜索,我认为这对于这类事情来说更符合 Elisp 的习惯。它只是说将点向后移动到不在语法类“空白”中的任何内容。跳过语法函数返回移动的距离而不是点的值,因此我们必须在保存游览结束时添加对point 的调用。

    如果您确实在这样的函数中使用正则表达式搜索,通常最好将t 传递给第四个参数noerror,这样它就不会在失败时将错误传递给用户匹配。不过,这确实意味着您必须自己检查返回值是否为nil

    最后,你可能想要使用add-hook 来代替push 添加完成功能,如下所示:

    (add-hook 'completion-at-point-functions 'my-completion-at-point nil t)
    

    这做了两件有用的事情:它在添加之前检查你的函数是否已经在钩子中,并且(通过将t 传递给第四个参数local)它只将函数添加到缓冲区-local 点完成挂钩的值。这几乎肯定是您想要的,因为当您按下 TAB 键时,您不想在所有其他 Emacs 缓冲区中使用这些补全。

    【讨论】:

    • 如果我有不止一票,你也会得到那一票!感谢您的回答。当我阅读文档时,re-search-backward 应该将 point 留在与 skip-syntax-backward 相同的位置,但显然它没有。我也开始使用 add-hook,但是函数名称被颠倒并且被错误地引用。现在一切正常。再次感谢!
    • 很高兴它有帮助!仔细想想,我怀疑re-search-backward 的问题与向后搜索时正则表达式运算符的贪婪有关。当然,"\\S *" 匹配空字符串,但即使将* 替换为+,它仍然只会向后移动一个非空格字符并停止。
    猜你喜欢
    • 2013-06-17
    • 2014-05-20
    • 2012-07-25
    • 2011-05-04
    • 1970-01-01
    • 2010-09-12
    • 2013-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多