¿Cómo escribo una función simple de 'completar-en-punto-funciones'?

9

Estoy considerando escribir un modo principal para editar mazos de Magic: the Gathering.

La mayor parte parece bastante sencilla, pero tengo una pregunta. Hay alrededor de 15 000 cartas mágicas únicas disponibles (cartas con nombres únicos). Me gustaría poder completar contra ellos escribiendo una función de finalización en el punto. He estado buscando un ejemplo simple y básico de una función capf que solo se completa con un conjunto de palabras para basar mi modo, pero hasta ahora no he podido encontrar nada. ¿Conoces algún buen ejemplo para que esto comience? ¿Y cree que sería fácil obtener un buen rendimiento o tendría que escribir mi propia estructura de datos (tal vez estoy pensando como un Trie)?

Obviamente, necesitaría encontrar una manera de sincronizar con nuevas tarjetas, etc. y en el futuro tal vez incluso pueda buscar tarjetas por otras características que no sean solo el nombre de la tarjeta, pero eso puede esperar.

Mattias Bengtsson
fuente

Respuestas:

17

Documentación

La finalización de API en la función de punto se puede encontrar en la documentación de completion-at-point-functions

Cada función en este gancho se llama por turnos sin ningún argumento y debe devolver nil para indicar que no es aplicable en el punto, o una función de ningún argumento para completar (desaconsejado), o una lista de la forma (INICIO FINAL COLECCIÓN . PROPS) donde START y END delimitan la entidad para completar y deben incluir el punto, COLLECTION es la tabla de finalización que se usará para completarlo, y PROPS es una lista de propiedades para información adicional.

start, endy propsson obvios, pero creo que el formato de collectionno está definido correctamente. Para eso puedes ver la documentación de try-completionoall-completions

Si COLLECTION es una lista, las claves (carros de elementos) son las posibles terminaciones. Si un elemento no es una celda de contras, entonces el elemento en sí es la posible finalización. Si COLLECTION es una tabla hash, todas las teclas que son cadenas o símbolos son las posibles terminaciones. Si COLLECTION es una matriz, los nombres de todos los símbolos en la matriz son las posibles terminaciones.

COLECCIÓN también puede ser una función para completarlo. Recibe tres argumentos: los valores STRING, PREDICATE y nil. Lo que devuelve se convierte en el valor de 'try-complete'.

Ejemplo

A continuación se muestra un ejemplo simple de finalización en la función de punto que utiliza las palabras definidas en /etc/dictionaries-common/wordspara completar las palabras en el búfer

(defvar words (split-string (with-temp-buffer
                              (insert-file-contents-literally "/etc/dictionaries-common/words")
                              (buffer-string))
                            "\n"))

(defun words-completion-at-point ()
  (let ((bounds (bounds-of-thing-at-point 'word)))
    (when bounds
      (list (car bounds)
            (cdr bounds)
            words
            :exclusive 'no
            :company-docsig #'identity
            :company-doc-buffer (lambda (cand)
                                  (company-doc-buffer (format "'%s' is defined in '/etc/dictionaries-common/words'" cand)))
            :company-location (lambda (cand)
                                (with-current-buffer (find-file-noselect "/etc/dictionaries-common/words")
                                  (goto-char (point-min))
                                  (cons (current-buffer) (search-forward cand nil t))))))))

La función de finalización busca la palabra en el punto (la biblioteca thingatptse usa para encontrar los límites de la palabra) y la completa contra las palabras en el /etc/dictionaries-common/wordsarchivo, la propiedad :exclusivese configura para noque emacs pueda usar otras funciones capf si falla. Finalmente, se establecen algunas propiedades adicionales para mejorar la integración en modo empresa.

Actuación

El archivo de palabras en mi sistema tenía 99171 entradas y emacs pudo completarlas sin ningún problema, por lo que supongo que 15000 entradas no deberían ser un problema.

Integración con modo empresa

El modo de empresa se integra muy bien con el completion-at-point-functionsuso del company-capfbackend, por lo que debería funcionar propsde forma inmediata para usted, pero puede mejorar las terminaciones ofrecidas por la empresa al devolver más en el resultado de la función capf. Los accesorios actualmente admitidos son

:company-doc-buffer - Utilizado por la empresa para mostrar metadatos para el candidato actual

:company-docsig - Utilizado por la empresa para hacer eco de metadatos sobre el candidato en el minibúfer

:company-location - Utilizado por la empresa para saltar a la ubicación del candidato actual

Iqbal Ansari
fuente
¡Oh mi! Gracias por la respuesta completa! Probaré esto un poco y aceptaré después de eso. Gracias adicionales por las sugerencias de la Compañía (que en realidad estoy usando).
Mattias Bengtsson
Gracias, que es muy útil ahora puedo configurar terminaciones personalizados fácilmente :)
clemera
Me alegro de poder ayudar :)
Iqbal Ansari
0

@Iqbal Ansari dio una gran respuesta. Aquí hay una respuesta complementaria, espero que ayude.

Aquí hay una implementación utilizando el mecanismo de finalización clásico de emacs, 2009.

;; this is your lang's keywords
(setq xyz-kwdList
      '("touch"
       "touch_start"
       "touch_end"
       "for"
       "foreach"
       "forall"
       ))

