Jak napisać prostą funkcję „uzupełniania w punkcie”?

9

Zastanawiam się nad napisaniem głównego trybu edytowania talii Magic: the Gathering.

Większość wydaje się dość prosta, ale mam jedno pytanie. Dostępnych jest około 15 000 unikalnych kart Magii (czyli kart o unikalnych nazwach). Chciałbym być w stanie ukończyć z nimi, pisząc funkcję zakończenia w punkcie. Szukałem prostego, podstawowego przykładu funkcji capf, która kończy się na zestawie słów, na których opiera się mój tryb, ale jak dotąd niczego nie znalazłem. Czy znasz jakiś dobry przykład, aby zacząć? I czy uważasz, że łatwo byłoby uzyskać dobrą wydajność, czy też musiałbym napisać własną strukturę danych (może myślę jak Trie).

Oczywiście musiałbym znaleźć sposób na synchronizację z nowymi kartami itp., A w przyszłości może nawet będę w stanie wyszukać karty według innych cech niż tylko nazwa karty, ale to może poczekać.

Mattias Bengtsson
źródło

Odpowiedzi:

17

Dokumentacja

Zakończenie funkcji API w punkcie można znaleźć w dokumentacji completion-at-point-functions

Każda funkcja na tym haku jest wywoływana po kolei bez żadnego argumentu i powinna zwracać zero lub oznaczać, że nie ma zastosowania w punkcie, lub funkcję braku argumentu do wykonania uzupełnienia (odradzane) lub listę postaci (START KOLEKCJA KOŃCOWA PROPS), gdzie START i END ograniczają jednostkę do uzupełnienia i powinny zawierać punkt, KOLEKCJA jest tabelą wypełnienia do użycia w celu jej uzupełnienia, a PROPS jest listą właściwości dla dodatkowych informacji.

start, endI propssą oczywiste, ale myślę, że format collectionnie jest poprawnie zdefiniowany. W tym celu możesz zobaczyć dokumentację try-completionluball-completions

Jeśli KOLEKCJA jest listą, możliwe są klucze (samochody elementów). Jeśli element nie jest komórką przeciwną, to sam element jest możliwym uzupełnieniem. Jeśli COLLECTION jest tablicą skrótów, wszystkie klucze, które są ciągami znaków lub symbolami, są możliwymi uzupełnieniami. Jeśli COLLECTION jest obarray, nazwy wszystkich symboli w obarray są możliwymi uzupełnieniami.

KOLEKCJA może być również funkcją do wykonania samego uzupełnienia. Otrzymuje trzy argumenty: wartości STRING, PREDICATE i zero. Cokolwiek zwróci, staje się wartością „try-complete”.

Przykład

Poniżej znajduje się prosty przykład zakończenia funkcji w punkcie, która używa słów zdefiniowanych w /etc/dictionaries-common/wordsdo uzupełnienia słów w buforze

(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))))))))

Funkcja uzupełniania szuka słowa w punkcie (biblioteka thingatptsłuży do znajdowania granic słowa) i uzupełnia je względem słów w /etc/dictionaries-common/wordspliku, właściwość :exclusivejest ustawiona na notak, aby emacs mógł korzystać z innych funkcji capf, jeśli się nie powiedzie. Wreszcie niektóre dodatkowe właściwości zostały ustawione w celu usprawnienia integracji w trybie firmowym.

Wydajność

Plik słów w moim systemie zawierał 99171 wpisów i emacs był w stanie je uzupełnić bez żadnych problemów, więc wydaje mi się, że 15000 wpisów nie powinno stanowić problemu.

Integracja z trybem firmowym

Tryb firmowy bardzo dobrze integruje się z completion-at-point-functionsużyciem company-capfbackendu, więc powinien on działać propsod razu po wyjęciu z pudełka, ale możesz ulepszyć uzupełnienia oferowane przez firmę, zwracając dodatkowe w wyniku funkcji capf. Obecnie obsługiwane są rekwizyty

:company-doc-buffer - Używany przez firmę do wyświetlania metadanych dla bieżącego kandydata

:company-docsig - Używany przez firmę do echa metadanych dotyczących kandydata w minibuforze

:company-location - Używany przez firmę w celu przejścia do lokalizacji aktualnego kandydata

Iqbal Ansari
źródło
O mój! Dzięki za dokładną odpowiedź! Wypróbuję to trochę i zaakceptuję później. Dodatkowe podziękowania za wskazówki firmy (z których faktycznie korzystam).
Mattias Bengtsson,
Dzięki, to jest naprawdę pomocne, teraz mogę łatwo konfigurować niestandardowe uzupełnienia :)
Clemera,
Cieszę się, że mogłem pomóc :)
Iqbal Ansari
0

@Iqbal Ansari dał świetną odpowiedź. Oto dodatkowa odpowiedź, mam nadzieję, że pomoże.

Oto implementacja z wykorzystaniem klasycznego mechanizmu uzupełniania emacsa, 2009.

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

Poniżej znajduje się kod, który wykonuje zakończenie.

(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")))))

Poniżej znajduje się implementacja korzystająca z interfejsu trybu ido. O wiele prostsze.

(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)))

Musisz zdefiniować xyz-kwdList jako listę swoich słów.

Xah Lee
źródło
2
-1 do wymyślania interfejs zakończenia w gorszej drodze, idąc za nullponad noti za pomocą camelCased identyfikatory i greckim symboli, które mają sens tylko własnymi środkami.
wasamasa,
3
-1 za brak odpowiedzi na pytanie, które dotyczyło completion-at-point-functions(nie zgadzam się z @wasamasa na temat nullvs vs not).
npostavs
3
@XahLee Funkcje w completion-at-point-functionspowinny zwracać dane o zakończeniu, a nie same wykonywać. Zatem funkcje w twojej odpowiedzi nie są użyteczne jako wpisy w completion-at-point-functions.
npostavs
1
@npostavs ah widzę. masz rację. Dzięki!
Xah Lee,
4
@npostavs Ten rodzaj funkcji nadal działałby, ale w rzeczywistości pisanie funkcji uzupełniającej w ten sposób jest sprzeczne z udokumentowanym interfejsem i jest mocno odradzane.
Dmitry