El siguiente es el código que hace la finalización.

(defun xyz-complete-symbol ()
  "Perform keyword completion on word before cursor."
  (interactive)
  (let ((posEnd (point))
        (meat (thing-at-point 'symbol))
        maxMatchResult)

    ;; when nil, set it to empty string, so user can see all lang's keywords.
    ;; if not done, try-completion on nil result lisp error.
    (when (not meat) (setq meat ""))
    (setq maxMatchResult (try-completion meat xyz-kwdList))

    (cond ((eq maxMatchResult t))
          ((null maxMatchResult)
           (message "Can't find completion for “%s”" meat)
           (ding))
          ((not (string= meat maxMatchResult))
           (delete-region (- posEnd (length meat)) posEnd)
           (insert maxMatchResult))
          (t (message "Making completion list…")
             (with-output-to-temp-buffer "*Completions*"
               (display-completion-list 
                (all-completions meat xyz-kwdList)
                meat))
             (message "Making completion list…%s" "done")))))

Lo siguiente es una implementación usando la interfaz de ido-mode. Mucho mas simple.

(defun abc-complete-symbol ()
  "Perform keyword completion on current symbol.
This uses `ido-mode' user interface for completion."
  (interactive)
  (let* (
         (bds (bounds-of-thing-at-point 'symbol))
         (p1 (car bds))
         (p2 (cdr bds))
         (current-sym
          (if  (or (null p1) (null p2) (equal p1 p2))
              ""
            (buffer-substring-no-properties p1 p2)))
         result-sym)
    (when (not current-sym) (setq current-sym ""))
    (setq result-sym
          (ido-completing-read "" xyz-kwdList nil nil current-sym ))
    (delete-region p1 p2)
    (insert result-sym)))

Deberá definir xyz-kwdList como una lista de sus palabras.

Xah Lee
fuente
2
-1 por reinventar la interfaz de finalización de una manera peor, yendo por nullencima noty usando identificadores en camello y símbolos griegos que solo tienen sentido en sus propios modos.
wasamasa
3
-1 por no responder a la pregunta sobre completion-at-point-functions(no estoy de acuerdo con @wasamasa sobre lo de nullvs not).
npostavs
3
@XahLee Se completion-at-point-functionssupone que las funciones en devolver datos de finalización, no realizar la finalización ellos mismos. Por lo tanto, las funciones en su respuesta no se pueden usar como entradas en completion-at-point-functions.
npostavs
1
@npostavs ah ya veo. tienes razón. ¡Gracias!
Xah Lee
44
@npostavs Este tipo de función aún funcionaría, pero de hecho escribir una función de finalización de esta manera está en contra de la interfaz documentada, y se desaconseja encarecidamente.
Dmitry